---
name: gz-obpi-pipeline
persona: pipeline-orchestrator
description: Post-plan OBPI execution pipeline — implement, verify, present evidence, and sync after a plan is approved. Use after exiting plan mode for an OBPI, when the user says "execute OBPI-X.Y.Z-NN", or to enforce governance on already-implemented work via --from=verify or --from=ceremony.
category: obpi-pipeline
lifecycle_state: active
owner: gzkit-governance
last_reviewed: 2026-09-19
metadata:
  skill-version: "6.59.1"
model: sonnet
---

# gz-obpi-pipeline

Post-plan execution pipeline: implement the approved plan, verify, present evidence, and sync.

Planning happens in Claude Code's native plan mode. This pipeline picks up **after** the plan is approved and enforces the governance stages that get lost in freeform execution.

The canonical runtime launch surface is `uv run gz obpi pipeline`. The CLI
runtime, generated hook surfaces, and reminder messages share the same
runtime engine in `src/gzkit/pipeline_runtime.py`. This skill remains the
wrapper/operator ritual around that runtime rather than a second stage engine.

**Durable acceptance (GHI #985).** The existing stages share the ledger-backed
`gz obpi acceptance` contract: canonical obligations, executed proof, scoped
independent reviews, and explicit finding closure. Read
`docs/governance/acceptance-obligations.md` and
`docs/user/manpages/obpi-acceptance.md` before producing or importing these records.
This replaces authored standing-verdict lines as the acceptance authority;
historical verdicts remain unchanged. It does not authorize starting an OBPI:
the operator initiates this pipeline, and GHI corrective work retains its direct route.

## Persona

**Active driver:** `pipeline-orchestrator` — read `.gzkit/personas/pipeline-orchestrator.md` and adopt its behavioral identity before executing this skill. Stage discipline, ceremony completion, and evidence anchoring are not rules to follow — they are who you are when running this pipeline.

## Persona Dispatch

The pipeline dispatches four subagent personas across its stages. Stage-2 implementer/spec-reviewer/quality-reviewer dispatch is documented procedurally below (§ Stage 2); the Stage-4 narrator dispatch is declared here:

| Persona | Function in this ceremony | Invoked at |
|---|---|---|
| `implementer` | Methodical, test-first code authoring per the approved plan; one task at a time; complete units (imports + usage + tests + docs as one edit) | Stage 2 step c–g (see § Stage 2 for dispatch mechanics) |
| `spec-reviewer` | Independent requirement-tracing against the brief's `## Requirements (FAIL-CLOSED)` list; each REQ must map to implementation and test. **Cannot execute** — reviews by reading (GHI #941, #968) | Stage 2 step h.i–viii (two-stage review) |
| `quality-reviewer` | Independent architectural assessment: SOLID, size-discipline, maintainability of the produced diff. **Cannot execute** — reviews by reading (GHI #941, #968) | Stage 2 step h.i–viii (two-stage review) |
| `narrator` | Composes the Stage 4 evidence packet in operator-value framing — value narrative, key proof, evidence table, REQ coverage rendered for the human's attestation decision | Stage 4 (Present Evidence) — see § Stage 4 |

The mechanical attestation that these dispatches occurred was scoped by `ADR-pool.obpi-pipeline-dispatch-attestation` Target Scopes #5/#6. That ADR is **Superseded** (`absorbed_into: ADR-0.0.73`, itself Validated 9/9), so there is no promotion pending and nothing arrives from one — the absorption delivered an absorption-marker audit, and that ADR's own § Notes place the receipt machinery (ledger events, bail-to-inline gates, validator scopes) in "a future feature-kind ADR work surface" that is not yet authored (GHI #846). **Stage-2 dispatch IS attestable today**: record each one with `uv run gz obpi dispatch <OBPI-ID> --role <Role> --model <tier>`, and `gz obpi precomplete` fails closed on a silent single-driver run (GHI #845). Credit is never inferred. The Stage-4 narrator dispatch has no channel yet.

Persona doctrine reference: ADR-0.0.11-persona-driven-agent-identity-frames (Validated). Runtime mapping: `src/gzkit/pipeline_runtime.py:129` (`ROLE_PERSONA_MAP`).

---

## The Iron Law

```
THE PIPELINE IS NOT COMPLETE UNTIL STAGE 5 FINISHES.
```

Every stage flows into the next. No "stop and summarize" between stages. No pause except the Stage 4 human attestation in Normal mode — and that pause comes only **after** Step 4b (the independent adversary) has run and its verdict is on the table. Soliciting attestation with Step 4b skipped is not a valid pause; it is a gate bypass. If you have not reached the end of Stage 5, you are not done — and violating the spirit of this rule is violating the rule. One other stop is legitimate: when Stage 4's round bound is reached or a round repeats the prior round's root, record `gz obpi block` and put the decision to the operator (§ Stage 4 — The review window, the bound, and the exit). That is the pipeline working, not a premature summary.

### Rationalization Prevention

These thoughts mean STOP — you are about to break the pipeline:

| Thought | Reality |
|---------|---------|
| "Implementation/tests done, let me summarize" | You are between stages. The pipeline runs to Stage 5. Proceed. |
| "No plan receipt exists — the brief is clear enough to skip planning" | The plan-audit handoff is a governance checkpoint. Invoke `/gz-plan-audit <OBPI-ID>` in this same turn (see § The Plan-Mode Gate); do not end the turn to ask permission. |
| "The hook blocked me, I'll work around it" | Hook blocks are signals. Diagnose the cause. NEVER create marker files manually to bypass. |
| "`gz obpi complete` needs a TTY, so I'll ask the operator to run it themselves" | No. There is no TTY gate. A plain non-TTY `uv run gz obpi complete ... --attestation-text "<operator's verbatim attestation>"` call completes the brief for every lane / kind / sensitivity. The operator already attested in Stage 4 — relay that phrase, never hand the invocation back. |
| "The operator said `attest completed` — maybe they want me to explain what to do next" | No. `attest completed` IS the attestation. Run `gz obpi complete` immediately with that phrase (enriched per § Attestation) in `--attestation-text`. Do not produce runbook-style instructions for the operator to execute. |
| "My Step 4a evidence is green — tests pass, REQs covered — so I can present it and await attestation" | STOP. Green-on-your-own-evidence is the EXACT state Step 4b exists to distrust. You authored that evidence; you are the GHI #643 fabrication surface. You may NOT solicit attestation until applicable independent approval and closure have been imported for the current proof claims. Confidence from the authoring agent is worth zero at this gate. |
| "Step 4b is probably overkill for this small/authoring-only/obviously-correct OBPI" | There is no size, lane, or kind exception to Step 4b. "Obviously correct" is the precise feeling that precedes a hollow-test or fabricated-evidence skip. Dispatch the adversary. You are not the exception. |
| "I'll present Step 4a now and run the adversary after the operator responds / in the next turn" | Sequence violation. The adversary runs BEFORE attestation, not after — the operator attests holding the adversary's verdict. Presenting 4a as the terminal step of the turn, with 4b deferred, is the skip this gate forbids. Dispatch any missing required review before soliciting attestation; retain already applicable imported review. |
| "An independent Claude subagent is fine for 4b — I don't need Codex" | Tier-order bypass (GHI #678). A Claude validating Claude shares this agent's blind spots — the exact failure Step 4b exists to break. Codex (tier 1) is REQUIRED first; tier 2 is permitted ONLY after a checked `ready: false`. Run `codex:setup`; if `ready: true`, the Claude subagent is forbidden. "It was convenient" is not a fallback reason. |
| "The adversary is there to find problems, so a round that confirms the feature works is a soft round" | Inverted. A second model CONFIRMING the first model's implementation is correct is the entire point of Step 4b; probing is how that confirmation is earned. A round that only lists what it broke, and never demonstrates the feature doing its job, has not done the job. |
| "The adversary refuted again, so I'll fix these findings and re-run" | Check its `Weakest point` against the previous round FIRST. If it names the same root, another fix cycle surfaces it one layer deeper — that is three rounds and ~9 hours in the measured case. Stop and put the design to the operator. |
| "I'll tell the adversary to REFUTE the claim — that's what adversarial means" | It is not, and this exact wording cost OBPI-0.35.0-04 six rounds. A model told "your job is to REFUTE this, not to confirm it" will escalate until something falls, and its best available outcome is "I could not refute it" — absence of evidence, never confirmation. Prompt for independent confirmation, with probing as the method. |
| "This is a security property, so the claim should be absolute" | An absolute claim cannot be refuted in bounded time: the adversary escalates the attacker until something falls. Declare the threat model in the brief FIRST, state it in the prompt, and forbid out-of-scope findings — otherwise the gate never converges. |
| "The adversary found something, so the OBPI cannot pass" | Apply the September 5 independent-closure rule below: every finding against the agreed requirements or their required proof needs a disposition and independent closure. Severity alone does not clear it. Track independent discoveries without silently making them acceptance prerequisites; never dismiss a relevant finding merely because it arose in an auxiliary audit. |
| "The round refuted, but I fixed everything it found — I'll complete with `--adversary-verdict refuted` and explain the fixes in the resolution" | **Refused, and a resolution string does not change that (GHI #960).** *"refuted is an outcome, but it is an input into if(4a && 4b) pass; else: loop"* (operator, 2026-09-04). Execute proof for the repaired obligation and import explicit independent closure. The current acceptance records determine readiness; preserve the original refutation as history. |
| "The block says refuted can't complete, so I'll pass `not-refuted` since the findings are fixed anyway" | A caller-supplied word cannot close a finding. Import the independent review's actual executed output with its original finding IDs and current proof IDs. `gz obpi acceptance ... status --stage stage4` derives readiness; historical verdicts remain unchanged. |

### The Plan-Mode Gate

**No plan receipt → no implementation.** If Stage 1 finds no `.plan-audit-receipt-<OBPI-ID>.json` (or the existing receipt's `obpi_id` does not match this OBPI) and the pipeline was invoked without `--from`, you MUST produce a canonical-name plan and a fresh PASS receipt before touching any source file.

**The mechanical action is: invoke `/gz-plan-audit <OBPI-ID>` in this same turn.** The `gz-plan-audit` skill authors a plan file in the project-local `.claude/plans/` directory under a canonical name and writes the receipt directly. After the receipt is in place, Stage 2 begins.

**Why `/gz-plan-audit` and not `EnterPlanMode` first (GHI #288).** Claude Code's native plan mode pins the plan file to a harness-generated random-name path under `~/.claude/plans/<random>.md` and forbids edits to any other path while plan mode is active. The `plan-audit-gate.py` PreToolUse hook scans both `.claude/plans/` and `~/.claude/plans/` and self-runs `gz plan audit` on `ExitPlanMode`, but a first-run OBPI with no canonical-name plan in the project-local dir can deadlock the harness — the hook fails closed and the agent cannot satisfy both surfaces simultaneously. Routing through `/gz-plan-audit` first sidesteps the deadlock by producing the canonical artifact in the project-local dir up front.

**If you choose to also use native plan mode** (for example, to compose the plan interactively with the operator), enter plan mode *after* `/gz-plan-audit` has written a PASS receipt, and re-run `/gz-plan-audit <OBPI-ID>` after `ExitPlanMode` to refresh the receipt against the harness-named plan. The `plan-audit-gate` hook will then accept the exit because a valid receipt newer than the plan file exists.

**Stopping the turn to ask "should I run plan-audit?" is a violation, not compliance.** The skill's "STOP" language directs you to stop *making source edits* and *redirect to plan-audit*, not to stop your turn and solicit permission. If you catch yourself composing a message that says "Required next step: invoke gz-plan-audit" or "Want me to proceed with plan-audit on the next turn?" — you are rationalizing. Call `/gz-plan-audit <OBPI-ID>` in this same turn instead.

This is not optional. This is not something you can "derive informally." The plan-audit handoff exists because agents consistently skip planning when allowed to. You are not the exception.

---

## Work selection (operator rulings, verbatim canon)

- The active docs/governance/*-campaign-*.md plan governs work selection; handoffs and triage advise. Select its topmost unchecked item whose gate is met, subject to ascending feature-ADR order and operator-only OBPI initiation. Campaign amendments require operator ratification; ADR, OBPI, and GHI repair remain the work mechanisms.

- Work feature ADRs in ascending semver order: the lowest version with unlanded OBPIs is in flight. Do not work, author, or recommend a higher feature ADR ahead of it. The campaign selects work but cannot override that order. If campaign sequencing conflicts, semver governs; surface the conflict to the operator rather than silently resolving it. “One feature at a time” does not authorize swapping the order.

> Carried verbatim from root `AGENTS.md` § Operator Doctrine on 2026-09-17 (GHI #921); the corpus `.gzkit/corpus/AGENTS.md.jsonl` keeps the ruling's history.

## When to Use

- After exiting plan mode for an OBPI (plan approved, ready to execute)
- When the user says `execute OBPI-X.Y.Z-NN` or `complete OBPI-X.Y.Z-NN`
- When implementation is already done but governance stages (verify, ceremony, or
  sync) were skipped — use `--from=verify` or `--from=ceremony`

## Corrections belong to the active OBPI

Within an operator-initiated OBPI, implementation, test, documentation, and
evidence corrections necessary to satisfy its approved obligations remain part
of that OBPI. Do not create a GHI for each adjustment or re-ask permission for
an authorized correction. A real requirement/allowlist/threat-model amendment
still needs the existing operator ruling. A GHI is reserved for a finding that
needs an independent work order or disposition, including separate infrastructure
or post-acceptance defects, or an explicit operator request to file an issue.
Filing one does not discharge an unmet obligation.

Keep a concise `### Change Log` under the brief's existing `## Evidence` section.
Record substantive corrections and decisions: the finding identity when one
exists, its REQ or contract clause, what changed, and the proof/independent-closure
references. Reuse identities; do not invent a parallel finding roster. Log related
repairs together, not every edit or command. Link receipts and transcripts rather
than reproducing them. Keep approved amendments in their normative sections and
reference the operator ruling from the log; history cannot amend the contract.

The log is a human-readable index. Existing ledger records continue to govern
proof, review, closure, and readiness. Optional commentary does not create a new
deliverable, and historical prose edits alone do not require another review.
A concrete counterexample against an obligation or its required proof still does.
For existing briefs, add the log during the authorized run; do not retrofit other
live or completed briefs. Keep independently routed issue links in Tracked Defects.

## When NOT to Use

- For planning — use Claude Code plan mode instead
- When no OBPI brief exists for the work
- For independently routed defect repairs: use AGENTS.md § Defect-fix routing and its GHI direct-repair authority. Do not start an OBPI merely to repair a defect. Corrections already owned by the active, operator-initiated OBPI stay in that pipeline regardless of patch size.

---

## Invocation

```text
/gz-obpi-pipeline OBPI-0.14.0-05
/gz-obpi-pipeline OBPI-0.14.0-05 --from=verify
/gz-obpi-pipeline OBPI-0.14.0-05 --from=ceremony
```

Short-form OBPI IDs are accepted: `0.14.0-05` expands to `OBPI-0.14.0-05`.

### `--from` Flag

| Flag | Stages Run | Use Case |
|------|------------|----------|
| *(none)* | 1 → 2 → 3 → 4 → 5 | Full post-plan execution |
| `--from=verify` | 1 → 3 → 4 → 5 | Already implemented, need governance |
| `--from=ceremony` | 1 → 4 → 5 | Already verified, need attestation + sync |

Stage 1 always runs — context is needed for ceremony and sync regardless of entry point.

---

## Pipeline Stages

The pipeline executes 5 stages sequentially. **Stage 4 behavior depends on execution mode** — all other stages are autonomous.

```text
┌──────────────────────────────────────────────────────────────┐
│  Stage 1: LOAD CONTEXT       (autonomous)                    │
│  Stage 2: IMPLEMENT          (autonomous)                    │
│  ├─ DO NOT STOP HERE. Proceed to Stage 3.                    │
│  Stage 3: VERIFY             (autonomous)                    │
│  ├─ DO NOT STOP HERE. Proceed to Stage 4.                    │
│  Stage 4: PRESENT EVIDENCE   (human gate — universal)        │
│  ├─ WAIT for human attestation, then Stage 5.                │
│  Stage 5: SYNC AND ACCOUNT   (autonomous)                   │
│  └─ Pipeline complete. NOW you may report status.            │
└──────────────────────────────────────────────────────────────┘

Stage 4 = HUMAN GATE (wait for attestation) — universal per ADR-0.0.36
```

### Stage 1: Load Context

1. Read this OBPI's plan-audit receipt, `.claude/plans/.plan-audit-receipt-<OBPI-ID>.json`, to find the approved plan (the bare `.plan-audit-receipt.json` is the legacy name, accepted only as a fallback)
   - If receipt exists and OBPI matches: load the plan file from `.claude/plans/`, extract implementation steps
   - If receipt verdict is `FAIL`: **abort** — plan did not pass audit
   - If no receipt found AND `--from` flag is NOT set: **STOP — invoke `/gz-plan-audit <OBPI-ID>` in this same turn** (see § The Plan-Mode Gate for why this comes before native plan mode — GHI #288). The skill authors a canonical-name plan in `.claude/plans/` and writes the receipt directly. Only resume the pipeline after the plan-audit receipt is written. Do NOT "derive tasks informally" or "proceed without a plan." The plan-audit handoff is a governance checkpoint, not an optimization.
   - If no receipt found AND `--from=verify` or `--from=ceremony`: proceed (the user is explicitly resuming a partially-completed pipeline where implementation already happened)
2. Locate the OBPI brief under:
   - `docs/design/adr/**/obpis/OBPI-{id}-*.md`
   - `docs/design/adr/**/briefs/OBPI-{id}-*.md`
3. **Resolve the full OBPI slug** from the brief's frontmatter `id` field (e.g.
   `OBPI-0.0.12-02-implementer-agent-persona`). Use this full slug for ALL
   subsequent `gz obpi` commands (`reconcile`, `complete`, `lock claim`,
   `lock release`). The short form (e.g. `OBPI-0.0.12-02`) may fail ledger
   lookup. Set a variable like `obpi_slug` at this step and reuse it throughout.
5. Extract: objective, requirements, allowed/denied paths, acceptance criteria, lane, verification commands
6. Identify the parent ADR and inherit its lane and execution constraints.
7. All OBPIs require human attestation — universal per ADR-0.0.36.
8. Check for existing handoffs and resume context when present:
   - `uv run gz handoff resume --adr <ADR-ID>` — handoffs live in `.gzkit/handoffs/`; the per-ADR `handoffs/` folders were retired (ADR-0.0.65)
9. Claim OBPI lock: `uv run gz obpi lock claim {OBPI-SLUG}` (use the full slug from step 3)
10. Create pipeline markers. **Preferred path: invoke the runtime to author the marker.**
    The runtime writes the canonical marker shape (including a ledger-witnessed
    `nonce` the validator binds against) — hand-authored markers are a maintenance
    surface that drifts (GHI #586).

    - Preferred: `uv run gz obpi pipeline {OBPI-SLUG}` (the runtime writes
      `.claude/plans/.pipeline-active-{OBPI-SLUG}.json` and the legacy
      `.pipeline-active.json` with the canonical schema).
    - Hand-author fallback (only when the runtime cannot be invoked):
      - `.claude/plans/.pipeline-active-{OBPI-ID}.json`
      - `.claude/plans/.pipeline-active.json` as a legacy compatibility marker
        for the same OBPI
      - Marker payload MUST include `obpi_id`, `parent_adr`, `lane`, `entry`,
        `execution_mode`, `current_stage`, `started_at`, `updated_at`,
        `receipt_state`, `blockers`, `required_human_action`, `next_command`,
        and `resume_point`.
      - `current_stage` MUST be one of the canonical enum values the validator
        accepts (`adr_audit.py` `_PIPELINE_MARKER_VALID_STAGES`):
        `implement` | `verify` | `ceremony` | `sync` | `audit`. Prose stage
        headings like `"Stage 2: Implement"` are **rejected** by the validator
        and are not the same string as the enum. Set `current_stage` to the
        enum value matching the lifecycle step in progress.
    - This unblocks the pipeline-gate PreToolUse hook for src/ and tests/ writes.
11. Apply the brief allowlist as the working scope contract before any edits.
12. Initialize the acceptance population once:
    `uv run gz obpi acceptance {OBPI-SLUG} init --author {implementing-session-id}`.
    Reuse the recorded author and obligations when resuming. Initialization reads
    the canonical brief and parent ADR; it cannot erase history or silently
    replace the obligation roster. `--from` does not waive current proof or review.

> **In-flight status is advanced AT LAUNCH (GHI #646, corrected by GHI #992).**
> Launching the pipeline emits `pipeline_launched`, which IS the `in_progress`
> transition — `_derive_obpi_runtime_state` resolves a launched OBPI to
> `in_progress`, and `status_vocab` maps that to frontmatter `Active`. Layer-1
> mirrors that truth **inside the launch transaction**: `obpi_pipeline_cmd`
> calls `_advance_brief_status_on_launch` immediately after appending the
> event.
>
> **Derived never meant deferred.** Operator ruling 2026-09-11, verbatim: *"if
> we start work on a obpi with draft status, its not draft."* Until #992 the
> flip waited on a human typing `uv run gz frontmatter reconcile`, so a
> launched brief asserted `Draft` for the entire in-flight window and the
> pre-push `gz check` blocked every push on a frontmatter mismatch. The only
> mandatory catch was `gz obpi precomplete`'s `_check_reconcile_idempotent` —
> self-described *"Reactive triage at Stage 5"*, the far end of the pipeline
> from where the drift is created.
>
> Do not hand-write the lifecycle field here. The advance routes through
> `guarded_obpi_status_write`, so the terminal-clobber verdict stays in the one
> monitor ADR-0.31.0 Decision item 4 declares — a terminal brief is refused,
> and a re-launch is a no-op (mirrors how completion is surfaced, not authored,
> by the pipeline).

**Abort if:** brief not found, brief already `Completed`, or plan receipt verdict is `FAIL`.

**On any abort:** Release lock via `uv run gz obpi lock release {OBPI-SLUG} --force`, then run `/gz-session-handoff` to preserve context.

#### Stage 1→2 Justification Gate

Before Stage 2 begins, check the approved plan against the brief for three conditions. Do not rate your confidence: a self-reported figure has no basis. This block supersedes the trigger it was authored with — Prime Directive invariant 11's "less than 90% sure" — which the operator retired on 2026-08-17 (`AGENTS.md` § Operator Doctrine: "Stop and ask the operator in case of uncertainty… Supersedes the prior '90% convinced/confident' framing").

Run the walkthrough when any of these holds:

1. The brief's scope boundary is ambiguous: an allowed or denied path the plan touches is unclear, or the plan reaches a surface the brief does not name.
2. The plan leaves an integration point unresolved: a caller, consumer, validator or mirror it has not named.
3. The plan relies on a surface you have not read, only searched or inferred.

```bash
uv run -m gzkit justify <current-OBPI-id> --save
```

The walkthrough renders an 8-section scaffold grounded in gathered evidence (matching rules, ledger events, recent commits, related anchors, regression taxonomy). Fill each `_[To be filled]_` block per the `gz-justify` skill's Procedure — no fabrication, every citation grounded in the gathered evidence. Then validate the filled artifact via `uv run -m gzkit justify validate <file>` and cite the artifact path in the subsequent implementer prompts (Stage 2) and in the Key Proof evidence (Stage 4).

The walkthrough takes 3-10 minutes on a clear anchor and 15-30 minutes on an ambiguous one; both are an order of magnitude cheaper than a discarded Stage 2 pass.

When none of the three holds, proceed to Stage 2 and say so in one line naming what you read. When one holds and the walkthrough does not resolve it, stop and ask the operator.

### Stage 2: Implement (skipped by `--from=verify` or `--from=ceremony`)

#### Red-Green-Refactor discipline (binding — applies to BOTH modes below)

Stage 2 is Test-Driven Development, not test-after. The law is absolute:
**no production code without a failing test first**, and the red must be a
**verified** red. This binds the inline path AND every dispatched implementer.

The per-behavior cycle (never batch all tests then implement the whole unit):

1. **RED — write ONE minimal test** for the next single behavior (one behavior
   per test, descriptive name, real code usage, minimal mocking).
2. **Watch it fail — MANDATORY, never skip.** Run the test and confirm all three:
   - it **FAILS** (an assertion fires) — *not* errors, *not* passes;
   - the **failure message matches the expected behavior** (e.g. `SystemExit not
     raised`, `marker not active`, `0 != 1`), not an unrelated message;
   - it fails because **the feature is missing**, not because of a typo or a
     missing import.

   *"If you didn't watch the test fail, you don't know if it tests the right
   thing."*
3. **GREEN — write the simplest code** that makes that one test pass.
4. **REFACTOR** — clean up (duplication, names, helpers) with the bar staying green.
5. Repeat for the next behavior.

**Execute acceptance proof before review (GHI #985).** For the current task's
BEHAVIOR obligations, write a proof specification naming its production source,
full covering test IDs, and exact behavioral substitutions; run
`uv run gz obpi acceptance {OBPI-SLUG} prove --spec {controls.json}`. The command
observes the baseline, activated controls, nominated assertion failures, source
restoration, and restored green run. A runtime error or skipped selector cannot
earn a behavioral kill. SUPPORT and STRUCTURAL-FENCE specifications name only the
REQ; the command executes their existing canonical proof resolvers. The complete
schema is in the acceptance manpage. Historical RED evidence remains useful, but
does not substitute for this current execution record.

The reviewer must still judge whether the oracle and control express the
requirement, exercise production behavior, and include legitimate positive cases.
The command proves recorded observations, not the semantic adequacy of arbitrary
assertions. Do not build an all-assertions classifier or count mutations as coverage.

> **Anti-pattern — the import-error red (the false red).** A first run that
> ERRORs with `ModuleNotFoundError` / `ImportError` / `AttributeError` because
> the module or symbol under construction does not exist yet is **not a verified
> red**. It proves the *module is absent* — it does **not** prove the test's
> assertion bites the behavior, because no assertion ran. A test that can only
> fail on a missing import cannot distinguish "behavior present" from "behavior
> absent" — the tautological/facade smell gzkit exists to kill. **Recovery:** get
> to an assertion-level failure first — create the importable skeleton (define
> the symbol as a no-op stub so the test imports cleanly), then watch each test
> fail on its OWN assertion for the right reason, *then* implement the behavior.
> Seeing the assertion-level red is the cheap negative control proving the test
> is not tautological. Do not report "RGR followed" when the only red observed
> was an import/collection error.

**Check the `--no-subagents` flag first.** If set, skip to the [Inline Fallback](#inline-fallback-no-subagents) below.

#### Subagent Dispatch Mode (default)

1. **Extract plan tasks** from the approved plan file using `extract_plan_tasks()` patterns (headings or numbered items).
2. **Create task list:**
   - Last task MUST be "Present OBPI Acceptance Ceremony" (universal human gate per ADR-0.0.36)
3. **Read brief requirements** — extract the `## Requirements (FAIL-CLOSED)` section from the OBPI brief. These are passed to each implementer as scoped context.
4. **For each plan task** (sequential — one implementer at a time, never parallel):

   a. **Classify complexity** based on allowed file count:
      - 1-2 files → `simple`
      - 3-5 files → `standard`
      - 6+ files → `complex`

   b. **Select model tier:**
      - `simple` → `haiku` (fast, economical)
      - `standard` → `sonnet` (balanced)
      - `complex` → `opus` (most capable)

   c. **Compose implementer prompt** via `compose_implementer_prompt(task, brief_requirements, why=..., project_root=..., extra_context=...)`:
      - Task description from the plan
      - Allowed files from the brief allowlist
      - Test expectations from the brief
      - Brief requirements (the FAIL-CLOSED list)
      - `why` and `project_root` are required keyword arguments (GHI #861). The
        composer emits the `implementer` persona frame from `project_root` and a
        `### Why` block from `why` — AGENTS.md § Behavior Rules — Always #6 makes
        the Why unconditional, so omitting it is a `TypeError`, not a thinner prompt.
      - The Red-Green-Refactor rules, including the `gz arb red` witness, are
        emitted by the composer and match `.claude/agents/implementer.md` — do not
        restate them by hand and do not weaken them
      - **The Red-Green-Refactor discipline above** — instruct the implementer to
        work one behavior per cycle and to report the *verified* red it watched
        (the assertion-level failure message), not merely that it wrote tests.
        A `HandoffResult` claiming RGR whose only red was an import error is a
        red-verification miss — treat it as a review finding in step h.

   d. **Dispatch via Agent tool:**
      ```
      Agent tool call:
        subagent_type: "implementer"
        model: <selected tier from step b>
        prompt: <composed prompt from step c>
        description: "Implement task N: <short description>"
      ```

   e. **Parse HandoffResult** from the subagent output — look for a JSON code block with `status`, `files_changed`, `tests_added`, `concerns` fields.

   f. **Record dispatch** — run `uv run gz obpi dispatch <OBPI-ID> --role Implementer --model <tier> --task <n>`. This appends a `stage2_dispatch_recorded` event to the ledger, which is what Stage 5 credits; the marker's `dispatch_state` is refreshed only as a cache. The credit therefore SURVIVES `--clear-stale` and a relaunch (GHI #886) — it did not before 2026-08-27, and a compliant 3/3 run lost its whole review record to that sanctioned recovery path. **Credit is never inferred** — `gz obpi precomplete` fails closed on an unrecorded Stage-2 dispatch, and the presence of code proves nothing about who wrote it (GHI #845). If this session genuinely cannot dispatch, declare it instead: `uv run gz obpi dispatch <OBPI-ID> --single-driver --reason "<why>"`. Declared single-driver passes Stage 5; silent single-driver does not.

   g. **Handle result status:**
      - `DONE` or `DONE_WITH_CONCERNS` → proceed to **two-stage review** (step h)
      - `NEEDS_CONTEXT` → provide additional context from the brief and redispatch **once**. A second `NEEDS_CONTEXT` is treated as `BLOCKED`.
      - `BLOCKED` → halt Stage 2, record blocker reason, present to user. **Do not continue to the next task.**

   h. **Two-stage review dispatch** (only when implementer returned `DONE` or `DONE_WITH_CONCERNS`):

      Use `should_dispatch_review(status)` to gate this step. Skip review entirely for
      `BLOCKED` or `NEEDS_CONTEXT` results — those tasks did not produce code to review.

      i. **Select review model** via `select_review_model(complexity)`:
         - `simple`/`standard` → `sonnet` (reviews always require judgment — never haiku)
         - `complex` → `opus`

      ii. **Compose spec reviewer prompt** via `compose_spec_review_prompt(task, brief_requirements, files_changed, why=..., project_root=..., acceptance_context=...)`:
         - Includes the task description, brief requirements, and the diff produced
         - Instructs the reviewer: "The implementer may be optimistic. Verify everything independently."
         - `why` and `project_root` are required keyword arguments — the composer emits the `spec-reviewer` persona frame and the Why block from them (GHI #861)

      iii. **Compose quality reviewer prompt** via `compose_quality_review_prompt(files_changed, test_files, why=..., project_root=..., acceptance_context=...)`:
         - Includes changed files, test files, and quality criteria (SOLID, coverage, error handling, cross-platform, Pydantic)
         - The size/complexity criterion is rendered from `.gzkit/rules/complexity-thresholds.json`, never restated as literals (GHI #861)
         - Findings are scoped by the obligation they affect. A mapped defect
           blocks regardless of severity; optional style observations stay
           unmapped and do not create another acceptance requirement.

         **Evidence handoff (GHI #984):** append the paths to the implementer's
         observed execution records to BOTH composed prompts before dispatch.
         Include the relevant requirement, test selector, base/changed revision
         (and dirty-tree source identity), command, actual result, and failure
         traceback for any RED or negative-control claim. If missing, obtain the
         record from the implementer; do not reconstruct it from a summary.
         Reviewers read the records against the code and contract. A green
         coverage inventory does not establish semantic adequacy. Check where
         expected values come from and whether the test exercises production
         behavior; shared-parser agreement is not independent boundary proof.

         Supply `acceptance_context` from
         `uv run gz obpi acceptance {OBPI-SLUG} status --stage stage2 --json`.
         A blocked status before reviews is expected; its payload contains the
         canonical obligations, actual proofs, and retained findings. The composer
         requests exactly one `gzkit.acceptance.review.v1` result in addition to
         the legacy `ReviewResult`. The reviewer uses actual IDs and digest from
         that context and explicitly lists `accepted_proof_ids`; the importer
         assigns the review and receipt IDs. Missing proof is recorded as a
         mapped `missing-proof` finding with no invented proof or approval ID.
         Each approved proof carries a `grounds` citation: the file the reviewer
         read (or the proof's recorded evidence) and an excerpt copied from it.
         The importer refuses an uncited approval from a reviewer whose grant has
         no shell or cannot be resolved (no single plain `--agent` persona, or a
         tool-override flag), and any excerpt that is not where it says (GHI #994). A check
         the reviewer could not perform stays in `verification_gaps`.

      iv. **Dispatch both reviewers concurrently through receipted agent execution.**
         Preserve the selected model, the `spec-reviewer` and `quality-reviewer`
         personas, their read-only tool grants, and separate contexts. Wrap each
         actual reviewer invocation in `gz arb step --max-output-chars -1`; do
         not wrap a command that merely prints previously authored review JSON.
         Native Claude execution is admitted for these Stage-2 roles. For example,
         with the composed prompt saved at the named path:

         ```bash
         uv run gz arb step --name specreview --max-output-chars -1 -- \
           claude --agent spec-reviewer --model sonnet --print \
           'Read /tmp/obpi-spec-review-prompt.md and perform that review.'
         ```

         Use the quality persona and its own prompt for the second invocation.
         Wait for both, parse their legacy `ReviewResult`, and import each actual
         ARB run ID with
         `uv run gz obpi acceptance {OBPI-SLUG} review --receipt {arb-run-id}`.
         A native Agent-tool summary alone does not supply the required receipt.

      v. **Record review dispatches** — run `uv run gz obpi dispatch <OBPI-ID> --role SpecReviewer --model <tier> --task <n>` and the same for `--role QualityReviewer`. Partial dispatch is still SINGLE-DRIVER: the reviewers catch what the implementer cannot see in its own work, so recording only the implementer launders the review that never ran. This records one `stage2_dispatch_recorded` ledger event per
         reviewer, and refreshes the marker's `SubagentDispatchRecord` cache with model, timestamps, and result.

      vi. **Handle review results** via `handle_review_cycle(state, task_index, spec_result, quality_result, acceptance=...)`:
         - Both reviewers pass AND current task-scoped Stage-2 acceptance is ready
           → **advance** to the next task. Obtain that readiness using
           `status --stage stage2 --req {REQ-ID}` (repeat `--req` for this task's
           obligations). Scope selects the readiness decision; the full canonical
           roster and finding history remain retained. Before leaving Stage 2,
           require unscoped `status --stage stage2` to clear every obligation.
         - Any mapped acceptance finding remains blocking until an independent
           closure names its original finding, obligation, and current proof.
         - Critical finding from either reviewer → **fix** — redispatch the implementer with
           the finding as additional context, then re-review after the fix
         - Fix cycles are bounded: maximum 2 fix cycles per task (`MAX_REVIEW_FIX_CYCLES`).
           After exhausting fix cycles → **blocked** — halt Stage 2 and escalate to user.
         - When both reviewers find critical issues, combine findings into a single fix dispatch.

      vii. **Log review concerns** — if `DONE_WITH_CONCERNS` from implementer, pass concerns
         as additional context to reviewers. Accumulate review findings in dispatch state for
         the Stage 4 ceremony.

      viii. **Read `verification_gaps` separately from `findings` (GHI #941).** Reviewers are
         granted `Read, Glob, Grep` and **cannot execute anything**. Both composers now disclose
         that grant — read from the agent definition, so it tracks the file rather than a prose
         claim — and instruct the reviewer to put its tool limitations into
         `verification_gaps`, never into `findings`.

         A reviewer capability gap is **not** a defect and never blocks: `review_blocks_advancement` reads
         `findings`, so a gap kept in its own channel cannot pull a passing review down.
         Measured 2026-09-02 on OBPI-0.35.0-04: a reviewer asked to re-derive a byte span and
         re-run a behave selection could do neither, reported both honestly as `info` findings,
         and returned **CONCERNS** — every finding about its own coverage, none about the code.
         Re-derived by the orchestrator, both checks passed.

         **Do not ask a reviewer to verify by running.** If a check needs execution, run it
         yourself and hand the reviewer the observed output to check *against the code*. A
         non-empty `verification_gaps` is a signal to you, not a verdict: it names what this
         review did not cover, and the coverage is yours to close.

         Distinguish that limitation from an identified defect in REQUIRED proof:
         an import failure described as an assertion failure, a test that never
         calls the required behavior, or an unsupported acceptance claim belongs
         in `findings`, with its requirement and consequence. Do not demote such
         a finding because the reviewer also lacks an execution tool.

5. **Persist dispatch state** after each task completes (success or failure), including review results.
6. **After all tasks complete:** persist dispatch summary for `gz roles --pipeline` queries.

> **Labor-subdivision discipline (GHI #590).** The pipeline mints one
> `seq=01` TASK per REQ as the coarse default bucket. When a REQ's labor was
> genuinely multi-step, subdivide it — `uv run gz task start --seq next` mints
> `seq=02`, `seq=03`, … so the attribution matches the work. When every REQ is
> genuinely one indivisible unit (no labor below the REQ), declare
> `req_atomic:` in the brief frontmatter with inline per-REQ rationale. Make
> this call **here**, where the labor happens — an OBPI that reaches Stage 5
> `seq=01`-only without a `req_atomic:` exemption is blocked fail-closed by the
> task-envelope chokepoint gate (Stage 5 Step 0 / `gz obpi complete`), so
> deferring the decision only stalls completion.

**Abort if:** Any task returns `BLOCKED` after retry or after exhausting review fix cycles. Release lock via `uv run gz obpi lock release {OBPI-SLUG} --force`, create handoff, and stop.

#### Inline Fallback (`--no-subagents`)

When `--no-subagents` is set, Stage 2 runs entirely in the main session (no Agent tool dispatch):

1. Create task list from plan steps (same as above)
2. Follow the approved plan step by step
3. Keep edits inside the brief allowlist and transaction contract
4. Implement each behavior via the **Red-Green-Refactor discipline above**: one
   minimal test → watch it fail on its assertion for the right reason (not an
   import error) → simplest code to pass → refactor green. Use `unittest`,
   `TempDBMixin` for DB, coverage >= 40%. Do not batch all tests then implement.
5. Run `uv run ruff check . --fix && uv run ruff format .` after code changes
6. Run `uv run gz test` after implementation

Record the existing single-driver declaration in the ledger via
`gz obpi dispatch ... --single-driver --reason ...`. That declaration preserves
the Stage-2 spec/quality exception; a marker flag alone does not. Execute the same
proof commands and retain mapped findings. Step 4b remains mandatory, and neither
the declaration nor an inline implementation can close its own acceptance finding.

**Abort if:** Tests fail after 2 fix attempts. Release lock via `uv run gz obpi lock release {OBPI-SLUG} --force`, create handoff, and stop.

**MANDATORY TRANSITION → Stage 3.** Do not summarize. Do not report. Proceed.

### Stage 3: Verify (skipped by `--from=ceremony`)

Stage 3 runs two phases: **baseline quality checks** and **REQ-level verification dispatch**.

#### Phase 1: Baseline Quality Checks

Run the standard quality checks sequentially (these are always inline, never dispatched). Each baseline command is ARB-wrapped so a green Stage 3 result emits canonical attestation receipts at parity with `AGENTS.md` § Attestation (GHI #317):

```bash
# Always — emits arb-ruff-*, arb-step-typecheck-*, arb-step-unittest-* receipts
uv run gz arb ruff
uv run gz arb typecheck
uv run gz arb step --name unittest -- uv run unittest-parallel -t . -s tests --buffer

# If Heavy lane (emits arb-step-mkdocs-* and arb-step-behave-* receipts)
uv run gz arb step --name mkdocs -- uv run mkdocs build --strict
uv run gz arb step --name behave -- uv run -m behave --tags=@REQ-X.Y.Z-NN-MM,... features/
uv run gz validate --documents
```

**Verification exit-code integrity (binding, GHI #589).** NEVER pipe a
verification command through `tail`/`head`/`grep`/`Select-Object`. A shell pipe
reports the *last* process's exit code (the filter's — always 0), masking a
non-zero unittest/behave/mkdocs exit: a green-looking Stage 3 over a red suite.
The harness `Background command … (exit code 0)` notification on a piped command
is the filter's status, not the verifier's — treat it as unverified. The
ARB receipt records the true `exit_status` (GHI #317): after each `gz arb step`,
read the emitted `arb-step-*` receipt and confirm `exit_status == 0` before
advancing to Stage 4. If you must trim console output for readability, redirect
to a file (`> out.log 2>&1`) and read the receipt — never `| tail`.

**Scope discipline (GHI #160, #185, #420).** At OBPI Stage 3, the
runtime resolves this OBPI's `@REQ-...` behave tags via
`resolve_obpi_behave_tags` and scopes the behave invocation to those
tags. When the OBPI has no @REQ-tagged scenarios, behave is omitted
from Stage 3 entirely — the full `features/` sweep is deferred to ADR
closeout (Stage 5 of the parent ADR), where cross-OBPI interactions
are caught. Pre-commit hooks (ruff + ty + unittest) still run on every
commit, so the full unittest suite isn't bypassed — it's just not
re-run synchronously at every OBPI increment. Heavy-lane BDD runs via
`gz test --bdd` at ADR closeout.

If any baseline check fails, attempt fix and re-verify once. If still failing, release lock via `uv run gz obpi lock release {OBPI-SLUG} --force`, create handoff, and stop.

#### Phase 1b: REQ → @covers Parity Gate (#113)

Every **BEHAVIOR** REQ defined in the brief MUST be reachable from a `@covers` reference in the test tree. The pipeline does not advance to Stage 4 until parity holds for those REQs.

**Proof channels are per-kind (ADR-0.0.59).** SUPPORT REQs are proven by a ledger event plus a structural validator scope; STRUCTURAL-FENCE REQs by a parent-ADR `## Boundary Invariants` entry. Neither carries a `@covers` test. **Never author a unit test merely to make a non-BEHAVIOR REQ appear covered** — that is the exact category error ADR-0.0.59 exists to prohibit (GHI #531 -> #571).

```bash
uv run gz covers {OBPI-SLUG} --json
```

Read the JSON output. **If `summary.uncovered_reqs > 0`, parity has failed.** The list of unreachable REQs is in `entries` (each entry with `covered: false`).

When parity fails:

1. Identify each uncovered REQ.
2. Add a `@covers REQ-X.Y.Z-NN-MM` reference in the relevant test — either as a decorator (`@covers("REQ-X.Y.Z-NN-MM")`) or in the test docstring (`@covers REQ-X.Y.Z-NN-MM`). Both forms are detected by the canonical scanner (#120).
3. Re-run `uv run gz covers {OBPI-SLUG} --json` and confirm `uncovered_reqs == 0`.

The Stage 4 evidence template requires the `@covers` location for every REQ — the parity gate makes that requirement mechanical instead of aspirational.

**Anti-pattern:** Filling in the Stage 4 REQ coverage table without first running the parity gate. The table is verified evidence, not author-attestation prose.

#### Phase 1c: RED Falsifiability Witness (GHI #642)

Phase 1b proves every REQ **has** a covering test. It never proves that test **can fail**. A BEHAVIOR test authored after the production code, passing on its first run, is byte-indistinguishable from a genuine RED-first test — `@covers` parity, the two-stage review, and Gate 5 all accept both identically.

For each **BEHAVIOR** REQ in the brief (SUPPORT and STRUCTURAL-FENCE REQs are exempt by proof channel — they carry no `@covers` test):

```bash
uv run gz arb red --req {REQ-ID} --obpi {OBPI-SLUG}
```

This reconstructs the base tree in a throwaway git worktree, copies in **only** the test files, and runs the covering test there. The production hunks are deliberately withheld — that asymmetry is the experiment. It emits an `arb-red-<REQ>` receipt and a `red_receipt_emitted` ledger event.

| `failure_class` | Meaning | Action |
|---|---|---|
| `assertion` | Strong RED — the test failed on an assertion | Proceed |
| `error` | Weak RED — failed for the wrong reason (usually a not-yet-existing symbol) | Proceed **only on a `working-tree` base**; on a `reconstructed` base it is inconclusive (below). **Never** report it as an assertion RED |
| `none` | The test PASSED without its implementation | **Blocking.** Rewrite the test to assert the REQ's semantics, then re-run |
| `not-applicable` | Nothing was withheld and no earlier tree could be reconstructed | **Non-blocking.** NOT a finding about the test; do **not** rewrite it. Note in the evidence that the witness did not run |

Every receipt and ledger event also records a **`base_provenance`**, and it changes what `error` means:

| `base_provenance` | Which tree | What `error` means there |
|---|---|---|
| `working-tree` | HEAD, with the production change still uncommitted | A weak RED. The withheld hunk is the only difference, so the import can only have failed for the missing implementation |
| `reconstructed` | The parent of the commit that introduced the covering test | **Inconclusive — not a RED and not an accusation.** The tree can be months older than the test, so the import is as likely to have failed on unrelated drift |

A `none` verdict means the test cannot fail when the business logic changes (AGENTS.md § DO IT RIGHT Rule 6), so it witnesses nothing. `uv run gz validate --red-parity`, a bound `gz check` step, re-audits this repo-wide past the cutover.

**On the `--from=verify` path the witness now runs, against a reconstructed base (GHI #849).** It used to be inert there: the base was HEAD, so once the production code landed the base tree already carried the implementation, nothing was withheld, and every BEHAVIOR REQ returned `not-applicable` — the pipeline mandating, on its own already-implemented path, a check that structurally could not execute. The base is now resolved from the commit that introduced the REQ's covering test, whose parent predates both the test and its implementation.

**What that path can and cannot conclude.** A `none` is a real finding on either base — a test that passes against a tree without its implementation cannot fail, whichever tree it was. An `error` is not: see the provenance table above. So `--from=verify` can still catch a hollow test, and cannot bank a weak RED. `not-applicable` survives for the case where no earlier tree exists at all (the `@covers` string is absent from the test tree's history, or the commit that introduced it is a root commit).

Read a non-verdict as *the experiment had no premise*, never as a verdict — a `none` that means "I could not run the experiment" must not share a name with a `none` that means "your test cannot fail", and the locally obvious response to ten blocking verdicts (rewrite ten passing tests until the witness goes quiet) weakens real assertions to satisfy a degenerate experiment. The strongest evidence still comes from running the witness **while the work is in flight**, before it lands: that is the only condition under which `error` counts for anything.

**Anti-pattern:** Treating the `error` class as equivalent to `assertion`. An ImportError proves only that the symbol is absent — not that the test asserts the REQ's semantics.

The historical RED classifications above do not determine acceptance readiness.
The current `acceptance prove` record supplies the executed behavioral control;
spec and quality reviewers judge its adequacy. Preserve RED receipts without
promoting a weak or unavailable experiment into a current proof.

**Behavioral evidence remains necessary (GHI #984).** The provenance rules above
classify the RED experiment; they do not certify the covering test's assertions.
Where that experiment cannot demonstrate the required behavior, inspect the
test and use a focused behavioral control for the unresolved claim under
`.gzkit/rules/tests.md`. Preserve its green baseline, activated change, selector,
actual failure and restoration result. A non-zero run does not identify the
guard or assertion that failed. If a claim names that assertion, inspect the
traceback; if another guard masks it, repair the fixture. Do not infer exclusive
detection or comparative strength, and do not build an all-assertions classifier
to satisfy a requirement that asks for production behavior.

#### Phase 2: REQ-Level Verification Dispatch

**Check the `--no-subagents` flag first.** If set, skip to the [Inline Verification Fallback](#inline-verification-fallback) below.

After baseline checks pass, dispatch parallel verification subagents for the brief's requirements:

1. **Extract verification scopes** from the brief using `prepare_stage3_verification(brief_content, test_paths)`. Each numbered `REQUIREMENT:` line becomes one `VerificationScope`.

2. **Analyze path overlap** — the `VerificationPlan` partitions requirements into independent groups:
   - Requirements with **non-overlapping test paths** are placed in **separate groups** (can run in parallel).
   - Requirements with **overlapping test paths** are merged into the **same group** (must run sequentially within a single subagent).
   - **NEVER dispatch parallel verification for overlapping file paths** — data corruption risk.

3. **Dispatch strategy selection:**
   - `parallel` — all groups are singletons (fully parallel dispatch)
   - `mixed` — some groups have multiple REQs (parallel between groups, sequential within)
   - `sequential` — single group or no test paths (fall back to inline)

4. **For each independent group** (concurrent dispatch using `run_in_background: true`):

   a. **Compose verification prompt** via `compose_verification_prompt(group_scopes, group_label=...)`. Each subagent receives:
      - Requirement text for each REQ in the group
      - Test file paths to run
      - Expected pass criteria
      - Current branch state (included in prompt context)

   b. **Dispatch verification subagent:**
      ```
      Agent tool call:
        subagent_type: "general-purpose"
        isolation: "worktree"
        run_in_background: true
        prompt: <verification prompt from step a>
        description: "Verify REQ group N"
      ```

   c. Worktree cleanup is **automatic** — the Agent tool cleans up the worktree when the subagent completes or fails. No orphaned worktrees.

5. **Wait for all verification subagents to complete.** All subagents MUST finish before Stage 3 advances.

6. **Parse and aggregate results:**
   - Parse each subagent output via `parse_verification_results(agent_output)`.
   - Aggregate via `aggregate_verification_results(results, expected_req_indices)`.
   - Create dispatch records via `create_verification_dispatch_records(plan, results)` and persist in the pipeline marker.

7. **Record timing metrics** via `compute_verification_timing(start_ns, end_ns, strategy, group_count)`. Always record wall-clock time savings from parallel vs sequential execution.

8. **Handle aggregate results:**
   - All REQs pass → advance to Stage 4.
   - Any REQ fails → attempt fix and re-verify once. If still failing, release lock via `uv run gz obpi lock release {OBPI-SLUG} --force`, create handoff, and stop.

#### Inline Verification Fallback

When `--no-subagents` is set, or when the verification plan strategy is `sequential`:

1. Run each brief-specific verification command sequentially inline.
2. Run any commands from the brief's Verification section.
3. Record all outputs as evidence.

No subagent dispatch, no worktree isolation, no parallel execution.

**Abort if:** Any verification fails. Attempt fix, re-verify once. If still failing, release lock via `uv run gz obpi lock release {OBPI-SLUG} --force`, create handoff, and stop.

**MANDATORY TRANSITION → Stage 4.** Do not summarize. Do not report. Proceed.

### Stage 4: Present Evidence

Stage 4 is three steps and one bounded loop:

```
4a author the packet → 4a-v replay the packet → 4b independent review → pass condition met?
   ├─ yes → present 4a + 4a-v + 4b together → await attestation
   └─ no  → ONE repair round (§ The review window) → re-enter at 4a
```

You do not get to skip 4b because your own evidence looks green. Your evidence
looking green is *why* 4b exists. You are not the exception. The sequence admits
no size, lane, or kind exception. An earlier turn or equivalent successful execution
does not invalidate review by itself; the acceptance consumer decides applicability.

#### The pass condition — stated once

**One authority.** Stage 4 passes when this command reports ready, and only then may
attestation be solicited:

```bash
uv run gz obpi acceptance {OBPI-SLUG} status --stage stage4 --json
```

Everything under this heading explains that verdict. None of it is a second test to
satisfy separately, and the rest of Stage 4 does not restate it. If this prose and the
command disagree, the command governs and the disagreement is a defect to report.

**Binding rule:** No OBPI reaches attestation without an independent adversary
review applicable to its current proof claims, with explicit closure of required
findings. A Stage 4 that solicits attestation while required Step 4b approval or
closure is missing violates the gate. A turn boundary alone requires no repeat. There is no
OBPI too small, too authoring-only, or too obviously-correct to exempt — those
are the precise descriptors that precede a skipped check.

**Step 4b closes on independent confirmation of the corrected state (operator ruling 2026-09-05).** Operator verbatim: "I think we want that as a matter of course moving forward. I don't want 12 iterations like with OBPI-0.35.0-04, but I don't think we should attest without the fixes creating a clean adversarial (4b) review."

The prior 2026-09-03 rule said: "A round returning no critical and no high IN-SCOPE findings converges the gate." This ruling supersedes that severity-only stopping condition: do not solicit completion attestation while any finding against the agreed OBPI requirements remains unresolved, or while a claimed fix has only the implementing agent's confirmation. A non-refuting verdict on the earlier state does not independently verify later repairs, including repairs to evidence or missing witnesses.

**Readiness is derived; historical verdicts are immutable (GHI #985).** Run:

```bash
uv run gz obpi acceptance {OBPI-SLUG} status --stage stage4 --json
```

Readiness requires current valid proof, accepted spec/quality/adversarial reviews
for every obligation, and independently verified closure of all mapped findings.
The ledger-backed single-driver declaration retains its documented Stage-2
spec/quality exception; proof and Step-4b closure remain mandatory.
An unchanged obligation cannot lose its finding because an auxiliary audit was
removed. An edit confined to recognized history sections does not invalidate executable proof.
The earlier GHI #964 standing-line convention is superseded as a gate: preserve
existing historical words, but never edit a Markdown verdict to change readiness.
`gz obpi precomplete` and completion consume the durable records.

**Clean means no unresolved in-scope findings, not absence of all limitations.** Accepted residual risks and future ADR-wide obligations remain disclosed separately; they are not failed present-tense OBPI requirements. Filing a GHI alone does not discharge an unmet requirement. A newly proposed boundary change requires operator ruling and independent revalidation; the implementing agent cannot move a finding outside scope to clear the gate. Before soliciting attestation, the retained independent review set must supply applicable approval and explicit closure on the corrected proof claims. Raw verdict wording alone neither grants approval nor reopens a closed obligation. Scope-boundary disclosures may remain, provided the adversary distinguishes them from unresolved findings.

Do not redispatch merely to remove harmless caveat wording after independent
closure is established. If a follow-up exposes the same root cause again, use
the design-escalation rule below. The runtime checks proof identities, current
input digests, and explicit closure links. Whether an oracle or control fulfills
the requirement remains the independent reviewer's judgment.

**Act on mapped findings before attestation.** A current counterexample or missing
required proof returns its obligation to repair, regardless of verdict or severity.
After repair, execute proof and import independent closure. Historical refutations
and auxiliary commentary with no unmet obligation do not require another repair or
review. Retain their provenance and present the applicable review set alongside
Step 4a; never relabel a required gap as auxiliary to clear acceptance.

**The gate consumes current closure (GHI #985).** A resolution string or new
caller-supplied verdict cannot discharge an open finding. Repair its obligation,
execute current proof, and import independent closure before soliciting
attestation. Historical refutations do not block a correctly closed current
state merely because their original words remain in the brief.

#### The review window, the bound, and the exit

**Know what stales a review before you touch anything.** Proof and review currency is
ONE digest over the whole audited population — `src/`, `tests/`, `features/`, `data/`,
`scripts/`, the canonical rules and schemas, the workflows and config files — plus the
brief's contract (`acceptance_execution.input_digest`). It is not per-obligation. Any
edit under those paths, however small and however unrelated, stales EVERY proof and
EVERY imported review for this OBPI at once. Edits confined to the brief's recognized
history sections and its Change Log do not move it. This is why an unplanned edit
between 4a and 4b costs a full round, and why the order below is binding.

**A round reviews a frozen subject.** Before dispatching 4b — first round or follow-up:

1. Finish every edit. Nothing is left to tidy afterwards.
2. Run `acceptance prove` for every obligation (a repair stales them all; see above).
3. Refresh the packet (`gz obpi present-evidence`) and replay it (Step 4a-v).
4. Record `git status --short` and the reviewed revision in the dispatch prompt.
5. Dispatch. From dispatch until the review is imported, edit nothing under the
   audited population: not the source, not a test, not a data file, not a rule, and
   do not `gz git-sync` in other work. An edit inside the window spends the round.

**When a round returns findings, run ONE repair round.** Collect all of them first.
Repair them as one batch (§ Repair the whole obligation, below). Then repeat steps 1–5
once, dispatching the focused follow-up described below — never one dispatch per
finding, and never a fresh unrestricted review.

After fixing a finding, obtain a focused independent Step 4b follow-up. Supply the prior findings, the changed artifacts and evidence, and the unchanged scope/threat-model boundary. The adversary must verify each claimed closure and check the affected requirements for regressions; it must not restart an unrestricted search for stronger guarantees. Record the actual new verdict and receipt, with each prior finding's disposition and demonstrated evidence. Preserve earlier rounds as history. Never request a preferred verdict or relabel `CORROBORATED-WITH-CAVEATS` as clean yourself.

**Repair the whole obligation before redispatch (GHI #984).** Carry the accepted
finding back through the existing implementation and verification stages. Trace
the relevant producers, consumers, fresh/retry paths, early returns and cleanup;
use the states needed by the requirement, not an unlimited neighboring audit.
A retry retaining its journal does not prove it restores durability before
witnessing. A rejection does not prove which guard caused it. Recheck affected
controls when a repair adds another guard that could mask them. Preserve the
complete finding and the execution record beside its closure, rather than
replacing it with a new explanatory claim.

For an operator-initiated OBPI, authorized corrections continue within its
approved scope; the initiation rule is not a new permission checkpoint for
each finding. A real requirement/allowlist/threat-model amendment still needs
the existing operator ruling. Repeated roots trigger the design escalation
below, not a round cap or a waived defect. Auxiliary diagnostic errors may be
removed from the acceptance argument when no requirement depends on them;
retain their history and every still-relevant finding. If required proof does
depend on the diagnostic, repair and verify it before using its result.

**The loop is bounded, and it has an exit.** Two focused follow-up rounds after the
first Step 4b round is the limit — three rounds in all (operator ruling 2026-09-18,
verbatim: "N=2"). When the limit is reached with a mapped finding still open, or when a round repeats the
prior round's root (next paragraph), stop dispatching:

```bash
uv run gz obpi block {OBPI-SLUG} \
  --reason "<which findings remain open, and after how many rounds>" \
  --next-action "<the design or scope decision the operator owes>"
```

Then present the open findings, what each round changed, and one recommendation. This
is the second legitimate stop in the pipeline (§ The Iron Law), not a premature summary:
another round without a decision is the failure, and recording that a human is needed
is the honest act (§ Blocked on the Operator).

**When a round repeats the prior round's ROOT, stop dispatching and escalate the DESIGN (operator ruling 2026-09-03).** Compare each round's `Weakest point` against the last. If it names the same root cause at a different surface, another fix cycle will surface it again one layer deeper: stop, and put the design decision to the operator (§ Behavior Rules — Always #9). Measured: rounds 2, 3 and 4 each patched a different surfacing of one root cause — provenance inferred from a witness's self-consistent claims rather than chained to prior ledger state — at roughly 3h per cycle; the operator ruled the design in a single exchange and it closed in one pass. Round 4's fix also INTRODUCED round 5's critical, which is the signature of patching a surfacing rather than the design.

#### Normal Mode — HUMAN GATE

**Trigger:** "Present OBPI Acceptance Ceremony" task becomes next pending. Mark `in_progress`.

**Narrator dispatch** (per § Persona Dispatch). Stage 4 evidence composition is the narrator's function — "evidence-to-decision," "operator-value-framing," "every word load-bearing." Dispatch a `narrator` subagent with the populated template fields (Value Narrative input, REQ coverage table from Stage 3, ARB receipts from quality gates) and instruct it to render the final attestation surface per the template below. The narrator dispatch has no recording channel yet (§ Persona Dispatch, GHI #846); do not improvise one through `SubagentDispatchRecord`, which accepts Stage-2 roles only.

**Compose from inspected evidence (GHI #984).** Give the narrator the actual
artifact paths and observed results, including limits, not only a success
summary. Keep one current REQ proof table; preserve prior rounds separately as
history tied to their reviewed state. The Proof cell states what the observation
establishes and any unresolved limitation. Do not invent counts, line references,
failure mechanisms, independence claims, or claims about the remedy's benefit.
Tool-emitted output establishes only what its method measures. Interpretation
must be identified as interpretation and supported; reproduction alone does not
make its method sound. Expected output may explain a planned demonstration but
cannot substitute for observed output in the Key Proof of completed work.

Load `gz obpi acceptance {OBPI-SLUG} status --stage stage4 --json` and give the
narrator its canonical obligations, current proof IDs, open findings, and imported
review records. The missing adversarial review blocks final readiness until 4b
runs; it does not prevent preparing 4a. Cite those proof IDs in the REQ table.
Preserve their executed payloads rather than reconstructing another evidence
classification. Packet replay remains a separate check of displayed transcripts.

Run `gz obpi present-evidence {OBPI-SLUG} --json` to generate Step-4a review input.
It exits 3 on proof blockers; when Stage-2 proof is ready and only Step-4b closure
is pending, it exits 0 with `attestable: false` and separate `review_blockers`.
Give that packet to the adversary. Do not confuse successful packet generation
with permission to solicit attestation. After importing independent approval and
finding closure, refresh the packet; final validation requires both proof and
review blockers to clear.

Present evidence using the **exact template below**. This is the human's attestation surface — they cannot provide attestation without seeing this output. Every field is mandatory. Do not omit, reorder, or freeform this.

**Required output template:**

```
## Stage 4: Present OBPI Acceptance Ceremony (Normal Mode — HUMAN GATE)

**1. Value Narrative**

<What problem existed before this OBPI? What capability exists now? 2-3 sentences.>

**2. Key Proof**

<One concrete command and its observed output. Cite the executed proof record.
Expected output is not evidence of completed work.>

**3. Evidence**

**Quality checks:**

> **Rendering rule (GHI #293):** Markdown table cells overflow in Claude
> Code's renderer when ARB-wrapped invocations embed their nested
> sub-command (e.g. `uv run gz arb step --name unittest -- uv run -m
> unittest discover -s tests/governance`). When a Command cell would
> exceed ~40 characters, hold a short label in the cell (e.g. `arb:unittest`,
> `arb:ruff`) and render the full incantation in a fenced code block
> beneath the table. Place the receipt ID in the Result cell. Operators
> attest against this table — overflow erodes legibility.

| Check | Command | Result |
|-------|---------|--------|
| Tests | `arb:unittest` (see below) | <N>/<N> pass — receipt `arb-step-unittest-<id>` |
| Lint | `arb:ruff` (see below) | clean — receipt `arb-ruff-<id>` |
| Typecheck | `arb:typecheck` (see below) | clean — receipt `arb-step-typecheck-<id>` |
| OBPI tests | `arb:unittest-scoped` (see below) | <N>/<N> pass — receipt `arb-step-unittest-<id>` |
| <brief-specific> | `<short-label>` (see below) | <result> — receipt `<id>` |

```bash
# arb:unittest — full unittest sweep
uv run gz arb step --name unittest -- uv run unittest-parallel -t . -s tests --buffer

# arb:ruff — lint
uv run gz arb ruff

# arb:typecheck — static type check
uv run gz arb typecheck

# arb:unittest-scoped — OBPI-scoped tests
uv run gz arb step --name unittest -- uv run -m unittest tests.<test_module> -v
```

Short Command cells (under ~40 characters, e.g. `uv run gz lint` or
`uv run gz test`) may stay inline without a fenced block — the rule
fires only when the incantation would overflow.

**Files created:**
- <path> (<description>)

**Files modified:**
- <path> (<description>)

**REQ coverage:** (every row populated; every cell concrete; BEHAVIOR rows verified by Stage 3 Phase 1b)

> **Rendering rule (GHI #301, generalizes GHI #293):** The REQ coverage
> table is the canonical single form — render it as a markdown table and
> nothing else. Do **not** append a plain-text labeled-list duplicate of
> the same rows beneath the table. When Claude Code's renderer truncates
> a cell, the fix is to shorten the cell, not to add a second rendering.
> When any cell (Mechanism, `@covers` location, Test Coverage) would
> exceed ~40 characters — long structural assertions like
> `test -d .gzkit/chores/<slug>/proofs && test -s ...`, multi-flag `uv run` invocations,
> file lists, or SHA-bearing paths — hold a short label in the cell
> (e.g. `req-01:absence-check`, `req-02:scoped-tests`) and render the
> full incantation in a single fenced code block beneath the table,
> keyed by the same label. One render, one form. The labeled-list
> fallback is the operator-confusion vector the rule exists to prevent.

| REQ | Kind | Mechanism | Proof location | Proof | Result |
|-----|------|-----------|----------------|-------|--------|
| REQ-X.Y.Z-NN-01 | BEHAVIOR | <function/mechanism> | `tests/<file>.py:<line>` or `TestClass.test_method` | <test class> (N tests) | Pass |
| REQ-X.Y.Z-NN-02 | SUPPORT | <artifact> | `<ledger event type>` + `gz validate --<scope>` | event cites path; validator admits shape | Pass |
| REQ-X.Y.Z-NN-03 | STRUCTURAL-FENCE | <invariant> | parent-ADR `## Boundary Invariants` anchor | audited at ADR closeout | Pass |

If any row uses short labels for long cells, expand them in a single fenced block immediately after the table:

```text
# req-01:scoped-tests — REQ-X.Y.Z-NN-01 (BEHAVIOR) Proof
uv run -m unittest tests.<module> -v

# req-02:support-proof — REQ-X.Y.Z-NN-02 (SUPPORT) Proof
uv run gz validate --<scope>   # structural validator admits the artifact's shape
```

The **Proof location** column is proof-channel specific, not always `@covers`. For BEHAVIOR REQs cite the `@covers` test location; for SUPPORT REQs cite the ledger event type/path and the `gz validate` scope; for STRUCTURAL-FENCE REQs cite the parent-ADR `## Boundary Invariants` anchor. A missing BEHAVIOR `@covers` location is a blocker — the Phase 1b parity gate will fail and the pipeline will not advance. **A non-BEHAVIOR REQ must never be forced into a unit test to fill the cell** (GHI #571).

**4. Awaiting attestation.** Do NOT proceed to Stage 5 until human responds.
```

**Every field above MUST be populated.** Do not skip the evidence table. Do not skip REQ coverage. Do not skip files created/modified. The human needs all of this to make an attestation decision. **This template is Step 4a — the agent's presentation. It is necessary but not sufficient: an agent authoring its own evidence is the GHI #643 fabrication surface. Step 4b is mandatory before attestation.**

#### Step 4a-v — Re-run the packet's own transcripts (GHI #942)

Write the composed packet to `.gzkit/evidence/<OBPI-ID>.stage4a.md`, then run it back
through the tool before presenting it:

```bash
uv run gz obpi verify-packet .gzkit/evidence/<OBPI-ID>.stage4a.md
```

Exit 0 = VERIFIED. Exit 3 = a pasted line did not reproduce; repair the packet and
re-run. Present the verdict alongside 4a and 4b — the operator attests against all
three.

**Why replay is separate from Step 4b.** 4b re-derives the *claim* from the REQs and
the repository; historically it was not handed the *packet*, so a fabricated transcript passed an
adversary that never looks at it. Observed 2026-09-02 on OBPI-0.35.0-04: a `$` block
rendered `gz covers --json` output with keys the command does not emit (`obpi_id`,
`coverage_pct`) around figures that were themselves correct — the numbers came from
the dispatch prompt and the *evidence was constructed around them*. It was caught only
because a human happened to re-run the commands.

**The authoring contract.** A `$` prompt is a claim — *"I ran this and this came
back"* — and every one of them is re-executed:

- **Paste only what the command produced.** The comparison is containment, so
  abridging and re-indenting are fine; a line the command never wrote is a blocker.
- **Elide what cannot reproduce.** A timestamp or a freshly minted receipt id is
  written `...`, never pasted. Pasting an unreproducible line is what fails.
- **Cite proof commands as transcripts.** A command offered as proof of a REQ goes
  under a `$` prompt with its output. The second half of the same observed instance
  was a REQ-08 proof command that returns nothing when run — as a transcript that is
  a `witnesses nothing` blocker; left bare it is only reported.
- **For a silent assert-shaped probe, show the status:** `$ <cmd>; echo "exit $?"`.
  The exit code is the information the reader needs, and it reproduces. **Then paste the
  line it emits.** The probe moves the status out of the process — the shell exits 0
  because `echo` succeeded — so a packet that appends the probe and omits the `exit 1` it
  printed is presenting a failing command as success again. Omitting it is a blocker, and
  `...` does not satisfy it: elision is for output that cannot reproduce, never for output
  you would rather not show.
- **A fenced shell block with no `$` claims no output** and is never re-run — that is
  what the `arb:` incantation blocks are, and their result is carried by the receipt
  rows. They are listed back as citations so the operator sees what was *not*
  witnessed.

- **A failing command cannot be presented as success.** A non-zero exit is a blocker
  outright, because containment alone lets a packet quote only the success lines of a
  command that failed — every quoted line reproduces, and the omission is the lie. To
  show a RED run, use the status form above **and paste the status it prints**; the
  convention is not the enforcement, and the check holds you to both halves.
- **Do not pipe a verifier into a filter.** The shell reports the filter's status, so a
  failing suite would replay green. Use `set -o pipefail` or redirect to a file.

**Reach, stated plainly.** Replay verifies *displayed output against command output*. It
does **not** prove the packet is **complete** — that the transcripts chosen are the ones
the REQs needed — nor that its **interpretation** is correct, that a reproducing command
demonstrates what the prose around it claims. It does not verify a command cited without a
prompt, and the piped-verifier guard covers recognized verifiers only, not every generic
command piped without `pipefail`. Those judgments stay with Step 4b and the operator.

#### Step 4b — Independent Adversarial Validation (GHI #643) — MANDATORY, NON-SKIPPABLE

**WHY THIS GATE EXISTS AT ALL — and why the second model is CODEX specifically.**

GHI #643: an agent presented Stage-4 evidence claiming the OBPI's central deliverable was
built and working. It was not built. `run_meta_validator()` discovered **zero** production
claims, and that was found only at Stage 5 by an operator who happened to run the brief's own
`## Demo`. **The operator had already attested "completed" against the fabricated evidence.**
No mechanical gate fired. Operator verdict, verbatim: *"you have outright lied, repeatedly, at
the pipeline's stage 4."*

That is the whole reason this gate is here. Step 4a is a model reporting on its own work, and
a model that has fabricated a deliverable will fabricate the evidence for it just as fluently.
So a SECOND model, which did not write the code and has no stake in the story, goes and checks
whether the thing actually exists and actually works.

**Codex, because a Claude checking Claude is the same eyes twice.** The failure mode is not
carelessness — it is a shared blind spot. An independent Claude subagent has a fresh context
and still shares this model's priors about what "looks right", so it can miss exactly what
this model missed. A different vendor's model does not inherit those priors. That is the only
reason the tier order is binding rather than a preference: tier 1 is not "the better tool",
it is *the different eyes*.

Read those two paragraphs before writing a Step-4b prompt. Every prompt this skill has
produced was built from the § Dispatch contract below, so an error there reproduces perfectly,
round after round, in every OBPI — and the one that sat there for ten weeks inverted the
purpose above into its opposite.

**What Step 4b is FOR (read this before writing any prompt).**

**Step 4b is an ACCEPTANCE REVIEW.** The decision it supports is: *"Does this
implementation correctly fulfill the bounded requirements?"* Therefore its purpose
must be independent corroboration.

Refutation remains essential, but as a METHOD:

1. Re-derive the claim independently.
2. Try hard to falsify it within the declared scope.
3. Demonstrate the intended behavior positively.
4. Corroborate only if both directions hold.

> **Purpose:** independently corroborate correctness.
> **Method:** adversarially attempt to falsify it.
> **Boundary:** the brief, requirements, and threat model.
> **Pass condition:** positive behavior demonstrated and independent closure of findings
> against the agreed requirements and their required proof, per § The pass condition above.

That gives the operator EVIDENCE FOR AN ATTESTATION DECISION, instead of an
unbounded argument.

**THE REVIEWER REPLAYS IN A DISPOSABLE WRITABLE CHECKOUT (operator instruction 2026-09-10,
GHI #961).** Step 4b judges executed proof, and judging it by reading is weaker than judging it
by running it. `codex-companion.mjs:414` still pins `sandbox: "read-only"` on the
`adversarial-review` path, so that subcommand can never replay a mutation. **It is no longer the
Step-4b dispatch.** The `task` path honours `--write` (`codex-companion.mjs:491`,
`sandbox: request.write ? "workspace-write" : "read-only"`), takes `--cwd`, and accepts
`--prompt-file`.

Point that writable sandbox at a THROWAWAY COPY of the reviewed source, never at the operator's
tree. Build it first:

```bash
uv run gz obpi adversary-workspace <OBPI-ID>
```

It copies tracked content at `HEAD` plus uncommitted modifications, prints a `source_digest` over
the materialized file set, and prints the dispatch below already pointed at the checkout. Measured
2026-09-10 under `task --write --cwd <checkout>`: `tempfile.mkdtemp()` returns a real path, a write
inside the checkout succeeds, and a write to the active repository is **refused**
(`operation not permitted`). The active checkout is protected by the sandbox boundary itself, not
by asking the reviewer nicely.

Dispatch, ARB-wrapped as always:

```bash
uv run gz arb step --name codexadversary --max-output-chars -1 -- \
  node "$HOME/.claude/plugins/cache/openai-codex/codex/<ver>/scripts/codex-companion.mjs" \
  task --write --cwd <checkout> --prompt-file <prompt.md>
```

Tier 1 is unaffected: the argv is still `node .../codex-companion.mjs`, `receipt_proves_cross_vendor`
walks past `node`, and `data/mandated_tier1_dispatch.json` declares this surface alongside the
read-only one. The plugin remains the only tier-1 dispatch surface — this changes which of its
subcommands Step 4b uses, never the vendor boundary, and `codex exec` stays FORBIDDEN.

**Ask for the replay, and take nothing on faith.** Instruct the reviewer to run the baseline, apply
the recorded substitution, observe the named test fail on its OWN ASSERTION, restore the source
byte-identically, and re-run. It reports those observations in a `replay` array on its
`gzkit.acceptance.review.v1` object — obligation, proof id, workspace digest, selectors, mutation
label, three runs, and the source digest before and after. Ingestion REFUSES a claim the record
does not support: a baseline that was not green, a failure whose class is `error` rather than
`assertion`, a selector that executed zero tests, a record with no substitution, a source not
restored, or a digest naming some other tree. Absence of the block claims nothing and is left
alone — silence is honest, only a claim is checked. Delete the checkout when the round ends.

**This constrains a technique. It does not lower the pass condition.** GHI #643's fabricated
evidence was entirely plausible ON READING — that is what made it fabricable, and running the
Demo is what exposed it. Demonstration is the point of this gate. An agent that softens
*"positive behavior demonstrated"* because some technique is unavailable has confused the
reviewer's tooling with the gate's objective, which is the same error the June-24 framing made
in the other direction.

**The June-24 framing confused the reviewer's TECHNIQUE with its OBJECTIVE.**
*"Your job is to refute"* creates a motivated critic that can continually expand
the attacker, or move outside the requirements, until something fails. It also
produces the exact measured pathology: on OBPI-0.35.0-04, six rounds tested
refusal paths while none demonstrated that the feature still worked.

Step 4a is authored by the same agent that may have fabricated it, so Step 4b does
not take 4a's word for anything: it RE-DERIVES the completion claim from the REQs
and the repository and must paste observed command output for every check. The
operator attests holding **both** outcomes.

**Confirmation must be EARNED, not inferred from silence.** "I looked and found nothing" is
not a confirmation. A corroborating round demonstrates, with pasted output, that each guard
FIRES when it should AND does NOT fire when it should not — a legitimate operation still
succeeds. A guard never made to fire is a guard not confirmed; a refusal path never exercised
with a legitimate input is a false-positive risk not ruled out. Ask for both
directions explicitly: six rounds on OBPI-0.35.0-04 tested refusals and not one asked whether the
feature still worked.

**The adversary must satisfy three properties AND be selected by the binding tier order.** The properties — *independent context + does-not-trust-4a framing (re-derive from the REQs and the repo; adversarial probing as method) + evidence-backed (pastes real command output)* — are necessary but NOT sufficient: "independent context" does not make the vendor interchangeable. A Claude validating Claude satisfies all three properties and still shares this agent's failure modes, which is the exact blind spot Step 4b exists to break. Vendor order is therefore **binding, not advisory** — you do not get to pick a lower tier because it is the frictionless path.

**Tier order (binding). You MUST attempt tier 1 and may only drop a tier after establishing its precondition:**

1. **Codex** — REQUIRED FIRST: a different-vendor model shares none of this agent's blind spots. **Dispatch through the Codex plugin's writable `task` path, exactly as shown in § THE REVIEWER REPLAYS IN A DISPOSABLE WRITABLE CHECKOUT above** — it is the only plugin subcommand that can replay a mutation, and the only one that returns the reviewer's output verbatim, which the acceptance importer requires. `/codex:adversarial-review` remains a mandated tier-1 surface (`data/mandated_tier1_dispatch.json`) and its receipt proves the tier, but it is read-only and renders a summary, so a review object authored through it does not survive transport (observed 2026-09-10: import refused, `exit 3`). Do not use it for Step 4b. Run the dispatch in the **foreground** (a single blocking call — no polling; use `timeout: 600000`, reviews run ~7–8 min); if you detach it, read progress via the **`/codex:status <task-id>`** slash command. Do NOT hand-roll a `node codex-companion.mjs status` poll loop — that is the low-level plumbing, not the published surface (operator-flagged, 2026-07-15). The general `codex:rescue` / `codex:codex-rescue` agent is an acceptable fallback dispatch path when you need a bespoke confirmation prompt. Before dropping to tier 2, you MUST check availability — run `codex:setup` (or the companion `setup --json`) and read `ready`. If `ready: true`, Codex is available and tier 2/3 are **forbidden**.
2. **Independent Claude subagent** — permitted ONLY when the tier-1 availability check returned `ready: false` (Codex not installed / not authenticated / unreachable). Dispatch a fresh `general-purpose` agent (separate context) with the same confirmation-framed prompt. Using this tier without a checked, genuine tier-1 unavailability is a Step 4b bypass of the same class as skipping 4b entirely (GHI #678).
3. **Human-as-adversary** — degraded floor: if neither fires, say so explicitly ("adversarial validation ran in degraded human-only mode") so the operator knows the independent check did not run.

> **⚠ Clear prop before firing up the tier-1 Codex adversary.** The Codex runtime (`openai/codex-plugin-cc`) routes every rescue through a long-lived, shared broker process that can silently wedge — a stale/hung broker is reused without a health check, and the adversarial job then **hangs indefinitely with no output** (the job log reaches "Turn started" and never completes; upstream `openai/codex-plugin-cc` #509). Before dispatching, *clear prop*: if a prior run may have left one wedged, reset it — `rm -f ~/.claude/plugins/data/codex-openai-codex/state/<workspace-slug>/broker.json` and kill any stray `app-server-broker` / `codex app-server` processes — then run `codex:setup` and confirm `ready: true`. **A wedged broker is NOT a genuine tier-1 unavailability.** `codex:setup` checks only the binary + auth, so it reports `ready: true` even when the broker is hung; the true signature is `ready: true` but a `task` that emits nothing within ~30s. In that case clear prop and retry tier 1 — do **not** record it as `ready: false` and drop to tier 2, which would be a tier-order bypass (GHI #678).

**Record the tier and why.** Preserve the actual reviewer execution and observed
fallback reason in the acceptance record. Completion carries its derived tier
and provenance into the `adversarial_validation` event (GHI #676, #985).
A caller declaration cannot promote a same-vendor execution to tier 1 or replace
missing review evidence. "The Claude subagent was convenient" is not a fallback
reason. Legacy `--adversary-tier` and `--adversary-fallback-reason` inputs remain
compatibility fields, not authority over the recorded execution.

**Acceptance transport preserves that tier order (GHI #985).** The acceptance
importer derives tier from actual execution; do not put a caller-authored `tier`
in the review JSON. A native Claude adversarial fallback includes
`fallback_reason` naming the observed tier-1 unavailability. Native Claude spec
and quality execution needs no fallback reason. If the operator explicitly acts
as the degraded human adversary after reviewing current proof and findings,
record their exact ruling with `gz obpi acceptance {OBPI-SLUG} human-review
--attestor g0 --ruling '<verbatim judgment>'`. This records the current proof
approval and finding closure as tier 3, without inventing an agent receipt.
Never infer that ruling from permission to implement or repair, and retain the
separate completion attestation. Completion records the resulting derived tier;
legacy tier flags cannot override the imported judgment.

> ### 🛑 THE PLUGIN IS THE ONLY TIER-1 DISPATCH SURFACE (operator directive, 2026-08-25)
>
> **`codex exec` is FORBIDDEN as a tier-1 invocation.** Dispatch through OpenAI's Codex
> plugin for Claude Code — its runtime `codex-companion.mjs`, on the `task --write` path
> shown above for Step 4b — and nothing else. Wrapping the raw binary in
> `gz arb step` is NOT an acceptable substitute, and this section used to show exactly
> that, which is how the violation happened.
>
> **Measured cost of one hand-rolled run (2026-08-25, OBPI-0.35.0-02):** ~15 minutes
> wedged at 0.07s CPU because `codex exec` blocked on an unredirected stdin — a state
> `codex:setup` reports as `ready: true`; then a 500KB undifferentiated blob that had to
> be `sed`-sliced to find the verdict; then a re-run **refused outright** by an upstream
> cyber filter (*"This content was flagged for possible cybersecurity risk"*) because a
> hand-written refute prompt named Unicode bypass techniques. The plugin path, launched
> against the identical work, streamed structured findings with severity and confidence
> and surfaced a **high**-severity defect the hand-rolled run had missed entirely.
>
> The failure was not the agent forgetting the rule — the rule was stated here and then
> contradicted three paragraphs later by a worked example using the forbidden form. An
> agent following the example is following this skill. **Never reintroduce a `codex exec`
> incantation to this file, even as an illustration.**

**Prove the tier; a declaration will not pass (GHI #765, #780).** A declared tier is a
second assertion from the same caller — as is `--adversary-job-id`, which **nothing
resolves**. Wrap the PLUGIN invocation in ARB and cite the receipt:

```bash
uv run gz arb step --name codexadversary --max-output-chars -1 -- \
  node "$HOME/.claude/plugins/cache/openai-codex/codex/<ver>/scripts/codex-companion.mjs" \
  task --write --cwd <checkout> --prompt-file <prompt.md>
# → arb step name=codexadversary exit_status=0 receipt=.../arb-step-codexadversary-<hash>.json
uv run gz obpi complete <OBPI> ... --adversary-tier 1 \
  --adversary-receipt arb-step-codexadversary-<hash>
```

Before completion, import that run with
`uv run gz obpi acceptance <OBPI> review --receipt arb-step-codexadversary-<hash>`.
The executed output must contain the structured acceptance judgment described
below. The receipt's successful exit alone cannot establish acceptance.

If the run cannot be ARB-wrapped, that is a **tier-2 outcome** and must be recorded as one
(`--adversary-tier 2 --adversary-fallback-reason '<observed>'`). Reaching for `codex exec`
to make a receipt appear is the substitution this gate exists to catch.

The gate **resolves** the receipt: it must exist, record `exit_status: 0`, and its
`step.command` must invoke a recognized different-vendor binary. The scan walks past a
bounded set of runtime wrappers (`node`, `npx`, `python`, `uv`, ...) to the binary they
front and **stops at the first non-wrapper**, so the mandated plugin dispatch
`node .../codex-companion.mjs` proves tier 1 while a vendor named only in the
adversary's PROMPT does not (GHI #884). Reading `command[0]` alone saw `node` and
refused every conforming dispatch. Precedence is **proven > declared > inferred**, and
a receipt contradicting a declared tier 1 fails closed.

**The receipt is MANDATORY for any cross-vendor claim (GHI #780).** It was optional
until 2026-08-09, which closed nothing: the gate cannot tell *"no receipt because the
adversary could not be wrapped"* from *"no receipt because none was run"*, so an honest
tier-1 run and a hollow one arrived as the same input. A tier-1 claim now fails closed
without one — and the requirement rides the **resolved** claim, not the declared one, so
naming a codex-shaped adversary while omitting `--adversary-tier` is refused too. If you
genuinely cannot wrap the run, that is a tier-2 outcome and must be recorded as one:
`--adversary-tier 2 --adversary-fallback-reason '<observed unavailability>'`. Do not
report an unwrappable Codex run as tier 1; that is the substitution the gate exists to
catch.

> **Why the name scan is left conservative rather than "fixed."** `_is_cross_vendor_adversary`
> prefix-scans, so `"independent Codex subagent"` reads as NOT cross-vendor and demands a
> fallback reason. That false negative is a *safe* wrong answer and is deliberate: any scan
> admitting a *mentioned* vendor would classify the ledger's existing
> `independent-claude-subagent (codex-unavailable; degraded tier)` as tier 1 — failing OPEN
> on the exact substitution Step 4b exists to catch. A name can mention; an argv ran.

> This paragraph named `SubagentDispatchRecord` and two fields (`adversary_tier`, `codex_availability_checked`) from 2026-07-12 until 2026-08-07 — a contract no surface implemented. That model is Stage-2 dispatch tracking, it is `extra="forbid"`, and no adversary is ever constructed through it, so an agent following the sentence literally raised `ValidationError` rather than recording anything (GHI #678, reopened). `codex_availability_checked` is deliberately **not** reinstated: the fallback reason must name *observed* unavailability, so it already evidences the check, and a separate boolean is redundant state that can disagree with the reason it duplicates. Omitting `--adversary-tier` no longer preserves name inference for a tier-1 claim — GHI #780 retired that path after measuring that it was not a legacy tail but the only route in use (of 17 recorded `adversarial_validation` events, zero declare a tier and 14 resolved cross-vendor by name).

**Dispatch contract.** Give the adversary the completion CLAIM (the brief's REQs
and what the agent says it built), the actual Step-4a packet path, source/test
paths, relevant raw execution artifacts, and prior findings with dispositions.
Identify the reviewed revision and dirty-tree artifact identities. Give the gzkit
tools as its framework — `gz obpi present-evidence <OBPI>`, `gz covers <OBPI> --json`,
the scoped tests, the brief's Demo, `git status --short` and `git diff` — subject to
the documented execution limits above. Instruct it to **INDEPENDENTLY CONFIRM THE
IMPLEMENTATION IS CORRECT**, probing the bounded requirements in both directions.
Judge whether each cited method supports its acceptance claim, not just whether
its output reproduces. For each finding, name the violated requirement or
required-proof claim, the counterexample or concrete evidence gap, and the
relevant verification. Review allegations also need source/observed support;
do not adopt an explanation solely because another reviewer wrote it.

Require `CORROBORATED` | `CORROBORATED-WITH-CAVEATS` | `NOT-CORROBORATED`, the
separate verdict token, output for checks it executed, artifact references for
records it inspected, an explicit statement of what it could NOT confirm, and
a "Weakest point" section. An auxiliary audit is not automatically a new
deliverable. Determine a finding's relevance by the acceptance claim it affects,
not the document or review round where it originated.

Also supply the current acceptance status JSON and require exactly one
`gzkit.acceptance.review.v1` object in the actual reviewer output, using the
schema in the acceptance manpage. Set `stage` to `adversarial`; copy the current
input digest and proof/obligation IDs from the supplied records. Explicitly list
`accepted_proof_ids` for the proof the reviewer approves; never infer this set
from a global verdict. The `accepted`/`refuted` verdict is retained as review
history. Mapped findings and explicit proof approvals govern readiness, so an
auxiliary observation cannot revoke an approved requirement through its wording.
Mapped findings retain stable IDs. A verified repair emits a closure containing
`finding_id`, `obligation_id`, and the current `proof_id`. The reviewer must not
author `id` or `receipt_id`; ingestion derives them from the executed receipt.
Record the review through `gz obpi acceptance ... review --receipt ...`, including
truthful refutations, so the next round receives the same outstanding obligations.

**Two transport facts that decide what actually reaches it (measured 2026-09-04, re-measured 2026-09-10 on the writable path, GHI #961).** The suite runs under the venv interpreter, never `uv run` — uv cannot initialize its cache in the sandbox. Inside the disposable checkout, run it as `PYTHONPATH=<checkout>/src <repo>/.venv/bin/python -m unittest <selector>`; the editable install points at the ORIGINAL `src`, so without that `PYTHONPATH` the reviewer would mutate the copy and import the original — a replay that proves nothing. `gz obpi adversary-workspace` prints the exact invocation. Second: the `task` path DOES take `--prompt-file` and stdin, so the positional-only limitation belongs to `adversarial-review` alone and no longer applies; still name large artifacts BY PATH and let the reviewer open them. And where `renderReviewResult` collapses a review to a rendered summary, `renderTaskResult` (`lib/render.mjs:315`) emits the reviewer's `rawOutput` **verbatim** — which is why the review object now reaches the ARB receipt the importer reads. Observed 2026-09-10: a complete, correct review object authored through `adversarial-review` was lost in transport and the import refused it (`exit 3`).

> **`not-refuted` IS the corroborated state — it is phrased passively, not missing.** Operator
> ruling 2026-09-04, verbatim: *"NOT-REFUTED is arguably a passive way of saying CORROBORATED"*.
> A model that attacked the implementation and could not break it has corroborated it, so
> `CORROBORATED` records as `not-refuted` and that is a faithful record, NOT a rounding. An
> earlier revision of this note claimed the enum "has no corroboration state" and that the
> mapping was a documented lie-by-rounding; both were wrong (GHI #954, narrowed by its own
> author). No change to `gz obpi complete` is warranted.
>
> Still require BOTH lines from the adversary — the `CORROBORATED | CORROBORATED-WITH-CAVEATS |
> NOT-CORROBORATED` line and the enum word — because the distinction that matters is
> *attacked hard and demonstrated working* versus *looked and found nothing*, and that
> distinction lives in the PROMPT's demand for positive demonstration, never in the verdict
> word. Two rounds can both land on `not-refuted` and mean entirely different things; the
> pasted evidence is what tells them apart. Record the outcome through `gz obpi complete`'s adversary flags (`--adversary-verdict`, `--adversary`, `--adversary-tier`, `--adversary-receipt` when the run was ARB-wrapped, and `--adversary-job-id` when the runtime supplies one) — the ledger event is the durable record, not a dispatch marker.

**Bound the claim BEFORE the first round, or the gate cannot converge (operator ruling 2026-09-03).** An adversary instructed to REFUTE will escalate the attacker one notch each round, so an ABSOLUTE claim ("no X can occur without Y") is unrefutable-in-bounded-time by construction. For any OBPI whose subject is a trust chain, provenance, or a tamper-evidence property, the brief MUST carry a `## Threat Model` section BEFORE Step 4b is first dispatched, naming what an attacker may do and what is an accepted residual — and the dispatch prompt MUST state that boundary and forbid the adversary from reporting an out-of-scope attack as a finding. Measured on OBPI-0.35.0-04: five rounds, 53 minutes of adversary compute across a 12.5-hour wall clock (7%); the rest was fix cycles. Rounds 4 and 5 spent ~9 hours hardening attacks whose reproduction required appending arbitrary rows to `.gzkit/ledger.jsonl` — strictly inside a residual the operator had already accepted for `.gzkit/ownership/`, the same directory and the same access. `docs/governance/trust-doctrine.md` covers AGENT trust-chain poisoning and declares no filesystem threat model, so nothing bounded the adversary and the agent never asked whether the attacker was in scope.

Wait for the human to respond "Accepted", "Completed", "attest completed", or equivalent. Do NOT proceed until attestation is received.

Do NOT mark ceremony task `completed` until attestation is received.

**When attestation arrives, immediately invoke `gz obpi complete` (Stage 5 Step 2) with the operator's phrase in `--attestation-text`.** The operator's short phrase is the attestation; the pipeline must not pause to ask for longer text and must not print runbook-style instructions for the operator to execute. Enrich the attestation text per `AGENTS.md` § Attestation (em-dash + concrete session evidence + receipt IDs) before passing it through.

**Human rejects:** Record feedback, return to Stage 2 with corrections.

**MANDATORY TRANSITION → Stage 5.** Once attestation is received, proceed to Stage 5 immediately. Do not summarize. Do not wait.

### Stage 5: Sync And Account

After attestation:

**Two-sync pattern:** Stage 5 uses two git-sync cycles. The `gz obpi complete`
command atomically writes the attestation to the ADR-level audit ledger, updates
the brief to Completed, and emits the completion receipt. Git-sync #1 commits all
these governance edits plus lock release and marker cleanup. Git-sync #2 commits
the reconcile output and ADR status refresh.

0. **Pre-flight checklist (MANDATORY, GHI #196)** — `uv run gz obpi precomplete {OBPI-SLUG}`
   Mechanical verification of all Stage 5 preconditions, each with a named
   remediation: brief authored readiness, reconcile idempotence (catches GHI
   #193 drift before it bites), lock ownership, ARB receipts passed (the newest
   lint/typecheck/unittest receipt since the lock claim each records exit 0 —
   GHI #889), plan-audit
   receipt PASS, brief-heading shape, scoped behave REQ coverage, and
   **task-envelope coherence** (GHI #590 — early warning that the OBPI would
   close with residue on any of the three signatures: Sig (a) a worklog event
   under an active TASK with no `task_id`, Sig (b) `seq=01`-only without a
   `req_atomic:` exemption, Sig (c) layer-drift across discovery channels;
   remediation: subdivide labor via `uv run gz task start --seq next` or declare
   `req_atomic:` (Sig b), attribute worklog events with a `task_id` (Sig a),
   reconcile divergent TASK ids across channels (Sig c) —
   `uv run gz task envelope diagnose {OBPI-SLUG}`), and **current acceptance
   proof plus independent closure** (GHI #985). The acceptance records are
   already in the ledger before this check; Markdown review history does not
   supply the current verdict.
   **If exit code is non-zero, do NOT invoke `gz obpi complete` — fix each
   reported precondition first using the named remediation.** Exit 0 here is
   the gate that prevents the reactive-triage class of failure (the original
   OBPI-04 Stage 5 cost ~3 turns to discover the same gaps one at a time).
   Note: `gz obpi complete` **independently re-enforces** the task-envelope,
   REQ-coverage, and Step-4b gates fail-closed (precomplete is the bypassable
   pre-flight; the completion command is the chokepoint), so the residue cannot
   reach `main` even if this step is skipped. `gz check` then re-audits the
   captured verdict repo-wide via `gz validate --adversarial-validation`.

1. **Closure-narrative gate (MANDATORY, GHI #267)** — Before invoking `gz obpi complete`, present the resolved Implementation Summary and Key Proof prose to the operator inline, in the exact form that will be written to the brief. This is the brief-narrative analog of the Stage 4 evidence gate: the brief is Layer 1 canon authorship surface, and a future reader six months from now will read the brief, not the ledger event. Empty or placeholder prose is a defect — `gz obpi complete` fails closed on it (exit 1, no ledger event, no brief mutation), but the skill must catch it before the CLI does.

   **Required walkthrough format:**

   ```
   ## Stage 5: Closure Narrative (preview before gz obpi complete)

   **Implementation Summary** (will be written to ### Implementation Summary):

   <verbatim text — bulleted "- Key: value" form preferred so the
    `_has_substantive_implementation_summary` check accepts it>

   **Key Proof** (will be written to ### Key Proof):

   <verbatim text — at least one concrete command + observed output,
    with ARB receipt ID(s) cited inline per AGENTS.md § Attestation>

   **Source:** [--implementation-summary flag | existing brief body at <line range>]
   **Source:** [--key-proof flag | existing brief body at <line range>]
   ```

   The operator already attested in Stage 4, but the prose is the artifact that survives the ledger event — name it explicitly so the operator can refuse before write.

   If the operator silently accepts (no objection), proceed. If they reject, return to authoring the brief sections directly, then re-present.

2. **Complete OBPI atomically** — `uv run gz obpi complete {OBPI-SLUG} --attestor {attestor} --attestation-text "{text}" [--implementation-summary "{summary}"] [--key-proof "{proof}"]`
   This single command atomically: validates brief state, writes attestation to the
   ADR-level audit ledger, updates the brief (status, evidence sections, human
   attestation), and emits the completion receipt to the main ledger. If any step
   fails, all changes are rolled back — no partial writes.
   - Pass the operator's attestation phrase (e.g. "attest completed") verbatim through `--attestation-text`, enriched per `AGENTS.md` § Attestation (em-dash + session evidence + receipt IDs). The operator's `attest completed` (or equivalent) IS the attestation — it is not a request for more instructions. Do not stop the turn to ask the operator to run the command themselves.
   - Use `--implementation-summary` and `--key-proof` to supply evidence sections.
     If omitted, the command reads existing content from the brief — but it MUST
     be substantive (non-empty, non-placeholder, satisfies
     `_has_substantive_implementation_summary` / `_has_substantive_key_proof`),
     or the command exits 1 with a recovery hint. The Step 1 walkthrough above
     is what catches this before the CLI does.

   **Gate-5 attestation.** The operator's Stage-4 verbatim attestation
   (e.g. "attest completed"), relayed via `--attestation-text`, IS the
   Gate-5 attestation for every lane / kind / sensitivity — the completion
   receipt records `attestation_type: operator-verbatim-conversational`.
   There is no TTY-typed `ATTEST` ceremony, no `--attestor-present`
   co-presence proxy, and no PTY launcher: a plain non-TTY `uv run gz obpi
   complete ...` call from the Bash tool completes the brief. A non-empty
   `--attestation-text` is required (it IS the attestation); an empty one
   exits 1. Never hand the invocation back to the operator — they already
   attested in Stage 4.

   **Completion uses the imported current review (GHI #985).** The command
   derives the accepted adversarial judgment from durable acceptance records
   after every current obligation and mapped finding clears. Legacy
   `--adversary-*` arguments remain compatibility/provenance inputs; they cannot
   replace a missing review, invent approval, or discharge a finding. Preserve
   the binding tier and observed fallback requirements in Step 4b.

   Operator ruling 2026-09-04 remains verbatim: ***"refuted is an outcome, but it
   is an input into if(4a && 4b) pass; else: loop"***. The loop now preserves the
   obligation and verified closure. Fix the required behavior or obtain the
   existing operator ruling for an actual contract boundary amendment, then
   independently verify the changed proof. A GHI or resolution paragraph alone
   cannot satisfy a missing obligation. Historical round verdicts remain exact;
   no authored standing line determines the current acceptance decision.

   ```bash
   uv run gz obpi complete {OBPI-SLUG} \
     --attestor '{attestor}' \
     --attestation-text "$(cat /tmp/obpi-attestation.txt)" \
     --implementation-summary "$(cat /tmp/obpi-summary.md)" \
     --key-proof "$(cat /tmp/obpi-keyproof.md)" \
     [--adversary-job-id '{job-id}'] \
     [--refuted-claim "$(cat /tmp/obpi-refuted-claim.txt)"] \
     [--adversary-resolution "$(cat /tmp/obpi-adversary-resolution.txt)"]
   ```

   Write long `--attestation-text` / `--implementation-summary` /
   `--key-proof` payloads to `/tmp/*.txt|md` first to keep the invocation
   tractable.
3. **Author the completion handoff register entry, THEN release the lock (ADR-0.0.41 coupling).**
   `gz obpi lock release` fail-closes without a register entry (token-block
   discipline § Sub-Invariant 5) — even for a *completed* OBPI, which is not
   abandoned. The Stage-5 ordering is therefore **handoff-before-release**, not
   the reverse:
   - Author a completion handoff via `/gz-session-handoff` (this is the step-10
     session handoff, pulled earlier because the release depends on it).
   - The handoff frontmatter `obpi_id:` MUST be the **full OBPI slug**
     (e.g. `OBPI-0.0.37-22-committed-rendition-store-deterministic-playback`),
     not the short form — `find_exchange_for_release` matches by exact equality
     against the lock's full-slug `obpi_id`. Known surface friction:
     `validate_handoff_document`'s `_OBPI_ID_RE` rejects the full slug, so the
     standalone validator will flag it; the full slug is nonetheless the
     de-facto working form for release pairing (prior-OBPI precedent). Do NOT
     "correct" the handoff to short form — that breaks the release match.
   - The handoff timestamp MUST postdate the lock claim.
   - Then release: `uv run gz obpi lock release {OBPI-SLUG}` (exit 0). Do NOT
     use `--abandon` for a completed OBPI — abandonment is the wrong semantics;
     the handoff is a completion register entry, not a surrender.
4. Remove `.claude/plans/.pipeline-active-{OBPI-ID}.json` if it was created.
5. Remove `.claude/plans/.pipeline-active.json` only when it still points at
   the same OBPI as the per-OBPI marker.
6. **Git-sync #1** — `uv run gz git-sync --apply`
   Commits all governance edits from steps 1-5. Tree is now clean.
7. Run `uv run gz obpi sync {OBPI-SLUG}` to confirm receipt and brief agree.
8. Run `uv run gz adr status {PARENT-ADR} --json` so the parent ADR view
   reflects the reconciled OBPI state.
9. **Git-sync #2** — `uv run gz git-sync --apply`
   Commits the reconcile output (step 7) and ADR status refresh (step 8).
10. The completion handoff authored in step 3 already serves as the session
    handoff — confirm its "Pending Work / Open Loops" captures remaining
    parent-ADR OBPIs and any deferred follow-up so the next session resumes
    cleanly. (Authored at step 3 because the lock release depends on it; this
    step is the content check, not a second handoff.)

**GHI closure discipline (cross-reference):** When a GHI is closed as part of
pipeline execution or handoff, apply `ghi-close` v2.4.0's dead-letter doctrine:
every close MUST cite a real, registered destination (commit SHA, ADR ID visible
in `gz adr report`, OBPI brief ID, or higher-numbered open GHI). A GHI closed
with a vague route-promise ("should become an ADR", "the operator can handle this
later") is a dead-letter and is forbidden. If no destination exists yet, leave the
GHI open with a blocker comment naming the next concrete operator action. See
`.gzkit/skills/ghi-close/SKILL.md` § Doctrine — NEVER, EVER, EVER dead-letter a
GHI for the binding rule.

---

## Gate Friction: Evaluator Escalation (stale brief/OBPI vs. reality)

A pipeline gate (Stage 1 reconcile; Stage 5 `precomplete` / `gz obpi complete`
reconcile-freshness, lock-handoff coupling, or security floor) can block not
because the *work* is wrong but because the *brief/OBPI has drifted from current
repo reality* — a stale allowlist, an under-declared coupled surface, a missing
`sensitivity:` axis, or a plan authored against an earlier tree. The two wrong
responses are (a) contorting convention-correct code to satisfy a stale brief,
and (b) filing a GHI and stalling. **The brief is the artifact that adjusts to
reality; code that follows established convention is usually right.**

When a gate blocks and you suspect the brief — not the code — is stale, run the
**implementer → evaluator → human-approval** loop instead of working around it:

1. **Dispatch an evaluator agent** (Agent tool; `general-purpose` or a review
   persona; read-only) with a `Why` naming the suspected staleness and the
   decision it drives. Ask it to determine, with cited evidence:
   - The established convention / prior art — how sibling code and sibling
     briefs handled the *identical* pattern (file placement, allowlist
     declaration, sensitivity, override precedent).
   - Whether the brief/OBPI under-declared or mis-declared the surface the gate
     is blocking on.
   - Whether the actual change is what the gate fears (e.g. a genuine security
     change) or a false positive (additive, no new surface introduced).
   - One concrete recommended resolution.
2. **Make a determination** from the evidence. Do not rubber-stamp — confirm the
   citations resolve (file paths, line numbers, sibling-brief frontmatter).
3. **Present the recommendation(s) to the operator** — the determination, the
   evidence, and the proposed brief/OBPI adjustment (allowlist amendment,
   sensitivity declaration, override flag + reason). One tight decision with a
   recommendation, not a re-derivation (operator economy of effort).
4. **On operator approval, adjust the brief/OBPI to fit reality** — amend the
   allowlist *surgically* (only genuinely-touched coupled surfaces, never the
   false positives the gate over-flagged), then proceed with the documented
   override (`--accept-stale-reconciliation --reason '<text>'`,
   `--accept-security-floor '<reason>'`, etc.). Append an `improvement` insight
   (Behavior Rule Always #11) capturing the staleness and the adjustment.

This loop keeps the human as final witness (the operator approves the
adjustment) while letting the system adjust the governance artifact to match
verified reality — fewer GHIs, less friction, the brief stays honest.

> **Anti-pattern:** silently applying an `--accept-*` override without the
> evaluator determination + operator approval. The override is the *outcome* of
> the loop, not a shortcut around it. Equally an anti-pattern: relocating
> convention-correct code into an awkward home purely to satisfy a stale
> allowlist — fix the brief, not the code.

---

## Error Recovery

| Failure Point | Action |
|---------------|--------|
| Brief not found | Report error, `gz obpi lock release --force`, stop |
| Receipt verdict FAIL | Report audit failure, `gz obpi lock release --force`, stop |
| No receipt found (full run) | STOP — enter plan mode, get approval, then resume pipeline |
| No receipt found (`--from` set) | Proceed — user is resuming a partial pipeline |
| Tests fail during implementation | Attempt fix (2 tries), then `gz obpi lock release --force` + handoff |
| Verification fails | Attempt fix (1 try), then `gz obpi lock release --force` + handoff |
| Human rejects attestation | Record feedback, return to Stage 2 with corrections |
| `git sync` fails or repo remains unsynced | Stop before `gz obpi complete` and repair blockers |
| Gate blocks on stale brief/allowlist (reconcile drift, security floor, under-declared coupled surface) | Run the **Gate Friction: Evaluator Escalation** loop (above) — dispatch evaluator → determination → operator approval → surgical brief amendment + documented override. Do NOT contort code to fit the brief or file a GHI to stall. |
| Lock release fail-closes ("no register entry") on a *completed* OBPI | Author the completion handoff FIRST (full-slug `obpi_id:`), then release — see Stage 5 step 3. Never `--abandon` a completed OBPI. |

**Lock bracket:** Lock is claimed at Stage 1 and released at Stage 5 AND on any abort/handoff. No orphaned locks.

**Handoff creation:** On any abort, release lock via `uv run gz obpi lock release {OBPI-SLUG} --force`, then run `/gz-session-handoff` to preserve context for the next session.

---

## Blocked on the Operator (GHI #887)

**When the next legitimate action is a human's, say so in the ledger and stop.**

A brief becomes *blocked* — not failed, not abandoned — when every remaining
finding needs an operator decision rather than an implementation: a REQ amendment
under attestation, an allowlist widening, a Denied-Path collision, a
conflicting-canon ruling. `AGENTS.md` § Behavior Rules — Always #18 and #9 both
address the agent here (*"Surface blocking failures clearly and upfront"*;
*"STOP, name confusion, present tradeoff, wait"*), and until GHI #887 neither had
a state the pipeline could enter.

```bash
uv run gz obpi block {OBPI-SLUG} \
  --reason "<what cannot proceed without a human>" \
  --next-action "<the concrete decision the operator owes>"
```

While the block stands, `gz obpi pipeline` refuses to launch against the OBPI
(exit 3) and `gz obpi precomplete` reports it instead of `READY`. The operator
clears it with their own words:

```bash
uv run gz obpi unblock {OBPI-SLUG} --ruling "<decision>" --operator "<who>"
```

**Why this is not a lock, a park, or a withdrawal.** The lock says *who is
working*; `obpi_parked` says *the parent ADR left active status* (its `parked_to`
is a pool id, and here the parent is live); withdrawal is permanent and attested.
A block says only that a decision is owed, and it is reversible by construction.

**No attestor is required to record a block.** Requiring a human to authorize
the statement *"a human is needed"* would reproduce the deadlock it exists to
break. The ruling that clears it is the operator's; the observation that one is
needed is the agent's, and recording it is the honest act.

**Measured cost of not having this** (`OBPI-0.35.0-02`, 2026-08-25/26): 21
`red_receipt_emitted`, 10 `task_started`, **zero** `task_completed`, four
`pipeline_launched` and three adversary rounds in the 24 hours after the brief
became structurally uncompletable. Four agents each re-derived that a human was
needed; none could record it, so each kept working the surrounding surface.

---

## Evidence Capture

> See references/evidence-capture.md for the full stage-by-stage evidence table.

---

## Plan-Audit-Receipt Contract

> See references/plan-audit-receipt-contract.md for the receipt JSON schema and contract details.

---

## Parallel Execution

Use one writer per governed checkout. Read-only investigation may run independently.
Only the operator initiates OBPI work; separate sessions do not grant initiation.
Capture the contract and input components before review and verify applicability
before consuming it. Sequence shared acceptance/schema writes outside review windows.
If another run changes the subject, identify affected evidence before continuing.
Never stash, pop, reset, revert, or sync another active run's work to clean the tree.

All OBPIs require per-OBPI human attestation (universal per ADR-0.0.36).

---

## Relationship to Existing Skills

| CLI Command / Skill | Role in Pipeline |
|---------------------|-----------------|
| `gz obpi lock claim/release` | Stage 1 claim, Stage 5 release, abort release (`--force`) |
| `/gz-plan-audit` | Pre-pipeline — runs in plan mode, produces receipt |
| `gz obpi complete` | Stage 5 atomic completion (attestation + brief + receipt) |
| `gz obpi sync` | Stage 5 confirmation — receipt and brief agree |
| `/gz-session-handoff` | Error recovery — preserves context on abort |

---

## Completion Contract

The pipeline is complete when — and ONLY when — all of these are true:

1. `gz obpi complete` ran successfully — attestation, brief, and receipt written atomically (Stage 5, Step 1)
2. Lock released via `gz obpi lock release` (Stage 5, Step 2)
3. Pipeline markers cleaned (Stage 5, Steps 3-4)
4. Git-sync #1 committed governance edits (Stage 5, Step 5)
5. `gz obpi sync` passed (Stage 5, Step 6)
6. Git-sync #2 committed reconcile output (Stage 5, Step 8)

If any of these have not happened, the pipeline is not complete. Do not claim otherwise.

**What "done" looks like:** The final output of a successful pipeline run is a short status line confirming Stage 5 completed — not a summary of the implementation, not a recap of what was built. Just: "Pipeline complete. OBPI-X.Y.Z-NN synced. The pipeline does not manage the work lock; if one is held on OBPI-X.Y.Z-NN, release it with 'gz obpi lock release OBPI-X.Y.Z-NN'."

### Anti-Pattern: The Premature Summary

The single most common pipeline failure is: the agent finishes writing code, prints a summary of files created and tests passing, and stops. This abandons the OBPI in a half-finished governance state — implemented but unverified, unattested, unsynced. The operator must then manually re-invoke the pipeline with `--from=verify` to finish the job.

**This is the failure mode this skill exists to prevent.** If you find yourself writing a summary after Stage 2 or Stage 3, you are committing this exact anti-pattern. Stop writing the summary. Start the next stage.

### Anti-Pattern: Hook Bypass

If a pipeline hook blocks a write, that means the pipeline is not active or evidence is missing. The correct response is to diagnose the cause — NOT to manually create marker files or ledger entries to bypass the hook. Manually creating files to bypass hooks defeats the entire enforcement mechanism.

---

## Design Notes

> See references/design-notes.md for architectural context, hook enforcement details, and AirlineOps lineage.

---

## Related

- OBPI Acceptance Protocol: `AGENTS.md` § OBPI Acceptance Protocol
- Plan audit: `.claude/skills/gz-plan-audit/SKILL.md`
- Session handoff: `.gzkit/skills/gz-session-handoff/SKILL.md`
- Governance workflow: `docs/user/concepts/workflow.md`
- Runbook: `docs/user/runbook.md`
- Transaction contract: `docs/governance/GovZero/obpi-transaction-contract.md`

## Related ADRs

- **ADR-0.0.19** — Pre-execution reasoning walkthrough. The Stage 1→2
  Justification Gate routes a plan with an ambiguous scope boundary, an
  unresolved integration point or an unread surface into the `gz-justify`
  walkthrough before implementation begins.
