# Audit PR Label Lifecycle

This document explains why the review-feedback audit flow creates two different
PR types and why the AI PR loop treats them oppositely.

Related handoff contract: [`docs/audit-batch-output-format.md`](./audit-batch-output-format.md)

```text
dispatch_audit_evaluation
  └─ assigns Copilot cloud agent (base_branch=main)
      └─ Evaluation PR on copilot/... branch  (excluded from the AI PR loop)
           └─ audit-takeover-eval-prs.yml (every 4h) → squash/takeover as a human
                └─ human `synchronize` un-gates audit-review-feedback-apply.yml
                     └─ agdt-audit-apply (sync git + gh pr create); closes the eval PR
                          └─ Instruction-update PR on audit/instruction-update-*
                               └─ add ai-auto-merge-allowed (loop rebase 3-way merges)
```

## The two PRs

### 1) Evaluation PR (handoff vehicle only)

- **Branch**: agent-named `copilot/auditbatch-*` in practice, and generally any
  branch under `copilot/**`.
- **Touches**: `audit-batches/<batch_id>/agent-output/**`.
- **Created by**: Copilot cloud coding agent assigned during dispatch
  (`dispatch_audit_evaluation` → `assign_issue_to_agent(base_branch="main")`).
- **Loop treatment**: **excluded** from the AI PR loop by `filter_eligible_prs`
  (not a reviewable change set); **reclaimed** under a human identity by the
  scheduled `audit-takeover-eval-prs.yml`, and **closed** by `agdt-audit-apply`
  once its output has been applied.

### 2) Instruction-update PR (real output PR)

- **Branch**: fixed `audit/instruction-update-*`.
- **Touches**: `**/copilot-instructions.md`.
- **Created by**: `agdt-audit-apply`, synchronously inside
  `audit-review-feedback-apply.yml` (plain git + `gh pr create`).
- **Loop treatment**: **processed + auto-merged**.
- **Label**: `ai-auto-merge-allowed`.

## Loop treatment of each PR

### Evaluation PR — excluded and reclaimed

The evaluation PR only carries `agent-output/**` back into CI. It is not the
final instruction-update PR and must never be reviewed or merged by the AI PR
loop, so `filter_eligible_prs` excludes it (see **Scheduler backstop**). Because
Copilot/automation-origin events are gated, a dedicated scheduled workflow,
`audit-takeover-eval-prs.yml`, reclaims it under a human identity — the human
`synchronize` un-gates the apply workflow. A successful `agdt-audit-apply` then
closes the eval PR.

### `ai-auto-merge-allowed` on the instruction-update PR

The instruction-update PR is the real deliverable (updated
`copilot-instructions.md` rule files). `ai-auto-merge-allowed` is the exact
signal used by the scheduler (`AUTO_MERGE_LABEL` in
`agentic_devtools/cli/ci/scheduler.py`) to treat the PR as merge-eligible.

⚠️ **Policy note:** auto-merging these PRs means auto-merging changes to the
instruction files that steer future agent runs. This is intentional. If a
tighter policy is needed later, restrict auto-merge eligibility to diffs
confined to `**/copilot-instructions.md`.

## Scheduler backstop

`filter_eligible_prs` keeps evaluation PRs out of the AI PR loop. It excludes
PRs when both are true:

1. Head branch contains `copilot`, and
2. PR touches `audit-batches/**/agent-output/**`.

The path check is gated behind the cheap branch-name check first, and any
enrichment/API failure is fail-safe (it does **not** exclude), so legitimate
non-audit PRs are not dropped by transient errors.

## Trigger + data-sourcing rationale in apply workflow

`audit-review-feedback-apply.yml` reacts to the Copilot evaluation PR via
`pull_request_target` (types `opened`, `synchronize`, `reopened`,
`ready_for_review`), gated by `audit-batches/**/agent-output/**` as the
authoritative change filter and a job-level `if` that requires a same-repo PR
whose head branch is `copilot/**` or `audit/batch-*`.

`push` cannot be used here: the evaluation branch is pushed by the Copilot cloud
coding agent (a `GITHUB_TOKEN`-class actor), and GitHub gates workflow runs on
Copilot/automation-origin events (`on: push`/`on: pull_request` and even
`pull_request_target` sit at "Action required"). The scheduled
`audit-takeover-eval-prs.yml` reclaims the eval PR under a **human** identity
(squash-preferred, takeover-amend fallback); the resulting human `synchronize`
is not gated, so this `pull_request_target` workflow then fires (same base-repo
mechanism as `ai-pr-loop-dispatcher.yml`).

The evaluation PR head carries only `agent-output/**`; `batch-meta.json` lives on
the `audit/batch-<id8>` staging branch. The workflow materializes both as **data**
(a pathspec `git checkout`, so no PR-head code is executed) before running
`agdt-audit-apply`. For metadata validation:

- `batch_id` is derived from the single `agent-output` directory on the PR head.
- `batch-meta.json` (from the staging branch) supplies `pr_numbers` and
  `tracking_issue`; `batch_id` must be present/non-empty.
- `output_dir` must strictly equal `audit-batches/{batch_id}`.
- If the staging branch is gone (apply deletes it on success), the run is a
  no-op — this makes repeated `pull_request_target` events idempotent.
- `base_sha` (from `batch-meta.json`) is passed to `agdt-audit-apply`, which
  branches the instruction-update PR off that commit so its diff is just the
  eval delta; the AI PR loop's rebase then 3-way merges it onto current `main`,
  integrating concurrent `copilot-instructions.md` changes.

⚠️ **Recursion warning:** apply branch/PR creation must remain synchronous
(plain git + `gh pr create`) and must not be delegated to a coding agent. The
apply output lands on `audit/instruction-update-*`, which the job-level `if`
excludes, so apply cannot re-trigger itself.
