diff --git a/docs/workflow-owned-lifecycle-closing-verification.md b/docs/workflow-owned-lifecycle-closing-verification.md new file mode 100644 index 0000000000..fb6979d789 --- /dev/null +++ b/docs/workflow-owned-lifecycle-closing-verification.md @@ -0,0 +1,102 @@ +# Closing-bar verification — workflow-owned lifecycle + +The programme's closing bar requires one verification pass on one tree: gate, +`verify:fast`, the live E2E families, and the census. This file is the runbook and the +record, so the pass is reproducible rather than re-derived each time. + +## How to run it + +```bash +git checkout --detach origin/main # one tree, no local commits +pnpm test:gate +pnpm verify:fast +# GLOB the E2E families — do not hard-code a count (see "Findings" below) +pnpm --filter @fusion/engine exec vitest run \ + $(cd packages/engine && ls src/__tests__/*live-e2e*.test.ts | tr '\n' ' ') --reporter=dot +node scripts/lifecycle-column-census.mjs +``` + +## Recorded passes + +### 2026-07-30, `origin/main` @ `a6138abeff` (latest) + +| Check | Result | +|---|---| +| `pnpm test:gate` | PASS | +| E2E families | **14 files / 112 tests PASS**, exit 0 | +| census | COLUMN 748 · ROLE 5 · STATUS 186 · DELIBERATE-LITERAL 17 · QUERY 83 · IR-node 43 | + +### 2026-07-30, `origin/main` @ `be63e72f1` (first pass) + +| Check | Result | +|---|---| +| `pnpm test:gate` | PASS — 132 + 10 + 487 + 71 tests | +| `pnpm verify:fast` | PASS — 13 steps in 89.9s; boot smoke `GET /api/health 200`, clean shutdown | +| E2E families | 13 files / 109 tests — 2 failed until the planning-lane seed fix (#2658) | +| census | COLUMN 787 · ROLE 0 · STATUS 182 · DELIBERATE-LITERAL 3 · triage 10 | + +**Both numbers moved within a few hours**, which is the point of recording the commit +alongside the result: a bare count in an instruction is stale by the time it is read. The +family count went 13 → 14 and the backlog 787 → 748 between these two passes, and the +census grew two categories (`QUERY filters`, `IR node definitions`) that did not exist in +the first run. + +## Findings + +### 1. Glob the E2E families; never count them + +The bar said "all-8 E2E". There were **13** at the time of this pass: + +``` +agent-count · agent-link · lease-rebound · lifecycle · merge-family · merge-rebound +merge-safeguards · merged-board · planner-lane · planner-lane-resolution +planning-lane · rebound-family · stranded-column +``` + +A pass scoped to eight would have skipped five — **including the only one that was red**. +The suite grows as evidence lands, so the count in any instruction is stale on arrival. +The command above globs for this reason. + +### 2. A release-path E2E fails when its fixture never wrote a spec + +Symptom: `runHoldReleaseSweep(...).released` comes back EMPTY, often for the suite's own +control card. + +Cause: task creation leaves a bootstrap-seed `PROMPT.md` (`# \n\n`), and +FN-7648's `isUnplannedForExecution` reads that file for any card resting in an `intake`- +or `hold`-trait column and refuses to move an unplanned card into a processing column. +**The sweep is correct; the fixture is asking it to release a card that was never +specified.** + +This has now occurred **twice** in independently written suites — +`workflow-lifecycle-live-e2e` (fixed in #2634) and `workflow-planning-lane-live-e2e` +(fixed in #2658, and independently by a second worker, wasting the duplicate effort). The +graph-entry contract doc already states the rule: + +> Scheduler/release test fixtures must model a card that cleared the gate ... A held +> unreviewed card is the gate working. + +**Any new suite that drives the release sweep must seed a planned `PROMPT.md`.** A shared +`seedPlannedTask` helper in `_workflow-vocabulary-fixture.ts` would end the pattern; it +was not added while several consolidation branches still touch those files. + +Diagnostic order that identifies this quickly, and two hypotheses it kills: + +1. `sweep()` returns `held: [{ reason: "move-rejected-or-no-slot" }]`. +2. Adding `maxConcurrent`/`maxWorktrees` to the settings changes nothing — **not** the + in-transaction capacity gate. +3. A direct `store.moveTask(id, wip)` **succeeds** — the move is not the blocker. +4. `isTaskBlockedOnApproval` is false, `isUnplannedForExecution` is **true**. +5. Read the seeded `PROMPT.md`: it is the stub. + +### 3. `DELIBERATE-LITERAL` is internally consistent + +`deliberate: 3` reconciles exactly: `hold-release.ts` 2 + `live-agent-count.ts` 1. +`replan-target.ts` carries two markers above `return "triage"` **return-value** literals, +which the census correctly does not count as guards at all — so they appear in neither +`deliberate` nor `column`. A lower number than a previously quoted one is not evidence of +lost markers; check `--json byFile` before treating it as a gap. + +`hasDeliberateMarker` walks every **ancestor**, not the enclosing statement, because real +markers sit above the enclosing function while the comparison is a `return` inside it. A +statement-only lookup silently reclassified three reviewed literals as backlog.