# fleet/harness/promote/ — the gated pipeline Rehearse on the sandbox → an independent agent judges → **a human decides** → production → a second agent verifies what actually landed. ## Why this exists as code The discipline was already written down — [ADR-0003](https://gitea.arcodange.lab/arcodange-org/factory/src/branch/main/vibe/ADR/0003-sandbox-state-lifecycle.md), the operating rules in [`AGENTS.md`](../../../AGENTS.md), the promote flow — and it still depended on whoever was driving choosing to follow it. On 2026-07-25 an agent session wrote five documents into the production ledger through direct API calls, bypassing the promote flow entirely. Nothing was wrong with the result; everything was wrong with the path. A rule an operator can skip is a recommendation. So the stages here are **chained by artefacts**, not by good intentions. Each stage refuses to run until the previous one has produced its file, and the file has to say what the stage needs to hear: | Stage | Produces | Refuses unless | | --- | --- | --- | | 1 `rehearse` | `01-rehearsal.json` | the target is the sandbox (host-guarded) | | 2 `judge --pre` | `02-pre-verdict.json` | a rehearsal exists and its writes succeeded | | 3 `gate` | `03-gate.json` | a pre-verdict exists; **a human types the decision** | | 4 `apply` | `04-applied.json` | the gate says `approved`, by a named human, for *this* manifest | | 5 `judge --post` | `05-post-verdict.json` | production was applied | The gate binds to a **manifest digest**: approving a change-set approves *that* change-set. Edit one amount afterwards and stage 4 refuses — the approval no longer matches what is about to be written. ## The two judges Both are context-free: they receive the manifest and the evidence, never the conversation that produced them. Per the PRD [cross-family rule](https://gitea.arcodange.lab/arcodange-org/factory/src/branch/main/vibe/PRD/ai-back-office/qa-strategy.md#independent-verification--no-self-grading), a judge SHOULD be a different model family than whoever built the change-set — the admitted runtimes are Mistral (`vibe -p`) and Ornith 35B (hermes MLX), both proven at verdict parity in [erp#63](https://gitea.arcodange.lab/arcodange-org/erp/issues/63). - **Pre-gate** — prompted to *refuse*: find why this change-set is not safe to promote. It reads the rehearsal evidence, not a description of it. Its verdict goes to the human as an opinion, not a veto: a `BLOCK` still lets the operator approve, and the override is recorded in the gate file. - **Post-gate** — prompted to *doubt the success*: compare what production now holds against what the sandbox rehearsal predicted, and report drift. It runs after the writes, so it cannot prevent them — it exists so a silent discrepancy becomes a recorded finding instead of a surprise months later. Judges are advisory by design. The blocking authority is the human gate and the host guards; an LLM verdict never silently stops or starts a production write. ## Usage ```bash P=fleet/harness/promote/pipeline.py python3 $P rehearse --manifest changeset.json --run-dir runs/2026-07-24-m3-deferred python3 $P judge --run-dir runs/... --stage pre --runtime mistral python3 $P gate --run-dir runs/... # interactive; records who and when python3 $P apply --run-dir runs/... # needs ARCO_PROD_CONFIRM python3 $P judge --run-dir runs/... --stage post --runtime ornith ``` Every stage appends to `journal.jsonl`. The run directory is the evidence pack: it is what you keep, and what an auditor reads. ## What this does not do It does not replace the host guards (`dol-write.sh` refusing non-sandbox hosts, `guard.ts` requiring an explicit production opt-in, the chronology guard in `invoice-create.sh`). Those are structural and stay underneath. This pipeline adds sequence and evidence on top of them.