Skip to content

Commit 3c15b8c

Browse files
copeusclaude
andcommitted
spec(v3.0 r7)+orchestrate: Codex round 2 — the cure had the disease
DO NOT BUILD, 8 findings, 3 critical. Closed before wave 2 dispatches, so they land ahead of the jobs they affect. CRITICAL 1, and it is the finding of the day: task-17 builds the integration authority and NOTHING was required to call it. Neither /v:dispatch nor /v:resume had to invoke the gate, and task-9 did not even depend on task-17, so the script might not exist in its worktree. That is precisely the defect this whole release exists to fix -- 7,883 lines of correct sizing code with no caller -- reproduced inside its own cure. task-9 now depends on task-17, dispatch and resume must invoke the gate before integrating any commit, and AC-24 tests the call path rather than the standalone script. CRITICAL 2: 'atomic recheck' was an assertion. The recheck now lands behind an expected-HEAD update-ref compare-and-swap, and the tests must prove a substituted path, a mutated taxonomy and a stale concurrent authorization all fail. CRITICAL 3: the spec on disk was r4 with 23 criteria while the manifest had 25 and task-13 was told they were copied verbatim from the spec -- an impossible instruction. Spec is now r7 with AC-24 and AC-25 present. Also: this run is pinned to the pre-cutover dispatcher/resume contract through release (task-9 rewrites the very mechanism the remaining waves need); engine selection PROBES a clamped spawn instead of inferring private-option support from the version floor; UserPromptSubmit becomes an idempotent nudge gated on 'no active run and no covering record' rather than something that mints a record on every prompt; the breaker numerator is negative actual outcomes; duration_ms and failures[] reach AC-11; merge-at-most-once joins Record idempotence; and AC-20 stops promising a universal Bash deny the design itself calls impossible. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 5af40c4 commit 3c15b8c

6 files changed

Lines changed: 328 additions & 22 deletions

File tree

commands/v-epic.md

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,106 @@ Skip this entire subsection when §0's watch binding found `autonomy.watch` abse
133133
A crash between the delete and the create leaves the epic temporarily Tier-1-unarmed rather than doubled — the conservative choice: Tier-2 (if armed) still covers resurrection in the interim, and worst case the epic simply waits for the next re-entry (human or Tier-2-fired) to re-arm Tier-1, which step 1's dedup (above) also protects against ever leaving two live Tier-1 entries behind.
134134
4. **Commit** the `epic-state.json` writes from steps 1–3 (§9) before continuing to §1.
135135
136+
### 0d. Goal-condition arming (v2.18 Feature A, marathon-only, OPT-IN — offer it, never arm silently)
137+
138+
The armed goal is this plugin's only **blocking** primitive: a `Stop` hook
139+
([`hooks/epic-goal-stop.sh`](../hooks/epic-goal-stop.sh), registered with `|| true`) that refuses
140+
to let the turn end while a deterministically-evaluated goal is armed and unmet. It is orthogonal to
141+
watch — watch resurrects an epic *after* a death, the goal stops the turn ending *before* one. Full
142+
design: [`epic-mode.md`](../skills/compound-v/epic-mode.md) "Armed goal condition".
143+
144+
**Gate.** Marathon-only (`--arm-goal` refuses a checkpoint epic). Run this **once**, at the start of
145+
the invocation, after §0's stance binding and after §0c if watch is on. **Never arm without the
146+
user's explicit yes** — a `Stop` block holds *their* session open, so this is offered, not defaulted.
147+
148+
1. **Get the session id from the harness, in this session.** The arm stores it and the hook compares
149+
it to the `session_id` on the `Stop` payload:
150+
151+
```bash
152+
printf '%s\n' "${CLAUDE_CODE_SESSION_ID:-}"
153+
```
154+
155+
**If it is empty, do not arm** — say so and continue without a goal. `--arm-goal` refuses an empty
156+
`--session-id` by design (an empty stored id disables isolation and would hold *every* session in
157+
the project open), so do not attempt to substitute a placeholder, a pid or an invented UUID.
158+
159+
**Stated honestly:** `CLAUDE_CODE_SESSION_ID` is the harness's own session id as seen from a Bash
160+
call in this session; that it is byte-identical to the id the harness puts on the `Stop` payload is
161+
**not verified here**. The failure direction is benign — a mismatched id makes the arm **inert**
162+
(the hook exits 0 silently on mismatch), never wrong. Step 4 is how you find out.
163+
164+
2. **Agree the condition and the bound with the user.** Exactly two conditions exist:
165+
`all_features_done` (every feature done **and** the final review passed) and `final_review_passed`.
166+
`max_continues` must be an integer **> 0**`0` is invalid, not "unlimited"; there is no unlimited
167+
setting. Start small (4–8); it is the only bound on how many turns the hook may hold open.
168+
169+
```bash
170+
python3 scripts/compound-v-epic-state.py --arm-goal \
171+
--state docs/superpowers/execution/epics/<epic-id>/epic-state.json \
172+
--condition all_features_done --session-id "$CLAUDE_CODE_SESSION_ID" --max-continues 8
173+
# → {"armed": {"condition": …, "session_id": …, "arm_id": "<fresh uuid4 hex>", "max_continues": 8},
174+
# "replaced": null}
175+
```
176+
177+
A second `--arm-goal` on the same epic is **REFUSED** naming the existing arm; pass `--replace-arm`
178+
only when you mean it — a replacement mints a fresh `arm_id`, which **abandons** the previous arm's
179+
continue counter rather than inheriting it. `--condition no_incomplete_jobs` is refused with its
180+
reason (it is run-scoped, and a `done` job whose files are not in git is not done). A non-zero exit
181+
is never fatal to the epic: report it and run the loop without a goal.
182+
183+
3. **Commit the arm (§9).** `goal_arm` is an `epic-state.json` write like any other — batch it into
184+
§0c's or the first pass's commit. Never leave it uncommitted.
185+
186+
4. **Check that the arm actually engaged, once, at the end of the first held turn.** The hook creates
187+
its slot under `${TMPDIR:-/tmp}/compound-v-stop-hook/goal-<digest>` the first time it blocks:
188+
189+
```bash
190+
ls "${TMPDIR:-/tmp}/compound-v-stop-hook/" 2>/dev/null || echo "no hook store yet"
191+
```
192+
193+
A `goal-*` slot means a goal arm is live on this machine (with at most one armed epic per project,
194+
that is this one). **No slot after a turn that should have been held** means the goal rule never
195+
reached the counter. The honest candidate causes, in likelihood order: the stored session id does
196+
not match the `Stop` payload's; more than one epic-state under `docs/superpowers/execution/epics/`
197+
carries an arm, so discovery **failed open**; `jq` or `python3` is missing on this machine; or the
198+
runtime discarded the block because the turn ended via a tool result or an MCP end-turn. Say which
199+
ones you checked, disarm, and continue without a goal; **do not** report the epic as goal-driven
200+
when it is not. The slot's `count` file is the hook's own state:
201+
read it if you want the count, never write it, and never infer a count you did not read.
202+
203+
**What the hook does with it, in one line each** (the authority is `epic-mode.md`): it reads
204+
`--goal-status` (strictly read-only) and blocks only while `should_continue` is true; it increments
205+
and **persists** its counter *before* emitting the block; it releases the turn when the counter is
206+
exhausted, when the goal is `met`, or when the epic goes `terminal`; and it **never writes**
207+
`epic-state.json`. A released turn is **not** a finished epic — read `met`, never `should_continue`,
208+
before reporting completion.
209+
210+
### 0e. Headless shim offer (v2.18 Feature F — offered and printed, NEVER installed)
211+
212+
When this invocation carries **`--watch`** in `{{args}}`, and only then, also print the OS-scheduler
213+
artifact for the headless resurrection shim so the user can install it themselves:
214+
215+
```bash
216+
python3 scripts/compound-v-headless-shim.py emit \
217+
--epic-id <epic-id> --state docs/superpowers/execution/epics/<epic-id>/epic-state.json
218+
# --os {macos,linux} to override the auto-detected target
219+
```
220+
221+
- **The offer is the whole wiring.** The shim prints a launchd plist / crontab line plus a runbook
222+
and installs nothing; `/v:epic` runs it and shows the output. **You** must not run `launchctl`,
223+
`crontab`, or any installer on the user's behalf — the shim's own AST selftest asserts it makes
224+
exactly one `subprocess` call naming neither, and doing it from here would relocate exactly the
225+
behavior that selftest forbids.
226+
- **Never pre-fill `--allow-build`.** Its default artifact is the safe subset: the fired session
227+
claims, checks liveness and reports, then **stops** — it refuses the build/commit/re-arm steps, and
228+
that refusal is the safety system. `--allow-build` widens the allowlist to run a marathon
229+
unattended; offer it only if the user asks for it, and never emit any permission-bypass flag.
230+
- **`--watch` here is a presentation flag only.** It requests this offer; it does **not** opt an epic
231+
into watch. That is `--init --watch` on a NEW epic (step 3), and the persisted `autonomy.watch`
232+
stays the sole authority afterwards.
233+
- The shim removes the desktop-app dependency; it does **not** make anything run while the machine
234+
sleeps. Repeat the honest boundary below when you print it.
235+
136236
### 1. Per-iteration progress + breaker check (before every feature)
137237

138238
At the top of each loop pass, pick a **stable cycle id for this pass** (an incrementing counter held in your own scratch state, or a UUID minted once per pass and reused for every call *within* that same pass, so one pass is never double-counted):
@@ -348,6 +448,30 @@ Skip entirely when §0's watch binding found `autonomy.watch` absent/false. Othe
348448

349449
No task/entry matching the exact id/marker, on either tier, is expected and harmless — not a failure, and **not a controlled error either**: `--record-watcher-disarmed` on a `(provider, task_id)` pair this registry never recorded (an older-convention id from before this fix, or a create that crashed before its own `--record-watcher-armed` write) succeeds as a no-op (v2.11 MEDIUM-4 fix) rather than aborting the sweep — so both sweeps above always run to completion regardless of what the registry does or doesn't already know about. Because both sweeps key off the PROVIDER's own list (not the registry) and match EXACTLY (not by a prefix that could cross epics), a task that was created but never recorded is still found and deleted here, and a different epic's watcher is never touched — there is no crash window where a Tier-2 task survives forever, and no cross-epic collision. Commit the resulting `--record-watcher-disarmed` writes (§9) co-located with the SAME commit that records the terminal status — never leave a disarm uncommitted, per the v2.6.4 rule §9 already states.
350450

451+
### Goal disarm (v2.18, run at EVERY terminal exit — §7 and §8, only when a goal was armed)
452+
453+
Skip entirely when §0d never armed a goal. Otherwise, right before this invocation stops at **any**
454+
terminal outcome — `done` (§8), `blocked_needing_human` from a tripped breaker or `halt_epic` (§7),
455+
or exhausted reachable work (§7) — pop the armed record:
456+
457+
```bash
458+
python3 scripts/compound-v-epic-state.py --disarm-goal \
459+
--state docs/superpowers/execution/epics/<epic-id>/epic-state.json
460+
# → {"disarmed": true, "mutated": true, "arm_id": "…"} (nothing armed → {"disarmed": false, "mutated": false, "arm_id": null})
461+
```
462+
463+
Idempotent by design: disarming when nothing is armed is a clean success with `mutated: false` and
464+
writes nothing, so it is always safe to run. **It is a tidy-up, not a safety mechanism** — the hook
465+
already stops holding the turn open by itself once the goal is `met` or the epic is `terminal`, so a
466+
crash before this step cannot leave a session wedged; what it prevents is a *stale* record that a
467+
later re-arm would have to `--replace-arm` around. Commit the write (§9) in the SAME commit as the
468+
terminal status, exactly like the watch disarm above.
469+
470+
**Report `met`, not "the turn was released."** Take the terminal wording from `--goal-status`'s `met`
471+
field (or the epic's own summary), never from the fact that the hook stopped blocking: a
472+
breaker-tripped or `halt_epic` epic reports `met: false, terminal: true, should_continue: false`, and
473+
calling that "goal met" would be a fabricated completion claim.
474+
351475
### 7. Halt-page runbook (whole-epic block only)
352476

353477
Page **only** when the epic itself is blocked — `blocked_needing_human` (tripped breaker or `halt_epic`) or exhausted reachable work. A single `blocked`/abandoned **feature** notice does **not** page here — it batches into the end-of-run report (§8) alongside a successful `done`. Commit first (§9) — the page must describe a state that is actually on disk in git, not one still sitting uncommitted in the worktree. **Run "Watch disarm" above before paging** (watch-only — a no-op when watch is off). The runbook carries, verbatim, every field the spec requires:
@@ -388,7 +512,7 @@ git add docs/superpowers/execution/epics/<epic-id>/epic-state.json \
388512
git commit -m "chore(v-epic): marathon <epic-id> <feature-id> -> <what happened>"
389513
```
390514

391-
(Omit the `arbiter/` path when nothing was written there — e.g. a bare `done` mark.) Trigger points: a feature reaching `done` — **committed together with its `--mark-sample-audit-due` when sampled, so `done` is never on disk without the obligation** (§4); a `--clear-sample-audit-due` on a passed audit (§4); a `--record-audit-failed` reverting a failed sample-audit (§4); every `--record-disposition` + its accompanying `--update` (§5/§6 — `retry_fix`/`halt_feature`/`blocked_external`/`halt_epic`); a `--record-disposition` recovered from a `consumed` `--resume-challenge` (§2 `needs_arbitration`); every `--trip-breaker` (§1/§5/§7); every `--record-final-review` (§8); and once more, belt-and-suspenders, right before the halt-page (§7) or the terminal `done` report (§8). **(v2.11, watch-only, additive):** every `--renew-lease` and `--record-watcher-armed`/`--record-watcher-disarmed` write from §0c or "Watch disarm" — co-located with the SAME commit as the nearby trigger point above (§0c's initial arm/heartbeat batches into the epic's `init` commit or its own first-pass commit; a terminal disarm batches into the SAME commit as the halt-page's or the terminal `done`'s status write) — never a separate uncommitted write.
515+
(Omit the `arbiter/` path when nothing was written there — e.g. a bare `done` mark.) Trigger points: a feature reaching `done` — **committed together with its `--mark-sample-audit-due` when sampled, so `done` is never on disk without the obligation** (§4); a `--clear-sample-audit-due` on a passed audit (§4); a `--record-audit-failed` reverting a failed sample-audit (§4); every `--record-disposition` + its accompanying `--update` (§5/§6 — `retry_fix`/`halt_feature`/`blocked_external`/`halt_epic`); a `--record-disposition` recovered from a `consumed` `--resume-challenge` (§2 `needs_arbitration`); every `--trip-breaker` (§1/§5/§7); every `--record-final-review` (§8); and once more, belt-and-suspenders, right before the halt-page (§7) or the terminal `done` report (§8). **(v2.11, watch-only, additive):** every `--renew-lease` and `--record-watcher-armed`/`--record-watcher-disarmed` write from §0c or "Watch disarm" — co-located with the SAME commit as the nearby trigger point above (§0c's initial arm/heartbeat batches into the epic's `init` commit or its own first-pass commit; a terminal disarm batches into the SAME commit as the halt-page's or the terminal `done`'s status write) — never a separate uncommitted write. **(v2.18, additive):** the `--arm-goal` write from §0d (batched into §0c's or the first pass's commit) and the `--disarm-goal` write from "Goal disarm" (batched into the SAME commit as the terminal status) — same rule, same reason.
392516

393517
### The honest boundary (v2.10 marathon + v2.11 auto-resurrection watch)
394518

0 commit comments

Comments
 (0)