From 5e3be386d7641d16d11e00a6346c81d7eb60e43f Mon Sep 17 00:00:00 2001 From: Lee Overy Date: Sun, 26 Jul 2026 19:35:11 +0100 Subject: [PATCH] test(prose): a case starts where a session starts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No session ever begins at a navigation skill, a processing skill, or a reference. `workflow-start` is the user's way in; after a context clear the bridge's plan file invokes a `workflow-*-entry` skill, or `workflow-discovery` for an epic continuation. That is the whole list. Four cases ignored it — two opened at `workflow-continue-*`, one at `workflow-discussion-process`, one inside `root-cause-validation.md`. A walk carries only the context it accumulates, so starting one in the middle leaves the world right and the reading that got there missing. The prose is then judged under conditions it was never written for, and the verdict is worthless whichever way it lands. continue-feature had the flaw written into its own expected path: "initialisation is casing only — no boot and no migrations, which the entry skill has already guaranteed". A case documenting the context it does not have. It is also the case that consumed a day of investigation producing findings that dissolved on inspection. The four are retired rather than repaired. Their stubs stay — stubs are a reusable library, not case-scenery. `entry` is now required and validated, so this cannot drift back in by authoring discipline alone. The same reasoning governs the far end of a walk, recorded as P9: stop at a gate and the delta is empty, the claims decay into descriptions of a display, and the asserter is left comparing the walker's account of what it showed against the case's account of what it should have shown — with no independent ground truth anywhere. Coverage of continue-* routing goes with them, and is not replaced here: it belongs in a case that starts at workflow-start and walks through selection until the route lands real state. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 2 +- design/prose-tests.md | 47 ++++++++++ .../act.md | 4 - .../assert.md | 10 --- .../case.json | 11 --- .../fixture-state.cjs | 12 --- .../fixture/.claude/settings.json | 9 -- .../fixture/.recipe-hash | 1 - .../fixture/.workflows/.state/migrations | 52 ----------- .../fixture/.workflows/_gitignore.fixture | 2 - .../discovery/sessions/session-001.md | 40 --------- .../.workflows/crash-fix/manifest.json | 8 -- .../fixture/.workflows/manifest.json | 7 -- .../act.md | 4 - .../assert.md | 13 --- .../case.json | 11 --- .../fixture-state.cjs | 12 --- .../fixture/.claude/settings.json | 9 -- .../fixture/.recipe-hash | 1 - .../fixture/.workflows/.state/migrations | 52 ----------- .../fixture/.workflows/_gitignore.fixture | 2 - .../fixture/.workflows/manifest.json | 7 -- .../pay/discovery/sessions/session-001.md | 39 -------- .../fixture/.workflows/pay/manifest.json | 8 -- .../case.json | 1 + .../discussion-session-substituted/act.md | 5 -- .../discussion-session-substituted/assert.md | 25 ------ .../discussion-session-substituted/case.json | 12 --- .../fixture-state.cjs | 13 --- .../discussion-session-substituted/fixture.md | 3 - .../fixture/.claude/settings.json | 9 -- .../fixture/.recipe-hash | 1 - .../fixture/.workflows/.state/migrations | 52 ----------- .../fixture/.workflows/_gitignore.fixture | 2 - .../fixture/.workflows/manifest.json | 7 -- .../pay/discovery/sessions/session-001.md | 39 -------- .../fixture/.workflows/pay/manifest.json | 8 -- .../implementation-picks-first-task/case.json | 1 + .../case.json | 1 + .../case.json | 1 + .../prose/cases/review-entry-fresh/case.json | 1 + .../act.md | 2 - .../assert.md | 20 ----- .../assertion-state.cjs | 27 ------ .../assertion/.claude/settings.json | 9 -- .../assertion/.recipe-hash | 1 - .../crash-fix/root-cause-validation-001.md | 10 --- .../investigation/crash-fix/state.json | 16 ---- .../assertion/.workflows/.state/migrations | 52 ----------- .../assertion/.workflows/_gitignore.fixture | 2 - .../discovery/sessions/session-001.md | 40 --------- .../crash-fix/investigation/crash-fix.md | 88 ------------------- .../.workflows/crash-fix/manifest.json | 16 ---- .../assertion/.workflows/manifest.json | 7 -- .../case.json | 12 --- .../fixture-state.cjs | 13 --- .../fixture.md | 3 - .../fixture/.claude/settings.json | 9 -- .../fixture/.recipe-hash | 1 - .../fixture/.workflows/.state/migrations | 52 ----------- .../fixture/.workflows/_gitignore.fixture | 2 - .../discovery/sessions/session-001.md | 40 --------- .../crash-fix/investigation/crash-fix.md | 88 ------------------- .../.workflows/crash-fix/manifest.json | 16 ---- .../fixture/.workflows/manifest.json | 7 -- .../smoke-start-boot-structure/case.json | 3 +- .../spec-entry-from-discussion/case.json | 1 + .../spec-entry-from-investigation/case.json | 1 + .../cases/start-lists-active-work/case.json | 1 + tests/prose/lib/cases.cjs | 44 +++++++++- tests/scripts/test-prose-invariants.cjs | 42 +++++++++ 71 files changed, 141 insertions(+), 1028 deletions(-) delete mode 100644 tests/prose/cases/continue-bugfix-routes-to-investigation/act.md delete mode 100644 tests/prose/cases/continue-bugfix-routes-to-investigation/assert.md delete mode 100644 tests/prose/cases/continue-bugfix-routes-to-investigation/case.json delete mode 100644 tests/prose/cases/continue-bugfix-routes-to-investigation/fixture-state.cjs delete mode 100644 tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.claude/settings.json delete mode 100644 tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.recipe-hash delete mode 100644 tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.workflows/.state/migrations delete mode 100644 tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.workflows/_gitignore.fixture delete mode 100644 tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.workflows/crash-fix/discovery/sessions/session-001.md delete mode 100644 tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.workflows/crash-fix/manifest.json delete mode 100644 tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.workflows/manifest.json delete mode 100644 tests/prose/cases/continue-feature-routes-to-discussion/act.md delete mode 100644 tests/prose/cases/continue-feature-routes-to-discussion/assert.md delete mode 100644 tests/prose/cases/continue-feature-routes-to-discussion/case.json delete mode 100644 tests/prose/cases/continue-feature-routes-to-discussion/fixture-state.cjs delete mode 100644 tests/prose/cases/continue-feature-routes-to-discussion/fixture/.claude/settings.json delete mode 100644 tests/prose/cases/continue-feature-routes-to-discussion/fixture/.recipe-hash delete mode 100644 tests/prose/cases/continue-feature-routes-to-discussion/fixture/.workflows/.state/migrations delete mode 100644 tests/prose/cases/continue-feature-routes-to-discussion/fixture/.workflows/_gitignore.fixture delete mode 100644 tests/prose/cases/continue-feature-routes-to-discussion/fixture/.workflows/manifest.json delete mode 100644 tests/prose/cases/continue-feature-routes-to-discussion/fixture/.workflows/pay/discovery/sessions/session-001.md delete mode 100644 tests/prose/cases/continue-feature-routes-to-discussion/fixture/.workflows/pay/manifest.json delete mode 100644 tests/prose/cases/discussion-session-substituted/act.md delete mode 100644 tests/prose/cases/discussion-session-substituted/assert.md delete mode 100644 tests/prose/cases/discussion-session-substituted/case.json delete mode 100644 tests/prose/cases/discussion-session-substituted/fixture-state.cjs delete mode 100644 tests/prose/cases/discussion-session-substituted/fixture.md delete mode 100644 tests/prose/cases/discussion-session-substituted/fixture/.claude/settings.json delete mode 100644 tests/prose/cases/discussion-session-substituted/fixture/.recipe-hash delete mode 100644 tests/prose/cases/discussion-session-substituted/fixture/.workflows/.state/migrations delete mode 100644 tests/prose/cases/discussion-session-substituted/fixture/.workflows/_gitignore.fixture delete mode 100644 tests/prose/cases/discussion-session-substituted/fixture/.workflows/manifest.json delete mode 100644 tests/prose/cases/discussion-session-substituted/fixture/.workflows/pay/discovery/sessions/session-001.md delete mode 100644 tests/prose/cases/discussion-session-substituted/fixture/.workflows/pay/manifest.json delete mode 100644 tests/prose/cases/root-cause-validation-clean-verdict/act.md delete mode 100644 tests/prose/cases/root-cause-validation-clean-verdict/assert.md delete mode 100644 tests/prose/cases/root-cause-validation-clean-verdict/assertion-state.cjs delete mode 100644 tests/prose/cases/root-cause-validation-clean-verdict/assertion/.claude/settings.json delete mode 100644 tests/prose/cases/root-cause-validation-clean-verdict/assertion/.recipe-hash delete mode 100644 tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/.cache/crash-fix/investigation/crash-fix/root-cause-validation-001.md delete mode 100644 tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/.cache/crash-fix/investigation/crash-fix/state.json delete mode 100644 tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/.state/migrations delete mode 100644 tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/_gitignore.fixture delete mode 100644 tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/crash-fix/discovery/sessions/session-001.md delete mode 100644 tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/crash-fix/investigation/crash-fix.md delete mode 100644 tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/crash-fix/manifest.json delete mode 100644 tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/manifest.json delete mode 100644 tests/prose/cases/root-cause-validation-clean-verdict/case.json delete mode 100644 tests/prose/cases/root-cause-validation-clean-verdict/fixture-state.cjs delete mode 100644 tests/prose/cases/root-cause-validation-clean-verdict/fixture.md delete mode 100644 tests/prose/cases/root-cause-validation-clean-verdict/fixture/.claude/settings.json delete mode 100644 tests/prose/cases/root-cause-validation-clean-verdict/fixture/.recipe-hash delete mode 100644 tests/prose/cases/root-cause-validation-clean-verdict/fixture/.workflows/.state/migrations delete mode 100644 tests/prose/cases/root-cause-validation-clean-verdict/fixture/.workflows/_gitignore.fixture delete mode 100644 tests/prose/cases/root-cause-validation-clean-verdict/fixture/.workflows/crash-fix/discovery/sessions/session-001.md delete mode 100644 tests/prose/cases/root-cause-validation-clean-verdict/fixture/.workflows/crash-fix/investigation/crash-fix.md delete mode 100644 tests/prose/cases/root-cause-validation-clean-verdict/fixture/.workflows/crash-fix/manifest.json delete mode 100644 tests/prose/cases/root-cause-validation-clean-verdict/fixture/.workflows/manifest.json diff --git a/CLAUDE.md b/CLAUDE.md index c76990ced..a041451b6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -210,7 +210,7 @@ Add or update a test alongside any change to engine scripts, adapters, migration ## Prose Tests -`tests/prose/` — end-to-end tests for the prose logic (design: `design/prose-tests.md`). A case is a directory under `tests/prose/cases/{case-id}/`: `case.json` (values code branches on), `fixture.md` + `fixture-state.cjs` (the starting world), `act.md` (the coarse instruction a walker agent follows through the real skills), `assert.md` (the expected path, seen only by the asserting agent), optional `assertion-state.cjs` (the world the walk should produce; absent = unchanged), and the two generated snapshots. Nothing is parsed and prose never shares a file with code. Shared pipeline stages live in `tests/prose/mainlines/`. Walks cost tokens — run on command via the `/prose-test` dev skill, never as part of a routine gate. The deterministic perimeter runs under `npm test`: corpus validation plus snapshot goldens — every snapshot must rebuild byte-identical from its recipe under the frozen clock, skipped when nothing feeding it changed. When an engine change moves a world, regenerate with `node tests/prose/run.cjs snap {case-id}` and land the snapshot diff in the same PR; never hand-edit a snapshot. Every review finding lands twice: the fix, and the case that would have caught it. At the end of a PR that touches skill prose, run `node tests/prose/run.cjs select --diff main` and suggest running the intersecting cases. +`tests/prose/` — end-to-end tests for the prose logic (design: `design/prose-tests.md`). A case is a directory under `tests/prose/cases/{case-id}/`: `case.json` (values code branches on, including the required `entry` and any deterministic `invariants`), `fixture.md` + `fixture-state.cjs` (the starting world), `act.md` (the coarse instruction a walker agent follows through the real skills), `assert.md` (the expected path, seen only by the asserting agent), optional `assertion-state.cjs` (the world the walk should produce; absent = unchanged), and the two generated snapshots. Nothing is parsed and prose never shares a file with code. **A case starts where a session starts and runs until state lands**: `entry` must name `workflow-start`, a `workflow-*-entry` skill, or `workflow-discovery` — never a navigation or processing skill, never a reference — because a walk begun mid-session lacks the context the prose was written for, and one that stops at a gate leaves an empty delta and nothing but display claims to judge. Claims that code can settle belong in `invariants` (`engine_before_write`, `calls_include`, `calls_exclude`), which run before the asserter and whose verdicts it cannot overturn. Shared pipeline stages live in `tests/prose/mainlines/`. Walks cost tokens — run on command via the `/prose-test` dev skill, never as part of a routine gate. The deterministic perimeter runs under `npm test`: corpus validation plus snapshot goldens — every snapshot must rebuild byte-identical from its recipe under the frozen clock, skipped when nothing feeding it changed. When an engine change moves a world, regenerate with `node tests/prose/run.cjs snap {case-id}` and land the snapshot diff in the same PR; never hand-edit a snapshot. Every review finding lands twice: the fix, and the case that would have caught it. At the end of a PR that touches skill prose, run `node tests/prose/run.cjs select --diff main` and suggest running the intersecting cases. ## Knowledge Base Subsystem diff --git a/design/prose-tests.md b/design/prose-tests.md index 7307f07ce..dba57d890 100644 --- a/design/prose-tests.md +++ b/design/prose-tests.md @@ -170,6 +170,30 @@ testing. A failure is a finished result; diagnosing it is a separate, human-led act. This is stated in prose in each definition rather than relied on from tool restrictions, which an agent may hold regardless. +- **P9 — a case starts where a session starts, and runs until state + lands.** The only real openings are `workflow-start` (the user's), a + `workflow-*-entry` skill (what a bridge plan file invokes after a + context clear), and `workflow-discovery` (epic continuation). Navigation + and processing skills are always reached mid-session; a reference is + never entered directly. A walk carries only the context it accumulates, + so one begun in the middle is judged under conditions the prose was + never written for, and its verdict means nothing in either direction. + Enforced in `lib/cases.cjs` by the required `entry` field, not left to + authoring discipline. The same rule ends a case: stop at a gate and the + delta is empty, the claims turn into descriptions of a display, and the + asserter is left comparing the walker's account of what it showed + against the case's account of what it should have shown — with no + independent ground truth anywhere in the loop. + +- **P10 — what code can decide, code decides.** A case declares + invariants over the recorded actions (`lib/invariants.cjs`) that run + before the asserter sees anything, and whose verdicts it may not + overturn. They exist because the world delta proves the outcome but not + that it was earned — a walk that ignored every instruction and wrote + the expected files lands the same state. Not a pinned call sequence: + recording and replaying one would freeze whatever the walker happened + to do, certifying broken prose instead of catching it. + - **P7 — a failing case is a finding either way.** Either the prose broke, or the world/design moved and the case is stale. The adjudication is the point; only the typo-class staleness is @@ -218,6 +242,29 @@ testing. ## Log +- 2026-07-26 — Two findings that dissolved a long-running mystery, and + the rules that follow from them. First: we were judging a walk by the + single message an agent returns, while the walk itself happens across + dozens of turns. Every step narrated at the time and compressed out of + the closing summary read as a step never taken — the "under-reporting" + blamed first on the walker, then on the model, was ours. A Sonnet walk + of continue-feature was marked down for skipping the selection menu; + its transcript shows it emitted the display, emitted the menu verbatim, + stopped, consumed the scripted answer and validated the selection. An + Opus walk of the same case made byte-identical tool calls. The runtime + writes every turn already and the stop payload names the file, so the + hook now lifts them into the world (P6e). Second, and underneath it: + four cases started where no session ever starts — two at navigation + skills, one at a processing skill, one inside a reference. They are + retired, and the rule is enforced rather than remembered (P9). The + residue is now decided in code where it can be (P10). Along the way the + recorder was found to have been reading `tool_output` when the field is + `tool_response`, so every successful call had recorded its status and + nothing else, and the stop hook had never once fired — its world is + resolved from the transcript now, which also stamps the model on every + verdict, since an unreloaded definition can otherwise run a walk on a + model nobody chose. + - 2026-07-26 — The first round that found more prose than instrument. Recording finally live (the blocker was `hasTrustDialogAccepted`, which gates frontmatter hooks in project subagents and fails silently), five diff --git a/tests/prose/cases/continue-bugfix-routes-to-investigation/act.md b/tests/prose/cases/continue-bugfix-routes-to-investigation/act.md deleted file mode 100644 index 0f11a5af9..000000000 --- a/tests/prose/cases/continue-bugfix-routes-to-investigation/act.md +++ /dev/null @@ -1,4 +0,0 @@ -Execute skills/workflow-continue-bugfix/SKILL.md with no arguments, as -it is invoked when the caller has no work unit pre-selected. Follow it -through selection and the bugfix's state display. Stop once the state -and its menu have been shown — do not invoke the route. diff --git a/tests/prose/cases/continue-bugfix-routes-to-investigation/assert.md b/tests/prose/cases/continue-bugfix-routes-to-investigation/assert.md deleted file mode 100644 index f975a31a2..000000000 --- a/tests/prose/cases/continue-bugfix-routes-to-investigation/assert.md +++ /dev/null @@ -1,10 +0,0 @@ -The prose should have taken this path: - -1. initialisation is casing only — boot and migrations belong to the - entry skill -2. shows the selection display and menu rather than auto-selecting the - only bugfix -3. validates the selection, then shows the bugfix's pipeline state -4. the state shows no phase started, and the continue action routes to - investigation entry — a bugfix's first phase is investigation, never - discussion or specification diff --git a/tests/prose/cases/continue-bugfix-routes-to-investigation/case.json b/tests/prose/cases/continue-bugfix-routes-to-investigation/case.json deleted file mode 100644 index 7a221fd7f..000000000 --- a/tests/prose/cases/continue-bugfix-routes-to-investigation/case.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "origin": "bugfix mainline — a fresh bug routes to investigation, not discussion", - "files": [ - "skills/workflow-continue-bugfix/SKILL.md", - "skills/workflow-continue-bugfix/references/select-bugfix.md", - "skills/workflow-continue-bugfix/references/bugfix-display-and-menu.md" - ], - "answers": [ - "the number listed against `crash-fix`" - ] -} diff --git a/tests/prose/cases/continue-bugfix-routes-to-investigation/fixture-state.cjs b/tests/prose/cases/continue-bugfix-routes-to-investigation/fixture-state.cjs deleted file mode 100644 index 6cd47642e..000000000 --- a/tests/prose/cases/continue-bugfix-routes-to-investigation/fixture-state.cjs +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -// The bug is captured and nothing has been started. - -const m = require('../../mainlines/bugfix.cjs'); - -module.exports = { - build(h) { - m.init(h); - m.create(h); - }, -}; diff --git a/tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.claude/settings.json b/tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.claude/settings.json deleted file mode 100644 index ae019ede5..000000000 --- a/tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.claude/settings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "showClearContextOnPlanAccept": true, - "permissions": { - "allow": [ - "Edit(.workflows/**)", - "Bash(mv .workflows/:*)" - ] - } -} diff --git a/tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.recipe-hash b/tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.recipe-hash deleted file mode 100644 index 259e11ddc..000000000 --- a/tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.recipe-hash +++ /dev/null @@ -1 +0,0 @@ -f2d22a09d2680d82822f6042b87baf77dacbf41d4d8cb2b6d26a33d5c4129ff9 diff --git a/tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.workflows/.state/migrations b/tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.workflows/.state/migrations deleted file mode 100644 index 3c7162acc..000000000 --- a/tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.workflows/.state/migrations +++ /dev/null @@ -1,52 +0,0 @@ -001 -002 -003 -004 -005 -006 -007 -008 -009 -010 -011 -012 -013 -014 -015 -016 -017 -018 -019 -020 -021 -022 -023 -024 -025 -026 -027 -028 -029 -030 -031 -032 -033 -034 -035 -036 -037 -038 -039 -040 -041 -042 -043 -044 -045 -046 -047 -048 -049 -050 -051 -052 diff --git a/tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.workflows/_gitignore.fixture b/tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.workflows/_gitignore.fixture deleted file mode 100644 index 779cbf28a..000000000 --- a/tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.workflows/_gitignore.fixture +++ /dev/null @@ -1,2 +0,0 @@ -.cache/ -.manifest.json.*.tmp diff --git a/tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.workflows/crash-fix/discovery/sessions/session-001.md b/tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.workflows/crash-fix/discovery/sessions/session-001.md deleted file mode 100644 index 6dc56d4e3..000000000 --- a/tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.workflows/crash-fix/discovery/sessions/session-001.md +++ /dev/null @@ -1,40 +0,0 @@ -# Discovery Session 001 - -Date: 2026-01-01 -Work unit: crash-fix - -## Description (as of session) - -Checkout crashes when an order has no shipping address. - -## Seed - -(none) - -## Imports - -(none) - -## Map State at Start - -(n/a — single-topic work) - -## Exploration - -Reported by two users this week: the checkout page 500s at the -payment step for orders with no shipping address. Reproducible on -staging with a digital-only basket. No error surfaces to the user — -the page just fails. Confirmed as a bugfix; no design question here, -so it routes straight to investigation. - -## Edits - -(none) - -## Topics Identified - -(none) - -## Conclusion - -(none) diff --git a/tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.workflows/crash-fix/manifest.json b/tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.workflows/crash-fix/manifest.json deleted file mode 100644 index eaaa56199..000000000 --- a/tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.workflows/crash-fix/manifest.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "crash-fix", - "work_type": "bugfix", - "status": "in-progress", - "created": "2026-01-01", - "description": "Checkout crashes when an order has no shipping address", - "phases": {} -} diff --git a/tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.workflows/manifest.json b/tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.workflows/manifest.json deleted file mode 100644 index 1cd498ed5..000000000 --- a/tests/prose/cases/continue-bugfix-routes-to-investigation/fixture/.workflows/manifest.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "work_units": { - "crash-fix": { - "work_type": "bugfix" - } - } -} diff --git a/tests/prose/cases/continue-feature-routes-to-discussion/act.md b/tests/prose/cases/continue-feature-routes-to-discussion/act.md deleted file mode 100644 index 93e469eef..000000000 --- a/tests/prose/cases/continue-feature-routes-to-discussion/act.md +++ /dev/null @@ -1,4 +0,0 @@ -Execute skills/workflow-continue-feature/SKILL.md with no arguments, as -it is invoked when the caller has no work unit pre-selected. Follow it -through selection and the feature's state display. Stop once the state -and its menu have been shown — do not invoke the route. diff --git a/tests/prose/cases/continue-feature-routes-to-discussion/assert.md b/tests/prose/cases/continue-feature-routes-to-discussion/assert.md deleted file mode 100644 index b789b6b49..000000000 --- a/tests/prose/cases/continue-feature-routes-to-discussion/assert.md +++ /dev/null @@ -1,13 +0,0 @@ -The prose should have taken this path: - -1. initialisation is casing only — no boot and no migrations, which the - entry skill has already guaranteed -2. shows the selection display and menu even though `pay` is the only - feature — never auto-selects -3. validates the selection, then shows the feature's pipeline state -4. the state shows no phase started, and the menu's continue action - routes to discussion entry for work type feature, work unit pay - -Further claims: - -- navigation only reads: no phase is started by looking at it diff --git a/tests/prose/cases/continue-feature-routes-to-discussion/case.json b/tests/prose/cases/continue-feature-routes-to-discussion/case.json deleted file mode 100644 index 33c362b36..000000000 --- a/tests/prose/cases/continue-feature-routes-to-discussion/case.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "origin": "feature mainline — navigation shows state and routes to the next phase", - "files": [ - "skills/workflow-continue-feature/SKILL.md", - "skills/workflow-continue-feature/references/select-feature.md", - "skills/workflow-continue-feature/references/feature-display-and-menu.md" - ], - "answers": [ - "the number listed against `pay`" - ] -} diff --git a/tests/prose/cases/continue-feature-routes-to-discussion/fixture-state.cjs b/tests/prose/cases/continue-feature-routes-to-discussion/fixture-state.cjs deleted file mode 100644 index e4b78cd71..000000000 --- a/tests/prose/cases/continue-feature-routes-to-discussion/fixture-state.cjs +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -// The feature exists and nothing has been started. - -const m = require('../../mainlines/feature.cjs'); - -module.exports = { - build(h) { - m.init(h); - m.create(h); - }, -}; diff --git a/tests/prose/cases/continue-feature-routes-to-discussion/fixture/.claude/settings.json b/tests/prose/cases/continue-feature-routes-to-discussion/fixture/.claude/settings.json deleted file mode 100644 index ae019ede5..000000000 --- a/tests/prose/cases/continue-feature-routes-to-discussion/fixture/.claude/settings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "showClearContextOnPlanAccept": true, - "permissions": { - "allow": [ - "Edit(.workflows/**)", - "Bash(mv .workflows/:*)" - ] - } -} diff --git a/tests/prose/cases/continue-feature-routes-to-discussion/fixture/.recipe-hash b/tests/prose/cases/continue-feature-routes-to-discussion/fixture/.recipe-hash deleted file mode 100644 index 92519878f..000000000 --- a/tests/prose/cases/continue-feature-routes-to-discussion/fixture/.recipe-hash +++ /dev/null @@ -1 +0,0 @@ -5b0a7d802b3a436a4656bc000122451bb015f2250f8fef47b1d05f3ea51ba96b diff --git a/tests/prose/cases/continue-feature-routes-to-discussion/fixture/.workflows/.state/migrations b/tests/prose/cases/continue-feature-routes-to-discussion/fixture/.workflows/.state/migrations deleted file mode 100644 index 3c7162acc..000000000 --- a/tests/prose/cases/continue-feature-routes-to-discussion/fixture/.workflows/.state/migrations +++ /dev/null @@ -1,52 +0,0 @@ -001 -002 -003 -004 -005 -006 -007 -008 -009 -010 -011 -012 -013 -014 -015 -016 -017 -018 -019 -020 -021 -022 -023 -024 -025 -026 -027 -028 -029 -030 -031 -032 -033 -034 -035 -036 -037 -038 -039 -040 -041 -042 -043 -044 -045 -046 -047 -048 -049 -050 -051 -052 diff --git a/tests/prose/cases/continue-feature-routes-to-discussion/fixture/.workflows/_gitignore.fixture b/tests/prose/cases/continue-feature-routes-to-discussion/fixture/.workflows/_gitignore.fixture deleted file mode 100644 index 779cbf28a..000000000 --- a/tests/prose/cases/continue-feature-routes-to-discussion/fixture/.workflows/_gitignore.fixture +++ /dev/null @@ -1,2 +0,0 @@ -.cache/ -.manifest.json.*.tmp diff --git a/tests/prose/cases/continue-feature-routes-to-discussion/fixture/.workflows/manifest.json b/tests/prose/cases/continue-feature-routes-to-discussion/fixture/.workflows/manifest.json deleted file mode 100644 index 987f6c236..000000000 --- a/tests/prose/cases/continue-feature-routes-to-discussion/fixture/.workflows/manifest.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "work_units": { - "pay": { - "work_type": "feature" - } - } -} diff --git a/tests/prose/cases/continue-feature-routes-to-discussion/fixture/.workflows/pay/discovery/sessions/session-001.md b/tests/prose/cases/continue-feature-routes-to-discussion/fixture/.workflows/pay/discovery/sessions/session-001.md deleted file mode 100644 index 2aab96961..000000000 --- a/tests/prose/cases/continue-feature-routes-to-discussion/fixture/.workflows/pay/discovery/sessions/session-001.md +++ /dev/null @@ -1,39 +0,0 @@ -# Discovery Session 001 - -Date: 2026-01-01 -Work unit: pay - -## Description (as of session) - -Accept card payments at checkout. - -## Seed - -(none) - -## Imports - -(none) - -## Map State at Start - -(n/a — single-topic work) - -## Exploration - -Shaped as a single feature: accept card payments at checkout using -the existing gateway account. Card-only for v1 came up early and was -softly agreed; wallet support was noted as a likely deferral. No -research need surfaced — routed straight to discussion. - -## Edits - -(none) - -## Topics Identified - -(none) - -## Conclusion - -(none) diff --git a/tests/prose/cases/continue-feature-routes-to-discussion/fixture/.workflows/pay/manifest.json b/tests/prose/cases/continue-feature-routes-to-discussion/fixture/.workflows/pay/manifest.json deleted file mode 100644 index 1cf8afb0e..000000000 --- a/tests/prose/cases/continue-feature-routes-to-discussion/fixture/.workflows/pay/manifest.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "pay", - "work_type": "feature", - "status": "in-progress", - "created": "2026-01-01", - "description": "Accept card payments at checkout", - "phases": {} -} diff --git a/tests/prose/cases/discussion-entry-seeds-from-carrier/case.json b/tests/prose/cases/discussion-entry-seeds-from-carrier/case.json index 85b404f92..514a9adfa 100644 --- a/tests/prose/cases/discussion-entry-seeds-from-carrier/case.json +++ b/tests/prose/cases/discussion-entry-seeds-from-carrier/case.json @@ -1,5 +1,6 @@ { "origin": "feature mainline \u2014 single-phase work seeds from its durable carrier", + "entry": "workflow-discussion-entry", "files": [ "skills/workflow-discussion-entry/SKILL.md", "skills/workflow-shared/references/ensure-discovery-item.md" diff --git a/tests/prose/cases/discussion-session-substituted/act.md b/tests/prose/cases/discussion-session-substituted/act.md deleted file mode 100644 index 09ae57f9a..000000000 --- a/tests/prose/cases/discussion-session-substituted/act.md +++ /dev/null @@ -1,5 +0,0 @@ -Execute skills/workflow-discussion-process/SKILL.md from its first step, -as the entry skill has just invoked it for work unit `pay`, topic `pay`. -Follow it through initialisation. Stop once the session loop is behind -you and the prose moves on to reviewing the discussion — do not begin -the review, dispatch any agent, or conclude the phase. diff --git a/tests/prose/cases/discussion-session-substituted/assert.md b/tests/prose/cases/discussion-session-substituted/assert.md deleted file mode 100644 index a45e704a3..000000000 --- a/tests/prose/cases/discussion-session-substituted/assert.md +++ /dev/null @@ -1,25 +0,0 @@ -The prose should have taken this path: - -1. resume detection finds no discussion file and treats this as a fresh - discussion — no resume choice is offered -2. initialisation registers the discussion in the manifest through the - engine before any map command, since the map requires the item to - exist -3. it creates the discussion file from the template, including the - terminal Triage section seeded as `(none)` -4. it derives initial subtopics from the context available and records - each on the map through the engine, each landing `pending` -5. it commits the initialised discussion -6. the session loop is where the conversation would happen, and the walk - resumes after it to reach the review step, which it does not enter - -Further claims: - -- the subtopic names are derived by the walker from the seed and context, - so which names appear and how many is expected to vary between runs. - What matters is that at least one was recorded through the engine, that - each began `pending`, and that by the end none remain `pending`. -- the discussion file's working sections hold the substituted content, - and its Triage section reads `(none)` -- the discussion item exists in the manifest with its subtopics; no other - phase has been touched diff --git a/tests/prose/cases/discussion-session-substituted/case.json b/tests/prose/cases/discussion-session-substituted/case.json deleted file mode 100644 index c024e03f7..000000000 --- a/tests/prose/cases/discussion-session-substituted/case.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "origin": "framework — the mid-flow substitution: an unscripted conversation stood in for, so the steps around it can be walked", - "world": "claims", - "files": [ - "skills/workflow-discussion-process/SKILL.md", - "skills/workflow-discussion-process/references/initialize-discussion.md", - "skills/workflow-discussion-process/references/template.md" - ], - "stubs": { - "discussion-pay-decided": "once the discussion file has been created from the template, the map seeded, and the initialisation commit made — the moment the prose would enter the session loop" - } -} diff --git a/tests/prose/cases/discussion-session-substituted/fixture-state.cjs b/tests/prose/cases/discussion-session-substituted/fixture-state.cjs deleted file mode 100644 index 546391b87..000000000 --- a/tests/prose/cases/discussion-session-substituted/fixture-state.cjs +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -// A feature routed to discussion with nothing started — the discussion -// file does not exist, so initialisation runs for real. - -const m = require('../../mainlines/feature.cjs'); - -module.exports = { - build(h) { - m.init(h); - m.create(h); - }, -}; diff --git a/tests/prose/cases/discussion-session-substituted/fixture.md b/tests/prose/cases/discussion-session-substituted/fixture.md deleted file mode 100644 index 9f3a2c546..000000000 --- a/tests/prose/cases/discussion-session-substituted/fixture.md +++ /dev/null @@ -1,3 +0,0 @@ -The feature exists and no phase has started. Discovery shaped it and -routed it to discussion; the discussion entry skill has already run and -handed over, so the processing skill is starting from its first step. diff --git a/tests/prose/cases/discussion-session-substituted/fixture/.claude/settings.json b/tests/prose/cases/discussion-session-substituted/fixture/.claude/settings.json deleted file mode 100644 index ae019ede5..000000000 --- a/tests/prose/cases/discussion-session-substituted/fixture/.claude/settings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "showClearContextOnPlanAccept": true, - "permissions": { - "allow": [ - "Edit(.workflows/**)", - "Bash(mv .workflows/:*)" - ] - } -} diff --git a/tests/prose/cases/discussion-session-substituted/fixture/.recipe-hash b/tests/prose/cases/discussion-session-substituted/fixture/.recipe-hash deleted file mode 100644 index af934cf8a..000000000 --- a/tests/prose/cases/discussion-session-substituted/fixture/.recipe-hash +++ /dev/null @@ -1 +0,0 @@ -9b48d4c996d773219dc60a44fcc014b1f534adc4d22537741a96575da66d2e46 diff --git a/tests/prose/cases/discussion-session-substituted/fixture/.workflows/.state/migrations b/tests/prose/cases/discussion-session-substituted/fixture/.workflows/.state/migrations deleted file mode 100644 index 3c7162acc..000000000 --- a/tests/prose/cases/discussion-session-substituted/fixture/.workflows/.state/migrations +++ /dev/null @@ -1,52 +0,0 @@ -001 -002 -003 -004 -005 -006 -007 -008 -009 -010 -011 -012 -013 -014 -015 -016 -017 -018 -019 -020 -021 -022 -023 -024 -025 -026 -027 -028 -029 -030 -031 -032 -033 -034 -035 -036 -037 -038 -039 -040 -041 -042 -043 -044 -045 -046 -047 -048 -049 -050 -051 -052 diff --git a/tests/prose/cases/discussion-session-substituted/fixture/.workflows/_gitignore.fixture b/tests/prose/cases/discussion-session-substituted/fixture/.workflows/_gitignore.fixture deleted file mode 100644 index 779cbf28a..000000000 --- a/tests/prose/cases/discussion-session-substituted/fixture/.workflows/_gitignore.fixture +++ /dev/null @@ -1,2 +0,0 @@ -.cache/ -.manifest.json.*.tmp diff --git a/tests/prose/cases/discussion-session-substituted/fixture/.workflows/manifest.json b/tests/prose/cases/discussion-session-substituted/fixture/.workflows/manifest.json deleted file mode 100644 index 987f6c236..000000000 --- a/tests/prose/cases/discussion-session-substituted/fixture/.workflows/manifest.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "work_units": { - "pay": { - "work_type": "feature" - } - } -} diff --git a/tests/prose/cases/discussion-session-substituted/fixture/.workflows/pay/discovery/sessions/session-001.md b/tests/prose/cases/discussion-session-substituted/fixture/.workflows/pay/discovery/sessions/session-001.md deleted file mode 100644 index 2aab96961..000000000 --- a/tests/prose/cases/discussion-session-substituted/fixture/.workflows/pay/discovery/sessions/session-001.md +++ /dev/null @@ -1,39 +0,0 @@ -# Discovery Session 001 - -Date: 2026-01-01 -Work unit: pay - -## Description (as of session) - -Accept card payments at checkout. - -## Seed - -(none) - -## Imports - -(none) - -## Map State at Start - -(n/a — single-topic work) - -## Exploration - -Shaped as a single feature: accept card payments at checkout using -the existing gateway account. Card-only for v1 came up early and was -softly agreed; wallet support was noted as a likely deferral. No -research need surfaced — routed straight to discussion. - -## Edits - -(none) - -## Topics Identified - -(none) - -## Conclusion - -(none) diff --git a/tests/prose/cases/discussion-session-substituted/fixture/.workflows/pay/manifest.json b/tests/prose/cases/discussion-session-substituted/fixture/.workflows/pay/manifest.json deleted file mode 100644 index 1cf8afb0e..000000000 --- a/tests/prose/cases/discussion-session-substituted/fixture/.workflows/pay/manifest.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "pay", - "work_type": "feature", - "status": "in-progress", - "created": "2026-01-01", - "description": "Accept card payments at checkout", - "phases": {} -} diff --git a/tests/prose/cases/implementation-picks-first-task/case.json b/tests/prose/cases/implementation-picks-first-task/case.json index cf3393720..f5c58f453 100644 --- a/tests/prose/cases/implementation-picks-first-task/case.json +++ b/tests/prose/cases/implementation-picks-first-task/case.json @@ -1,5 +1,6 @@ { "origin": "feature mainline \u2014 entry through resume detection to the first task", + "entry": "workflow-implementation-entry", "files": [ "skills/workflow-implementation-entry/SKILL.md", "skills/workflow-implementation-entry/references/validate-phase.md", diff --git a/tests/prose/cases/investigation-entry-seeds-from-carrier/case.json b/tests/prose/cases/investigation-entry-seeds-from-carrier/case.json index 71758ea1b..8f64940f7 100644 --- a/tests/prose/cases/investigation-entry-seeds-from-carrier/case.json +++ b/tests/prose/cases/investigation-entry-seeds-from-carrier/case.json @@ -1,5 +1,6 @@ { "origin": "bugfix mainline \u2014 a bug shaped in discovery is never re-interrogated", + "entry": "workflow-investigation-entry", "files": [ "skills/workflow-investigation-entry/SKILL.md", "skills/workflow-investigation-entry/references/invoke-skill.md" diff --git a/tests/prose/cases/planning-entry-asks-for-late-context/case.json b/tests/prose/cases/planning-entry-asks-for-late-context/case.json index 83b632fd8..8ff061076 100644 --- a/tests/prose/cases/planning-entry-asks-for-late-context/case.json +++ b/tests/prose/cases/planning-entry-asks-for-late-context/case.json @@ -1,5 +1,6 @@ { "origin": "feature mainline \u2014 planning offers a last chance to add context", + "entry": "workflow-planning-entry", "files": [ "skills/workflow-planning-entry/SKILL.md", "skills/workflow-planning-entry/references/validate-spec.md", diff --git a/tests/prose/cases/review-entry-fresh/case.json b/tests/prose/cases/review-entry-fresh/case.json index e19d4dfa4..a365e2cd2 100644 --- a/tests/prose/cases/review-entry-fresh/case.json +++ b/tests/prose/cases/review-entry-fresh/case.json @@ -1,5 +1,6 @@ { "origin": "feature mainline \u2014 review refuses to start until delivery is complete", + "entry": "workflow-review-entry", "files": [ "skills/workflow-review-entry/SKILL.md", "skills/workflow-review-entry/references/validate-phase.md" diff --git a/tests/prose/cases/root-cause-validation-clean-verdict/act.md b/tests/prose/cases/root-cause-validation-clean-verdict/act.md deleted file mode 100644 index c3225285c..000000000 --- a/tests/prose/cases/root-cause-validation-clean-verdict/act.md +++ /dev/null @@ -1,2 +0,0 @@ -Walk root-cause-validation.md from the top, as the processing skill -would. Stop when the reference returns to its caller. diff --git a/tests/prose/cases/root-cause-validation-clean-verdict/assert.md b/tests/prose/cases/root-cause-validation-clean-verdict/assert.md deleted file mode 100644 index 291c65e4b..000000000 --- a/tests/prose/cases/root-cause-validation-clean-verdict/assert.md +++ /dev/null @@ -1,20 +0,0 @@ -The prose should have taken this path: - -1. the scripted `yes` is consumed at the run-or-skip choice, so the walk - takes the run arm — a `skip` would have returned to the caller with no - dispatch at all -2. the dispatch is recorded through the engine, and the report lands at - the path that response returned — the prose does not invent a path or - pre-create the file -3. exactly one validation agent stands behind that dispatch: one dispatch - recorded, one report written, no second of either -4. once the report has landed, the row is promoted by a scan and closed - by an incorporate — the verdict is consumed whole, never surfaced - finding by finding -5. the report is read and, the verdict being validated, the reference - returns to its caller — the gap-handling choice is never reached, so - no gaps are recorded and the investigation file is never edited - -Further claims: - -- nothing is committed: the validated path has no gaps to record diff --git a/tests/prose/cases/root-cause-validation-clean-verdict/assertion-state.cjs b/tests/prose/cases/root-cause-validation-clean-verdict/assertion-state.cjs deleted file mode 100644 index 12c381e52..000000000 --- a/tests/prose/cases/root-cause-validation-clean-verdict/assertion-state.cjs +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -// The fixture state plus what the walk should have done: the dispatch -// recorded, the agent's report landed at the path the dispatch returned, -// scan promoting the row and incorporate closing it. The report content -// comes from the same stub the case arms, so the bytes the walk writes -// and the bytes expected here cannot drift apart. - -const fs = require('fs'); -const path = require('path'); - -const fixture = require('./fixture-state.cjs'); -const m = require('../../mainlines/bugfix.cjs'); -const { readStub } = require('../../lib/cases.cjs'); - -module.exports = { - build(h) { - fixture.build(h); - - const dispatch = JSON.parse(h.engine( - 'agent', 'dispatch', m.WU, 'investigation', m.WU, '--kind', 'root-cause-validation')); - fs.mkdirSync(path.dirname(path.join(h.dir, dispatch.file)), { recursive: true }); - h.write(dispatch.file, readStub('root-cause-validated').content); - h.engine('agent', 'scan', m.WU, 'investigation', m.WU); - h.engine('agent', 'incorporate', m.WU, 'investigation', m.WU, dispatch.id); - }, -}; diff --git a/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.claude/settings.json b/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.claude/settings.json deleted file mode 100644 index ae019ede5..000000000 --- a/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.claude/settings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "showClearContextOnPlanAccept": true, - "permissions": { - "allow": [ - "Edit(.workflows/**)", - "Bash(mv .workflows/:*)" - ] - } -} diff --git a/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.recipe-hash b/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.recipe-hash deleted file mode 100644 index d38ba55a6..000000000 --- a/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.recipe-hash +++ /dev/null @@ -1 +0,0 @@ -b65c43b1fced1a2d5742f96a2d6d1a8278f67d44c143552dbb42bee06badded6 diff --git a/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/.cache/crash-fix/investigation/crash-fix/root-cause-validation-001.md b/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/.cache/crash-fix/investigation/crash-fix/root-cause-validation-001.md deleted file mode 100644 index ce1c9b4c4..000000000 --- a/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/.cache/crash-fix/investigation/crash-fix/root-cause-validation-001.md +++ /dev/null @@ -1,10 +0,0 @@ -# Root Cause Validation - -The tax context is built before the payment intent and reads the shipping -address unconditionally. Traced fresh: an address-less order fails at that -read, which matches the documented root cause. - -STATUS: validated -CONFIDENCE: high -GAPS_COUNT: 0 -SUMMARY: Root cause confirmed by an independent trace. diff --git a/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/.cache/crash-fix/investigation/crash-fix/state.json b/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/.cache/crash-fix/investigation/crash-fix/state.json deleted file mode 100644 index 7eb5a78f1..000000000 --- a/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/.cache/crash-fix/investigation/crash-fix/state.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "agents": { - "root-cause-validation-001": { - "id": "root-cause-validation-001", - "kind": "root-cause-validation", - "phase": "investigation", - "topic": "crash-fix", - "set": "001", - "status": "incorporated", - "announced": false, - "findings": [], - "surfaced": [], - "created": "2026-01-01T00:00:00.000Z" - } - } -} diff --git a/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/.state/migrations b/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/.state/migrations deleted file mode 100644 index 3c7162acc..000000000 --- a/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/.state/migrations +++ /dev/null @@ -1,52 +0,0 @@ -001 -002 -003 -004 -005 -006 -007 -008 -009 -010 -011 -012 -013 -014 -015 -016 -017 -018 -019 -020 -021 -022 -023 -024 -025 -026 -027 -028 -029 -030 -031 -032 -033 -034 -035 -036 -037 -038 -039 -040 -041 -042 -043 -044 -045 -046 -047 -048 -049 -050 -051 -052 diff --git a/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/_gitignore.fixture b/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/_gitignore.fixture deleted file mode 100644 index 779cbf28a..000000000 --- a/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/_gitignore.fixture +++ /dev/null @@ -1,2 +0,0 @@ -.cache/ -.manifest.json.*.tmp diff --git a/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/crash-fix/discovery/sessions/session-001.md b/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/crash-fix/discovery/sessions/session-001.md deleted file mode 100644 index 6dc56d4e3..000000000 --- a/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/crash-fix/discovery/sessions/session-001.md +++ /dev/null @@ -1,40 +0,0 @@ -# Discovery Session 001 - -Date: 2026-01-01 -Work unit: crash-fix - -## Description (as of session) - -Checkout crashes when an order has no shipping address. - -## Seed - -(none) - -## Imports - -(none) - -## Map State at Start - -(n/a — single-topic work) - -## Exploration - -Reported by two users this week: the checkout page 500s at the -payment step for orders with no shipping address. Reproducible on -staging with a digital-only basket. No error surfaces to the user — -the page just fails. Confirmed as a bugfix; no design question here, -so it routes straight to investigation. - -## Edits - -(none) - -## Topics Identified - -(none) - -## Conclusion - -(none) diff --git a/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/crash-fix/investigation/crash-fix.md b/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/crash-fix/investigation/crash-fix.md deleted file mode 100644 index d5fc263a9..000000000 --- a/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/crash-fix/investigation/crash-fix.md +++ /dev/null @@ -1,88 +0,0 @@ -# Investigation: Checkout Crash On Missing Shipping Address - -## Symptoms - -### Problem Description - -Checkout returns a 500 at the payment step when the order has no -shipping address. No error is surfaced to the user. - -### Manifestation - -Server error on POST to the payment step; the page fails blank. - -### Reproduction Steps - -1. Add a digital-only product to the basket. -2. Proceed to checkout without entering a shipping address. -3. Continue to the payment step — the request 500s. - -### Environment - -Reproduced on staging; reported twice in production this week. - -### Impact - -Digital-only orders cannot complete checkout. - -### References - -(none) - -## Analysis - -### Hypotheses - -- The payment step assumes a shipping address is always present. -- Tax calculation may be the first consumer of the missing address. - -### Code Trace - -The payment step builds a tax context from the order before creating -the payment intent. The tax context reads the shipping address -unconditionally; for a digital-only order the address is absent, so -the read fails and the request aborts before any handler catches it. - -### Root Cause - -The tax context treats the shipping address as mandatory. Digital-only -orders legitimately have none, so the assumption is wrong rather than -the data being wrong. - -### Contributing Factors - -Digital-only baskets were introduced after the tax context was written. - -### Why It Wasn't Caught - -No test covers a basket with no shippable line items. - -### Blast Radius - -Any flow building a tax context from an address-less order. - -## Fix Direction - -### Chosen Approach - -(pending) - -### Options Explored - -(none yet) - -### Discussion - -(none yet) - -### Testing Recommendations - -(pending) - -### Risk Assessment - -(pending) - -## Notes - -(none) diff --git a/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/crash-fix/manifest.json b/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/crash-fix/manifest.json deleted file mode 100644 index 88bf183ff..000000000 --- a/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/crash-fix/manifest.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "crash-fix", - "work_type": "bugfix", - "status": "in-progress", - "created": "2026-01-01", - "description": "Checkout crashes when an order has no shipping address", - "phases": { - "investigation": { - "items": { - "crash-fix": { - "status": "in-progress" - } - } - } - } -} diff --git a/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/manifest.json b/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/manifest.json deleted file mode 100644 index 1cd498ed5..000000000 --- a/tests/prose/cases/root-cause-validation-clean-verdict/assertion/.workflows/manifest.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "work_units": { - "crash-fix": { - "work_type": "bugfix" - } - } -} diff --git a/tests/prose/cases/root-cause-validation-clean-verdict/case.json b/tests/prose/cases/root-cause-validation-clean-verdict/case.json deleted file mode 100644 index aab675685..000000000 --- a/tests/prose/cases/root-cause-validation-clean-verdict/case.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "origin": "bugfix mainline — the validation agent's lifecycle on a clean verdict", - "files": [ - "skills/workflow-investigation-process/references/root-cause-validation.md" - ], - "answers": [ - "yes — run root cause validation" - ], - "stubs": { - "root-cause-validated": "when the engine records a dispatch of kind root-cause-validation" - } -} diff --git a/tests/prose/cases/root-cause-validation-clean-verdict/fixture-state.cjs b/tests/prose/cases/root-cause-validation-clean-verdict/fixture-state.cjs deleted file mode 100644 index 67d91fdbf..000000000 --- a/tests/prose/cases/root-cause-validation-clean-verdict/fixture-state.cjs +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -// The investigation is open with its root cause documented, and no agent has run. - -const m = require('../../mainlines/bugfix.cjs'); - -module.exports = { - build(h) { - m.init(h); - m.create(h); - m.investigateToRootCause(h); - }, -}; diff --git a/tests/prose/cases/root-cause-validation-clean-verdict/fixture.md b/tests/prose/cases/root-cause-validation-clean-verdict/fixture.md deleted file mode 100644 index 95d92d942..000000000 --- a/tests/prose/cases/root-cause-validation-clean-verdict/fixture.md +++ /dev/null @@ -1,3 +0,0 @@ -The investigation is open with its symptoms gathered and its root cause -documented. The processing skill has worked down to its root-cause -validation step; nothing has been validated yet and no agent has run. diff --git a/tests/prose/cases/root-cause-validation-clean-verdict/fixture/.claude/settings.json b/tests/prose/cases/root-cause-validation-clean-verdict/fixture/.claude/settings.json deleted file mode 100644 index ae019ede5..000000000 --- a/tests/prose/cases/root-cause-validation-clean-verdict/fixture/.claude/settings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "showClearContextOnPlanAccept": true, - "permissions": { - "allow": [ - "Edit(.workflows/**)", - "Bash(mv .workflows/:*)" - ] - } -} diff --git a/tests/prose/cases/root-cause-validation-clean-verdict/fixture/.recipe-hash b/tests/prose/cases/root-cause-validation-clean-verdict/fixture/.recipe-hash deleted file mode 100644 index d38ba55a6..000000000 --- a/tests/prose/cases/root-cause-validation-clean-verdict/fixture/.recipe-hash +++ /dev/null @@ -1 +0,0 @@ -b65c43b1fced1a2d5742f96a2d6d1a8278f67d44c143552dbb42bee06badded6 diff --git a/tests/prose/cases/root-cause-validation-clean-verdict/fixture/.workflows/.state/migrations b/tests/prose/cases/root-cause-validation-clean-verdict/fixture/.workflows/.state/migrations deleted file mode 100644 index 3c7162acc..000000000 --- a/tests/prose/cases/root-cause-validation-clean-verdict/fixture/.workflows/.state/migrations +++ /dev/null @@ -1,52 +0,0 @@ -001 -002 -003 -004 -005 -006 -007 -008 -009 -010 -011 -012 -013 -014 -015 -016 -017 -018 -019 -020 -021 -022 -023 -024 -025 -026 -027 -028 -029 -030 -031 -032 -033 -034 -035 -036 -037 -038 -039 -040 -041 -042 -043 -044 -045 -046 -047 -048 -049 -050 -051 -052 diff --git a/tests/prose/cases/root-cause-validation-clean-verdict/fixture/.workflows/_gitignore.fixture b/tests/prose/cases/root-cause-validation-clean-verdict/fixture/.workflows/_gitignore.fixture deleted file mode 100644 index 779cbf28a..000000000 --- a/tests/prose/cases/root-cause-validation-clean-verdict/fixture/.workflows/_gitignore.fixture +++ /dev/null @@ -1,2 +0,0 @@ -.cache/ -.manifest.json.*.tmp diff --git a/tests/prose/cases/root-cause-validation-clean-verdict/fixture/.workflows/crash-fix/discovery/sessions/session-001.md b/tests/prose/cases/root-cause-validation-clean-verdict/fixture/.workflows/crash-fix/discovery/sessions/session-001.md deleted file mode 100644 index 6dc56d4e3..000000000 --- a/tests/prose/cases/root-cause-validation-clean-verdict/fixture/.workflows/crash-fix/discovery/sessions/session-001.md +++ /dev/null @@ -1,40 +0,0 @@ -# Discovery Session 001 - -Date: 2026-01-01 -Work unit: crash-fix - -## Description (as of session) - -Checkout crashes when an order has no shipping address. - -## Seed - -(none) - -## Imports - -(none) - -## Map State at Start - -(n/a — single-topic work) - -## Exploration - -Reported by two users this week: the checkout page 500s at the -payment step for orders with no shipping address. Reproducible on -staging with a digital-only basket. No error surfaces to the user — -the page just fails. Confirmed as a bugfix; no design question here, -so it routes straight to investigation. - -## Edits - -(none) - -## Topics Identified - -(none) - -## Conclusion - -(none) diff --git a/tests/prose/cases/root-cause-validation-clean-verdict/fixture/.workflows/crash-fix/investigation/crash-fix.md b/tests/prose/cases/root-cause-validation-clean-verdict/fixture/.workflows/crash-fix/investigation/crash-fix.md deleted file mode 100644 index d5fc263a9..000000000 --- a/tests/prose/cases/root-cause-validation-clean-verdict/fixture/.workflows/crash-fix/investigation/crash-fix.md +++ /dev/null @@ -1,88 +0,0 @@ -# Investigation: Checkout Crash On Missing Shipping Address - -## Symptoms - -### Problem Description - -Checkout returns a 500 at the payment step when the order has no -shipping address. No error is surfaced to the user. - -### Manifestation - -Server error on POST to the payment step; the page fails blank. - -### Reproduction Steps - -1. Add a digital-only product to the basket. -2. Proceed to checkout without entering a shipping address. -3. Continue to the payment step — the request 500s. - -### Environment - -Reproduced on staging; reported twice in production this week. - -### Impact - -Digital-only orders cannot complete checkout. - -### References - -(none) - -## Analysis - -### Hypotheses - -- The payment step assumes a shipping address is always present. -- Tax calculation may be the first consumer of the missing address. - -### Code Trace - -The payment step builds a tax context from the order before creating -the payment intent. The tax context reads the shipping address -unconditionally; for a digital-only order the address is absent, so -the read fails and the request aborts before any handler catches it. - -### Root Cause - -The tax context treats the shipping address as mandatory. Digital-only -orders legitimately have none, so the assumption is wrong rather than -the data being wrong. - -### Contributing Factors - -Digital-only baskets were introduced after the tax context was written. - -### Why It Wasn't Caught - -No test covers a basket with no shippable line items. - -### Blast Radius - -Any flow building a tax context from an address-less order. - -## Fix Direction - -### Chosen Approach - -(pending) - -### Options Explored - -(none yet) - -### Discussion - -(none yet) - -### Testing Recommendations - -(pending) - -### Risk Assessment - -(pending) - -## Notes - -(none) diff --git a/tests/prose/cases/root-cause-validation-clean-verdict/fixture/.workflows/crash-fix/manifest.json b/tests/prose/cases/root-cause-validation-clean-verdict/fixture/.workflows/crash-fix/manifest.json deleted file mode 100644 index 88bf183ff..000000000 --- a/tests/prose/cases/root-cause-validation-clean-verdict/fixture/.workflows/crash-fix/manifest.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "crash-fix", - "work_type": "bugfix", - "status": "in-progress", - "created": "2026-01-01", - "description": "Checkout crashes when an order has no shipping address", - "phases": { - "investigation": { - "items": { - "crash-fix": { - "status": "in-progress" - } - } - } - } -} diff --git a/tests/prose/cases/root-cause-validation-clean-verdict/fixture/.workflows/manifest.json b/tests/prose/cases/root-cause-validation-clean-verdict/fixture/.workflows/manifest.json deleted file mode 100644 index 1cd498ed5..000000000 --- a/tests/prose/cases/root-cause-validation-clean-verdict/fixture/.workflows/manifest.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "work_units": { - "crash-fix": { - "work_type": "bugfix" - } - } -} diff --git a/tests/prose/cases/smoke-start-boot-structure/case.json b/tests/prose/cases/smoke-start-boot-structure/case.json index 07b9e0424..c47d0f93f 100644 --- a/tests/prose/cases/smoke-start-boot-structure/case.json +++ b/tests/prose/cases/smoke-start-boot-structure/case.json @@ -1,5 +1,6 @@ { - "origin": "framework smoke — a structure-only walk, no world", + "origin": "framework smoke \u2014 a structure-only walk, no world", + "entry": "workflow-start", "files": [ "skills/workflow-start/SKILL.md#Boot" ] diff --git a/tests/prose/cases/spec-entry-from-discussion/case.json b/tests/prose/cases/spec-entry-from-discussion/case.json index f7f4aa938..95045863f 100644 --- a/tests/prose/cases/spec-entry-from-discussion/case.json +++ b/tests/prose/cases/spec-entry-from-discussion/case.json @@ -1,5 +1,6 @@ { "origin": "feature mainline \u2014 a feature's spec draws on its discussion", + "entry": "workflow-specification-entry", "files": [ "skills/workflow-specification-entry/SKILL.md", "skills/workflow-specification-entry/references/validate-source.md", diff --git a/tests/prose/cases/spec-entry-from-investigation/case.json b/tests/prose/cases/spec-entry-from-investigation/case.json index e0849f435..c48f2332e 100644 --- a/tests/prose/cases/spec-entry-from-investigation/case.json +++ b/tests/prose/cases/spec-entry-from-investigation/case.json @@ -1,5 +1,6 @@ { "origin": "bugfix mainline \u2014 the same entry skill, sourced from an investigation", + "entry": "workflow-specification-entry", "files": [ "skills/workflow-specification-entry/SKILL.md", "skills/workflow-specification-entry/references/validate-source.md", diff --git a/tests/prose/cases/start-lists-active-work/case.json b/tests/prose/cases/start-lists-active-work/case.json index c4b7667c2..8cd90c53f 100644 --- a/tests/prose/cases/start-lists-active-work/case.json +++ b/tests/prose/cases/start-lists-active-work/case.json @@ -1,5 +1,6 @@ { "origin": "feature mainline \u2014 the entry skill surfaces work without touching it", + "entry": "workflow-start", "files": [ "skills/workflow-start/SKILL.md" ], diff --git a/tests/prose/lib/cases.cjs b/tests/prose/lib/cases.cjs index e26d82620..371f8cc37 100644 --- a/tests/prose/lib/cases.cjs +++ b/tests/prose/lib/cases.cjs @@ -8,8 +8,8 @@ // modules. // // case.json the values code branches on: -// { origin, files[], answers[], stubs{name: trigger}, -// world: "claims" (optional), +// { origin, entry, files[], answers[], +// stubs{name: trigger}, world: "claims" (optional), // invariants{} (optional) — see lib/invariants.cjs } // fixture.md optional. Prose describing the starting world — // what has already happened, where the session @@ -35,6 +35,20 @@ // variability is genuinely the model's to decide; a world a recipe can // build must be built. // +// `entry` names where the walk starts, and it may only be somewhere a +// real session starts: `workflow-start` (the user's way in), a +// `workflow-*-entry` skill (what the bridge's plan file invokes after a +// context clear), or `workflow-discovery` (epic continuation). Nothing +// else is a legitimate opening — navigation and processing skills are +// always invoked mid-session, and a reference is never entered directly. +// +// This matters because a walk carries only the context it accumulates. +// Start one in the middle and the world may be right while the reading +// that got there is missing, so the prose is judged under conditions it +// was never written for and the verdict means nothing either way. The +// cases that broke this rule were the ones producing findings that +// dissolved on inspection. +// // act.md and assert.md are separate files because that is the P4 // boundary: the walker must never see the expected trace, and a file // boundary enforces it structurally rather than by convention. @@ -98,6 +112,7 @@ function loadCase(id) { rel: path.relative(ROOT, dir), metaError, origin: meta.origin || null, + entry: meta.entry || null, files: (meta.files || []).map((spec) => { const hash = spec.indexOf('#'); return hash === -1 @@ -160,6 +175,27 @@ function headingExists(absPath, anchor) { }); } +/** + * Where a walk may begin. A session starts at workflow-start, or at the + * entry skill a bridge plan file names after a context clear, or at + * discovery for an epic continuation — never anywhere else. + */ +function entryErrors(entry) { + if (!entry) return ['has no entry — name the skill the walk starts at']; + const allowed = entry === 'workflow-start' + || entry === 'workflow-discovery' + || /^workflow-[a-z-]+-entry$/.test(entry); + if (!allowed) { + return [`entry "${entry}" is not somewhere a session starts — use workflow-start, ` + + 'a workflow-*-entry skill, or workflow-discovery. Navigation and processing ' + + 'skills are only ever reached mid-session, and a reference never directly']; + } + if (!fs.existsSync(path.join(ROOT, 'skills', entry, 'SKILL.md'))) { + return [`entry "${entry}" is not a skill in skills/`]; + } + return []; +} + function validateCorpus(cases) { const errors = []; const stubs = new Set(listStubs()); @@ -169,6 +205,7 @@ function validateCorpus(cases) { if (c.metaError) { errors.push(`${at}: ${c.metaError}`); continue; } if (!/^[a-z0-9][a-z0-9-]*$/.test(c.id)) errors.push(`${at}: case directory is not kebab-case`); if (!c.origin) errors.push(`${at}: ${FILES.meta} has no origin`); + for (const e of entryErrors(c.entry)) errors.push(`${at}: ${FILES.meta} ${e}`); if (c.files.length === 0) errors.push(`${at}: ${FILES.meta} scopes no files`); for (const f of c.files) { @@ -225,5 +262,6 @@ function validateCorpus(cases) { module.exports = { ROOT, PROSE_DIR, CASES_DIR, STUBS_DIR, FILES, SNAPSHOTS, - listCaseIds, loadCase, loadAllCases, requireState, listStubs, readStub, validateCorpus, + listCaseIds, loadCase, loadAllCases, requireState, listStubs, readStub, + validateCorpus, entryErrors, }; diff --git a/tests/scripts/test-prose-invariants.cjs b/tests/scripts/test-prose-invariants.cjs index 4d79cd855..8179495f1 100644 --- a/tests/scripts/test-prose-invariants.cjs +++ b/tests/scripts/test-prose-invariants.cjs @@ -189,3 +189,45 @@ describe('declaration validation', () => { assert.match(invariants.declarationErrors(['engine_before_write'])[0], /must be an object/); }); }); + +describe('entry points — where a walk may begin', () => { + const cases = require('../prose/lib/cases.cjs'); + + it('accepts the user entry point', () => { + assert.deepEqual(cases.entryErrors('workflow-start'), []); + }); + + it('accepts an entry skill, which a bridge plan invokes after a context clear', () => { + assert.deepEqual(cases.entryErrors('workflow-implementation-entry'), []); + assert.deepEqual(cases.entryErrors('workflow-specification-entry'), []); + }); + + it('accepts discovery, the one continuation that is not an entry skill', () => { + assert.deepEqual(cases.entryErrors('workflow-discovery'), []); + }); + + it('rejects a navigation skill — always invoked by workflow-start, never cold', () => { + const [error] = cases.entryErrors('workflow-continue-feature'); + assert.match(error, /not somewhere a session starts/); + }); + + it('rejects a processing skill — always invoked by its entry skill', () => { + assert.match(cases.entryErrors('workflow-discussion-process')[0], /not somewhere a session starts/); + }); + + it('rejects a reference, which is never entered directly', () => { + assert.match(cases.entryErrors('root-cause-validation.md')[0], /not somewhere a session starts/); + }); + + it('rejects a plausible name that is not a skill on disk', () => { + assert.match(cases.entryErrors('workflow-imaginary-entry')[0], /not a skill in skills\//); + }); + + it('requires one at all', () => { + assert.match(cases.entryErrors(null)[0], /has no entry/); + }); + + it('holds for the live corpus', () => { + assert.deepEqual(cases.validateCorpus(cases.loadAllCases()), []); + }); +});