harness-everything 0.40.0 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/.agents/harness-everything/project-docs.json +9 -0
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/.claude-plugin/plugin.json +1 -1
  4. package/CHANGELOG.md +43 -0
  5. package/benchmarks/results/f1-doc-path-baseline/1-source-clean.stderr.txt +0 -0
  6. package/benchmarks/results/f1-doc-path-baseline/1-source-clean.stdout.json +20 -0
  7. package/benchmarks/results/f1-doc-path-baseline/2-injected-architecture.md +229 -0
  8. package/benchmarks/results/f1-doc-path-baseline/2-source-injected.stderr.txt +0 -0
  9. package/benchmarks/results/f1-doc-path-baseline/2-source-injected.stdout.json +33 -0
  10. package/benchmarks/results/f1-doc-path-baseline/3-source-reverted.stderr.txt +0 -0
  11. package/benchmarks/results/f1-doc-path-baseline/3-source-reverted.stdout.json +20 -0
  12. package/benchmarks/results/f1-doc-path-baseline/4a-packaged-clean.stderr.txt +0 -0
  13. package/benchmarks/results/f1-doc-path-baseline/4a-packaged-clean.stdout.json +20 -0
  14. package/benchmarks/results/f1-doc-path-baseline/4b-packaged-injected.stderr.txt +0 -0
  15. package/benchmarks/results/f1-doc-path-baseline/4b-packaged-injected.stdout.json +33 -0
  16. package/benchmarks/results/f1-doc-path-baseline/5-source-missing-config.stderr.txt +0 -0
  17. package/benchmarks/results/f1-doc-path-baseline/5-source-missing-config.stdout.json +15 -0
  18. package/benchmarks/results/f1-doc-path-baseline/5b-packaged-missing-config.stderr.txt +0 -0
  19. package/benchmarks/results/f1-doc-path-baseline/5b-packaged-missing-config.stdout.json +15 -0
  20. package/benchmarks/results/f1-doc-path-baseline/README.md +54 -0
  21. package/benchmarks/results/f1-doc-path-baseline/before-fix/1-source-clean.stderr.txt +0 -0
  22. package/benchmarks/results/f1-doc-path-baseline/before-fix/1-source-clean.stdout.json +115 -0
  23. package/benchmarks/results/f1-doc-path-baseline/before-fix/2-injected-document.md +328 -0
  24. package/benchmarks/results/f1-doc-path-baseline/before-fix/2-source-injected.stderr.txt +0 -0
  25. package/benchmarks/results/f1-doc-path-baseline/before-fix/2-source-injected.stdout.json +127 -0
  26. package/benchmarks/results/f1-doc-path-baseline/before-fix/3-source-reverted.stderr.txt +0 -0
  27. package/benchmarks/results/f1-doc-path-baseline/before-fix/3-source-reverted.stdout.json +115 -0
  28. package/benchmarks/results/f1-doc-path-baseline/before-fix/4a-packaged-clean.stderr.txt +0 -0
  29. package/benchmarks/results/f1-doc-path-baseline/before-fix/4a-packaged-clean.stdout.json +115 -0
  30. package/benchmarks/results/f1-doc-path-baseline/before-fix/4b-packaged-injected.stderr.txt +0 -0
  31. package/benchmarks/results/f1-doc-path-baseline/before-fix/4b-packaged-injected.stdout.json +127 -0
  32. package/benchmarks/results/f1-doc-path-baseline/before-fix/5-source-missing-config.stderr.txt +0 -0
  33. package/benchmarks/results/f1-doc-path-baseline/before-fix/5-source-missing-config.stdout.json +15 -0
  34. package/benchmarks/results/f1-doc-path-baseline/before-fix/5b-packaged-missing-config.stderr.txt +0 -0
  35. package/benchmarks/results/f1-doc-path-baseline/before-fix/5b-packaged-missing-config.stdout.json +15 -0
  36. package/benchmarks/results/f1-doc-path-baseline/before-fix/6-supplementary-fixture-ok.input.md +8 -0
  37. package/benchmarks/results/f1-doc-path-baseline/before-fix/6-supplementary-fixture-ok.stdout.json +16 -0
  38. package/benchmarks/results/f1-doc-path-baseline/before-fix/README.md +34 -0
  39. package/benchmarks/results/f1-doc-path-baseline/probe-31-docs/after.json +248 -0
  40. package/benchmarks/results/f1-doc-path-baseline/probe-31-docs/before.json +254 -0
  41. package/benchmarks/results/f1-doc-path-baseline/probe-31-docs/probe.js +26 -0
  42. package/benchmarks/results/f2-doc-craft-baseline/README.md +32 -0
  43. package/benchmarks/results/f2-doc-craft-baseline/arm-f1-report.md +84 -0
  44. package/benchmarks/results/f2-doc-craft-baseline/arm-f1-task.md +21 -0
  45. package/benchmarks/results/f2-doc-craft-baseline/arm-f1.diff +19 -0
  46. package/benchmarks/results/f2-doc-craft-baseline/arm-f2-report.md +110 -0
  47. package/benchmarks/results/f2-doc-craft-baseline/arm-f2-task.md +21 -0
  48. package/benchmarks/results/f2-doc-craft-baseline/arm-f2.diff +19 -0
  49. package/benchmarks/results/f3-derived-views-baseline/README.md +34 -0
  50. package/benchmarks/results/f3-derived-views-baseline/arm-f2-report.md +43 -0
  51. package/benchmarks/results/f3-derived-views-baseline/arm-f2-task.md +21 -0
  52. package/benchmarks/results/f3-derived-views-baseline/arm-f2.diff +21 -0
  53. package/benchmarks/results/f3-derived-views-baseline/arm-f3-report.md +57 -0
  54. package/benchmarks/results/f3-derived-views-baseline/arm-f3-task.md +21 -0
  55. package/benchmarks/results/f3-derived-views-baseline/arm-f3.diff +21 -0
  56. package/benchmarks/results/live-host/claude-325-3b-s2b/README.md +99 -0
  57. package/benchmarks/results/live-host/claude-325-3b-s2b/capture.sanitized.jsonl +26 -0
  58. package/ci/archive/325-prep/README.md +71 -0
  59. package/ci/{prep → archive/325-prep}/doc-reconciliation-red.js +9 -7
  60. package/ci/archive/325-prep/f4-expected-red.json +34 -0
  61. package/ci/archive/325-prep/historical-qualification.test.js +143 -0
  62. package/ci/fixtures/doc-obligations/reconciliation-prep/cases.json +17 -4
  63. package/ci/fixtures/doc-obligations/reconciliation-prep/pending-red.json +8 -56
  64. package/ci/fixtures/doc-obligations/reconciliation-prep/trust-contract.json +2 -2
  65. package/ci/fixtures/doc-obligations/reconciliation-s1/cases.json +17 -5
  66. package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/harness.js +415 -0
  67. package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/off-handoff-golden.json +16 -0
  68. package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-post-tool-use-failure.json +11 -0
  69. package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-post-tool-use-success-minimal.json +9 -0
  70. package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-post-tool-use-success.json +13 -0
  71. package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-subagent-start.json +7 -0
  72. package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-subagent-stop-freetext-reject.json +11 -0
  73. package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-subagent-stop-reject.json +11 -0
  74. package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-subagent-stop.json +11 -0
  75. package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-task-result.json +19 -0
  76. package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/off-capture-only.json +99 -0
  77. package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/off-legacy.json +67 -0
  78. package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/on-concurrent-writers.json +62 -0
  79. package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/on-lookalikes-runtime-mutations.json +67 -0
  80. package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/on-malformed.json +85 -0
  81. package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/on-normal-session.json +74 -0
  82. package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/on-runtime-drift-later-npm-test.json +58 -0
  83. package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/on-valid-direct-quoted-review.json +85 -0
  84. package/ci/fixtures/doc-obligations/reconciliation-s2b/f4/cases.json +2267 -0
  85. package/ci/fixtures/doc-obligations/reconciliation-s2b/wiring/cases.json +1110 -0
  86. package/ci/fixtures/doc-obligations/reconciliation-s2b/wiring/legacy-off-golden.json +674 -0
  87. package/ci/lib/doc-reconciliation-file-spy.js +23 -0
  88. package/ci/lib/doc-reconciliation-prep-fixtures.js +136 -10
  89. package/ci/lib/doc-reconciliation-qualification-preload.js +37 -0
  90. package/ci/lib/doc-reconciliation-replay.js +104 -0
  91. package/ci/lib/doc-reconciliation-staged-tree.js +102 -0
  92. package/ci/mechanism-2p-opencode-v2-entrypoint.test.js +179 -0
  93. package/ci/mechanism-325-3b-preparation.test.js +27 -101
  94. package/ci/mechanism-325-3b-s2b-capture.test.js +561 -0
  95. package/ci/mechanism-325-3b-s2b-wiring.test.js +668 -0
  96. package/ci/mechanism-325-f1-doc-check.test.js +674 -0
  97. package/ci/mechanism-325-f2-doc-craft.test.js +597 -0
  98. package/ci/mechanism-325-f3-derived-views.test.js +798 -0
  99. package/ci/mechanism-325-f4-invariants.test.js +606 -0
  100. package/ci/mechanism-325-f4-projections.test.js +382 -0
  101. package/ci/mechanism-325-f4-qualification.test.js +228 -0
  102. package/ci/mechanism-325-f4-trust-boundary.test.js +880 -0
  103. package/ci/mutation-check.js +26 -3
  104. package/docs/architecture.md +5 -0
  105. package/docs/doc-workflow-reconciliation.md +331 -4
  106. package/docs/document-lifecycle.md +11 -0
  107. package/docs/governed-index.md +20 -0
  108. package/docs/platform-capabilities.md +22 -2
  109. package/grill-with-docs/SKILL.md +1 -1
  110. package/grill-with-docs/references/session-playbook.md +4 -0
  111. package/harness-everything/SKILL.md +1 -1
  112. package/harness-everything/references/doc-check.md +186 -0
  113. package/harness-everything/references/documentation/_shared/maintenance.md +41 -0
  114. package/harness-everything/references/documentation/_shared/writing-craft.md +59 -0
  115. package/harness-everything/references/triage-and-tiers.md +4 -0
  116. package/harness-everything/scripts/doc-check.js +442 -0
  117. package/harness-everything/scripts/doc-lifecycle.js +3 -0
  118. package/hooks/hooks.json +46 -0
  119. package/hooks/scripts/lib/doc-reconciliation-capture.sanitized.jsonl +26 -0
  120. package/hooks/scripts/lib/doc-reconciliation-pilot.js +171 -2
  121. package/hooks/scripts/lib/doc-reconciliation-provider.js +1059 -0
  122. package/hooks/scripts/lib/doc-reconciliation-qualification.json +34 -0
  123. package/hooks/scripts/lib/workflow-obligations.js +7 -0
  124. package/hooks/scripts/state-persist.js +78 -2
  125. package/hooks/scripts/workflow-stop-gate.js +7 -2
  126. package/opencode-plugin/README.md +15 -0
  127. package/opencode-plugin/index.mjs +77 -0
  128. package/opencode-plugin/plugin.json +1 -1
  129. package/package.json +1 -1
  130. package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
  131. package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-capture.sanitized.jsonl +26 -0
  132. package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-pilot.js +171 -2
  133. package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-provider.js +1059 -0
  134. package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-qualification.json +34 -0
  135. package/plugins/harness-everything/hooks/scripts/lib/workflow-obligations.js +7 -0
  136. package/plugins/harness-everything/hooks/scripts/state-persist.js +78 -2
  137. package/plugins/harness-everything/hooks/scripts/workflow-stop-gate.js +7 -2
  138. package/plugins/harness-everything/plugin.json +1 -1
  139. package/plugins/harness-everything/skills/grill-with-docs/SKILL.md +1 -1
  140. package/plugins/harness-everything/skills/grill-with-docs/references/session-playbook.md +4 -0
  141. package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
  142. package/plugins/harness-everything/skills/harness-everything/references/doc-check.md +186 -0
  143. package/plugins/harness-everything/skills/harness-everything/references/documentation/_shared/maintenance.md +41 -0
  144. package/plugins/harness-everything/skills/harness-everything/references/documentation/_shared/writing-craft.md +59 -0
  145. package/plugins/harness-everything/skills/harness-everything/references/triage-and-tiers.md +4 -0
  146. package/plugins/harness-everything/skills/harness-everything/scripts/doc-check.js +442 -0
  147. package/plugins/harness-everything/skills/harness-everything/scripts/doc-lifecycle.js +3 -0
  148. package/plugins/harness-everything/skills/repo-docs/SKILL.md +1 -1
  149. package/plugins/harness-everything/skills/repo-docs/references/process-guide.md +4 -0
  150. package/plugins/harness-everything/skills/to-spec/SKILL.md +1 -1
  151. package/plugins/harness-everything/skills/to-spec/references/process.md +4 -0
  152. package/plugins/harness-everything/skills/to-tickets/SKILL.md +1 -1
  153. package/plugins/harness-everything/skills/to-tickets/references/publishing-and-templates.md +4 -0
  154. package/repo-docs/SKILL.md +1 -1
  155. package/repo-docs/references/process-guide.md +4 -0
  156. package/sbom.cdx.json +7 -7
  157. package/to-spec/SKILL.md +1 -1
  158. package/to-spec/references/process.md +4 -0
  159. package/to-tickets/SKILL.md +1 -1
  160. package/to-tickets/references/publishing-and-templates.md +4 -0
@@ -0,0 +1,71 @@
1
+ # Archived #325 prep gates (F4-T2)
2
+
3
+ Historical evidence only. Nothing here is a GREEN gate.
4
+
5
+ - **Not discovered:** `ci/mechanism-test.js` runs only top-level `ci/mechanism-*.test.js`, so nothing in this directory runs in `npm run test:mechanism`.
6
+ - **Owner clause (T2, 2026-10-22):** "only archive the prep fingerprint and the RED-plan gates as historical evidence, and tag tests with `locks: <anchor clause>`. List the unmapped tests, but don't delete them."
7
+ - **Revision of the historical results:** `0308386058ad942e2764475d48fd65a013910e1e`. The registry was captured at `c271249a8a99d7e0320288af8cb086bb03f8fa1a`.
8
+
9
+ ## Manifest
10
+
11
+ | # | Archived entry | Original path (sha256 at 0308386) | Removed live gate | Old result at 0308386 | Replacement behaviour command | Residual failure |
12
+ |---|---|---|---|---|---|---|
13
+ | 1 | Prep runner `doc-reconciliation-red.js` | `ci/prep/doc-reconciliation-red.js` (`5e85af19…7849c`) | Runtime-fingerprint check that forces every replay to FAIL on drift; `--expect-red` qualification | `--expect-red`: exit 1, "runtime fingerprint changed" | `node ci/mechanism-325-3b-preparation.test.js` (live infrastructure bounds only) | Fingerprint mismatch: registry `a7679becc1cb…8153b`, current `17a4821a21e1…2394` |
14
+ | 2 | Frozen registry `pending-red.json` | `ci/fixtures/doc-obligations/reconciliation-prep/pending-red.json` (`08ad9eef…7ff0`), kept at its fixture path (outside the T2 write-set) | Registry used as the qualified-gap gate | Validates; fingerprint field stale (see #1) | none (historical data) | Same fingerprint mismatch |
15
+ | 3 | Historical checks from the 3b suite (10 checks) | `ci/mechanism-325-3b-preparation.test.js` (pre-split) | Expected-RED mode, expected-RED report, gap registry, strict-mode gap log, registry validator, omitted-gap and unexpected-PASS checks, fingerprint invalidation, recapture | 4 FAIL, 6 PASS (see `historical-qualification.test.js`) | `node ci/archive/325-prep/historical-qualification.test.js` reproduces the old result | 4 FAIL: expected-RED mode, expected-RED report, gap registry, recapture (all fingerprint-bound) |
16
+ | 4 | T12 `EXPECTED_RED` list and `--expect-red` mode | `ci/mechanism-325-f4-invariants.test.js` (`bd61e6e7…bfcca19`), list in `f4-expected-red.json` | Frozen 20-entry RED plan; `STATUS: RED-PLAN-MATCHED` | `--expect-red`: `RED-PLAN-MISMATCH (expected 20, observed 14, infra 0)`, exit 1. S2-I4, S5-I4 and S6-I4 are CONTROL on both legs | `node ci/mechanism-325-f4-invariants.test.js` (plain mode reports the actual failures) | 14 RED cells, unchanged by T2 (see the live report) |
17
+ | 5 | Probe `P-ignore-missing-expected-must` | `ci/mechanism-325-f4-invariants.test.js` `MUTANTS` (pre-archive line 408, owner S6-I1) | "must become APPLIED" pending handoff in `--mutants` | `NOT APPLIED`. Never detected | Actual missing-MUST refusal tests stay live in the core suite (`ci/mechanism-325-3b-s2a-core.test.js`) | Open gap: no mutant witnesses the missing-MUST refusal. Record it, do not add a resolver redesign to manufacture one |
18
+
19
+ ## Relocation edits (entry 1)
20
+
21
+ Code changes against the original runner (sha256 `5e85af19…7849c`) are limited to:
22
+
23
+ - `ROOT` resolves three levels up (`path.resolve(__dirname, '..', '..', '..')`).
24
+ - The fixtures `require` path is `../../lib/doc-reconciliation-prep-fixtures`.
25
+ - The printed re-capture command names `ci/archive/325-prep/doc-reconciliation-red.js`.
26
+
27
+ The header comment also gained an archive note. Logic, the registry path and the fingerprint files are unchanged.
28
+
29
+ ## Commands
30
+
31
+ ```
32
+ node ci/archive/325-prep/doc-reconciliation-red.js --expect-red # exit 1, runtime fingerprint changed
33
+ node ci/archive/325-prep/doc-reconciliation-red.js --strict --case P03 # exit 1, recorded gaps
34
+ node ci/archive/325-prep/historical-qualification.test.js # 4 FAIL, 6 PASS
35
+ ```
36
+
37
+ ## Live suite after T2
38
+
39
+ - `ci/mechanism-325-3b-preparation.test.js` still runs the archived runner, but only for its infrastructure path (exit 2, child deadline, output bound). Those checks are live.
40
+ - The live suite no longer reads the registry for qualification.
41
+
42
+ ## Coordinator disposition (pending)
43
+
44
+ The WA pending-probe gate (`P-ignore-missing-expected-must`, WA report 28–30) is superseded for WB by the coordinator's disposition. The coordinator records that disposition. It is not recorded here.
45
+
46
+ ## Retired 2026-10-23 (owner decision 5)
47
+
48
+ Owner decision 5 (2026-10-23) retired the schema and preload-only predicates below. Each entry keeps its old predicate and its last result at `0c53a57` (source and packaged legs gave the same result unless noted). Each retired cell left a `// locks: historical — retired 2026-10-23 (owner decision 5)` comment where it was.
49
+
50
+ **Retired from the active pass/fail set**
51
+
52
+ | Cell | Old predicate | Last result at `0c53a57` | Where |
53
+ |---|---|---|---|
54
+ | S6-I3 | `pilot.acceptedEvents` is an array with one `provenance` per accepted ref (`on-valid-direct-quoted-review` / `review`) | RED: `{"acceptedRefs":2,"acceptedEvents":null}` | `ci/mechanism-325-f4-invariants.test.js` |
55
+ | S7-I3 | `Boolean(pilot.identity)` (`on-valid-direct-quoted-review` / `review`) | RED: `null` | `ci/mechanism-325-f4-invariants.test.js` |
56
+ | S8-I3 | some key of the withheld `obligations` matches `/origin\|withheld\|source/i` (`on-malformed` / `base`) | RED: keys are `schemaVersion, workflowId, sessionId, …` with no origin key | `ci/mechanism-325-f4-invariants.test.js` |
57
+ | F8 preload spy | the internal modules `./lib/harness-state`, `./lib/workflow-isolation`, `./lib/telemetry` are not eagerly loaded in any OFF route/switch case | FAIL: internal-module-load 24/24 OFF cases | `ci/mechanism-325-3b-s2b-capture.test.js` (`R2C1-OFF-CAPTURE-ONLY-FAST-PATH`) |
58
+
59
+ F8's OFF side-effect assertions (state, capture, output, exit = 0) and the legacy OFF controls stay in the active set. At `0c53a57` they observed 0 / 0 / 0 / 0.
60
+
61
+ **Replaced oracles (cell kept, key predicate swapped for a behaviour oracle)**
62
+
63
+ | Cell | Old predicate | Last result at `0c53a57` | Behaviour oracle now |
64
+ |---|---|---|---|
65
+ | S1-I2 | no `session-workspaces/` file exists after the OFF capture-only route (state created by route/plan/start included) | RED: `session-workspaces/346be55a….json` present | file-set delta across the hook (OFF adds none; ON control adds some) |
66
+ | S2-I3 | a `handoff` key matches `/lastVerify/` and `/(source\|kind\|origin)/` | RED: no such key | after drift, `lastDocVerifyAt` is withdrawn, the generic record survives, the workflow Stop names `req-verify` |
67
+ | S10-I3 | the same key predicate on the OFF legacy run | RED: no such key | after drift, `lastGenericVerifyAt` equals the aggregate, and the generic Stop stays silent |
68
+
69
+ Limits: the S1-I2 delta compares file sets, so an in-place rewrite of a binding that route/plan/start already created is not seen. Pre-existing OFF handoff writes are legacy behaviour and are not part of this cell.
70
+
71
+ Sources: `F4-T3-before-invariants.log` and `F4-T3-before-capture.log` (`0c53a57`), `F4-T3-after-invariants.log` and `F4-T3-after-capture.log` (this change).
@@ -2,17 +2,19 @@
2
2
  'use strict';
3
3
 
4
4
  // #325 Phase 3b preparation: strict and expected-RED replay over the real CLI, hook and Stop entry points.
5
+ // ARCHIVED by F4-T2 (historical evidence, revision 0308386); see ci/archive/325-prep/README.md. Relocation edits only:
6
+ // ROOT, the fixtures require path and the printed re-capture command point at this directory.
5
7
  //
6
- // node ci/prep/doc-reconciliation-red.js --strict exits 1 on any desired-contract gap or control failure
7
- // node ci/prep/doc-reconciliation-red.js --expect-red exits 0 only when every gap is qualified by the registry
8
- // node ci/prep/doc-reconciliation-red.js --recapture-candidate <file> --baseline-sha <40-hex>
8
+ // node ci/archive/325-prep/doc-reconciliation-red.js --strict exits 1 on any desired-contract gap or control failure
9
+ // node ci/archive/325-prep/doc-reconciliation-red.js --expect-red exits 0 only when every gap is qualified by the registry
10
+ // node ci/archive/325-prep/doc-reconciliation-red.js --recapture-candidate <file> --baseline-sha <40-hex>
9
11
  // writes a candidate registry for review; never the registry
10
12
  //
11
13
  // Optional: --case <id> (repeatable), --cases <file>, --registry <file>, --json (print the report), --log <file>.
12
14
  // Exit 2 means infrastructure or registry error (replay crash, malformed registry, unreadable file, a spawn error,
13
15
  // a signal, an exit outside the action's expectExit policy, or missing required output).
14
16
  //
15
- // This file is not discovered by filename. The preparation suite invokes --expect-red and parses the report.
17
+ // This file is not discovered by filename. The live preparation suite runs it only for infrastructure checks (exit 2).
16
18
  // A qualified gap is an ON assertion that fails today and matches a registry entry exactly (case, assertion,
17
19
  // distribution, actual and desired). Infrastructure faults are decided first and never qualify as gaps.
18
20
  // Qualification is bound to the runtime fingerprint of the shared runtime files, not to the commit: a registry
@@ -22,9 +24,9 @@ const crypto = require('crypto');
22
24
  const fs = require('fs');
23
25
  const path = require('path');
24
26
  const { spawnSync } = require('child_process');
25
- const fx = require('../lib/doc-reconciliation-prep-fixtures');
27
+ const fx = require('../../lib/doc-reconciliation-prep-fixtures');
26
28
 
27
- const ROOT = path.resolve(__dirname, '..', '..');
29
+ const ROOT = path.resolve(__dirname, '..', '..', '..');
28
30
  const PREP_DIR = path.join(ROOT, 'ci', 'fixtures', 'doc-obligations', 'reconciliation-prep');
29
31
  const DEFAULT_CASES = path.join(PREP_DIR, 'cases.json');
30
32
  const DEFAULT_REGISTRY = path.join(PREP_DIR, 'pending-red.json');
@@ -63,7 +65,7 @@ function currentCommit() {
63
65
  }
64
66
 
65
67
  function recaptureCommand(commit) {
66
- return `node ci/prep/doc-reconciliation-red.js --recapture-candidate ${CANDIDATE_FILE} --baseline-sha ${commit}`;
68
+ return `node ci/archive/325-prep/doc-reconciliation-red.js --recapture-candidate ${CANDIDATE_FILE} --baseline-sha ${commit}`;
67
69
  }
68
70
 
69
71
  function parseArgs(argv) {
@@ -0,0 +1,34 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "archivedBy": "F4-T2 r1 (owner clause T2, 2026-10-22)",
4
+ "revision": "0308386058ad942e2764475d48fd65a013910e1e",
5
+ "removedFrom": "ci/mechanism-325-f4-invariants.test.js",
6
+ "removedGate": "EXPECTED_RED constant and the --expect-red mode (STATUS: RED-PLAN-MATCHED / RED-PLAN-MISMATCH)",
7
+ "frozenFiles": {
8
+ "ci/mechanism-325-f4-invariants.test.js": "bd61e6e79f48b108b45cab04f1ee7df0a1d13594878ce3dbfe531337bbfcca19",
9
+ "ci/lib/doc-reconciliation-prep-fixtures.js": "d5ad9b9a66e63ac7c157deb157843c99463b95ce68c84ecff8f2b8ffcacf70db",
10
+ "ci/fixtures/doc-obligations/reconciliation-prep/pending-red.json": "08ad9eef0475b0121c13a38f5e61f5bd1cd4ad76aa4140c7ff662112c95c7ff0"
11
+ },
12
+ "frozenAt": "F4-T12 report, 20 frozen entries, RED-PLAN-MATCHED",
13
+ "resultAtRevision": {
14
+ "command": "node ci/mechanism-325-f4-invariants.test.js --expect-red",
15
+ "status": "RED-PLAN-MISMATCH",
16
+ "expected": 20,
17
+ "observed": 14,
18
+ "infra": 0,
19
+ "notObservedRed": [
20
+ "S2-I4@source", "S2-I4@packaged",
21
+ "S5-I4@source", "S5-I4@packaged",
22
+ "S6-I4@source", "S6-I4@packaged"
23
+ ],
24
+ "unexpectedRed": [],
25
+ "note": "S2-I4, S5-I4 and S6-I4 are CONTROL at 0308386 on both legs; the 0308386 resolver commit closed them. Plain mode reports the 14 RED cells as actual failures."
26
+ },
27
+ "frozenList": [
28
+ "S1-I2@packaged", "S1-I2@source", "S10-I3@packaged", "S10-I3@source",
29
+ "S2-I3@packaged", "S2-I3@source", "S2-I4@packaged", "S2-I4@source",
30
+ "S5-I4@packaged", "S5-I4@source", "S6-I3@packaged", "S6-I3@source",
31
+ "S6-I4@packaged", "S6-I4@source", "S7-I1@packaged", "S7-I1@source",
32
+ "S7-I3@packaged", "S7-I3@source", "S8-I3@packaged", "S8-I3@source"
33
+ ]
34
+ }
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // #325 Phase 3b historical qualification, archived by F4-T2 (owner clause T2, 2026-10-22) from
5
+ // ci/mechanism-325-3b-preparation.test.js at revision 0308386. Not discovered by ci/mechanism-test.js and not part of
6
+ // `npm run test:mechanism`. Run explicitly: node ci/archive/325-prep/historical-qualification.test.js
7
+ // These checks gate the frozen registry and the runtime fingerprint. They record the prep qualification; they are not a
8
+ // live behaviour gate. See README.md in this directory for the manifest and the residual failures.
9
+
10
+ const assert = require('assert');
11
+ const fs = require('fs');
12
+ const os = require('os');
13
+ const path = require('path');
14
+ const { spawnSync } = require('child_process');
15
+
16
+ const ROOT = path.resolve(__dirname, '..', '..', '..');
17
+ const PREP_DIR = path.join(ROOT, 'ci', 'fixtures', 'doc-obligations', 'reconciliation-prep');
18
+ const RUNNER = path.join(__dirname, 'doc-reconciliation-red.js');
19
+ const registry = JSON.parse(fs.readFileSync(path.join(PREP_DIR, 'pending-red.json'), 'utf8'));
20
+ const workDir = fs.mkdtempSync(path.join(os.tmpdir(), 'doc-recon-prep-historical-'));
21
+
22
+ let failed = 0;
23
+ function check(condition, message) {
24
+ if (condition) console.log(' PASS ' + message);
25
+ else { console.error(' FAIL ' + message); failed++; }
26
+ }
27
+ function checkCase(name, runCase) {
28
+ try { runCase(); check(true, name); } catch (error) { check(false, `${name}: ${error.message.split('\n')[0]}`); }
29
+ }
30
+ function runRunner(args) {
31
+ return spawnSync(process.execPath, [RUNNER, ...args], { cwd: ROOT, encoding: 'utf8' });
32
+ }
33
+ function writeRegistry(name, mutate) {
34
+ const copy = JSON.parse(JSON.stringify(registry));
35
+ mutate(copy);
36
+ const file = path.join(workDir, name);
37
+ fs.writeFileSync(file, `${JSON.stringify(copy, null, 2)}\n`, 'utf8');
38
+ return file;
39
+ }
40
+
41
+ console.log('=== #325 3b historical qualification (archived by F4-T2) ===');
42
+
43
+ // The full replay runs once, through the runner's JSON report. Every case must be a control or a qualified gap,
44
+ // with matching parity between the two legs and no pilot credit.
45
+ let report = null;
46
+ // locks: historical — T2
47
+ checkCase('expected-RED mode: every case is CONTROL_PASS or EXPECTED_RED and exits 0', () => {
48
+ const run = runRunner(['--expect-red', '--json']);
49
+ assert.strictEqual(run.status, 0, `exit ${run.status}: ${(run.stderr || run.stdout).slice(0, 400)}`);
50
+ report = JSON.parse(run.stdout);
51
+ });
52
+
53
+ // locks: historical — T2
54
+ checkCase('expected-RED report: legs agree, controls are green, no pilot credit, runtime fingerprint matches', () => {
55
+ assert.ok(report, 'the report was produced');
56
+ assert.strictEqual(report.pilotCredit, false, 'preparation reports never grant pilot credit');
57
+ assert.strictEqual(report.runtimeFingerprintMatches, true, 'registry describes the current shared runtime bytes');
58
+ for (const item of report.cases) {
59
+ assert.ok(['CONTROL_PASS', 'EXPECTED_RED'].includes(item.status), `${item.caseId} is ${item.status}`);
60
+ assert.strictEqual(item.parity, 'match', `${item.caseId} source and packaged legs agree`);
61
+ }
62
+ const controls = report.cases.filter(item => item.status === 'CONTROL_PASS').map(item => item.caseId).sort();
63
+ // P02e is a control since F4-T3: its review gap closed (the ON self-authored review PASS is refused) and its registry entries were retired.
64
+ assert.deepStrictEqual(controls, ['P01', 'P02a', 'P02b', 'P02c', 'P02d', 'P02e', 'P02f', 'P02g', 'P04', 'P06', 'P07-fable-multi-agent-workspace', 'P07-fable-parallel', 'P07-fable-staged', 'P07u']);
65
+ });
66
+
67
+ // locks: historical — T2
68
+ checkCase('gap registry: every entry is qualified and the expected-RED mode accounts for all of them', () => {
69
+ assert.ok(report, 'the report was produced');
70
+ const qualified = report.cases.flatMap(item => item.assertions.filter(entry => entry.status === 'gap-qualified'));
71
+ assert.strictEqual(qualified.length, registry.entries.length, 'each registry entry is observed exactly once');
72
+ assert.ok(!report.cases.some(item => item.assertions.some(entry => entry.status === 'gap-unqualified' || entry.status === 'unexpected-pass')));
73
+ });
74
+
75
+ // locks: historical — T2
76
+ checkCase('strict mode fails on the recorded gaps and preserves the RED log', () => {
77
+ // Retargeted to P03 in F4-T3: P02e's gaps are closed, and P03 still has open verify-retry gaps on both legs.
78
+ const log = path.join(workDir, 'p03-red.json');
79
+ const run = runRunner(['--strict', '--case', 'P03', '--log', log]);
80
+ assert.strictEqual(run.status, 1, `strict mode exits 1 on gaps: ${run.stdout}`);
81
+ const saved = JSON.parse(fs.readFileSync(log, 'utf8'));
82
+ assert.ok(saved.cases[0].assertions.some(entry => entry.status === 'gap-qualified'), 'the log keeps the qualified gaps');
83
+ });
84
+
85
+ // locks: historical — T2
86
+ checkCase('malformed registry is an infrastructure error, not a RED pass', () => {
87
+ const file = writeRegistry('missing-reason.json', copy => { delete copy.entries[0].reasonCode; });
88
+ const run = runRunner(['--expect-red', '--case', 'P01', '--registry', file]);
89
+ assert.strictEqual(run.status, 2, `registry errors exit 2: ${run.stdout} ${run.stderr}`);
90
+ });
91
+
92
+ // locks: historical — T2
93
+ checkCase('a registry that omits a gap fails expected-RED mode', () => {
94
+ const file = writeRegistry('omit-gap.json', copy => { copy.entries = copy.entries.filter(entry => entry.assertionId !== 'P03.on.verify-retry-exit' || entry.distribution !== 'source'); });
95
+ const run = runRunner(['--expect-red', '--case', 'P03', '--registry', file]);
96
+ assert.strictEqual(run.status, 1, `an unqualified gap fails: ${run.stdout}`);
97
+ });
98
+
99
+ // locks: historical — T2
100
+ checkCase('an unexpected PASS on a control fails expected-RED mode', () => {
101
+ const file = writeRegistry('unexpected-pass.json', copy => {
102
+ copy.entries.push({
103
+ caseId: 'P06', assertionId: 'P06.on.stop-satisfied', distribution: 'source', baselineSha: copy.baselineSha,
104
+ actual: 'satisfied', desired: 'running', blocker: '#341 E0', reasonCode: 'pretend-gap'
105
+ });
106
+ });
107
+ const run = runRunner(['--expect-red', '--case', 'P06', '--registry', file]);
108
+ assert.strictEqual(run.status, 1, `a passing assertion with a registry entry fails: ${run.stdout}`);
109
+ });
110
+
111
+ // locks: historical — T2
112
+ checkCase('a runtime fingerprint change invalidates qualification and prints the exact deliberate re-capture command', () => {
113
+ const file = writeRegistry('stale-fingerprint.json', copy => { copy.runtimeFingerprint = '0'.repeat(64); });
114
+ const run = runRunner(['--expect-red', '--case', 'P01', '--registry', file]);
115
+ assert.strictEqual(run.status, 1, `a fingerprint mismatch fails: ${run.stdout}`);
116
+ const output = run.stdout + run.stderr;
117
+ assert.ok(output.includes('--recapture-candidate ci/fixtures/doc-obligations/reconciliation-prep/pending-red.candidate.json'), 'the re-capture command names the candidate');
118
+ assert.ok(/--baseline-sha [0-9a-f]{40}/.test(output), 'the re-capture command names the commit');
119
+ assert.ok(output.includes('nothing is auto-accepted'), 'the message says nothing is auto-accepted');
120
+ });
121
+
122
+ // locks: historical — T2
123
+ checkCase('recapture writes a reviewable candidate and never the committed registry', () => {
124
+ const candidate = path.join(workDir, 'candidate.json');
125
+ const before = fs.readFileSync(path.join(PREP_DIR, 'pending-red.json'), 'utf8');
126
+ const run = spawnSync(process.execPath, [RUNNER, '--recapture-candidate', candidate, '--baseline-sha', registry.baselineSha, '--case', 'P06'], { cwd: ROOT, encoding: 'utf8' });
127
+ assert.strictEqual(run.status, 0, run.stderr);
128
+ assert.strictEqual(fs.readFileSync(path.join(PREP_DIR, 'pending-red.json'), 'utf8'), before, 'the committed registry is untouched');
129
+ const saved = JSON.parse(fs.readFileSync(candidate, 'utf8'));
130
+ assert.strictEqual(saved.entries.length, 0, 'a control case has no gaps to record');
131
+ assert.strictEqual(saved.runtimeFingerprint, registry.runtimeFingerprint, 'the candidate describes the current runtime');
132
+ });
133
+
134
+ // locks: historical — T2
135
+ checkCase('a candidate with unreviewed reasons is rejected by the registry validator', () => {
136
+ const file = writeRegistry('unreviewed.json', copy => { copy.entries[0].reasonCode = 'needs-reason'; });
137
+ const run = runRunner(['--expect-red', '--case', 'P01', '--registry', file]);
138
+ assert.strictEqual(run.status, 2, 'an unreviewed reason is a registry error');
139
+ });
140
+
141
+ fs.rmSync(workDir, { recursive: true, force: true });
142
+ console.log(failed ? `\n${failed} historical check(s) failed` : '\nAll historical checks passed');
143
+ process.exitCode = failed ? 1 : 0;
@@ -1497,7 +1497,11 @@
1497
1497
  "stepId": "req-review",
1498
1498
  "disposition": "pass",
1499
1499
  "evidence": "self-review by the author of the change"
1500
- }
1500
+ },
1501
+ "expectExit": {
1502
+ "on": 2
1503
+ },
1504
+ "note": "F4-T3 decision 1 (#325 S2 slice plan, 2026-10-21): the ON self-authored review PASS is refused with exit 2 by the F4 I2a review-mode refusal; the step stays pending. OFF is unchanged."
1501
1505
  },
1502
1506
  {
1503
1507
  "id": "stop",
@@ -1787,7 +1791,11 @@
1787
1791
  "stepId": "req-review",
1788
1792
  "disposition": "pass",
1789
1793
  "evidence": "review scope recorded"
1790
- }
1794
+ },
1795
+ "expectExit": {
1796
+ "on": 2
1797
+ },
1798
+ "note": "F4-T3 decision 1 (#325 S2 slice plan, 2026-10-21): the ON self-authored review PASS is refused with exit 2 by the F4 I2a review-mode refusal; the step stays pending. OFF is unchanged."
1791
1799
  },
1792
1800
  {
1793
1801
  "id": "verify-bind",
@@ -1797,7 +1805,11 @@
1797
1805
  "bindingId": "verification-loop",
1798
1806
  "disposition": "loaded",
1799
1807
  "evidence": "verification loop skill located"
1800
- }
1808
+ },
1809
+ "expectExit": {
1810
+ "on": 2
1811
+ },
1812
+ "note": "F4-T3: refused with exit 2 on ON because the review step stays active after the refused review PASS (bindings may only be resolved for the active step). OFF unchanged."
1801
1813
  },
1802
1814
  {
1803
1815
  "id": "verify-pass",
@@ -1896,7 +1908,8 @@
1896
1908
  "exitCode"
1897
1909
  ],
1898
1910
  "operator": "equals",
1899
- "expected": 0
1911
+ "expected": 2,
1912
+ "note": "F4-T3 decision 1 (#325 S2 slice plan, 2026-10-21): the ON self-authored review PASS is refused with exit 2 by the F4 I2a review-mode refusal; the step stays pending. OFF is unchanged."
1900
1913
  },
1901
1914
  {
1902
1915
  "id": "verify-pass-exit",
@@ -1,62 +1,14 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "phase": "325-3b-H2",
4
- "baselineSha": "5f9726c7d01364cb75a61eff54c8f5b89027e9d6",
5
- "runtimeFingerprint": "50700bc05748ee1901279681c6bfc33f9676cbb3a1151edf775e8aa55fbe911f",
4
+ "baselineSha": "c271249a8a99d7e0320288af8cb086bb03f8fa1a",
5
+ "runtimeFingerprint": "a7679becc1cbd190377816c8c6cf14e2a1e94d42dd2a83bbd1ade5bd7b38153b",
6
6
  "entries": [
7
- {
8
- "caseId": "P02e",
9
- "assertionId": "P02e.on.review-pass-exit",
10
- "distribution": "source",
11
- "baselineSha": "5f9726c7d01364cb75a61eff54c8f5b89027e9d6",
12
- "actual": 0,
13
- "desired": 2,
14
- "blocker": "#341 E0",
15
- "reasonCode": "self-review-pass-accepted"
16
- },
17
- {
18
- "caseId": "P02e",
19
- "assertionId": "P02e.on.stop-reports",
20
- "distribution": "source",
21
- "baselineSha": "5f9726c7d01364cb75a61eff54c8f5b89027e9d6",
22
- "actual": [
23
- "step:req-verify:pending",
24
- "verify:pending",
25
- "binding:req-verify:verification-loop:pending"
26
- ],
27
- "desired": "step:req-review:pending",
28
- "blocker": "#341 E0",
29
- "reasonCode": "unsatisfied-review-not-reported"
30
- },
31
- {
32
- "caseId": "P02e",
33
- "assertionId": "P02e.on.review-pass-exit",
34
- "distribution": "packaged",
35
- "baselineSha": "5f9726c7d01364cb75a61eff54c8f5b89027e9d6",
36
- "actual": 0,
37
- "desired": 2,
38
- "blocker": "#341 E0",
39
- "reasonCode": "self-review-pass-accepted"
40
- },
41
- {
42
- "caseId": "P02e",
43
- "assertionId": "P02e.on.stop-reports",
44
- "distribution": "packaged",
45
- "baselineSha": "5f9726c7d01364cb75a61eff54c8f5b89027e9d6",
46
- "actual": [
47
- "step:req-verify:pending",
48
- "verify:pending",
49
- "binding:req-verify:verification-loop:pending"
50
- ],
51
- "desired": "step:req-review:pending",
52
- "blocker": "#341 E0",
53
- "reasonCode": "unsatisfied-review-not-reported"
54
- },
55
7
  {
56
8
  "caseId": "P03",
57
9
  "assertionId": "P03.on.verify-retry-exit",
58
10
  "distribution": "source",
59
- "baselineSha": "5f9726c7d01364cb75a61eff54c8f5b89027e9d6",
11
+ "baselineSha": "c271249a8a99d7e0320288af8cb086bb03f8fa1a",
60
12
  "actual": 2,
61
13
  "desired": 0,
62
14
  "blocker": "#341 E0",
@@ -66,7 +18,7 @@
66
18
  "caseId": "P03",
67
19
  "assertionId": "P03.on.verify-retry-exit",
68
20
  "distribution": "packaged",
69
- "baselineSha": "5f9726c7d01364cb75a61eff54c8f5b89027e9d6",
21
+ "baselineSha": "c271249a8a99d7e0320288af8cb086bb03f8fa1a",
70
22
  "actual": 2,
71
23
  "desired": 0,
72
24
  "blocker": "#341 E0",
@@ -76,7 +28,7 @@
76
28
  "caseId": "P05",
77
29
  "assertionId": "P05.on.stop-on-2-withheld",
78
30
  "distribution": "source",
79
- "baselineSha": "5f9726c7d01364cb75a61eff54c8f5b89027e9d6",
31
+ "baselineSha": "c271249a8a99d7e0320288af8cb086bb03f8fa1a",
80
32
  "actual": 2,
81
33
  "desired": 0,
82
34
  "blocker": "#341 E0",
@@ -86,7 +38,7 @@
86
38
  "caseId": "P05",
87
39
  "assertionId": "P05.on.stop-on-3-withheld",
88
40
  "distribution": "source",
89
- "baselineSha": "5f9726c7d01364cb75a61eff54c8f5b89027e9d6",
41
+ "baselineSha": "c271249a8a99d7e0320288af8cb086bb03f8fa1a",
90
42
  "actual": "running",
91
43
  "desired": "satisfied",
92
44
  "blocker": "#341 E0",
@@ -96,7 +48,7 @@
96
48
  "caseId": "P05",
97
49
  "assertionId": "P05.on.stop-on-2-withheld",
98
50
  "distribution": "packaged",
99
- "baselineSha": "5f9726c7d01364cb75a61eff54c8f5b89027e9d6",
51
+ "baselineSha": "c271249a8a99d7e0320288af8cb086bb03f8fa1a",
100
52
  "actual": 2,
101
53
  "desired": 0,
102
54
  "blocker": "#341 E0",
@@ -106,7 +58,7 @@
106
58
  "caseId": "P05",
107
59
  "assertionId": "P05.on.stop-on-3-withheld",
108
60
  "distribution": "packaged",
109
- "baselineSha": "5f9726c7d01364cb75a61eff54c8f5b89027e9d6",
61
+ "baselineSha": "c271249a8a99d7e0320288af8cb086bb03f8fa1a",
110
62
  "actual": "running",
111
63
  "desired": "satisfied",
112
64
  "blocker": "#341 E0",
@@ -121,7 +121,7 @@
121
121
  "baselineShaSemantics": "40-hex capture commit copied to every entry for provenance and internal consistency; it is not compared with the current checkout and does not qualify a gap.",
122
122
  "runtimeFingerprintContract": {
123
123
  "format": "64 lowercase hexadecimal SHA-256",
124
- "scope": "Exact bytes of the ordered FINGERPRINT_FILES list in ci/prep/doc-reconciliation-red.js.",
124
+ "scope": "Exact bytes of the ordered FINGERPRINT_FILES list in ci/archive/325-prep/doc-reconciliation-red.js (historical T2 evidence, not a GREEN gate).",
125
125
  "currentFiles": [
126
126
  "hooks/scripts/lib/workflow-obligations.js",
127
127
  "hooks/scripts/workflow-disposition.js",
@@ -134,7 +134,7 @@
134
134
  "entryExactKeys": true,
135
135
  "assertionIdFormat": "<caseId>.on.<expectation-id> and must name an ON assertion in the corresponding case",
136
136
  "recapture": {
137
- "command": "node ci/prep/doc-reconciliation-red.js --recapture-candidate ci/fixtures/doc-obligations/reconciliation-prep/pending-red.candidate.json --baseline-sha <40-hex-commit>",
137
+ "command": "node ci/archive/325-prep/doc-reconciliation-red.js --recapture-candidate ci/fixtures/doc-obligations/reconciliation-prep/pending-red.candidate.json --baseline-sha <40-hex-commit>",
138
138
  "review": "The runner writes a separate candidate. Review entries and reason codes before replacing pending-red.json, then delete the candidate; there is no auto-accept."
139
139
  },
140
140
  "distributionValues": ["source", "packaged"],
@@ -2067,6 +2067,9 @@
2067
2067
  "stepId": "req-review",
2068
2068
  "disposition": "pass",
2069
2069
  "evidence": "self-review by the author of the change"
2070
+ },
2071
+ "expectExit": {
2072
+ "on": 2
2070
2073
  }
2071
2074
  },
2072
2075
  {
@@ -2127,8 +2130,9 @@
2127
2130
  "exitCode"
2128
2131
  ],
2129
2132
  "operator": "equals",
2130
- "expected": 0,
2131
- "s1Kind": "control"
2133
+ "expected": 2,
2134
+ "s1Kind": "control",
2135
+ "note": "F4-T3 decision 1 (#325 S2 slice plan, comment of 2026-10-21: S2 retires P02e x2, P03 verify-retry-exit, P05 x2). The ON self-authored review PASS is refused with exit 2 by the F4 I2a review-mode refusal; the step stays pending. The OFF expectation is unchanged."
2132
2136
  },
2133
2137
  {
2134
2138
  "id": "stop-exit-0",
@@ -2225,6 +2229,9 @@
2225
2229
  "stepId": "req-review",
2226
2230
  "disposition": "pass",
2227
2231
  "evidence": "review scope recorded"
2232
+ },
2233
+ "expectExit": {
2234
+ "on": 2
2228
2235
  }
2229
2236
  },
2230
2237
  {
@@ -2235,7 +2242,11 @@
2235
2242
  "bindingId": "verification-loop",
2236
2243
  "disposition": "loaded",
2237
2244
  "evidence": "verification loop skill located"
2238
- }
2245
+ },
2246
+ "expectExit": {
2247
+ "on": 2
2248
+ },
2249
+ "note": "F4-T3: after the ON review PASS is refused (review-pass exit 2) the review step stays active, so the verification binding is refused with exit 2 (bindings may only be resolved for the active step). Declared as an ON refusal; the OFF leg is unchanged."
2239
2250
  },
2240
2251
  {
2241
2252
  "id": "verify-pass",
@@ -2354,8 +2365,9 @@
2354
2365
  "exitCode"
2355
2366
  ],
2356
2367
  "operator": "equals",
2357
- "expected": 0,
2358
- "s1Kind": "control"
2368
+ "expected": 2,
2369
+ "s1Kind": "control",
2370
+ "note": "F4-T3 decision 1 (#325 S2 slice plan, comment of 2026-10-21: S2 retires P02e x2, P03 verify-retry-exit, P05 x2). The ON self-authored review PASS is refused with exit 2 by the F4 I2a review-mode refusal; the step stays pending. The OFF expectation is unchanged."
2359
2371
  },
2360
2372
  {
2361
2373
  "id": "verify-pass-exit",