execute-plan · diff

git:20260925.9b3c461 to git:20260926.e0ca045

14 added, 9 removed. Audit B to B.

---
name: execute-plan
description: "Executes a PM-approved plan via dispatched per-chunk executor waves."
allowed-tools: ["Read", "Edit", "Write", "Bash", "Grep", "Glob", "Agent", "Skill"]
argument-hint: <plan-path>
---
# Execute Plan — End-to-End Plan Execution
Run a PM-approved plan end-to-end to full completion without stopping for permission between
tasks. Invoking `/execute-plan` on a plan IS the PM's authorizing act; plan-frontmatter
`execution_authorized_at` is the record of that act, not a precondition for it. **It also
satisfies a chunk body's own "needs PM assent at execution dispatch" clause** — that clause names
this exact moment, and the invocation is it. Never re-ask per chunk, and never offer to halt a
fired run at that chunk's wave; a plan the PM authorized is authorized to its hot-path edit. A
chunk gate the invocation does NOT satisfy is one naming a different act — a cross-repo commit
(per-session assent, obtained at that dispatch; on a managed-remote host that gate stands down, the
assent is registered at the PR carrying the branch instead, and it stands down to a
warning and one repo's work per commit stream is what binds) or an external-facing action. Does not chain
into branch disposition — that's the PM-gated `/merging-to-main`. Full rationale and mechanics:
wiki.
Executing a plan is restructure-then-dispatch, not "type the plan's steps": build the
dispatch-gate graph, decompose into per-chunk dispatches — parallel where gates allow, serial
where they don't, default vehicle a background Workflow. A serial chain is still N fresh
dispatches with EM-verify between, never one long-lived executor. No per-chunk reviewer gate —
code review defers to `/workstream-complete`. **EM-verify means the EM itself runs the chunk's
tests, never trusts an executor's pass claim** — a dispatch vehicle can narrow the executor's Bash
surface below what the tests need (wiki: `workflow-orchestration.md`), and an honest executor then
reports "PASS (by inspection)". Inspection is not verification. **A host-dependent chunk's red is
not a verdict until the interpreter is confirmed** — before treating red as a regression, confirm
the interpreter EM-verify runs under matches the canonical gate env (host venv, or the sibling on
path) rather than a bare `python3`; a sibling-host import error reads as a test failure, not a
skip, and masquerades as a chunk regression. **Unit-green is not reachable —
a chunk that ships a new helper/hook/injector is not done on green tests alone.** An executor's
own note flagging the mechanism as unwired does not discharge the EM's check, and a mechanism
with no caller is never accepted as shipped. It is a gated step
in Phase 3's per-task loop (mark-complete is conditioned on it) and re-checked in Phase 4's
close-out checklist before the stamp. Detail: `coordinator/docs/wiki/reviewer-pipeline/review-integration-doctrine.md`
§ An observable-outcome acceptance criterion is never satisfied by a tested pure function alone.
Dispatched executors are always
Sonnet; self-execute only on a named token-economics carve-out. Phase boundaries are not stop
boundaries: ship Phase N green, dispatch Phase N+1 immediately, no checkpoint offer.
**Dispatch authorization — invoking this skill IS the request.** The dispatches named below are constitutive steps of this skill, not a separate thing to get cleared: invoking a skill requests the actions that skill performs. A harness line permitting dispatch "unless the user requested it" is therefore **satisfied here, not overridden** — no precedence claim is needed and none is made. Re-asking spends the very context the dispatch exists to protect. The rule attaches to skill entry and dissolves no PM-authored gate: keyword-gated skills gate entry, and every gate a skill names for itself still binds — per-session cross-repo-commit assent, ask-before-external-action, and any other this skill's own body names. Tripwire: `UNATTRIBUTED-HARNESS-LINE-IS-NOT-PM`.
**Workflow-approval — same shape.** Firing the background Workflow Phase 1.5/1.6 assembles is a constitutive step of executing the plan: invoking `/execute-plan` requests running the workflow it hands back. A harness line asking approval to run a workflow is therefore **satisfied here, not overridden**. Tripwire: `UNATTRIBUTED-HARNESS-LINE-IS-NOT-PM`.
---
## Arguments
`$ARGUMENTS` is the plan document path. No path or file not found → report and stop.
---
## Phase 1: Load, Authorize, Review
1. Read the plan in full.
- 2. Unless `/autonomous`: run `pickup-assemble stamp-check <plan-path>` FIRST, before minting —
+ 2. Unless `/autonomous`: run `pickup-assemble brief <plan-path>` FIRST, before minting — it emits
+ `gates.execution_stamp_match`, the check this step needs (the CLI has no `stamp-check` verb) —
minting takes a fresh timestamp when the body sha differs from a prior stamp, so minting first
would erase the staleness signal this check exists to catch. FRESH or STALE-bookkeeping →
proceed, and on STALE-bookkeeping proceed **without re-stamping**: the stamp is correct and
only ratification fields moved, and advancing `stamp_commit` throws away the drift a later
substantive edit must stand out against. `stale-bookkeeping` promotes no `d-stamp`
directive; UNSTAMPABLE still does, and that one is mechanical. A business-fail of "carries no
`execution_authorized_sha`" means there is nothing to compare yet → proceed, not a refusal.
STALE-substantive surfaces the delta and STOPS. THEN mint the record from this invocation:
`review-exec-auth-stamp authorize-invocation <plan-path> --typed-command /execute-plan
- [--utterance "<PM's verbatim words>"]` — `--utterance` is optional, a bare `/execute-plan`
- mints just as well, and the verb always writes `execution_authorized_by: PM`, convergent across
- re-invocation and date boundaries. Under `/autonomous`, skip both legs.
+ [--utterance "<PM's verbatim words>"]`. Pass `--utterance "<PM's verbatim words>"` whenever the
+ PM's invocation carries words. Emit refuses a plan whose PM words resolve nowhere: it walks the
+ chain once — the plan's `## PM brief` section, then `execution_authorized_note`, then the
+ baton's `summary:` plus `## What this covers` — and names the remedy, which is to re-stamp with
+ `--utterance` or add a `## PM brief` section. Under `/autonomous` the stamp is skipped, so the
+ plan's own `## PM brief` section or its baton is the only source; skip both legs.
**`mise_prepped_*` is a different axis; neither it nor the quartet substitutes for the other.**
A plan arriving by `plan-blitz → mise-prep` carries both, and this step writes only the
quartet — the attest survives that, because `mise_prepped_sha` hashes the plan BODY. Tripwire:
`A-HANDOFF-AN-EM-RETYPES-IS-NOT-A-SEAM`.
3. **Remaining-context gate** (skip under `/autonomous`): read this session's own remaining-context
reading before committing to same-session execution — the same context-window percentage the
statusline captures and publishes each turn. A session already carrying the plan authorship +
review dialogue with LOW remaining context is the narrow carve-out, not the default — a fresh
(picked-up) session with a full context budget dedicated to execution is the intended path at
any plan size. The failure this gate catches is degraded tool-call reliability in a
context-saturated session, so the meter reading — not planning provenance — is what it reads.
Detail: wiki.
<!-- engine-gap: field=execute_plan.session_freshness_verdict producer=unknown memo=2026-08-27-claude-klabauter-em-doe-unmarked-obligations-and-four-lost-markers.md -->
4. Resolve EM-resolvable concerns at EM altitude — not the moment to surface them to the PM. A
concern revealing the plan isn't actually executable → Phase 1.4.
5. Announce and continue.
---
## Phase 1.4: Executability Gate
Bounce to `/plan` on any of: an embedded decision gate ("evaluate X before continuing", "Phase 0
— investigate"); a fact-finding chunk with no fix-locus; an unpopulated downstream wave-map;
in-prose deferral of an EM-resolvable (not PM-altitude) decision; open questions gating whether
downstream chunks can be authored; an unbuilt external prerequisite with no landed commit/date.
Read the last two off the spine, not prose. A non-deferred open row with no `writes:` key IS an
unpopulated wave-map, whatever the plan body's prose says. For the external prerequisite, a row's
`external_gate` entries are where it is declared: `blocks: execution` uncleared bounces;
`blocks: ac-closure` does not — proceed, and tell the PM at dispatch that this run's terminal
state is `approved`, not `implemented`, and why.
Full signal catalog and non-signals: wiki.
---
## Phase 1.5/1.6: Dispatch-Gate Graph and Wave-Map
**Roadmap-baton execution gate — check before claiming.** A plan executing a `blocked_by` roadmap
baton needs every blocker **coded**, not merely planned. An approved blocker plan opens that
baton's *planning* gate and nothing else: planning consumes the blocker's decisions, execution
calls its code. Read it, never derive it —
`coordinator-invoke roadmap.plan_gate '{"subject":"<baton-id>","gate":"execution"}'` returns
`verdict.open` plus the blockers holding it shut. Shut → stop; the plan is written and waiting, and
that is the correct state. Tripwire: `A-PLANNING-GATE-IS-NOT-AN-EXECUTION-GATE`.
Claim the plan (`session-claim-cli claim-plan <slug> --for-execution`) before any gate-graph work
— a live peer holding it means reconcile with them first, never race. A live holder that has
formally handed off — relinquishment evidence present, not mere liveness — is not "reconcile with
them"; `take_over_claim` is the named next move, fail-loud without evidence and never a bare retry
of `claim-plan` (`docs/decisions/DR-205-claim-relinquishment-is-not-liveness.md`). **`--for-execution` is not
optional here.** It is what flips the plan to `status: executing`, and this step is its only
caller fleet-wide; a bare `claim-plan` takes the lock and leaves the plan reading `draft` through
its entire execution. The flag is scripted into this step, not typed by the EM — the rung stays
invisible, per `coordinator-tripwires/plan-status-ladder.md`.
Immediately after that claim, run `plan-completeness generate "$ARGUMENTS"`. This is a BASELINE
SNAPSHOT of the plan at execution start, nothing more — nothing reads it to steer dispatch, and
the wave-map does not consume it. Its value is the diff against the Phase 4 run: it is honest
about being a snapshot of a recompute-on-read projection, not a live signal.
**Plan prose does not pick the vehicle.** An Anti-scope or body sentence forbidding fan-out, or
prescribing EM-sequenced chunk-at-a-time execution, is overridden here: the vehicle follows from
the classification below, default a background Workflow. Note the override in one line and
continue — do not ask. A vehicle prohibition traceable to a genuine Workflow-inexpressible shape
(`coordinator/docs/wiki/em-operating-model/workflow-orchestration.md` § What qualifies as a carve-out) is the one that survives.
Tripwire: `A-PLAN-DOES-NOT-PICK-THE-EXECUTION-VEHICLE`.
**Classify every chunk pair before dispatching — this decides whether you can run them together:**
- **File-write overlap** (same path) → gates authoring, no escape hatch — predecessor must land
before the other authors.
- **Output/contract consumption** (B reads A's static output, or A changes a schema/signature B
depends on) → author both concurrently if A's interface is pinned up front (verify at merge);
no pinnable interface → predecessor-wave instead.
- **Runtime consumption** (B needs A's artifact to *exist and run*, e.g. a dry-run over a
not-yet-shipped pipeline) → gates authoring unconditionally, no pinning escape.
- **Epistemic/premise** (A decides whether B's chunks should exist at all) → gates authoring
unconditionally — A ships alone in its own wave first, B isn't even drafted until A's verdict
lands.
- **Independent** → same wave, no gate.
A row carrying an uncleared `external_gate` entry with `blocks: execution` is unschedulable in any
wave — that gate is on another repo, not on a chunk pair, so no pair-classification clears it.
**Cross-check the AC table against the chunk list before emitting.** An `## Acceptance Criteria`
row with no chunk citing it (frontmatter list, `covers:`, or body reference) is a silent gap — the
row renumbers under a chunk reshuffle and nothing catches it, since `plan-coverage-checker`
explicitly excludes this table from its oracle. Walk every AC row, confirm at least one `## Tasks`
row names it, and treat an uncovered AC as an authoring gap to fix in the plan before dispatching,
not a `plan-coverage-checker` finding to defer.
**A signature/param-removal chunk that scopes only production handler signatures is the same kind
of authoring gap.** Test fixture defs and test call sites passing the removed param break at the
same cut a production call site would — walk the chunk's task list for a scope that also names
fixtures and call sites, and fix it in the plan before dispatching, same as an uncovered AC row
above.
**Invoke `dispatch.emit` — don't derive the wave shape by hand and don't stop at deriving it.** It
reads the spine's `writes:`/`depends_on` and emits the ready-to-fire Workflow itself, each row's
`gate_kind`, `write_files`, and `agentType` already resolved and non-dispatchable rows already
filtered — not wave-map metadata for the EM to transcribe into a hand-authored script.
<!-- engine-gap: field=execute_plan.gate_kind_fallback_classification producer=unknown memo=2026-08-27-claude-klabauter-em-doe-unmarked-obligations-and-four-lost-markers.md -->
`NoWritesDeclaredError` means the spine is unpopulated — an authoring gap to fix in the plan, not a
<!-- engine-gap: field=execute_plan.ses_fire_check producer=unknown memo=2026-08-27-claude-klabauter-em-doe-unmarked-obligations-and-four-lost-markers.md -->
licence to hand-derive. Write the emitted script to a plan-relative on-disk path
(`<plan-basename>.workflow.mjs`, next to the plan) — a disk artifact, never plan-body prose, a
hand-authored wave map, or a chat emission of a wave table.
<!-- engine-gap: field=execute_plan.wave_map_validation.violations producer=unknown memo=2026-08-27-claude-klabauter-em-doe-unmarked-obligations-and-four-lost-markers.md -->
**Emit and dispatch are ONE action, and the dispatch leg is not optional.** In an interactive
- session the EM runs `python <plugin-root>/bin/emit-dispatch-workflow.py --plan <plan-path>`
- (plugin-local, no settings-home launcher — resolve per `${CLAUDE_PLUGIN_ROOT}/snippets/resolve-coordinator-bin.md`
- § CLIs with no launcher; never cwd-relative), then
+ session the EM runs
+ `"${COORDINATOR_SETTINGS_HOME:-${CLAUDE_HOME:-$HOME}/.coordinator-claude-settings}/bin/emit-dispatch-workflow" --plan <plan-path>`
+ (settings-home launcher — resolve per `${CLAUDE_PLUGIN_ROOT}/snippets/resolve-coordinator-bin.md`;
+ never cwd-relative), then
calls `Workflow({scriptPath: "<emitted path>", args: {repoRoot: "<absolute repo root>"}})` in this
session, using the exact `fire with: Workflow(...)` line the emitter prints on stderr. The
emitter's output is already a valid `scriptPath` input — no flag, no re-authoring. That call
carries the same imperative force as the emit: it is not EM discretion about whether to dispatch,
and it is not hand-dispatch — it is the emitted script, one action. Emitting and stopping writes a
script nothing runs.
**An emitted script is not a delivered dispatch.**
**An emitted fire raises no permission prompt; a hand-rolled one does.**
`allow-emitted-workflow-fire.py` (PreToolUse, matcher `Workflow`) auto-approves a `scriptPath`
whose `<script>.emitted.json` digest still matches the bytes on disk, and stays silent for
everything else. So an unexpected prompt means the script was hand-authored or edited in place —
`--restamp`, not a re-emit, is the route back. Tripwire:
`AN-EMITTED-WORKFLOW-FIRES-WITHOUT-A-PROMPT-A-HAND-ROLLED-ONE-DOES-NOT`.
Firing in-session is what makes the run the operator's: visible in their workflow list, inspectable
while it runs, resumable via `resumeFromRunId` (same-session-only), under their permissions, costed
to their session, completion arriving as a task notification.
**`--fire` is the headless and cron path only.** It hands the script to `engine_fire.fire_workflow`
(a module function, not a `workflow.fire` op dispatch), which spawns a detached `claude -p` child
and returns a run handle
(`{"script": ..., "handle": {...}}`; the handle's `fire_id` is what `workflow.fire_status`
re-reads). The resulting workflow is native to that child, not to any operator: it runs under the
child's own `--allowedTools` (no `PowerShell`; denials surface only as `permission_denials` in the
final JSON envelope, which reads identically to a chunk that made no edit), the run cannot be
resumed, and the fire log is written only at process exit — so a live run and a killed one are
indistinguishable through it.
Correct where no session exists to call the tool, and wrong everywhere a session does.
**There is no hand-dispatch fallback.** If the dispatch refuses — the `Workflow` call, or on the
headless path a named fire-leg refusal (`ScriptNotFoundError`, `PluginDirResolutionError`,
`ConcurrencyCapExceededError`, `ChildSpawnFailedError`) — report it and stop. Hand-dispatching the
same chunks with the Agent tool after a refused or unattempted dispatch is the failure this whole
surface exists to prevent, and it is never the recovery. A concurrency-cap refusal means wait, not
dispatch by hand.
**A fifth state exists beyond the four named refusals: fired-then-died.** The four refusals above
are all fire-time — the child never started. A child that started and then died mid-run is
different. Check the returned handle's `log_size_bytes`: `0` is a free
liveness signal — the child has written nothing yet, whether because it is still starting or
because it died before its first write. Treat a stalled handle with `log_size_bytes: 0` past a
reasonable startup window as fired-then-died: report it and stop, the same as a fire-time refusal.
A workflow-spawned agent IS its declared `agentType` — the type propagates — but the catering
layered on top of it at dispatch time does not: no `contract_blocks`, no provisioned report
sidecar, no dispatched-worker role framing (measured both ways against a discriminator and the
catering trace, `state/audits/2026-08-18-contract-blocks-workflow-delivery.md`). That shapes what a
spawn-time catering fix must carry, not whether the emitted workflow gets fired.
Wave shape comes from the file-write graph, never the plan's section/theme structure — the op
derives it; a second derivation is a silently desyncing source of truth for a fact the graph
already knows. One dispatch per chunk inside the emitted script — never bundle serial chunks into
one long-lived executor; a serial dependency removes concurrency, not decomposition. Full taxonomy,
malformed-wave checks, sizing, and authoring mechanics: wiki.
**No checkpoint prompt; scripted gates still hold.** What roll-on removes is the EM checkpoint
prompt — the fired workflow does not pause to ask a human whether to continue, not before the
first phase and not between waves. What it does NOT remove: a `phase()`'s own deterministic gate
MAY still halt the run (`return { halted: ... }`, wiki: `workflow-orchestration.md`), and a halted
or edited phase resumes via `resumeFromRunId` without re-paying phases that already succeeded.
Removing the prompt is not removing the gates.
**One emit per plan — a partial re-run resumes, it does not re-emit.** The emitted script already
carries every open wave, preflight through terminal test phase; firing it once is the whole plan.
When a run halts — a `Commit wave N` phase with no `COMMIT-LANDED <sha>`, a BLOCKed executor — the
recovery is `Workflow({scriptPath, resumeFromRunId: <run>})` in the same session — but resume
alone does not recover. Resume serves the longest UNCHANGED prefix of `agent()` calls from cache,
and the commit agent that refused *completed*; its refusal is cached. Relaunching the script
untouched replays that cached refusal and re-halts at the same gate. **Edit the halting phase's
agent step first, and no earlier one** — an edited completed step loses its cache and re-runs,
re-committing or halting on an already-clean pathspec — fix what the refusal named — then
**re-stamp the receipt**, then resume; the
run id comes back in the `Workflow` tool result, not from anything the script can read about
itself. The re-stamp is not a formality: `block-workflow-foreign-emission.py` denies a fire whose
bytes differ from the receipt beside the script, and the edit is exactly that difference.
- `emit-dispatch-workflow.py --restamp <script>` re-stamps it and prints the phase spine it
+ `emit-dispatch-workflow --restamp <script>` re-stamps it and prints the phase spine it
authorizes, refusing unless the receipt already names this session. A second
- `emit-dispatch-workflow.py` is not the recovery: `read_spine` excludes rows whose `disposition` is
+ `emit-dispatch-workflow` is not the recovery: `read_spine` excludes rows whose `disposition` is
closed, so an emit against a plan whose early chunks have landed silently produces a narrowed
one-wave script, indistinguishable on disk from an emit that was always meant to be partial, and
re-pays the preflight and every phase that already succeeded. An `--out` naming a chunk id
(`<plan>.c9.workflow.mjs`) is the tell. Re-emit only when the spine itself changed — a row added, a
gate cleared, a `writes:` corrected — and name the change in the report. Tripwire:
`A-SECOND-EMIT-AFTER-A-PARTIAL-RUN-NARROWS-SILENTLY`.
**Watching without waiting — arm a `Monitor`, don't poll by hand.** Roll-on removes the checkpoint
prompt, not the EM's eyes on the run. Once the workflow is fired, arm the harness `Monitor` tool
against the run's own progress signal — the transcript directory's `journal.jsonl` plus the
harness's own completion report — with a command that surfaces phase-boundary and failure lines
(`persistent: true` for a run that outlives one Monitor window). Cover every terminal state a wave
can end in, not just success, per `Monitor`'s own filter-coverage guidance — a filter that only
matches the happy path stays silent through a stuck or crashed wave, which looks identical to
"still running." Notifications land asynchronously in chat while the EM keeps working — never a
second checkpoint — and a stuck or failed wave surfaces as a notification to act on (inspect disk,
`resumeFromRunId`) the moment it lands.
Don't build a new watcher for this. `Monitor` already is the "eyes on it with a chron or
something" the PM asked for — no new script under `coordinator/bin/`, no new reporting format. Do
not copy the `CronCreate`/`RemoteTrigger` refusal in
`coordinator/skills/strategic-self-description-refresh/SKILL.md`: that gate exists to force a human
decision, and a fired workflow has none to protect.
---
## Phase 2: Create Flight Recorder
TaskCreate: one task per plan phase/major task, added BENEATH the stage rows the sizing lobby
already opened (`skills/sizing/SKILL.md` § 4b) — the lobby is the single opener, and its
session-goal task is already `in_progress`. Entered without the lobby (no stage rows on the
list): open the recorder here instead, session goal plus phases, marked `in_progress`
immediately.
<!-- BEGIN task-tool-availability (synced from snippets/task-tool-availability.md) -->
`TaskCreate` absent from this session's surface (`ToolSearch("select:TaskCreate")` returns nothing)
→ fall back to `coordinator-tasks-mirror` for the same flight-recorder role; do not assume either
state without checking. When Task* is unavailable, dispatch the phases in order, waiting on each
completion notification — that is the ordering a `blockedBy` chain would otherwise express.
<!-- END task-tool-availability -->
Executing a plan in a repo other than the one this session is anchored in → pass
`coordinator-tasks-mirror --repo-root <plan's repo>`. Bare, the mirror resolves its root from cwd
and the repo-identity gate refuses the write as a MISMATCH — a deliberate cross-repo call is
otherwise indistinguishable from the `cd`-drift accident that gate exists to catch. The flag takes
the ungated EXPLICIT arm; it never softens the gate on the bare arm.
---
## Phase 3: Execute All Tasks
<!-- engine-gap: field=execute_plan.wave_boundary_gated_artifact_check producer=unknown memo=2026-08-27-claude-klabauter-em-doe-unmarked-obligations-and-four-lost-markers.md -->
Default: execute every task in sequence without stopping to ask. Per task: write-ahead (mark
`In progress` on disk + TaskUpdate `in_progress`) → execute (follow the plan, fix routine errors,
move on) → **reachability gate** (task shipped a new helper/hook/injector? confirm a production
call site reaches it — caller-grep or traced entry point, never an import smoke-check — before
proceeding; unit-green alone does not clear this, per Phase 1 § EM-verify) → mark complete (on
disk + TaskUpdate `completed`) → proceed immediately, including across phase boundaries, same
session, same flight recorder.
Mid-dispatch decisions are EM decisions — pick, record a one-line rationale inline, continue; only
the Phase 5 list escalates. A residual (a site the sweep missed, a fix wider than the AC) needs a
closed exit — dispatch it, add a spine row for the Phase 4 harvest, `coordinator-queue-append
--schema bug-backlog|debt-backlog|improvement-queue`, or take it to the PM. A written reason with
no queue id/spine row/commit behind it is not a routed item.
---
## Phase 4: Finalize and Report
**Precondition:** every wave-map chunk has landed, confirmed via the recovery triple. Unconfirmed
chunks → return to Phase 3. Leg 1 alone yields candidates, never a verdict — corroborate against
leg 2 or leg 3.
1. **Leg 1** — the engine op purpose-built for this read, named `chunk-commits <plan-path>
<chunk-id>` (`ceremony.chunk_commits`): it resolves the plan's own add-commit, range-scopes to
`<add-sha>..HEAD`, and filters on the commit SUBJECT (never `--grep`, which false-positives on
body-line matches). Its own negative-spec is the reason: it never accepts a pathspec-scoped
query — a doctrine-conforming chunk commit is forbidden from touching the plan document
(`snippets/plan-doc-oos-block.md`), which is exactly what Phase 4's own scoped-commit mandate
above requires — so a pathspec-scoped join returns empty on every conforming plan and reports
every chunk missing, at exit 0. Meeting a `no_join_candidates`-shaped result is this
contradiction, not a sign the chunks didn't ship; the subject-prefix join `chunk-commits` does
internally is the only one that works. It is also never a bare repo-wide/all-branches grep:
chunk ids restart at C1 per plan, so an unscoped grep can match a different plan's
same-numbered chunk and report this plan's precondition satisfied when it was never
dispatched — `chunk-commits`' fixed `<add-sha>..HEAD` anchor is not caller-overridable, so
this failure mode is unreachable through it.
2. **Leg 2 — the Workflow's own resumable script**, persisted and resumable via
`resumeFromRunId` (Phase 1 above).
3. **Leg 3 — the Task-list flight recorder** (Phase 2 above), persists through compaction by
design.
The engine-side half of this same join defect is the engine repo's, tracked at the engine repo's
`state/sizings/2026-08-08-close-out-and-stamp-no-join-candidates-s.yaml` — one contradiction, not
two independent bugs.
This defect has been found three times independently and credited zero:
`state/lessons/2026-07-27-the-chunk-shipped-recovery-signal-keys-o-4eb89c9a1830.yaml` ("corroborate
against the other two legs of the triple ... plus a git log by chunk-id subject unscoped by path")
and `state/lessons/2026-07-31-a-closure-detector-keyed-on-the-plan-pat-83ecabed9809.yaml` ("key the
detector on something the permitted writer produces (the chunk-id commit subject anywhere in
range, not filtered by path)").
**`close-out-and-stamp` reads no commit message at all.** The commit-subject/`Deliverable-Id`-trailer
join was deleted — not narrowed — on measured low recall; its absence is a
ruling, so do not restore it as an oversight. Two evidence paths survive, both pure sha-ancestry
checks: a `disposition: coded` spine row's own `disposition_ref`, and, for a plan predating the
`## Tasks` spine, its `## Dispatch Ledger` table's `committed <sha>` cells. Neither infers
completion from a commit message, so a correct subject and a correct trailer are not, together or
alone, evidence that any row shipped.
**The `## Tasks` spine is the only row family close-out reads.** Delivery evidence is the
falsifier delta on `prime_exit_criterion` — its verdict, not a row's ticked-or-open state, is what
discharges the plan. Tripwire: `AN-UNTICKED-AC-CELL-CARRIES-NO-INFORMATION`.
**Before any cleanup:** `coordinator-harvest-deferrals --plan "$ARGUMENTS"`, surfacing its
`"Queued N ..."` line even on `Queued 0`. A `defer` grouping approval (or legacy
`pm_approved: true`) is a claim of ratification the harvest selects on, not something this step
may stamp — closing a row mid-execution is a scope decision that needs the PM first.
**Commit sequence, two commits, never one — the resolve step below writes the plan, not a third
commit:**
1. Land the chunk work in your own scoped commit(s) — explicit pathspec, never `git add -A`. The
`prepare-commit-msg` hook attaches the `Deliverable-Id:` trailer when you commit per
`snippets/scoped-commit-route.md`; never hand-add it. The trailer is provenance for other
consumers — close-out does not read it, so its presence proves nothing about delivery and its
absence stamps nothing partial.
2. Resolve each landed row's own `disposition_ref`, the only thing close-out counts:
`plan-tasks-resolve --plan "$ARGUMENTS" --id <row> --coded <sha> --disposition-detail "<why>"`,
per row, then close out. Failure signature — `missing_chunk_ids` at exit 0 over a range that
provably holds every chunk SHA — means unresolved rows, not missing trailers and not a range
problem; resolve and re-run, never rewrite shared history. Record the sha where the work
actually landed: `disposition_ref` is hand-written and the anti-self-attestation gate cannot
catch a row pointing at a peer's commit, since that commit is an ancestor of `HEAD` too — a
spine can be fully green and fully misattributed.
2.5. Run `plan-completeness status "$ARGUMENTS"` against the plan. `status` never writes, so the
Phase 1.5/1.6 baseline sidecar survives untouched. Paste its raw rollup line into
`exit_criterion_met.prose`, beside the baseline's own rollup line — the diff between the two is
the ledger's value here, not either line alone. Any `CONTRADICTION: ` line in the output carries
verbatim into the close-out report; never stamp over it silently.
3. Re-run `prime_exit_criterion.falsifier.how` against `HEAD`, paste its raw output into
`exit_criterion_met.falsifier_output`, and judge it against
`prime_exit_criterion.falsifier.expected_when_true` — never against `baseline_output`. Record
the verdict in `exit_criterion_met.falsifier_verdict` (`pass`/`fail`); the gate refuses the
stamp unless it is `pass`. A changed-but-non-matching output is still a `fail`: non-inertness
alone does not prove the criterion, so no separate zero-movement rule is needed — an unchanged
output already fails the same check. `exit_criterion_met.prose` is the signature tying that
verdict to the prime exit criterion, not a substitute for it. Verdict `fail` →
`asserted: false`, Phase-5-halted, no stamp.
3.5. **Promote the falsifier, when it promotes.** An executable, deterministic falsifier graduates
into the repo's test suite: record `promotion: promoted` and `promoted_to: <test path>`, or
`promotion: partial` with the path the promoted portion landed at. A falsifier that was already
a standing test before the plan records `promotion: already-in-suite` and no `promoted_to` —
nothing graduated, and the standing test it already was goes in `promotion_reason`. PROMOTION IS
AN OUTCOME, NOT A GATE: a one-shot corpus query, a manual observation, or a measurement against a
live index records `promotion: not-applicable` plus a reason, and close-out accepts it. Do NOT
make promotability a precondition for the stamp — EMs would then choose falsifiers for their
filing convenience rather than for what they actually falsify, which is the vacuous-AC failure
wearing a new costume.
3.6. **Adversarial criterion-only reader, M+ plans that went green first time only.** M+ per
`sizing_object.estimate.tshirt` (§ Proportionality; an S-lane spec-dispatch never gets this) AND
every wave-map chunk landed without an executor BLOCKing on this run — dispatch one reader that
receives only the prime exit criterion statement and `HEAD`, and answers one question: does HEAD
do this? A plan that halts is self-flagging; the plan that sails is the one nobody re-reads.
**THE DENIAL LIST IS THE MECHANISM AND MUST BE EXPLICIT IN THE DISPATCH, not implied:** no plan
body, no AC table, no chunk bodies, no run reports, no reviewer sidecars. A reader who never sees
AC3 cannot be misled by AC3 — that is the entire value, and a well-meaning "here is the context
you need" destroys it.
3.7. **Reachability re-check, before the stamp.** Any landed row that shipped a new
helper/hook/injector: re-confirm at `HEAD` that the production call site found by the Phase 3
reachability gate still reaches it — a caller-grep, not an import smoke-check. Unconfirmed (or
never checked) → Phase-5-halted, no stamp; step 4's `close-out-and-stamp` does not perform this
check itself and its green is not evidence of it.
4. `close-out-and-stamp "$ARGUMENTS"` — stamps `status: implemented` and commits the plan path
(full-plan-shipped), or reports remaining uncommitted chunks and skips the stamp
(Phase-5-halted). Folding the stamp into your own commit is acceptable — state that you did.
**Offer, stamp-aware, never parroted.** The branch is whether step 4 stamped `implemented`, never
how shipped the session feels. Stamped → offer `/workstream-complete`, note
`/merging-to-main`/`/workday-complete` ship it. Unstamped for **any** reason — Phase 5 halt, open
spine row, a leg unmet in another repo → do not offer `/workstream-complete`; offer
resolve-and-resume, `/handoff`, or commit-and-stop. An accurate "partial and honest" report does not
earn the offer. Never auto-invoke any of those or `coordinator:finishing-a-development-branch`.
Tripwire: `AN-HONEST-INCOMPLETE-DOES-NOT-EARN-THE-WRAP-OFFER`.
**A cross-repo leg names its failing conjunct, not its repo** — *undeclared*, *unaddressed*, or
*unanswered* per `coordinator/snippets/cross-repo-block-exchange.md`. Tripwire:
`A-SENT-MEMO-IS-NOT-AN-EXCHANGE`.
---
## Phase 5: When to Stop — PM-Only Emergencies
The default is complete the plan. Stop only for: **external trust surface change**
(user-visible behavior, privacy, security boundary, billing/pricing/onboarding, or any
externally-observable contract the plan didn't call out); **plan-invalidating substrate change**
(disk state changed since drafting in a way that makes the plan structurally wrong — bounce to
`/plan`); **scope explosion** (≥3× anticipated size, no 5-15min-chunk decomposition articulable
for the remainder — route back to `/plan`); **unauthorized irreversible action required**
(destructive op, force-push, cross-repo write to a sibling's code, credential/cookie write, or
anything gated by `~/.claude/CLAUDE.md` § Executing actions with care); **discovery the plan
would ship something not authorized** (approved on premise X, execution reveals it would also do
Y, and Y is not a mechanical consequence of X).
Not on the list (EM decisions, made inline): accumulating patches, ambiguity, structural
verification failure (`/systematic-debugging`), routine fixable errors, minor judgment calls,
wanting to check in. Record `Tried:/Failed:` in the plan doc and the task's
`metadata.tried_and_abandoned`. Surface with a recommendation, not a question.
---
## Relationship to Other Commands
Default upstream entry is `/handoff` + `/pickup`: review can stamp `execution_authorized_at` as
supporting evidence and writes an execution handoff. Enrichment appropriate to the plan's size
has already happened upstream — for L/XL plans, via the size-keyed lane dispatch of
`coordinator:enricher` directly over the plan body (see the plan's residue segments), not via
`/enrich-and-review`, which remains a separate chunk-directory/stub pipeline this skill does not
route through. `/review-code` is an optional post-execution pass. `coordinator:workstream-complete`
is offered, never auto-invoked, in Phase 4; `coordinator:finishing-a-development-branch` is not
chained here — reached separately via `/merging-to-main`. Full failure-mode table: wiki.