red-team · git:20261030.8fef1e1 · 2026-10-30 · sha256 3cb82f8d41beee52
red-team git:20261030.8fef1e1A
Immutable. This exact content never changes and is served at /api/v1/blob/3cb82f8d41beee52.
---
name: red-team
description: Adversarially verify a proposed plan before you execute it. Reads the plan, derives a hybrid attack strategy (a universal spine + plan-specific probes), authors and runs a verification Workflow that PROVES claims by running them in throwaway sandboxes, then grills you on every blocker and patches the plan in place until the gate emits a proceed verdict — CLEARED, or ADJUDICATED when a patched blocker was not re-verified. Plan-agnostic — implementation plans, design specs, PRDs. Use when the user runs /red-team, wants to stress-test / verify / harden / find the holes in a plan before building it.
---
# /red-team — harden a plan before you build it
You are the **Red Team Lead**. You attack a proposed plan, prove what is wrong by running it in throwaway sandboxes, and drive it to a **gate-emitted proceed verdict** — `CLEARED`, or `ADJUDICATED` when a blocker was patched but not re-verified ([ADR 0043](../../docs/adr/0043-adjudicated-clear-distinct-terminal-verdict.md)) — by grilling the user on blockers and patching the plan in place. You are the adversary to `writing-plans`. Reference: [references/lenses.md](references/lenses.md) — spine + probe catalog, schemas, severity rubric, report template.
This skill authorizes the **Workflow tool**: you adapt [assets/workflow-scaffold.js](assets/workflow-scaffold.js) and run it.
## Run
```
/red-team <plan-file> [--spec <file>] [--repo <path>] [--fast]
```
Default `<plan-file>` = the most recent `docs/plans/*.md`; `--repo` = cwd. **`--repo` routinely differs from the session cwd** — you can launch `/red-team` from one project's session to verify another project's plan. When it does, pass an absolute `<plan-file>` and `--repo`; the `--repo = cwd` default holds only when verifying the current project.
## Steps
1. **Read** the plan. Find the **source of truth** it implements. **Merged arm:** a plan whose Part 1 carries the decision record is **its own source of truth** — `--spec` defaults to the plan itself, and the coverage-vs-source lens then reads Part-1→Part-2 coverage (every Part-1 requirement/decision maps to a Part-2 phase/task). **Legacy arm** (no Part-1 decision record): grep the plan for a `docs/specs/…` or issue link — `--spec` defaults to that. **Seed cumulative rounds** (the loop budget's counter): glob `docs/red-team/*-<plan-slug>.md`, take the newest by filename date, and read its first line matching `^\s*[-*]?\s*\*\*Rounds:\*\*\s*(\d+)` — the captured integer is the seed. Anything else — no prior report, no matching line, a non-integer value (every legacy variant) — seeds **0**: fail-open, clean slate.
2. **Pre-flight + derive probes.** First resolve `planFile`/`repo`/`sourceSpec` to **absolute paths** and assert the plan exists **under** the repo, e.g. `test -f "$PLAN" && case "$PLAN" in "$REPO"/*) ;; *) echo "plan not under repo"; esac` and `git -C "$REPO" rev-parse --show-toplevel`. If `repo` ≠ cwd (or the plan is not under cwd), **warn the user** you are verifying a foreign repo. Then compute the **plan fingerprint** to pass into the Workflow (the scaffold cannot read files):
```bash
PLAN=/abs/plan.md; TITLE=$(grep -m1 '^# ' "$PLAN")
# fingerprint = { absPath: "$PLAN", titleLine: "$TITLE", tokens: [first few '## ' headings] }
```
**Classify the `artifactKind`** alongside the fingerprint — a documented heuristic, not clever: a `docs/plans/` file with per-task `Files:` under a `## Build order` ⇒ `impl-plan`; when those tasks are red-first (`requiresTest` tasks whose tests ship failing pre-implementation) ⇒ `tdd-plan`; a `docs/specs/*-design.md` ⇒ `design-doc`; an external PRD ⇒ `prd`; undeterminable ⇒ `impl-plan`, the conservative, suppression-safe default. Pass it in `args.artifactKind` (Step 3) — it threads into every probe's future-work rule so a claimed-but-unbuilt deliverable is graded as the expected baseline, not a defect. An absent arg reproduces today's suppression-biased grading byte-for-byte (back-compat). Report the computed kind in the run header so the operator can eyeball a misclassification.
Now derive probes: the six **spine** lenses always run; scan the plan for features and add **bespoke probes** from the catalog in `references/lenses.md` (before/after snippet → `snippet-fidelity`; code+test → `tests-run`; command+expected → `command-diff`; cited line → `anchor-check`; baseline claim → `baseline-repro`). **Mandatory `ff-topology` derivation** — WAR merges each task as a `--no-ff` merge commit carrying `WAR-Task`, so a `<merge>^1` or `--first-parent` per-task anchor is valid and a fast-forward-only anchor (a task tip assumed on the first-parent line) is the void. When the plan anchors any per-task evidence on git **integration topology** (`^1` on a task/merge ref, a `--first-parent` per-task diff, a post-merge three-dot floor base `<integration>...<task>`, or prose claiming a per-task "merge commit" or a task tip on the first-parent line), you MUST add the **`ff-topology`** executed probe from the catalog. It is **mandatory** when any such anchor appears and is **never skipped under `--fast`** (same posture as the drift-guard probes below). Token grep is only the **floor**: also hand-read the plan's evidence-pipeline / floor-invocation prose for merge-anchor claims phrased without those tokens, and add the probe on a prose match too. The rule holds for **every** `artifactKind` including `design-doc`/`prd` — the probe builds its own runnable fixture (a synthetic repo whose task merges are `--no-ff` merge commits carrying `WAR-Task`), so the "no runnable artifacts → drop the executed probes" guidance in `references/lenses.md` never exempts it. Like `default-flip-old-absent`, add it as a bespoke executed probe (scaffold array or `args.probes`) — **never** by editing the `SPINE` const. **REA probe derivation** — when the plan carries a Target artifacts block (Target artifacts marker: **Target artifacts**; Part 1 rows `path` / `sha256` / `provider`; grammar and the cite-only rule in the evidence doctrine home `artifact-evidence.md`), thread the block's rows as `args.targetArtifacts` and, in the same args, the doctrine home's **absolute** path as `doctrineFile: ${CLAUDE_PLUGIN_ROOT}/skills/war-strategy/references/artifact-evidence.md` (resolved by you, the Lead, never relative; Step 3) so the scaffold's scope-lock admits the declared artifact path and that one doctrine file read-only on both techniques (the catalog rows point at "the evidence doctrine file the scope-lock names" for the grammar and the target-content definition, ruling #2397), and add **both** catalog probes: **`rea-evidence`** (executed — replays each cited REA operation read-only against the declared artifact path, never a copy, never REA on the repo copy; REA absent on this machine is an env-gap, `envGap: true` + `status:"warn"`, never a fail) and **`untrusted-content`** (analyzed — target content in the plan body, a `rea:` tag the advisory lint's rea-source rule would flag, evidence cited without tool and parameters). With no block omit both args and every prompt stays byte-identical. Neither probe runs REA inside the sandbox copy. When the plan carries a `## Deferred validations (backstops)` section (either heading) or any `judge:` / `HARD at audit_sha` tagged End state, read [references/backstop-legitimacy.md](references/backstop-legitimacy.md) and run the checks it defines — the **backstop-legitimacy check** (a literal `None` is a valid, complete declaration — pass it without the cold read; the `judge:` rule still runs) and the **`judge:`-tag grading rule** (for each judged End state or backstop entry, grade "could this have been a `check:` command?"; commandable-but-judged → `needsDecision`). The four **drift-guard spine probes** (below) run every red-team too — each vacuous when the plan introduces no new inline mirror, carries no default-flip task, splits no drift guard away from the task authoring the fact it guards, and rewrites no doc carrying machine-source-derivable facts. Preview the attack surface (skip under `--fast`).
3. **Run the Workflow.** Copy `assets/workflow-scaffold.js` to a scratch path (e.g. `.claude/red-team/<run>.js`), add your bespoke probes (edit the array or pass `args.probes`), and run `Workflow({ scriptPath, args: { planFile, repo, sourceSpec, probes, fingerprint, provision, artifactKind, analyzedAgentType, model, effort, targetArtifacts, doctrineFile } })` — **all paths absolute**, `fingerprint` and `artifactKind` from the pre-flight (the scaffold refuses to run without the fingerprint). **Analyzed-agent pre-flight (`analyzedAgentType`)** — before launching, check the harness agent **registry** your own system prompt enumerates and pick one of three arms: (1) `Explore` **present** ⇒ **omit** the arg — the preferred read-only type works, today's behavior; (2) `Explore` **absent** ⇒ pass `args.analyzedAgentType: 'general-purpose'` so the run makes **zero dead dispatches** (the analyzed probes/confirms spawn straight on the working type); (3) registry **not enumerable** ⇒ **omit** the arg — the scaffold's **sticky pin** backstops in-run (one dead preferred dispatch pins the whole run onto `general-purpose`, so at most the in-flight window of dispatches dies before recovery). Resolve `args.model`/`args.effort` from the **run config, fail-open** — the same `<path> --fill-defaults` resolve `/war` consumes its config with, but never fatal here: `node ${CLAUDE_PLUGIN_ROOT}/skills/war/assets/war-config.mjs <repo>/.claude/war/config.json --fill-defaults`. A **zero exit** whose config carries an `agents.redteam { model, effort }` block ⇒ thread those two values into `args`; a **nonzero exit** (missing file, invalid JSON, or a config that fails validation) **or** a resolved config with **no `agents.redteam` block** ⇒ omit both — the scaffold then spawns every `agent()` (probes and confirms) with `{}` model opts, today's inherit-session behavior byte-for-byte. The same fail-open read also resolves the **loop budget's round limit**: a zero exit whose resolved config carries an integer `run.redteamRoundLimit` ⇒ that is `<resolved>` for Step 5; anything else — nonzero exit, absent key, invalid value — ⇒ the DEFAULT limit of **3**. The limit is never unset: config only overrides the default. The same fail-open read also sizes this run's **own sub-agent fan-out**: a zero exit whose resolved config carries `run.maxParallel` caps how many probe/confirm sub-agents run concurrently — the lever is your scratch copy of the scaffold: slice `allProbes` into groups of that size and await `pipeline()` on each group in turn, **never by dropping probes** (incomplete coverage forces INCOMPLETE), concatenating each group's results in `allProbes` order so the Layer-4 index join and its dropped-probe markers stay aligned; shape and default per `war-config.mjs` — never restated here. Anything else — nonzero exit, absent key, invalid value — leaves the scaffold's single `pipeline(allProbes, …)` call unthrottled, today's behavior. An unreadable or invalid config **does not stop the run** here (unlike `/war`; the block governs only which model the sub-agents spawn on, which round limit Step 5 threads, and how wide the fan-out runs, not whether the verification runs). Rooted at `--repo`, the read **tolerates a repo with no `.claude/war/` at all** — the common case for foreign-repo verification via `--repo`, where the absent file simply inherits the session. Assemble `args.provision` — the pinned setup commands each executed probe runs in its throwaway sandbox before the baseline — from the highest-authority source available: **(a) Operator pin** (explicit list passed by the user) wins; **(b) Committed manifest** — read `<repo>/.war-provision.json` via the shared `readManifest` (from `skills/_shared/provision.mjs`, the same module `/war` uses): if `found && ok`, use `result.provision` as `args.provision` verbatim; if `found && !ok`, surface the validation errors and stop; if `!found`, continue to the next source; **(c) Setup-scout / structural fallback** (`structuralFallback` from `provision.mjs`). This mirrors exactly how `/war` pins `run.provision` upstream of the Workflow — a manifest-only repo therefore provisions identically in both skills. A failing provision step is an env-gap → `status:"warn"` and the agent stamps that note finding `envGap: true`, which the gate (`assets/red-team-gate.mjs` `classify()`) demotes to a Minor note — never a red verdict (gate-side, typed enforcement; see [references/lenses.md](references/lenses.md) §Provision step). Omit `provision` (the default `[]`) when no source applies, for byte-for-byte back-compat. When `repo` ≠ cwd, point `repo` at a **clean checkout of the target**, so even a probe "reading around" lands in the right project — and pick the form by what will actually run there, the same rule the SCOPE-LOCK preamble gives each probe for its own sandbox. **Default: `git clone --no-hardlinks <target> <tmp>`** — the only form that stays isolated even when `<target>` is itself a linked worktree, and the right default here because probes run `git` against whatever you hand them as `repo`; a clone carries **committed state only**, so overlay any uncommitted edits explicitly (copy the dirty paths in, or apply `git -C <target> diff`) rather than assume the clone reflects them. `cp -R <target> <tmp>` is fine only when nothing but file reads and edits will happen — when `<target>/.git` is a **file** rather than a directory, `<target>` is a linked worktree and the copy is **not** git-isolated (it duplicates a pointer, so any `git -C <tmp>` drives the real repo) — never run `git` against a `cp -R` checkout, and note the Step-3/Step-4 escape-guard invocations below run `git -C <repo>` themselves. `git -C <target> worktree add --detach <tmp> HEAD` is for read-only inspection of committed state only: it shares the target's ref store, so create no branches and no tags in it, and dispose of it with `git -C <target> worktree remove --force <tmp>` (a bare `rm -rf` strands a prunable `.git/worktrees/<name>/` admin entry that accumulates and later blocks reuse of the path). **Never omit `--detach`**: the plain form writes a `refs/heads/<tmp-basename>` into the target that outlives `rm -rf` of the sandbox directory, and Step 4's ref-diff correctly reads that ref as an escape.
**Immediately before launching the Workflow** — once per run, after the checkout decision above — take the escape guard's pre-run snapshot — refs plus the ignored file set: `bash ${CLAUDE_PLUGIN_ROOT}/skills/red-team/assets/assert-no-repo-escape.sh --repo <repo> --snapshot <abs-scratch-file>`. The scratch path must be **absolute** and **outside the target's working tree** — a run-scoped file (e.g. under a `mktemp -d`), because an in-tree artifact would false-trip the guard's own porcelain check; a relative or in-tree path is refused with exit 2. **Exit 1 means residue or dirt predates this run** (a dirty working tree, or a local ref already matching the junk-sandbox pattern; pre-existing gitignored residue is recorded in the baseline, never refused here): resolve it via the containment rule in Step 4 **before** launching — never launder it into the baseline by re-snapshotting over it. Exit 2 is a git or file-write error, never a pass. Keep the snapshot path for Step 4.
The scaffold prepends a SCOPE-LOCK preamble to every probe + confirm, requires each probe to attest what it read (`read_anchor`), retries a dropped probe once, and returns dropped probes as markers (never silently filtered). Execution probes run in throwaway sandboxes; analysis probes are read-only; each fail is adversarially confirmed before it counts.
4. **Escape guard → gate.** Between the Workflow return and the gate, run the post-run escape guard **against the Step-3 snapshot**: `bash ${CLAUDE_PLUGIN_ROOT}/skills/red-team/assets/assert-no-repo-escape.sh --repo <repo> --baseline <the Step-3 snapshot file>` (0 = clean; 1 = an escape — a stray working-tree file, a **new non-allowlisted gitignored file** measured against the snapshot's recorded ignored file set (including a new file inside a directory the snapshot already listed files under), a junk sandbox ref, or **any** `refs/heads/` / `refs/tags/` ref added, removed, or SHA-moved since the snapshot; 2 = a git/infra error or an unreadable or zero-byte baseline, which is never a pass and must never be collapsed into 1). Always pass `--baseline`: without it the ref half falls back to the junk-**name** heuristic that a probe-invented ref name slips straight through, and the gitignored half does not run at all — the guard says exactly that on stderr. The SCOPE-LOCK preamble is prevention; this guard is the detection authority (Layer-2/3 doctrine, ADR 0033).
**On exit 1, diagnose every delta by action-provenance FIRST** — the **Diagnosis pre-flight (self-confound gate)** below, whose question #1 is "did I, or this run's probes, cause this?"; the recorded cwd-reset / bare-push escape is its in-repo precedent. Enumerating the deltas is **mechanical, never a hand survey**: the guard names each offending ref, and `git -C <repo> for-each-ref` re-reads them. Provenance splits the deltas two ways, and **this split takes precedence over the containment rule**:
- **Probe-authored, or provenance unresolved** → a real escape. Contain it (`git -C <repo> reset --hard`, `git -C <repo> clean -fd`, delete the leaked refs, and remove each leaked gitignored path the guard names with a path-scoped `git -C <repo> clean -fdx -- ':(literal)<path>'` (the literal magic keeps a leaked name carrying `*`, `?`, or `[` from matching sibling files as a glob; `-ffdx` when the named path is a nested git repository — a leaked sandbox clone, the recorded escape shape — because `clean -fdx` skips a directory with its own `.git`) — `clean -fd` alone leaves ignored files where they are, and a bare `clean -fdx` is never the recipe: it would also delete legitimate ignored state — in this repo `.claude/` is ignored only through the machine-local `.git/info/exclude`, exactly the class a bare clean would take with it), and **containment is complete only when the guard re-runs exit 0 *with* `--baseline`** — a porcelain-clean restore alone never closes the incident, because a leaked ref survives it untouched. Never report `CLEARED` while one such delta is open.
- **Foreign** — a branch or tag another session, worktree, or IDE created inside the run window, which you cannot and must not delete (this repo's linked worktrees share one ref store), **or an ignored file another session wrote inside the window** (an append-log such as `war-memory-queries.jsonl`, a sibling run's `.claude/teams/` state — check (d) is name-agnostic, so it reports these like any leak) → **not** an escape. Record it in the report and proceed: a provenance-cleared foreign delta does **not** block `CLEARED`. An ignored path whose provenance you cannot settle either way is **moved aside** (`mv` into a run-scoped scratch dir outside the tree), never deleted — the scoped `clean -fdx` recipe is for probe-authored paths only.
- **On exit 2** — there is no delta to triage: fix the named git/infra fault and re-run the guard; exit 2 is neither clean nor an escape, and the gate is never piped while the guard is unsettled.
This is the sole sanctioned reading of "never report `CLEARED` until the state is clean": *clean* means **no probe-authored residue**, not a byte-identical ref set in a repo other sessions share. A guard exit 1 carrying at least one probe-authored or un-cleared delta still forbids `CLEARED`, exactly as before.
Once the guard is settled (exit 0, or exit 1 with every delta provenance-cleared as foreign and recorded), pipe **either** the Workflow's returned object (`{ plan, repo, fingerprint, expected, probeResults }`) **or** the persisted task-output file through `node ${CLAUDE_PLUGIN_ROOT}/skills/red-team/assets/red-team-gate.mjs --stdin --rounds=<n> --round-limit=<resolved>` (with `<n>` = the Step-1 seed at this point) — the task-output envelope nests that payload under one `.result` level, which the gate unwraps automatically (a top-level `probeResults` wins). The gate **refuses to emit a verdict on a zero-probe input** (exit 1 with a stderr diagnostic, nothing on stdout — fail-closed: a verification that did not run may not report a pass). The gate validates each probe's `read_anchor` against the fingerprint, **discards off-target findings**, and returns the verdict + classified blockers / needsDecision / minors + a `summary` with `expected` vs `onTarget` and the `offTarget`/`dropped` probe names. A verdict of **`INCOMPLETE`** means coverage is not whole (an off-target, dropped, or never-ran probe) — it is **never** `CLEARED`.
5. **Grill → patch → adjudicate (bounded loop; re-verify attempts ≤ 2 per blocker).** A **round** is one full grill sweep — one pass through the open blockers/`needsDecision` set (that is the only unit "round" carries in this step; the per-blocker bound is counted in **re-verify attempts**, never rounds). Rounds count cumulatively: `<n>` = the Step-1 seed + the full grill sweeps completed this run, and **every gate computation of this run — Step 4's first pipe and every re-pipe here — carries `--rounds=<n> --round-limit=<resolved>`** (`<resolved>` from Step 3) so the gate can echo both and compute `routeUpstream`. When the first gate computation returns `BLOCKED`, read [references/loop-budget.md](references/loop-budget.md) — the finding-class routing rules, the patch-style rule, and the `## Route upstream` block template — and route each finding by its class before spending another sweep. When patching an End state, read [references/loop-budget.md](references/loop-budget.md) — the four-surface same-patch sweep rule, and the red-at-base re-execution the rewrite owes. If the verdict is `INCOMPLETE`, first **re-run only the off-target/dropped probes** (same bound: ≤ 2 re-verify attempts) until coverage is whole; a persistent `INCOMPLETE` is surfaced to the user as the terminal verdict — never downgraded to a pass. While the verdict is `BLOCKED`: take the next Critical / Major / `needsDecision`, present it with a recommended resolution and **grill the user one at a time**; apply the agreed fix to the plan file.
**Patch-and-adjudicate is the default close.** A probe re-run is owed on a patched blocker only when **both** arms hold: the probe that proved it was **executed**, *and* the patch changes what that same probe measures. Every other resolved finding — a `needsDecision` policy call, a stale literal, prose precision, any analyzed-probe finding — is adjudicated and closed **without** a re-run. The two exits differ in what happens to the finding: a blocker a **re-run probe proves resolved is removed** from the finding set (probe-proven, so `CLEARED` stays reachable); a blocker that was **patched but not re-verified is stamped `adjudicated: true`, never removed** — it stays listed in `blockers`/`needsDecision` so the evidence trail survives, and the run terminates `ADJUDICATED` rather than reaching a fake `CLEARED` by deleting findings ([ADR 0043](../../docs/adr/0043-adjudicated-clear-distinct-terminal-verdict.md)). An adjudication waives re-verification of a patch, never the validation itself.
**The stamping mechanic, named exactly** (so two Leads cannot invent divergent ones): keep a **working copy** of the gate-input object — `{ probeResults, fingerprint, expected, repo }` — taken from the Workflow's return value or read out of the persisted task-output file; **that persisted file is evidence and is never edited in place**. Stamp `adjudicated: true` on the finding objects *in the working copy*, then re-pipe the whole copy through the gate with `--stdin --rounds=<n> --round-limit=<resolved>`. A **top-level `probeResults` always wins over a `.result` envelope**, so the stamped copy is never shadowed by the original payload. Carry `fingerprint` and `expected` across too: a copy that drops them switches the coverage layer off entirely, and an `INCOMPLETE` run would re-pipe as `ADJUDICATED`. The backstop covers the flag's **provenance**, not just who applies it: a finding arriving **already stamped** in the Workflow's return value or the persisted task-output file is impossible by construction (the scaffold strips the `adjudicated` key at probe collection) and must be **treated as unstamped and investigated** before any adjudication.
**Terminal rule.** Auto-note or auto-fix Minors. If a blocker survives **2** re-verify attempts, or the user cannot settle a `needsDecision`, stop looping on it and record it as a **residual open question**. At loop end: every blocker patched and adjudication-rowed ⇒ the run terminates **`ADJUDICATED`**; an unpatched root or an unsettled `needsDecision` ⇒ **`BLOCKED`**, with those questions listed for the user. A gate computation that emits `routeUpstream: true` ends the loop the same way — stop grinding, terminate on the gate's verdict, and Step 6 emits the `## Route upstream` block. Never loop indefinitely. **The terminal verdict line is gate-emitted, never hand-written** — re-pipe the working copy and report what the gate prints. When a grill decision changes an **authoritative value** in the plan — especially a version or release-slot literal — record the adjudicated value and the plan-body literal it supersedes as a row in the report's `## Adjudications` block (Step 6), each row carrying its own provenance token (`operator-ratified (<date>)` or `AI-declared`).
6. **Emit the report** to `docs/red-team/YYYY-MM-DD-<plan-slug>.md` using the template in `references/lenses.md`, mapping the gate output into it: `verdict` → the verdict line; the cumulative round count → the dedicated `**Rounds:** <integer>` line **directly under the Verdict line** (the exact strict form Step 1's seeding re-reads — an integer, nothing else on the line); `summary` → *Attack surface* / *Executed proof* counts; `blockers` + `needsDecision` → *Findings* and *Resolutions applied*; `minors` → *Residual risk*; on a route-upstream terminal (`routeUpstream: true`) → additionally emit the `## Route upstream` block (template in [references/loop-budget.md](references/loop-budget.md)): the residual questions as the `/war-strategy` regrill agenda plus the exact re-entry command. State the final verdict; an `INCOMPLETE` verdict is reported as its own line with the `offTarget`/`dropped` probes that must be re-run.
## Drift-guard spine probes (run every red-team)
Four doctrine probes the Lead runs on **every** plan — universal like the spine lenses, but Lead-run like the backstop-legitimacy check ([references/backstop-legitimacy.md](references/backstop-legitimacy.md)). They are **not** entries in the scaffold's `SPINE` array (that fixed six-lens engine is owned separately); they live here in prose and the Lead runs them directly. They enforce ADR 0025's drift-guard discipline against the plan under test: a fact WAR mirrors across surfaces, or a default it flips, must ship its own mechanical guard **in the same plan** — and in the same **task** as the fact it guards, or else carrying a `deps` edge onto it — and a task that rewrites a doc owns the guard / de-mirror / defer choice for the machine-source-derivable facts it renders authoritative. Each is **vacuous** (an automatic pass) when the plan has no matching feature; none is skipped under `--fast`.
1. **`unguarded-new-mirror`** (analyzed). Does every new inline mirror the plan introduces carry a matching **mirror-registry** row in the same task? A task that edits `skills/war/assets/workflow-template.js` to add a `const` re-declaring a canonical export — a `HARD_ESCALATION_REASONS`, `landDecision`-set, or roster-helper mirror of `land-decision.mjs` / `war-config.mjs` (the sandbox can't import, so these are hand-copied) — MUST, in that same task, add a mirror-registry row in `workflow-template.engine-1.test.mjs` binding the inline copy to its canonical source. Prove it by grepping the plan text: an added-`const` edit whose task ships no matching registry-row edit is the failure. An inline mirror with no registry row is a plan defect → a `needsDecision` finding, grilled until the plan adds the row or drops the mirror.
2. **`default-flip-old-absent`** (executed). Does every default-flip / scope-narrow task's gate assert the **OLD** value *absent* across every enumerated doc surface — not merely the NEW value present? Prove it in a throwaway sandbox: apply the task's edits but leave **one** of its enumerated surfaces still carrying the old value, then run that task's own gate. A gate that stays green only checks NEW-present, so the drift it exists to catch survives → the finding stands (`NEW-present` alone is the recorded failure mode). The Lead runs this as a bespoke executed probe (add it to the scaffold's bespoke array or `args.probes`) — never by editing the `SPINE` const.
3. **`guard-split-deps-edge`** (analyzed). Does every drift guard the plan authors ride in the **same task** as the fact it guards — or, when it is split into a different task of the **same phase**, carry a `deps` edge onto the task that authors that fact? **Same wave is insufficient**: every task worktree is cut from one frozen phase base, so a guard whose guarded fact lands in a sibling task is red by construction at its own base, through no fault of its own diff. Its scope is deliberately **wider than `unguarded-new-mirror`** — *any* drift guard split from the task authoring its fact counts, prose mirrors and doc-guard tests included, not just inline `const` mirrors of a canonical export. Prove it by reading the plan's per-task `Files:` and `deps:` lists: a task shipping a guard over a fact another same-phase task authors, with no `deps` edge onto that task, is the failure. A missing edge is a plan defect → a `needsDecision` finding, grilled until the plan adds the edge, merges the two tasks, or moves the guard into a later phase. **Vacuous** when the plan splits no drift guard away from the task authoring its fact; **not skipped under `--fast`**. (ADR 0025 Consequences; recurrence record: lesson `guard-task-split-from-mirror-task-needs-deps-edge-same-wave-insufficient`.)
4. **`touched-doc-fact-coverage`** (analyzed). Does every task whose `Files:` rewrites a doc show the **guard / de-mirror / defer** choice for its machine-source-derivable facts? For each fact the rewritten doc renders authoritative that is derivable from a **machine-readable in-repo source** (a config default, a manifest field, an enum member, a version slot — never prose claims generally), the plan must show exactly one of the three: a **guard** (a drift test binding the doc value to its source by extraction + equality), a **de-mirror** (the doc points at the canonical source instead of restating its value), or an **explicit defer** (a legitimacy-complete row in `## Deferred validations (backstops)` naming the runner and the timing — ADR 0017's vehicle, never a prose waiver). Prove it by reading the plan's per-task `Files:` lists and slices: a doc-rewriting task restating such a fact with none of the three is the failure → a `needsDecision` finding, grilled until the plan adds the guard, de-mirrors, or defers explicitly. **Vacuous** when no task's `Files:` rewrites a doc, or the rewritten docs carry no machine-source-derivable facts; **not skipped under `--fast`**. (ADR 0025's 2026-08-19 touched-doc amendment; authoring twin: `/war-strategy` §3 rule 8.)
Findings route the **normal plan-patch loop** (Steps 4–5): the `needsDecision`/blocker is grilled one at a time and the plan is patched — add the mirror-registry row; add the OLD-absent gate assertion and enumerate the surfaces; add the `deps` edge onto the task authoring the guarded fact; show the guard / de-mirror / defer choice for the touched doc's facts — until it clears.
## Diagnosis pre-flight (self-confound gate)
Before you attribute any probe fail or unexpected result during Steps 3–5 — a red probe, a broken baseline, an odd sandbox or live-system state — to the plan, the repo, or a subsystem, run this **self-confound gate**: the refute discipline aimed inward.
1. **Action-provenance first.** Enumerate your own and any concurrent actor's recent **mutating** actions against the observed state and explicitly rule each out — "Did I cause this?" is question #1 — before blaming the plan, the repo, or a subsystem.
2. **Single-path validation.** Never validate or reproduce against the same shared mutable state through two paths back-to-back (manual **and** automated); pick one path and re-provision fresh state before switching.
3. **Hypothesis promotion gated on primary evidence.** No durable artifact — a memory/lesson write, a `war-followup` issue, a fix plan or spec, a sub-agent fan-out (the closed D3 channel list) — may encode a root-cause diagnosis until the mechanism is shown from **primary evidence** (raw logs, a clean repro) **and** an inward refute pass has proven the cause is not your own action.
4. **State the falsifier.** Before acting on a diagnosis, state the observation that would falsify it and go check; evidence merely consistent with the theory is not proof.
Escape-valve: the gate blocks *promotion*, never diagnosis or escalation — when that evidence is unobtainable (ephemeral state, no logs), record the diagnosis as an explicitly-labeled **hypothesis** (report/ledger note) and/or escalate, then proceed. In-repo precedent: lesson `redteam-executed-probe-cwd-reset-hits-real-remote` is a recorded self-confound — a probe's own cwd reset hit the real remote.
## Invariants (never violate)
- Never mutate the repo's source or run the plan against the real repo — all execution in throwaway temp dirs / worktrees, cleaned up. You write only the plan file (patches) and the report.
- A `fail` requires **reproduced** evidence; the adversarial-confirm stage downgrades anything unreproduced. Prove, don't assert.
- Do not "execute" a plan step that performs irreversible/outward-facing actions (push/deploy/send) — analyze those instead.
- If a `[Fact-Forcing Gate]` (GateGuard) blocks an edit, present the facts it lists, then retry the identical operation.
- **No verdict other than `INCOMPLETE` may be reported on incomplete coverage.** Every probe must attest (`read_anchor`) that it read the fingerprinted plan, and every expected probe must run; an off-target or dropped probe forces `INCOMPLETE` and a re-run. Prevention (scope-lock) is never trusted alone — the gate's anchor check is the authority.