@pareto4/pp-core 1.13.0 → 1.17.1

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 (201) hide show
  1. package/.claude-plugin/plugin.json +3 -3
  2. package/README.md +26 -9
  3. package/agents/pp-code-reviewer.md +2 -1
  4. package/agents/pp-debug-session-manager.md +9 -6
  5. package/agents/pp-executor.md +10 -13
  6. package/agents/pp-plan-checker.md +4 -4
  7. package/agents/pp-planner.md +23 -9
  8. package/agents/pp-vault-reviewer.md +244 -21
  9. package/agents/pp-verifier.md +7 -3
  10. package/bin/install.js +10 -3
  11. package/commands/pp-execute-phase.md +1 -3
  12. package/commands/pp-ns-review.md +1 -1
  13. package/commands/pp-plan-phase.md +1 -2
  14. package/commands/pp-vault-review.md +5 -3
  15. package/commands/pp-verify-work.md +1 -2
  16. package/gemini-extension.json +1 -1
  17. package/hooks/dist/pp-check-update.js +0 -0
  18. package/hooks/dist/pp-cursor-session-start.js +1 -1
  19. package/hooks/dist/pp-statusline.js +1 -1
  20. package/hooks/dist/pp-update-banner.js +0 -0
  21. package/hooks/pp-check-update.js +0 -0
  22. package/hooks/pp-cursor-session-start.js +1 -1
  23. package/hooks/pp-statusline.js +1 -1
  24. package/hooks/pp-update-banner.js +0 -0
  25. package/package.json +14 -19
  26. package/pareto-skills/_shared/create-project-board.sh +0 -0
  27. package/pareto-skills/pp-project-setup/SKILL.md +1 -1
  28. package/pp-core/bin/check-latest-version.cjs +0 -0
  29. package/pp-core/bin/detect-build-commands.sh +2 -2
  30. package/pp-core/bin/lib/capability-activation.cjs +83 -9
  31. package/pp-core/bin/lib/capability-registry.cjs +396 -82
  32. package/pp-core/bin/lib/capability-state.cjs +160 -4
  33. package/pp-core/bin/lib/capability-writer.cjs +19 -5
  34. package/pp-core/bin/lib/check-command-router.cjs +3334 -946
  35. package/pp-core/bin/lib/clusters.cjs +3 -0
  36. package/pp-core/bin/lib/command-aliases.cjs +0 -35
  37. package/pp-core/bin/lib/commands.cjs +9 -4
  38. package/pp-core/bin/lib/compose-bindings.cjs +251 -88
  39. package/pp-core/bin/lib/config-loader.cjs +7 -103
  40. package/pp-core/bin/lib/config-schema.cjs +1 -1
  41. package/pp-core/bin/lib/config.cjs +154 -62
  42. package/pp-core/bin/lib/decisions.cjs +4 -4
  43. package/pp-core/bin/lib/discovery-vault-check.cjs +246 -0
  44. package/pp-core/bin/lib/frontmatter.cjs +1 -38
  45. package/pp-core/bin/lib/git-base-branch.cjs +13 -11
  46. package/pp-core/bin/lib/git-provider.cjs +4 -4
  47. package/pp-core/bin/lib/init-command-router.cjs +5 -8
  48. package/pp-core/bin/lib/init-contract.cjs +648 -738
  49. package/pp-core/bin/lib/init-interactive.cjs +76 -4
  50. package/pp-core/bin/lib/init.cjs +24 -51
  51. package/pp-core/bin/lib/install-profiles.cjs +1 -1
  52. package/pp-core/bin/lib/io.cjs +8 -0
  53. package/pp-core/bin/lib/loop-resolver.cjs +189 -129
  54. package/pp-core/bin/lib/org-profile.cjs +469 -152
  55. package/pp-core/bin/lib/package-identity.cjs +3 -3
  56. package/pp-core/bin/lib/phase-command-router.cjs +1 -29
  57. package/pp-core/bin/lib/phase-scope.cjs +1 -1
  58. package/pp-core/bin/lib/phase.cjs +11 -253
  59. package/pp-core/bin/lib/platform-bindings.cjs +40 -19
  60. package/pp-core/bin/lib/practices-review-check.cjs +15 -9
  61. package/pp-core/bin/lib/profile-output.cjs +1 -1
  62. package/pp-core/bin/lib/shell-command-projection.cjs +104 -1
  63. package/pp-core/bin/lib/state.cjs +11 -85
  64. package/pp-core/bin/lib/template.cjs +1 -1
  65. package/pp-core/bin/lib/vault-audit.cjs +650 -126
  66. package/pp-core/bin/lib/vault-command-router.cjs +2604 -367
  67. package/pp-core/bin/lib/vault-coordinates.cjs +13 -4
  68. package/pp-core/bin/lib/vault-index.cjs +699 -0
  69. package/pp-core/bin/lib/vault-mode.cjs +235 -0
  70. package/pp-core/bin/lib/vault-practices.cjs +139 -72
  71. package/pp-core/bin/lib/vault-review.cjs +194 -451
  72. package/pp-core/bin/lib/vault-seal.cjs +1936 -0
  73. package/pp-core/bin/lib/vault-standards.cjs +1505 -0
  74. package/pp-core/bin/lib/vault-trace.cjs +607 -254
  75. package/pp-core/bin/lib/vault-update.cjs +9 -33
  76. package/pp-core/bin/lib/vault-write-back.cjs +70 -42
  77. package/pp-core/bin/lib/vault.cjs +2734 -920
  78. package/pp-core/bin/lib/verify.cjs +0 -11
  79. package/pp-core/bin/portable-timeout.sh +1 -1
  80. package/pp-core/bin/pp-tools.cjs +184 -54
  81. package/pp-core/bin/shared/config-defaults.manifest.json +0 -7
  82. package/pp-core/bin/shared/config-schema.manifest.json +0 -3
  83. package/pp-core/bin/shared/model-catalog.json +2 -2
  84. package/pp-core/bin/verify-reapply-patches.cjs +0 -0
  85. package/pp-core/org-profile.json +26 -0
  86. package/pp-core/references/autonomous-best-effort.md +8 -7
  87. package/pp-core/references/loop-hook-dispatch.md +0 -25
  88. package/pp-core/references/planner-gap-closure.md +0 -1
  89. package/pp-core/references/planner-tdd.md +0 -1
  90. package/pp-core/references/planner-vault-practices.md +17 -34
  91. package/pp-core/references/planner-vault-standards.md +320 -0
  92. package/pp-core/references/planning-config.md +0 -8
  93. package/pp-core/references/practice-arithmetic-contract.md +121 -0
  94. package/pp-core/references/tdd.md +0 -1
  95. package/pp-core/references/verifier-practice-verdicts.md +263 -0
  96. package/pp-core/templates/README.md +1 -0
  97. package/pp-core/templates/VAULT-REVIEW.md +202 -0
  98. package/pp-core/templates/phase-prompt.md +2 -6
  99. package/pp-core/templates/practices/README.md +20 -23
  100. package/pp-core/workflows/_runtime-launcher.snippet.sh +0 -0
  101. package/pp-core/workflows/add-tests.md +55 -4
  102. package/pp-core/workflows/audit-fix.md +63 -2
  103. package/pp-core/workflows/audit-milestone.md +1 -1
  104. package/pp-core/workflows/autonomous.md +85 -11
  105. package/pp-core/workflows/check-todos.md +13 -2
  106. package/pp-core/workflows/code-review-fix.md +59 -3
  107. package/pp-core/workflows/code-review.md +10 -1
  108. package/pp-core/workflows/complete-milestone.md +1 -1
  109. package/pp-core/workflows/debug.md +54 -4
  110. package/pp-core/workflows/discovery-sprint/prefill.md +19 -7
  111. package/pp-core/workflows/discovery-sprint/prototype.md +46 -49
  112. package/pp-core/workflows/discovery-sprint/session-13-gates.md +2 -0
  113. package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +1 -3
  114. package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +1 -3
  115. package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +1 -3
  116. package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +20 -24
  117. package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +1 -3
  118. package/pp-core/workflows/discovery-sprint/sessions/06-security.md +1 -3
  119. package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +1 -3
  120. package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +1 -3
  121. package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +1 -3
  122. package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +1 -6
  123. package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +1 -3
  124. package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +1 -3
  125. package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +1 -3
  126. package/pp-core/workflows/discovery-sprint/templates/context-template.md +1 -1
  127. package/pp-core/workflows/discovery-sprint/vault-context.md +213 -0
  128. package/pp-core/workflows/discovery-sprint.md +7 -3
  129. package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +4 -4
  130. package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +43 -1
  131. package/pp-core/workflows/discovery-to-build.md +10 -2
  132. package/pp-core/workflows/discuss-phase.md +3 -10
  133. package/pp-core/workflows/execute-phase/steps/check-query-gate.md +329 -0
  134. package/pp-core/workflows/execute-phase/steps/client-context-gate.md +3 -2
  135. package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +1 -1
  136. package/pp-core/workflows/execute-phase/steps/execute-pre-dispatch.md +98 -0
  137. package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +94 -0
  138. package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +6 -8
  139. package/pp-core/workflows/execute-phase/steps/vault-fetch.md +115 -14
  140. package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +322 -20
  141. package/pp-core/workflows/execute-phase/steps/verifier-practice-payload.md +165 -0
  142. package/pp-core/workflows/execute-phase/steps/verify-post-gate.md +163 -0
  143. package/pp-core/workflows/execute-phase.md +155 -128
  144. package/pp-core/workflows/execute-plan.md +56 -6
  145. package/pp-core/workflows/fast.md +53 -4
  146. package/pp-core/workflows/help/modes/full.md +3 -3
  147. package/pp-core/workflows/init/steps/client-context-capture.md +101 -0
  148. package/pp-core/workflows/init/steps/gitlab-provisioning.md +130 -0
  149. package/pp-core/workflows/init/steps/project-md-deferral-narration.md +66 -0
  150. package/pp-core/workflows/init/steps/reachability-outcome.md +70 -0
  151. package/pp-core/workflows/init.md +232 -272
  152. package/pp-core/workflows/new-project.md +31 -23
  153. package/pp-core/workflows/plan-phase.md +98 -45
  154. package/pp-core/workflows/quick.md +117 -3
  155. package/pp-core/workflows/secure-phase.md +1 -1
  156. package/pp-core/workflows/ship.md +57 -28
  157. package/pp-core/workflows/undo.md +1 -1
  158. package/pp-core/workflows/update.md +3 -3
  159. package/pp-core/workflows/validate-phase.md +58 -3
  160. package/pp-core/workflows/vault-review.md +282 -22
  161. package/pp-core/workflows/verify-work.md +12 -14
  162. package/scripts/changeset/cli.cjs +6 -5
  163. package/scripts/changeset/lint.cjs +0 -0
  164. package/scripts/changeset/new.cjs +33 -50
  165. package/scripts/dev-env.cjs +25 -85
  166. package/scripts/gen-capability-registry.cjs +199 -11
  167. package/scripts/gen-practice-arithmetic-contract.cjs +412 -0
  168. package/scripts/lib/allowlist-ratchet.cjs +1 -103
  169. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -2
  170. package/scripts/lint-docs-required.cjs +0 -0
  171. package/scripts/lint-pr-check-project-dir.cjs +1 -3
  172. package/scripts/lint-test-file-count.allowlist.json +12 -10
  173. package/scripts/practice-arithmetic-contract.allow.json +35 -0
  174. package/scripts/prompt-injection-scan.sh +22 -17
  175. package/scripts/record-vault-mode-golden.cjs +89 -0
  176. package/scripts/release-channel.cjs +0 -0
  177. package/scripts/sync-manifest-versions.cjs +5 -4
  178. package/scripts/sync-rulesets.sh +1 -1
  179. package/scripts/update-size-baseline.cjs +30 -274
  180. package/scripts/vault-calibrate.cjs +996 -0
  181. package/scripts/verify-opengsd-sweep.cjs +3 -5
  182. package/scripts/workflow-size.cjs +9 -61
  183. package/pp-core/bin/lib/dispatch-plan.cjs +0 -483
  184. package/pp-core/bin/lib/install-run.cjs +0 -536
  185. package/pp-core/bin/lib/loop-gates.cjs +0 -213
  186. package/pp-core/bin/lib/phase-manifest.cjs +0 -527
  187. package/pp-core/bin/lib/phase-tier.cjs +0 -480
  188. package/pp-core/bin/lib/plan-graph.cjs +0 -368
  189. package/pp-core/references/executor-vault-trace.md +0 -47
  190. package/pp-core/references/planner-plan-graph.md +0 -66
  191. package/pp-core/workflows/discovery-sprint/vault-fetch.md +0 -37
  192. package/pp-core/workflows/execute-phase/steps/checkpoint-handling.md +0 -45
  193. package/pp-core/workflows/execute-phase/steps/install-run.md +0 -29
  194. package/pp-core/workflows/execute-phase/steps/phase-close-rebaseline.md +0 -23
  195. package/pp-core/workflows/execute-phase/steps/serial-group-dispatch.md +0 -73
  196. package/pp-core/workflows/execute-phase/steps/vault-door-gate.md +0 -19
  197. package/pp-core/workflows/plan-phase/steps/phase-tier.md +0 -43
  198. package/scripts/lib/size-budget.cjs +0 -311
  199. package/scripts/lint-neutrality-names.cjs +0 -694
  200. package/scripts/lint-org-coordinates.allowlist.json +0 -35
  201. package/scripts/lint-org-coordinates.cjs +0 -426
@@ -3,115 +3,162 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  /**
6
- * src/vault-review.cts — the DETERMINISTIC half of the agent practice review
7
- * (`/pp-vault-review`, vault assurance R5).
6
+ * src/vault-review.cts — the DETERMINISTIC half of Phase 6's agent tier
7
+ * (Vault Assurance #71924, phase 06 plan 03; VERIFY-01, D-06-09 … D-06-15).
8
8
  *
9
9
  * ## What this module is
10
10
  *
11
- * The agent review judges vault practices no `check:` block can settle. A
12
- * judgement is not reproducible, so everything the agent must NOT invent lives
13
- * here instead: the input set, both derived-N-A populations, the counts, the
14
- * rate line, the git range the review was computed over, the artifact the agent
15
- * fills in, and the lines that artifact must state. Every one of them is an
16
- * exported, unit-tested function, so the agent's discretionary surface shrinks
17
- * to the genuinely ambiguous remainder and nothing else.
11
+ * The agent tier judges vault practices no regex can settle. A judgement is not
12
+ * reproducible, so everything the agent must NOT invent lives here instead: the
13
+ * input set, both derived-N-A populations, the counts, the rate line, and the
14
+ * git range the review was computed over. Every one of them is an exported,
15
+ * unit-tested pure function, so the agent's discretionary surface shrinks to
16
+ * the genuinely ambiguous remainder and nothing else.
18
17
  *
19
18
  * Purely a LEAF. It imports leaves (`./vault-audit.cjs`, `./vault-practices.cjs`,
20
- * `./vault-trace.cjs`, `./practices-review-check.cjs`, `./git-base-branch.cjs`,
21
- * `./shell-command-projection.cjs`) and no adapter. The two functions that touch
22
- * the world — the oracle run and the range resolver — take INJECTED seams
23
- * (`_audit`, `exec`), so a test drives them without a repo. The I/O composition
24
- * (reading the trace, the vault cache and the verdict owner) is the CLI arm's,
25
- * in `src/check-command-router.cts`.
26
- *
27
- * The sibling modules are imported at the top rather than required at call
28
- * time: under `recommendedTypeChecked` a bare `require()` returns `any`, while
19
+ * `./git-base-branch.cjs`, `./shell-command-projection.cjs`) and no adapter, and
20
+ * every derivation function is pure text/set/list in, structure out. The two
21
+ * functions that touch the world — the oracle run and the range resolver — take
22
+ * INJECTED seams (`_audit`, `exec`) in the `_check`/`_core` idiom
23
+ * `cmdCheckPracticesReview` established, so a test drives them without a repo.
24
+ *
25
+ * A note on that idiom: the shipped one defers `require` to call time. Here the
26
+ * sibling modules are imported at the top instead and the seam is the injection
27
+ * parameter alone. Reason: this file is linted with
28
+ * `tseslint.configs.recommendedTypeChecked`, under which a bare `require()`
29
+ * returns `any` and every use of the result is a `no-unsafe-*` error, whereas
29
30
  * `import x = require('./y.cjs')` resolves to the sibling's `.cts` source and is
30
- * fully typed. None of them performs I/O at load.
31
+ * fully typed. Neither sibling performs I/O at load, so nothing is paid for the
32
+ * eager import, and the property the idiom exists for — a test can substitute
33
+ * the module — is preserved exactly.
31
34
  *
32
- * ## The verdict vocabulary is LOCKED
35
+ * ## The verdict vocabulary is LOCKED (D-06-10)
33
36
  *
34
- * `REVIEW_VERDICT` holds exactly PASS / FAIL / N-A / INCONCLUSIVE. It is a module
35
- * constant and is never re-derived from configuration or from the vault — the
36
- * rule `src/vault-trace.cts` and `src/vault-audit.cts` state for the same
37
- * reason: a verdict vocabulary a client repository could move is not one.
37
+ * `REVIEW_VERDICT` holds exactly PASS / FAIL / N-A / INCONCLUSIVE, with `N-A`
38
+ * spelled with the hyphen D-06-10's own text uses. It is a module constant and
39
+ * is never re-derived from configuration or from the vault — the same rule
40
+ * `src/vault-trace.cts` and `src/vault-audit.cts` state, for the same reason: a
41
+ * verdict vocabulary a client repository could move is not a verdict vocabulary.
38
42
  *
39
- * It shares no token with the mechanical audit's `AUDIT_VERDICT`. `NO_CHECK` and
43
+ * It shares no token with the mechanical tier's `AUDIT_VERDICT`. `NO_CHECK` and
40
44
  * `NOT_MATCHED` are facts about a machine-checkable rule; `N-A` and
41
- * `INCONCLUSIVE` are facts about a judgement.
42
- *
43
- * A DELIBERATE DIVERGENCE from code review's practices findings ("likely
44
- * violates, never certainty", capped at Warning): this review asserts a FLAT
45
- * `FAIL`. It can, because it gates nothing — a FAIL here moves no score and
46
- * blocks no ship. Uncertainty has its own honest home, `INCONCLUSIVE`.
47
- *
48
- * ## Arm A widens the judged boundary, and says so in every row
49
- *
50
- * `deriveNaAUniverse` (Arm A) reports on practices that were NEVER traced — the
51
- * vault practice universe recorded at the pinned ref minus the ledger. That is
52
- * wider than "traced practices with no usable `check:` block", and every Arm A
53
- * row's reason names the universe as its source, so a reader meets the widening
54
- * at the row that caused it. It is needed because `buildTraceStatement` fills
55
- * the ledger from `matchPractices(...).matched ++ .global` only: every ledger
56
- * entry already matched, so "never matched" can only come from the universe.
57
- * Arm B re-derives applicability at review time from the phase's real diff.
58
- *
59
- * ## `check:`-presence is read LIVE, with an advisory — no trace-schema change
45
+ * `INCONCLUSIVE` are facts about a judgement, and a shared token would invite
46
+ * one to be read as the other.
47
+ *
48
+ * RECORDED DIVERGENCE from `agents/pp-code-reviewer.md`, which deliberately says
49
+ * "likely violates, never certainty": this tier asserts a FLAT `FAIL`. It can,
50
+ * because it gates nothing (D-06-03, VERIFY-03) — a FAIL here moves no score and
51
+ * blocks no ship, so hedging the word buys no safety and costs the reader the
52
+ * one signal the artifact exists to carry. Uncertainty has its own honest home:
53
+ * `INCONCLUSIVE`, which D-06-10 split out precisely so "I could not determine
54
+ * this" never hides inside "not applicable here". The agent-side rationale is
55
+ * plan 06-06's; the vocabulary decision is recorded here, where the constant is.
56
+ *
57
+ * ## STATED DEVIATION — Arm A widens the judged boundary
58
+ *
59
+ * `06-CONTEXT.md`'s `<domain>` fixes this phase's input set to *traced practices
60
+ * that carry no usable `check:` block*. `deriveNaAUniverse` (Arm A, plan 06-03
61
+ * task 2) reports on practices that were NEVER traced — the whole vault practice
62
+ * universe recorded at the pinned ref. That is WIDER than the stated boundary.
63
+ * It is an operator-approved deviation, approved 2026-09-03 when F-2 was
64
+ * resolved, and it is stated here rather than buried in a count because a
65
+ * denominator that silently grew is a denominator nobody can audit.
66
+ *
67
+ * It also surfaces in the ARTIFACT, not only in this comment: every Arm A row's
68
+ * reason text names the vault universe at the pinned ref as its source, so a
69
+ * reader of the rendered review meets the widening at the row that caused it.
70
+ *
71
+ * WHY the widening was necessary. `buildTraceStatement`
72
+ * (`src/vault-trace.cts:645-680`) populates the ledger from
73
+ * `matchPractices(...).matched ++ .global` and nothing else. Every ledger entry
74
+ * therefore ALREADY matched: there is no "never matched" ledger entry to derive
75
+ * D-06-09's N-A from. Arm A derives it from `practiceUniverse \ ledger` — data
76
+ * the statement already carries (`src/vault-trace.cts:618`) — and Arm B
77
+ * re-derives applicability at review time from the phase's real diff. Two arms,
78
+ * two DISTINCTLY LABELLED derived reasons that never blur.
79
+ *
80
+ * ## DECISION — `check:`-presence is read LIVE, with an advisory; NO schema change
60
81
  *
61
82
  * The trace records no `check:`-presence field, so "traced ∩ NO_CHECK" needs the
62
- * vault cache at review time. This module reads it through the shipped
63
- * evaluator (`evaluatePractices` + `isNoCheckVerdict`); when the trace's pin and
64
- * the cache's `resolvedSha` disagree it emits one advisory shaped like the trace
65
- * gate's `PIN_DIVERGED` note — it reports, never blocks. A `checkPresent` field
66
- * on the trace would be a schema change under `docs/vault-trace-format.md`'s
67
- * freeze discipline for a consumer that does not need it, and this review is not
68
- * a gate, so environment-dependence is acceptable here.
83
+ * vault cache at review time. This module reads it live and harvests the answer
84
+ * through the SHIPPED evaluator (`evaluatePractices` + `isNoCheckVerdict`); when
85
+ * the trace's pin and the cache's `resolvedSha` disagree it emits one advisory
86
+ * shaped like the shipped `PIN_DIVERGED` wording — reports, never blocks,
87
+ * explicit that no reader action is required.
88
+ *
89
+ * Justification, recorded because the alternative was live:
90
+ * (a) adding `checkPresent` to `TracePractice` is a trace-SCHEMA change,
91
+ * carrying the `predicateType`/`schemaVersion` freeze discipline
92
+ * (`docs/vault-trace-format.md`), for a consumer this phase can serve
93
+ * without it;
94
+ * (b) the advisory idiom is shipped and mutation-proved one module over
95
+ * (`src/check-command-router.cts:2687-2725`), including the discipline that
96
+ * an advisory stays in the record and is excluded from every block
97
+ * decision;
98
+ * (c) this tier is not a gate, so environment-dependence is acceptable here —
99
+ * the exact carve-out `docs/vault-trace-format.md`'s own warning permits.
69
100
  *
70
101
  * ## The oracle is HARVESTED, never re-derived
71
102
  *
72
103
  * `deriveCandidateSet` learns `check:`-presence by running the shipped
73
- * `evaluatePractices` and filtering with `isNoCheckVerdict`. It never scans for a
74
- * `check:` key, because the audit tests `hasInvalidCheck` FIRST: an INVALID check
75
- * is the mechanical audit's `FAIL`, not a `NO_CHECK`. The oracle runs over an
76
- * EMPTY file set on purpose: `NO_CHECK` is decided before any file is consulted,
77
- * so the answer is file- and machine-independent.
78
- *
79
- * ## Findings this module is built around
80
- *
81
- * - `matchPractices` returns `practicesProcessed: 0` for a never-fetched vault
82
- * and an empty one alike, so Arm B consumes a `discoverPracticeFiles` result
83
- * (whose `vaultPresent` and `readFailed` stay DISTINCT) plus the verdict
84
- * owner's view of the cache (`computeVaultVerdict`), never the match count.
85
- * - A parser-rejected practice is reported as a NAMED exclusion, and a degraded
86
- * one as a note — never a silent narrowing.
87
- * - The review range SHARES the audit's phase-scoped resolution first
88
- * (`resolvePhaseRange`: the manifest's `startCommit`, its recorded commits,
89
- * then SUMMARY trace digests). Only when that falls back does this module run
90
- * its own `(NN-` commit-token scan over `base..HEAD`, and only then the whole
91
- * base-branch span. `REVIEW_RANGE_SOURCE` mirrors the phase-scoped values
92
- * rather than referencing `AUDIT_RANGE_SOURCE`, which belongs to the blocking
93
- * gate.
94
- * - An invalid `check:` is the mechanical audit's FAIL: it is excluded from the
95
- * candidate set and reported as a named exclusion (the practices README states
96
- * the audit blocks on it). Every judgement here binds to the audit's verdicts,
97
- * never to prose.
104
+ * `evaluatePractices` and filtering with the shipped `isNoCheckVerdict`. It
105
+ * never scans for a `check:` key, because the shipped ladder
106
+ * (`src/vault-audit.cts:381-396`) tests `hasInvalidCheck` FIRST: an INVALID
107
+ * check is the mechanical tier's `FAIL`, not a `NO_CHECK`. A hand-rolled scan
108
+ * gets exactly that case backwards and would hand a typo'd practice to the agent
109
+ * as if the mechanical tier had declined it.
110
+ *
111
+ * The oracle runs over an EMPTY file set on purpose. The ladder returns
112
+ * `NO_CHECK` before any file is consulted (`if (!check)` precedes both the pair
113
+ * cap and the matching), so the answer is file-independent — and running it over
114
+ * no files makes it machine-independent too.
115
+ *
116
+ * ## Phase 5 findings this module is planned AROUND, not assumed fixed
117
+ *
118
+ * - **CR-01.** `matchPractices` returns `practicesProcessed: 0` on a
119
+ * never-fetched vault, indistinguishable from an empty one. This module does
120
+ * not modify `src/vault-practices.cts`; Arm B instead consumes a
121
+ * `discoverPracticeFiles` result, whose typed shape keeps `vaultPresent` and
122
+ * `readFailed` DISTINCT (`src/vault-practices.cts:165-175`).
123
+ * - **WR-02.** A parser-rejected practice silently leaves the practice set.
124
+ * `deriveCandidateSet` reports discovery's `skipped` entries as NAMED
125
+ * exclusions and its `degraded` entries as notes. Never silently narrowed.
126
+ * - **WR-04 / F-3, and UAT-6-F1.** The review range now SHARES the mechanical
127
+ * tier's phase-scoped resolution first: `resolvePhaseRange` answers from the
128
+ * phase manifest's `startCommit` (written at execute:pre by `vault manifest
129
+ * start`), then its recorded `commits` (written at verify:post), then the
130
+ * SUMMARY trace digests of a phase built before that writer existed. Only when
131
+ * all three fall back does this module run its OWN `(NN-` commit-token scan
132
+ * over `base..HEAD`, and only then the whole base-branch span. The vocabulary
133
+ * stays SEPARATE: `REVIEW_RANGE_SOURCE` mirrors the three phase-scoped values
134
+ * rather than referencing `AUDIT_RANGE_SOURCE`, because that object belongs to
135
+ * the blocking tier, and a shared vocabulary would couple the wall's two sides
136
+ * and shift producer-audit citations for no gain.
137
+ * - **CR-04 (05-REVIEW:364) — OUT OF SCOPE and NOT assumed fixed.**
138
+ * `pp-core/templates/practices/README.md` tells vault authors that a skipped
139
+ * or check-invalid practice "never breaks the run", while at HEAD both halt
140
+ * the blocking gates. This module builds on the SHIPPED semantics as measured:
141
+ * an invalid `check:` is a mechanical-tier FAIL, excluded from the candidate
142
+ * set and reported as a named exclusion, whatever the README claims. Fixing
143
+ * the README is vault-repo docs work. If it is fixed later nothing here
144
+ * changes, because every judgement below binds to `verdictForPractice` /
145
+ * `isNoCheckVerdict` and never to the README's prose.
98
146
  *
99
147
  * ## Nothing measured must never render as clean
100
148
  *
101
- * Every degenerate path returns a record that SAYS what did not happen —
102
- * `oracleSource: 'unavailable'` with a note, a `notDerived` record per arm, and
103
- * `reviewRefusesCleanRender`, the single predicate that forbids a clean review
104
- * when no file was considered or an input was not measured.
149
+ * Every degenerate path here returns a record that SAYS what did not happen —
150
+ * `oracleSource: 'unavailable'` with a note, a `notDerived` record per arm
151
+ * (task 2), and `reviewRefusesCleanRender` (task 3), the single predicate
152
+ * (D-06-15 / F-3) that forbids a clean review whenever `filesConsidered === 0`.
105
153
  *
106
154
  * ## Trust boundary
107
155
  *
108
156
  * Practice ids, `applies-to` globs and practice prose are CLIENT-AUTHORED DATA.
109
157
  * They are bounded with the shipped `boundedToken` before they reach any string
110
158
  * this module returns, and applicability is decided by the shipped `globMatch`
111
- * alone. No client-authored byte is ever compiled into a regular expression and
112
- * no second glob grammar is written here (the declarative-check rule,
113
- * docs/practices-format.md). Reason texts name practice ids and mechanisms,
114
- * never absolute filesystem paths.
159
+ * alone. No client-authored byte is ever compiled into a regular expression
160
+ * (ADR-71924 §3), and no second glob grammar is written here (§2). Reason texts
161
+ * name practice ids and mechanisms, never absolute filesystem paths.
115
162
  */
116
163
  const node_path_1 = __importDefault(require("node:path"));
117
164
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
@@ -182,12 +229,9 @@ const NOT_DERIVED_GROUND = Object.freeze({
182
229
  VAULT_STATE_UNKNOWN: 'vault-state-unknown',
183
230
  VAULT_NEVER_FETCHED: 'vault-never-fetched',
184
231
  VAULT_UNREADABLE: 'vault-unreadable',
185
- /**
186
- * The cache the vault verdict owner reads records a fetch MISS, or holds no
187
- * file: a vault directory may exist, but no client vault was loaded into it.
188
- * Without this ground a miss rendered as an empty, clean review.
189
- */
190
- VAULT_FETCH_MISS: 'vault-fetch-miss',
232
+ // Quick 261015-at3: the vault was read, and it is a standards vault the
233
+ // project reads in practices mode — no practice layer was read at all.
234
+ VAULT_STANDARDS_ONLY: 'vault-standards-only',
191
235
  NO_FILES_CONSIDERED: 'no-files-considered',
192
236
  MATCHER_UNAVAILABLE: 'matcher-unavailable',
193
237
  });
@@ -209,8 +253,8 @@ const ORACLE_UNAVAILABLE_GROUND = 'no-check-oracle-unavailable';
209
253
  * difference between fixing D3 and shouting it. That ground says the TRACE
210
254
  * records no practice universe — which is true of every reconstruct review by
211
255
  * definition, because a phase with no trace has no universe key. Refusing on it
212
- * would refuse the entire reconstruct arm, which is the primary path (every
213
- * phase of a project that never traced takes it), not a degraded one. A review that refuses
256
+ * would refuse the entire reconstruct arm, and `pp-core/workflows/vault-review.md`
257
+ * §3 calls that arm the primary path, not a degraded one. A review that refuses
214
258
  * everything judges exactly as much as one that judges nothing; it is only
215
259
  * louder about it.
216
260
  */
@@ -218,7 +262,7 @@ const UNMEASURED_GROUND = new Set([
218
262
  NOT_DERIVED_GROUND.VAULT_STATE_UNKNOWN,
219
263
  NOT_DERIVED_GROUND.VAULT_NEVER_FETCHED,
220
264
  NOT_DERIVED_GROUND.VAULT_UNREADABLE,
221
- NOT_DERIVED_GROUND.VAULT_FETCH_MISS,
265
+ NOT_DERIVED_GROUND.VAULT_STANDARDS_ONLY,
222
266
  NOT_DERIVED_GROUND.NO_FILES_CONSIDERED,
223
267
  NOT_DERIVED_GROUND.MATCHER_UNAVAILABLE,
224
268
  ORACLE_UNAVAILABLE_GROUND,
@@ -231,7 +275,7 @@ const UNMEASURED_GROUND = new Set([
231
275
  * shared vocabulary would couple the two sides of the wall D-06-01 exists to
232
276
  * keep apart — and would shift the producer-audit citations for no gain.
233
277
  *
234
- * * UAT-6-F1 added three members whose VALUES mirror the
278
+ * 260915-ik2 (UAT-6-F1) added three members whose VALUES mirror the
235
279
  * phase-scoped sources of the shared resolution. They are this object's own
236
280
  * strings, reached from the audit's through `REVIEW_SOURCE_FOR_AUDIT_SOURCE`,
237
281
  * never a reference to the audit's object.
@@ -266,7 +310,7 @@ const REVIEW_SOURCE_FOR_AUDIT_SOURCE = Object.freeze({
266
310
  * still has no way to review the phase in front of them.
267
311
  */
268
312
  const REVIEW_RANGE_UNRESOLVABLE_REMEDY = 'Anchor the phase by running it through /pp-execute-phase (execute:pre records the start commit ' +
269
- 'via phase manifest start), or audit it by hand over a range you name: ' +
313
+ 'via vault manifest start), or audit it by hand over a range you name: ' +
270
314
  'pp-tools vault audit <phase-dir> --range <from>..<to>.';
271
315
  /**
272
316
  * The opening of the note `resolveReviewRange` pushes when the SHARED
@@ -344,8 +388,8 @@ function isNonVerdict(verdict) {
344
388
  * every check-bearing one); crossing that universe with the review's real diff
345
389
  * is `deriveNaANoFile`'s job and reaches the SHIPPED `globMatch` through the
346
390
  * same `_practices` seam. No second matcher is written for the reconstruct arm,
347
- * because a second matcher is a second answer to one question (the
348
- * declarative-check rule). The practices that fall outside the range therefore leave
391
+ * because a second matcher is a second answer to one question (ADR-71924 §2,
392
+ * and CR-02's class). The practices that fall outside the range therefore leave
349
393
  * this set as STATED derived N-A rows rather than as silent absences.
350
394
  *
351
395
  * Three oracle paths, and the third one SAYS so:
@@ -494,8 +538,8 @@ function deriveCandidateSet(opts) {
494
538
  /**
495
539
  * The pin advisory (recorded decision above; no trace-schema change).
496
540
  *
497
- * Mirrors the wording discipline of the trace gate's `PIN_DIVERGED` note
498
- * (`cmdVaultTrace`) and harvests its KIND from
541
+ * Mirrors the wording discipline of the shipped `PIN_DIVERGED` note at
542
+ * `src/check-command-router.cts:2687-2716` and harvests its KIND from
499
543
  * `TRACE_PROBLEM_KIND` so the two cannot drift. It reports; it never blocks;
500
544
  * it states that no reader action is required. An ABSENT cache emits NOTHING —
501
545
  * an absent cache is not a divergence, and a check that fired on absence would
@@ -519,8 +563,8 @@ function derivePinAdvisories(tracePinSha, cacheSha) {
519
563
  }
520
564
  // ─── Counts and the rate line ────────────────────────────────────────────────
521
565
  /**
522
- * Count the four verdicts, in the shape the audit's `countVerdicts`
523
- * established one tier down.
566
+ * Count the four verdicts, in the shape `countVerdicts`
567
+ * (`src/vault-audit.cts:404-414`) established one tier down.
524
568
  *
525
569
  * `judged` is `total - na`. Recorded because it is a choice: an `INCONCLUSIVE`
526
570
  * practice WAS judged — the agent looked at it and could not determine an
@@ -580,7 +624,7 @@ function countReviewVerdicts(rows) {
580
624
  counts.malformed.push({
581
625
  id: quotableId(id),
582
626
  verdict,
583
- problem: 'A non-verdict must carry a stated reason. A bare N-A or INCONCLUSIVE is ' +
627
+ problem: 'D-06-11: a non-verdict must carry a stated reason. A bare N-A or INCONCLUSIVE is ' +
584
628
  'indistinguishable from the agent giving up.',
585
629
  });
586
630
  }
@@ -669,7 +713,7 @@ function deriveMechanicalCoverageLine(opts) {
669
713
  *
670
714
  * BOUNDARY WIDENING, surfaced in every row (and in the module header): these
671
715
  * ids were never traced, so reporting them widens the judged boundary beyond
672
- * the traced input set (see the module header). The reason
716
+ * `06-CONTEXT.md`'s stated input set. Operator-approved 2026-09-03. The reason
673
717
  * text names the vault universe as its source so a reader of the ARTIFACT meets
674
718
  * the widening at the row that caused it, never only in a plan nobody reopens.
675
719
  *
@@ -694,9 +738,9 @@ function deriveNaAUniverse(opts) {
694
738
  ? {
695
739
  ground: NOT_DERIVED_GROUND.UNIVERSE_WITHHELD,
696
740
  statement: 'Arm A derived NOTHING: this trace DOES record a practice universe, and it was ' +
697
- 'deliberately withheld because the trace is DEGRADED — it records that the vault could ' +
698
- 'not be loaded when the work was planned, so its universe proves nothing about which ' +
699
- 'practices governed that work and may not be subtracted from. This is a ' +
741
+ 'deliberately withheld because the trace is DEGRADED — it records that no trusted vault ' +
742
+ 'load existed when the work was planned, so its universe proves nothing about which ' +
743
+ 'practices governed that work and may not be subtracted from (D-06-15). This is a ' +
700
744
  // Same wording constraint as the sibling branch below: the
701
745
  // prompt-injection scanner's regex carries no leading word
702
746
  // boundary, so "is a different fact from …" is written in place
@@ -767,8 +811,8 @@ function deriveNaAUniverse(opts) {
767
811
  * global practice applies to the whole change set by definition, so "no file
768
812
  * fell under it" is not a statement that can be true of one.
769
813
  *
770
- * Applicability is decided by the SHIPPED `globMatch` alone (the declarative-check
771
- * rule: no second grammar, no client-authored byte compiled into a RegExp).
814
+ * Applicability is decided by the SHIPPED `globMatch` alone (ADR-71924 §2 — no
815
+ * second grammar; §3 — no client-authored byte compiled into a RegExp).
772
816
  */
773
817
  function deriveNaANoFile(opts) {
774
818
  const arm = NA_DERIVED_REASON.NO_FILE_IN_DIFF;
@@ -787,20 +831,24 @@ function deriveNaANoFile(opts) {
787
831
  'honest N-A.');
788
832
  }
789
833
  if (discovery.vaultPresent === false) {
790
- return refuse(NOT_DERIVED_GROUND.VAULT_NEVER_FETCHED, 'Arm B derived NOTHING: no fetched client vault cache exists on this machine — the vault was ' +
834
+ return refuse(NOT_DERIVED_GROUND.VAULT_NEVER_FETCHED, 'Arm B derived NOTHING: the client vault directory does not exist on this machine — the vault was ' +
791
835
  'NEVER FETCHED. This is not an empty vault, and no applicability was derived from it. Repair by ' +
792
836
  'fetching the vault, then re-run the review.');
793
837
  }
794
- if (discovery.fetchMiss === true) {
795
- return refuse(NOT_DERIVED_GROUND.VAULT_FETCH_MISS, 'Arm B derived NOTHING: the vault cache records a FETCH MISS (or holds no file), so no client vault ' +
796
- 'was loaded and no applicability was derived from it. An empty practice set read from a miss is ' +
797
- 'not an empty vault. Repair by fetching the vault, then re-run the review.');
798
- }
799
838
  if (discovery.readFailed === true) {
800
839
  return refuse(NOT_DERIVED_GROUND.VAULT_UNREADABLE, 'Arm B derived NOTHING: the client vault is PRESENT but a read of it REFUSED — the vault is ' +
801
840
  'unreadable. This is not an empty vault and not a missing one, and no applicability was derived ' +
802
841
  'from it. Repair by making the cached vault readable, then re-run the review.');
803
842
  }
843
+ // Quick 261015-at3 (entry-point sweep): read from the same typed discovery
844
+ // result as the two rungs above, never from prose. A trace an older engine
845
+ // recorded `loaded` over this vault would otherwise take the traced arm and
846
+ // render a clean "0 of 0" review of a phase no practice governed.
847
+ if (discovery.vaultStandardsOnly === true) {
848
+ return refuse(NOT_DERIVED_GROUND.VAULT_STANDARDS_ONLY, 'Arm B derived NOTHING: the client vault holds standards (INDEX.md) and no practice files, and this ' +
849
+ 'project reads it in practices mode, so no practice layer was read. This is not an empty vault, and ' +
850
+ 'no applicability was derived from it. Repair by setting vault.mode to standards, then re-plan the phase.');
851
+ }
804
852
  const diffFiles = asStringList(opts.diffFiles).map((f) => f.replace(/\\/g, '/'));
805
853
  if (diffFiles.length === 0) {
806
854
  return refuse(NOT_DERIVED_GROUND.NO_FILES_CONSIDERED, 'Arm B derived NOTHING: the review range yielded zero files, so no applicability was measured. ' +
@@ -811,7 +859,7 @@ function deriveNaANoFile(opts) {
811
859
  const matcher = opts._practices ?? practicesMod;
812
860
  if (typeof matcher.globMatch !== 'function') {
813
861
  return refuse(NOT_DERIVED_GROUND.MATCHER_UNAVAILABLE, 'Arm B derived NOTHING: the shipped glob matcher could not be reached, and no second matcher is ' +
814
- 'written here (the declarative-check rule). No applicability was measured.');
862
+ 'written here (ADR-71924 §2). No applicability was measured.');
815
863
  }
816
864
  const byId = new Map();
817
865
  for (const p of opts.practices ?? []) {
@@ -916,7 +964,7 @@ function normalisePhaseToken(v) {
916
964
  * Resolve the range this review is computed over, and RECORD where it came from
917
965
  * (F-3 / WR-04).
918
966
  *
919
- * First — the SHARED phase-scoped resolution (UAT-6-F1). When a
967
+ * First — the SHARED phase-scoped resolution (260915-ik2, UAT-6-F1). When a
920
968
  * `phaseDir` is given, the mechanical tier's `resolvePhaseRange` is asked
921
969
  * before anything else, and its ladder is the one this tier adopts:
922
970
  * `phase-start-commit` (the manifest anchor written at execute:pre),
@@ -945,7 +993,7 @@ function normalisePhaseToken(v) {
945
993
  * Fallback — `base-branch`: `resolveBaseBranchResult(cwd)...HEAD`. The 5-tier
946
994
  * ladder is never re-derived (#1146 records what the naive `origin/HEAD` +
947
995
  * `:-main` form cost) and every git call goes through the timeout-bounded
948
- * `execGit` seam, never `execFileSync`. The RESULT form is used rather than the
996
+ * `execGitLocal` seam (D-116), never `execFileSync`. The RESULT form is used rather than the
949
997
  * bare string so a REFUSED `git.base_branch` becomes a stated note instead of a
950
998
  * silent change of range (T-6-49).
951
999
  *
@@ -966,7 +1014,7 @@ function normalisePhaseToken(v) {
966
1014
  * that could not be read must never look like a phase that changed nothing.
967
1015
  */
968
1016
  function resolveReviewRange(opts) {
969
- const exec = typeof opts?.exec === 'function' ? opts.exec : shell_command_projection_cjs_1.execGit;
1017
+ const exec = typeof opts?.exec === 'function' ? opts.exec : shell_command_projection_cjs_1.execGitLocal;
970
1018
  const cwd = typeof opts?.cwd === 'string' && opts.cwd !== '' ? opts.cwd : process.cwd();
971
1019
  const notes = [];
972
1020
  let base = 'main';
@@ -990,7 +1038,7 @@ function resolveReviewRange(opts) {
990
1038
  }
991
1039
  let range = `${base}...HEAD`;
992
1040
  let rangeSource = REVIEW_RANGE_SOURCE.BASE_BRANCH;
993
- // The shared phase-scoped resolution, ahead of the token scan (UAT-6-F1).
1041
+ // The shared phase-scoped resolution, ahead of the token scan (260915-ik2).
994
1042
  let sharedResolved = false;
995
1043
  if (typeof opts?.phaseDir === 'string' && opts.phaseDir !== '') {
996
1044
  try {
@@ -1224,8 +1272,9 @@ const REVIEW_PROBLEM_KIND = Object.freeze({
1224
1272
  /** D-06-13: the counts line's numbers are not the ones the rows support. */
1225
1273
  COUNTS_LINE_ARITHMETIC: 'counts-line-arithmetic',
1226
1274
  // ── MECH-02 / D-10-20: the mechanical coverage line ────────────────────────
1227
- // An instructed-only line is an unenforced one: an artifact that dropped it
1228
- // validated clean. These four kinds close it.
1275
+ // Phase 10 verification found this line INSTRUCTED (agents/pp-vault-reviewer.md
1276
+ // :56-68) and ENFORCED NOWHERE — an artifact that dropped it validated clean,
1277
+ // while that line is MECH-02's whole deliverable. These four kinds close it.
1229
1278
  /** The payload carried a coverage line and the artifact does not. */
1230
1279
  COVERAGE_LINE_ABSENT: 'coverage-line-absent',
1231
1280
  /** A coverage line is present but is not the engine's sentence, verbatim. */
@@ -1246,13 +1295,6 @@ const REVIEW_PROBLEM_KIND = Object.freeze({
1246
1295
  FENCE_UNBALANCED: 'fence-unbalanced',
1247
1296
  /** The parse cap was reached; rows beyond it were NOT judged. */
1248
1297
  ROWS_TRUNCATED: 'rows-truncated',
1249
- // ── The population, checked against the payload by `vault review finalize` ──
1250
- /** A practice of the review's population has no row: dropped rather than judged. */
1251
- ROW_POPULATION_MISSING: 'row-population-missing',
1252
- /** A row names a practice outside the population the payload derived. */
1253
- ROW_POPULATION_UNEXPECTED: 'row-population-unexpected',
1254
- /** One practice carries more than one row. */
1255
- ROW_DUPLICATE: 'row-duplicate',
1256
1298
  // The four D-06-13 score forms.
1257
1299
  FORBIDDEN_PERCENTAGE: 'forbidden-percentage',
1258
1300
  FORBIDDEN_RATIO: 'forbidden-ratio',
@@ -1264,8 +1306,8 @@ const REVIEW_SECTION_HEADING = '## Practice Verdicts (agent-judged)';
1264
1306
  /** The D-06-12 rate line, as a shape. The renderer above is the only writer. */
1265
1307
  const RATE_LINE_SHAPE = /^(\d+) of (\d+) practices judged · (\d+) N-A · (\d+) INCONCLUSIVE$/;
1266
1308
  /**
1267
- * The D-10-03 mechanical coverage line, as a shape. The audit's
1268
- * `renderMechanicalCoverageLine` is the only writer; this recognises its output so a
1309
+ * The D-10-03 mechanical coverage line, as a shape. `deriveMechanicalCoverageLine`
1310
+ * (`src/vault-audit.cts:823`) is the only writer; this recognises its output so a
1269
1311
  * MISPLACED or UNEXPECTED line can be named even when no expectation was supplied.
1270
1312
  */
1271
1313
  const COVERAGE_LINE_SHAPE = /^(\d+) of (\d+) practices mechanically checked · (\d+) PASS · (\d+) FAIL · (\d+) NOT_MATCHED · (\d+) NO_CHECK$/;
@@ -1346,18 +1388,12 @@ function isSeparatorLine(line) {
1346
1388
  * empty result.
1347
1389
  */
1348
1390
  function locateReviewSection(lines) {
1349
- // A line between a closed pair of fence markers is client prose, never
1350
- // structure: a heading-shaped client line can neither open the section nor
1351
- // end it (the same trust boundary `parseReviewRows` draws for rows).
1352
- const fenced = fencedLineMap(lines);
1353
- const start = lines.findIndex((l, i) => !fenced[i] && l.trim() === REVIEW_SECTION_HEADING);
1391
+ const start = lines.findIndex((l) => l.trim() === REVIEW_SECTION_HEADING);
1354
1392
  if (start === -1)
1355
1393
  return { present: false, start: 0, end: lines.length };
1356
1394
  let end = lines.length;
1357
1395
  for (let i = start + 1; i < lines.length; i += 1) {
1358
1396
  const line = lines[i];
1359
- if (fenced[i])
1360
- continue;
1361
1397
  if (line.startsWith('## ') || line.trim() === '---') {
1362
1398
  end = i;
1363
1399
  break;
@@ -1497,11 +1533,11 @@ function judgeReviewRow(unit) {
1497
1533
  }
1498
1534
  const recognised = isJudgedVerdict(verdict) || isNonVerdict(verdict);
1499
1535
  if (!recognised) {
1500
- problems.push(reviewProblem(REVIEW_PROBLEM_KIND.ROW_UNKNOWN_VERDICT, id === '' ? null : id, line, `"${safe(verdict)}" is not one of the four locked verdicts. A coined fifth verdict ` +
1536
+ problems.push(reviewProblem(REVIEW_PROBLEM_KIND.ROW_UNKNOWN_VERDICT, id === '' ? null : id, line, `"${safe(verdict)}" is not one of the four locked verdicts (D-06-10). A coined fifth verdict ` +
1501
1537
  'is counted as unrecognised and is never absorbed into a real one.'));
1502
1538
  }
1503
1539
  else if (isNonVerdict(verdict) && reason === '') {
1504
- problems.push(reviewProblem(REVIEW_PROBLEM_KIND.ROW_MISSING_REASON, id === '' ? null : id, line, 'A non-verdict must carry a stated reason. A bare N-A or INCONCLUSIVE is ' +
1540
+ problems.push(reviewProblem(REVIEW_PROBLEM_KIND.ROW_MISSING_REASON, id === '' ? null : id, line, 'D-06-11: a non-verdict must carry a stated reason. A bare N-A or INCONCLUSIVE is ' +
1505
1541
  'indistinguishable from the agent giving up.'));
1506
1542
  }
1507
1543
  const derived = DERIVED_SOURCE_SHAPE.test(source);
@@ -1517,7 +1553,7 @@ function judgeReviewRow(unit) {
1517
1553
  'and a reader who cannot tell them apart cannot act on either.'));
1518
1554
  }
1519
1555
  if (isJudgedVerdict(verdict)) {
1520
- problems.push(reviewProblem(REVIEW_PROBLEM_KIND.ROW_DERIVED_VERDICT_CONFLICT, id === '' ? null : id, line, `a derived row carries "${safe(verdict)}", a judged verdict. Derived rows are handed to ` +
1556
+ problems.push(reviewProblem(REVIEW_PROBLEM_KIND.ROW_DERIVED_VERDICT_CONFLICT, id === '' ? null : id, line, `a derived row carries "${safe(verdict)}", a judged verdict. D-06-09 hands derived rows to ` +
1521
1557
  'the agent to reproduce, not to reclassify: a derived N-A promoted to PASS or FAIL is a ' +
1522
1558
  'computed fact overwritten by a judgement.'));
1523
1559
  }
@@ -1574,7 +1610,7 @@ function validateReviewArtifact(text, opts) {
1574
1610
  }
1575
1611
  else if (!section.present) {
1576
1612
  problems.push(reviewProblem(REVIEW_PROBLEM_KIND.SECTION_ABSENT, null, null, `the artifact carries no "${REVIEW_SECTION_HEADING}" heading, so no verdict row was found ` +
1577
- 'and nothing was judged. The heading is locked; a section under any other heading is ' +
1613
+ 'and nothing was judged. D-06-02 locks that heading; a section under any other heading is ' +
1578
1614
  'not this section.'));
1579
1615
  }
1580
1616
  const { units, truncated } = section.present
@@ -1593,7 +1629,8 @@ function validateReviewArtifact(text, opts) {
1593
1629
  }
1594
1630
  if (section.present && units.length === 0) {
1595
1631
  problems.push(reviewProblem(REVIEW_PROBLEM_KIND.NO_ROWS, null, null, 'the section heading is present and carries no verdict row. An empty section is not a clean ' +
1596
- 'review: a gap is reported loudly, never as an empty register.'));
1632
+ 'review: D-06-14 requires a gap to be reported loudly, and §10 of the verifier contract ' +
1633
+ 'requires no input to produce NO section rather than an empty one.'));
1597
1634
  }
1598
1635
  const counts = countReviewVerdicts(rows);
1599
1636
  const expectedRateLine = renderRateLine(counts);
@@ -1603,19 +1640,19 @@ function validateReviewArtifact(text, opts) {
1603
1640
  const foundCountsLine = body.map((l) => l.trim()).find((l) => COUNTS_LINE_SHAPE.test(l)) ?? null;
1604
1641
  if (section.present) {
1605
1642
  if (foundRateLine === null) {
1606
- problems.push(reviewProblem(REVIEW_PROBLEM_KIND.RATE_LINE_ABSENT, null, null, `The section carries no non-verdict rate line. Expected the computed line ` +
1643
+ problems.push(reviewProblem(REVIEW_PROBLEM_KIND.RATE_LINE_ABSENT, null, null, `D-06-12: the section carries no non-verdict rate line. Expected the computed line ` +
1607
1644
  `"${expectedRateLine}" — the denominator is the point, and a list of green rows without ` +
1608
1645
  'it cannot tell a reader that a review is hollow.'));
1609
1646
  }
1610
1647
  else if (foundRateLine !== expectedRateLine) {
1611
- problems.push(reviewProblem(REVIEW_PROBLEM_KIND.RATE_LINE_ARITHMETIC, null, null, `The rate line reads "${safe(foundRateLine)}" while the rows support ` +
1648
+ problems.push(reviewProblem(REVIEW_PROBLEM_KIND.RATE_LINE_ARITHMETIC, null, null, `D-06-12: the rate line reads "${safe(foundRateLine)}" while the rows support ` +
1612
1649
  `"${expectedRateLine}". The arithmetic is computed from the rows and never narrated.`));
1613
1650
  }
1614
1651
  if (foundCountsLine === null) {
1615
- problems.push(reviewProblem(REVIEW_PROBLEM_KIND.COUNTS_LINE_ABSENT, null, null, `The section carries no counts line. Expected "${expectedCountsLine}".`));
1652
+ problems.push(reviewProblem(REVIEW_PROBLEM_KIND.COUNTS_LINE_ABSENT, null, null, `D-06-13: the section carries no counts line. Expected "${expectedCountsLine}".`));
1616
1653
  }
1617
1654
  else if (foundCountsLine !== expectedCountsLine) {
1618
- problems.push(reviewProblem(REVIEW_PROBLEM_KIND.COUNTS_LINE_ARITHMETIC, null, null, `The counts line reads "${safe(foundCountsLine)}" while the rows support ` +
1655
+ problems.push(reviewProblem(REVIEW_PROBLEM_KIND.COUNTS_LINE_ARITHMETIC, null, null, `D-06-13: the counts line reads "${safe(foundCountsLine)}" while the rows support ` +
1619
1656
  `"${expectedCountsLine}".`));
1620
1657
  }
1621
1658
  }
@@ -1628,10 +1665,7 @@ function validateReviewArtifact(text, opts) {
1628
1665
  const expected = opts.expectedCoverageLine;
1629
1666
  const inSection = (i) => section.present && i >= section.start && i < section.end;
1630
1667
  let foundIdx = -1;
1631
- const fencedLines = fencedLineMap(lines);
1632
1668
  for (let i = 0; i < lines.length; i += 1) {
1633
- if (fencedLines[i])
1634
- continue;
1635
1669
  if (COVERAGE_LINE_SHAPE.test(lines[i].trim())) {
1636
1670
  foundIdx = i;
1637
1671
  break;
@@ -1640,23 +1674,23 @@ function validateReviewArtifact(text, opts) {
1640
1674
  const found = foundIdx >= 0 ? lines[foundIdx].trim() : null;
1641
1675
  if (typeof expected === 'string' && expected.length > 0) {
1642
1676
  if (found === null) {
1643
- problems.push(reviewProblem(REVIEW_PROBLEM_KIND.COVERAGE_LINE_ABSENT, null, null, `The payload carried the mechanical coverage line "${expected}" and the ` +
1677
+ problems.push(reviewProblem(REVIEW_PROBLEM_KIND.COVERAGE_LINE_ABSENT, null, null, `D-10-03: the payload carried the mechanical coverage line "${expected}" and the ` +
1644
1678
  'artifact reproduces it nowhere. That line is how a reader learns how much of the ' +
1645
1679
  'vault was mechanically checked; without it a page of judged rows cannot say ' +
1646
1680
  'whether the mechanical tier ran at all.'));
1647
1681
  }
1648
1682
  else if (found !== expected) {
1649
- problems.push(reviewProblem(REVIEW_PROBLEM_KIND.COVERAGE_LINE_MISMATCH, null, null, `The coverage line reads "${safe(found)}" where the engine computed ` +
1683
+ problems.push(reviewProblem(REVIEW_PROBLEM_KIND.COVERAGE_LINE_MISMATCH, null, null, `D-10-03: the coverage line reads "${safe(found)}" where the engine computed ` +
1650
1684
  `"${expected}". It is the engine's sentence, reproduced verbatim and never restated.`));
1651
1685
  }
1652
1686
  else if (inSection(foundIdx)) {
1653
- problems.push(reviewProblem(REVIEW_PROBLEM_KIND.COVERAGE_LINE_MISPLACED, null, null, 'The coverage line sits inside the verdicts section, where a second bare ' +
1687
+ problems.push(reviewProblem(REVIEW_PROBLEM_KIND.COVERAGE_LINE_MISPLACED, null, null, 'D-10-03: the coverage line sits inside the verdicts section, where a second bare ' +
1654
1688
  'shaped line makes the section contradict itself — its figures count a different ' +
1655
1689
  'population from the rate line above them. It belongs under "## Range".'));
1656
1690
  }
1657
1691
  }
1658
1692
  else if (expected === null && found !== null) {
1659
- problems.push(reviewProblem(REVIEW_PROBLEM_KIND.COVERAGE_LINE_UNEXPECTED, null, null, `The artifact carries "${safe(found)}" while the payload carried no coverage ` +
1693
+ problems.push(reviewProblem(REVIEW_PROBLEM_KIND.COVERAGE_LINE_UNEXPECTED, null, null, `D-10-03: the artifact carries "${safe(found)}" while the payload carried no coverage ` +
1660
1694
  'line at all. Its absence means the inputs were never measured, so this figure ' +
1661
1695
  'states a measurement nobody took.'));
1662
1696
  }
@@ -1686,7 +1720,7 @@ function validateReviewArtifact(text, opts) {
1686
1720
  'marks it as reference data, which is the escape the fencing exists to prevent.'));
1687
1721
  }
1688
1722
  for (const finding of scanForbiddenScoreTokens(body.join('\n'))) {
1689
- problems.push(reviewProblem(finding.kind, null, section.present ? section.start + finding.line : finding.line, `No single-number roll-up may appear anywhere in this section; "${finding.match}" is one. ` +
1723
+ problems.push(reviewProblem(finding.kind, null, section.present ? section.start + finding.line : finding.line, `D-06-13 forbids a single-number roll-up anywhere in this section; "${finding.match}" is one. ` +
1690
1724
  'Counts by verdict stay sortable and trackable across phases without inviting a ' +
1691
1725
  'measured-sounding claim about a judgement.'));
1692
1726
  }
@@ -1815,7 +1849,7 @@ function buildReviewPayload(opts) {
1815
1849
  const range = resolveReviewRange({
1816
1850
  cwd: opts.cwd,
1817
1851
  phaseToken: opts?.phaseToken ?? null,
1818
- // UAT-6-F1: the check router already supplies `phaseDir: phase.rel`, so
1852
+ // 260915-ik2: the check router already supplies `phaseDir: phase.rel`, so
1819
1853
  // forwarding it is all the shared phase-scoped resolution needs.
1820
1854
  phaseDir: typeof opts?.phaseDir === 'string' && opts.phaseDir !== '' ? opts.phaseDir : null,
1821
1855
  ...(typeof opts?.exec === 'function' ? { exec: opts.exec } : {}),
@@ -1825,26 +1859,13 @@ function buildReviewPayload(opts) {
1825
1859
  'applicability judgement below is scoped to exactly that set.');
1826
1860
  for (const note of range.notes)
1827
1861
  statements.push(`Range note: ${note}`);
1828
- // ── The vault verdict owner's answer, stated when it is not `loaded`: the
1829
- // practices below were read from the cache as it stands, and a reader must
1830
- // know whether the load gate considers that cache the client's loaded vault.
1831
- const ownerVerdict = opts?.vaultVerdict ?? null;
1832
- const vault = ownerVerdict !== null && typeof ownerVerdict === 'object' && typeof ownerVerdict.verdict === 'string'
1833
- ? { verdict: safe(ownerVerdict.verdict), reason: typeof ownerVerdict.reason === 'string' ? safe(ownerVerdict.reason) : '' }
1834
- : null;
1835
- if (vault !== null && vault.verdict !== 'loaded') {
1836
- statements.push(`The vault verdict owner reports this project's vault as ${vault.verdict}` +
1837
- (vault.reason === '' ? '' : ` (${vault.reason})`) +
1838
- ': the practices reviewed below were read from the local cache as it stands, which the vault load ' +
1839
- "gate does not treat as the client's loaded vault.");
1840
- }
1841
1862
  // ── D-06-15: an absent trace does not refuse; it says so and RECONSTRUCTS.
1842
1863
  if (!tracePresent) {
1843
1864
  statements.push('No practice trace was found for this phase' +
1844
1865
  (phaseDir === null ? '' : ` in ${phaseDir}`) +
1845
1866
  '. This review therefore took the RECONSTRUCT arm: the applicable practice set is rebuilt ' +
1846
1867
  "from this phase's real diff and the vault's own practice list rather than from a ledger. " +
1847
- 'Any completed phase is eligible for review and a missing trace is itself a finding, ' +
1868
+ 'Any completed phase is eligible for review (D-06-14) and a missing trace is itself a finding, ' +
1848
1869
  'so it is recorded here rather than used as a reason to refuse.');
1849
1870
  }
1850
1871
  // ── G1: a trace that PARSES but records a failed vault load. This is not the
@@ -1859,9 +1880,9 @@ function buildReviewPayload(opts) {
1859
1880
  'A practice trace WAS found for this phase' +
1860
1881
  (phaseDir === null ? '' : ` in ${phaseDir}`) +
1861
1882
  `, and it records a FAILURE — ${traceReason ?? 'no reason recorded'}. A degraded block is a valid ` +
1862
- 'record of a failure — it says the vault could not be loaded when the work was planned — so it ' +
1863
- 'proves nothing about which practices governed that work and may not stand in for a loaded trace. ' +
1864
- 'This review therefore took the RECONSTRUCT arm: the applicable practice set is rebuilt ' +
1883
+ 'record of a failure — it says no trusted vault load existed when the work was planned — so it ' +
1884
+ 'proves nothing about which practices governed that work and may not stand in for a loaded trace ' +
1885
+ '(D-06-15). This review therefore took the RECONSTRUCT arm: the applicable practice set is rebuilt ' +
1865
1886
  "from this phase's real diff and the vault's own practice list, and this trace's own ledger and " +
1866
1887
  'practice universe are deliberately not consulted.');
1867
1888
  }
@@ -1882,7 +1903,7 @@ function buildReviewPayload(opts) {
1882
1903
  `${said(o.who)} at ${said(o.when)} with the stated reason "${said(o.reason)}", suppressing the ` +
1883
1904
  `verdict ${said(o.suppressedReason)}. The work this phase records was therefore planned or ` +
1884
1905
  'executed under a deliberate, attributed bypass of that gate; this review reports the fact ' +
1885
- 'and does not judge it.');
1906
+ '(D-08-11) and does not judge it.');
1886
1907
  }
1887
1908
  // ── The input set: this arm's universe ∩ NO_CHECK, harvested through the
1888
1909
  // shipped ladder. On the reconstruct arm the universe is the vault's own
@@ -2019,7 +2040,6 @@ function buildReviewPayload(opts) {
2019
2040
  // the latter and never re-derives it from the former (06-12, T-6-48).
2020
2041
  arm: traceLoaded ? 'traced' : 'reconstruct',
2021
2042
  phaseDir,
2022
- vault,
2023
2043
  statements,
2024
2044
  trace: {
2025
2045
  present: tracePresent,
@@ -2044,278 +2064,6 @@ function buildReviewPayload(opts) {
2044
2064
  notes,
2045
2065
  };
2046
2066
  }
2047
- // ─── The artifact the agent fills in, and the lines only code writes ─────────
2048
- /**
2049
- * The verdict cell of a remainder row until the agent judges it. Deliberately
2050
- * outside the locked vocabulary: a row left unjudged is reported by the
2051
- * validator as `row-unknown-verdict`, never counted as any verdict.
2052
- */
2053
- const REVIEW_UNJUDGED_VERDICT = 'TODO';
2054
- /** The `## Range` heading the coverage line is stamped under. */
2055
- const RANGE_SECTION_HEADING = '## Range';
2056
- /** A table cell: one line, no column separator, never empty. */
2057
- function tableCell(v, empty = 'none.') {
2058
- const s = (typeof v === 'string' ? v : '').replace(/[\r\n]+/g, ' ').replace(/\|/g, '¦').trim();
2059
- return s === '' ? empty : s;
2060
- }
2061
- /**
2062
- * The gaps the payload recorded, one row each, in a fixed order. Every row is
2063
- * written on every run; a row with nothing to report says `none.`, so "nothing
2064
- * to report" and "not looked at" never look alike.
2065
- */
2066
- function reviewGapRows(p) {
2067
- const notDerived = (arm) => (arm.notDerived ? arm.notDerived.statement : '');
2068
- const vaultGround = p.naNoFile.notDerived?.ground ?? '';
2069
- const vaultGap = [
2070
- NOT_DERIVED_GROUND.VAULT_STATE_UNKNOWN,
2071
- NOT_DERIVED_GROUND.VAULT_NEVER_FETCHED,
2072
- NOT_DERIVED_GROUND.VAULT_FETCH_MISS,
2073
- NOT_DERIVED_GROUND.VAULT_UNREADABLE,
2074
- ].some((g) => g === vaultGround)
2075
- ? notDerived(p.naNoFile)
2076
- : '';
2077
- const traceGap = !p.trace.present
2078
- ? 'absent — no practice trace was found, so this review took the reconstruct arm'
2079
- : p.trace.loaded
2080
- ? ''
2081
- : `degraded — ${p.trace.reason ?? 'no reason recorded'}`;
2082
- const said = (v) => v ?? '(not recorded)';
2083
- const overrides = p.trace.overrides
2084
- .map((o) => `${said(o.gate)} overridden by ${said(o.who)} at ${said(o.when)} in ${said(o.source)}: "${said(o.reason)}" (suppressed ${said(o.suppressedReason)})`)
2085
- .join('; ');
2086
- const rangeGap = p.range.outcome === auditMod.AUDIT_RANGE_OUTCOME.RANGE_UNRESOLVABLE
2087
- ? `${p.range.outcome}: ${REVIEW_RANGE_UNRESOLVABLE_REMEDY}`
2088
- : '';
2089
- const skipped = p.inputSet.exclusions
2090
- .filter((e) => e.reason === AUDIT_FAIL_REASON.PRACTICE_UNLOADABLE)
2091
- .map((e) => quotableId(e.id))
2092
- .join(', ');
2093
- const unmeasured = unmeasuredGrounds({
2094
- filesConsidered: p.range.filesConsidered,
2095
- inputSet: p.inputSet,
2096
- naUniverse: p.naUniverse,
2097
- naNoFile: p.naNoFile,
2098
- });
2099
- const refused = p.refusesCleanRender
2100
- ? `no clean review may be rendered: ${p.range.filesConsidered === 0 ? 'zero files were considered' : `inputs not measured (${unmeasured.join(', ')})`}`
2101
- : '';
2102
- return [
2103
- ['Vault absent, never fetched, missed or unreadable', vaultGap],
2104
- ['Practice universe not recorded at the pin', notDerived(p.naUniverse)],
2105
- ['Trace absent or degraded', traceGap],
2106
- ['Gate override recorded (who, when, reason)', overrides],
2107
- ['Range refused or unresolvable', rangeGap],
2108
- ['Empty diff — no file considered', p.range.filesConsidered === 0 ? 'zero files were considered' : ''],
2109
- ['Practices skipped or rejected before matching', skipped],
2110
- ['Clean review refused', refused],
2111
- ];
2112
- }
2113
- /**
2114
- * Render the `NN-VAULT-REVIEW.md` the agent fills in, from the payload alone.
2115
- *
2116
- * Everything computed is written here, by code: provenance, the range, every
2117
- * derived row with its reason and arm label, every gap row, the payload's
2118
- * statements, the fixed limits, and — through `stampReviewLines` — the rate,
2119
- * counts and coverage lines. Each remainder practice gets one row whose verdict
2120
- * reads `TODO` and whose reason is empty: those two cells are the agent's whole
2121
- * writable surface. The remainder's practice text follows, last, inside the
2122
- * fences the shipped renderer already put around it.
2123
- *
2124
- * Pure: payload and date in, text out.
2125
- */
2126
- function renderReviewScaffold(payload, meta = {}) {
2127
- const p = payload;
2128
- const token = typeof meta.phaseToken === 'string' && meta.phaseToken !== '' ? safe(meta.phaseToken) : '?';
2129
- const date = typeof meta.date === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(meta.date) ? meta.date : '';
2130
- const traceCell = !p.trace.present
2131
- ? 'absent'
2132
- : p.trace.loaded
2133
- ? 'present and loaded'
2134
- : `degraded — ${p.trace.reason ?? 'no reason recorded'}`;
2135
- const advisories = p.inputSet.advisories.map((a) => a.detail).join(' ');
2136
- const rows = [];
2137
- for (const id of p.partition.remainder) {
2138
- rows.push(`| \`${tableCell(quotableId(id))}\` | ${REVIEW_UNJUDGED_VERDICT} | | agent-judged |`);
2139
- }
2140
- for (const r of [...p.partition.naUniverse, ...p.partition.naNoFile]) {
2141
- rows.push(`| \`${tableCell(quotableId(r.id))}\` | ${r.verdict} | ${tableCell(r.reason)} | derived (${tableCell(r.naDerivedReason ?? '')}) |`);
2142
- }
2143
- const lines = [
2144
- '---',
2145
- `phase: "${token}"`,
2146
- 'artifact: vault-review',
2147
- `reviewed: ${date === '' ? 'unknown' : date}`,
2148
- `arm: ${p.arm}`,
2149
- '---',
2150
- '',
2151
- `# Phase ${token} — Vault Practice Review`,
2152
- '',
2153
- '> This review reports and never gates: no verdict here changes a phase status, a score, a gap or a',
2154
- '> ship. Verdicts are agent-judged, not measured, and two runs may differ. Counts by verdict only.',
2155
- '',
2156
- '## Provenance',
2157
- '',
2158
- '| Field | Value |',
2159
- '| --- | --- |',
2160
- `| Phase directory | ${tableCell(p.phaseDir ?? '')} |`,
2161
- `| Arm | ${p.arm} |`,
2162
- `| Trace | ${tableCell(traceCell)} |`,
2163
- `| Trace sources | ${tableCell(p.trace.sources.join(', '))} |`,
2164
- `| Vault verdict | ${tableCell(p.vault ? `${p.vault.verdict}${p.vault.reason ? ` (${p.vault.reason})` : ''}` : '', 'not supplied')} |`,
2165
- `| Pin advisory | ${tableCell(advisories)} |`,
2166
- '',
2167
- RANGE_SECTION_HEADING,
2168
- '',
2169
- '| Field | Value |',
2170
- '| --- | --- |',
2171
- `| \`range\` | \`${tableCell(p.range.range)}\` |`,
2172
- `| \`rangeSource\` | \`${tableCell(p.range.rangeSource)}\` |`,
2173
- `| \`filesConsidered\` | ${p.range.filesConsidered} |`,
2174
- `| \`outcome\` | \`${tableCell(p.range.outcome)}\` |`,
2175
- '',
2176
- REVIEW_SECTION_HEADING,
2177
- '',
2178
- '| Practice | Verdict | Reason / evidence | Source |',
2179
- '| --- | --- | --- | --- |',
2180
- ...rows,
2181
- '',
2182
- '## Not Derived — the gaps',
2183
- '',
2184
- '| Gap | Statement |',
2185
- '| --- | --- |',
2186
- ...reviewGapRows(p).map(([gap, statement]) => `| ${gap} | ${tableCell(statement)} |`),
2187
- '',
2188
- '## Derivation Statements',
2189
- '',
2190
- ...(p.statements.length + p.notes.length === 0
2191
- ? ['none.']
2192
- : [...p.statements.map((st) => `- ${st}`), ...p.notes.map((n) => `- Note: ${n}`)]),
2193
- '',
2194
- '## Limits of This Review',
2195
- '',
2196
- '- Nothing in this artifact gates anything. A `FAIL` is a report to a human, not a halt.',
2197
- '- A verdict is one agent run\'s judgement; reproducibility is not claimed.',
2198
- '- The non-verdict rate is reported, not judged: there is no threshold at which a review is declared',
2199
- ' low-confidence.',
2200
- '- A practice absent from the register was not in the input set; the gaps table says why. Absence',
2201
- ' is not a statement that it was followed.',
2202
- '',
2203
- '## Practice Text (client reference data)',
2204
- '',
2205
- ...(p.practiceEntries.length === 0 ? ['none.'] : p.practiceEntries.flatMap((e) => [e, ''])),
2206
- '',
2207
- ];
2208
- return stampReviewLines(lines.join('\n'), { coverageLine: p.mechanicalCoverageLine ?? null }).text;
2209
- }
2210
- /** Index of the first unfenced line equal to `heading`, or -1. */
2211
- function unfencedHeadingIndex(lines, heading) {
2212
- const fenced = fencedLineMap(lines);
2213
- return lines.findIndex((l, i) => !fenced[i] && l.trim() === heading);
2214
- }
2215
- /**
2216
- * Write the three computed lines into an artifact — the ONLY writer of them.
2217
- *
2218
- * Every unfenced rate- or counts-shaped line inside the verdicts section, and
2219
- * every unfenced coverage-shaped line anywhere, is removed; then the rate and
2220
- * counts lines the validator computes from the section's own rows are written
2221
- * directly under the section heading, and the payload's coverage line (when it
2222
- * carries one) at the end of `## Range` — or above the verdicts section when
2223
- * there is no `## Range`. A narrated figure therefore cannot survive, and the
2224
- * two count populations (the payload's derived rows and the register's rows)
2225
- * cannot be confused by anyone: the register's lines are always the register's.
2226
- *
2227
- * Fenced client prose is never touched. Without the verdicts heading nothing is
2228
- * stamped and the validator reports the absent section.
2229
- */
2230
- function stampReviewLines(text, opts = {}) {
2231
- if (typeof text !== 'string')
2232
- return { text: '', stamped: false };
2233
- const src = text.split(/\r?\n/);
2234
- const section = locateReviewSection(src);
2235
- if (!section.present)
2236
- return { text, stamped: false };
2237
- const fenced = fencedLineMap(src);
2238
- const kept = [];
2239
- for (let i = 0; i < src.length; i += 1) {
2240
- const t = src[i].trim();
2241
- if (!fenced[i]) {
2242
- if (COVERAGE_LINE_SHAPE.test(t))
2243
- continue;
2244
- if (i >= section.start && i < section.end && (RATE_LINE_SHAPE.test(t) || COUNTS_LINE_SHAPE.test(t)))
2245
- continue;
2246
- }
2247
- kept.push(src[i]);
2248
- }
2249
- const probe = validateReviewArtifact(kept.join('\n'));
2250
- const head = unfencedHeadingIndex(kept, REVIEW_SECTION_HEADING);
2251
- let after = head + 1;
2252
- while (after < kept.length && kept[after].trim() === '')
2253
- after += 1;
2254
- kept.splice(head + 1, after - (head + 1), '', probe.expectedRateLine, probe.expectedCountsLine, '');
2255
- const coverage = typeof opts.coverageLine === 'string' ? opts.coverageLine.trim() : '';
2256
- if (coverage !== '' && COVERAGE_LINE_SHAPE.test(coverage)) {
2257
- const fencedNow = fencedLineMap(kept);
2258
- const range = unfencedHeadingIndex(kept, RANGE_SECTION_HEADING);
2259
- let at = unfencedHeadingIndex(kept, REVIEW_SECTION_HEADING);
2260
- if (range !== -1) {
2261
- at = kept.length;
2262
- for (let i = range + 1; i < kept.length; i += 1) {
2263
- if (!fencedNow[i] && (kept[i].startsWith('## ') || kept[i].trim() === '---')) {
2264
- at = i;
2265
- break;
2266
- }
2267
- }
2268
- while (at > range + 1 && kept[at - 1].trim() === '')
2269
- at -= 1;
2270
- kept.splice(at, 0, '', coverage);
2271
- }
2272
- else {
2273
- kept.splice(at, 0, coverage, '');
2274
- }
2275
- }
2276
- // Removing a line leaves its blank neighbours behind; collapse every unfenced
2277
- // run of blank lines to one, so stamping is idempotent.
2278
- const fencedOut = fencedLineMap(kept);
2279
- const out = kept.filter((l, i) => fencedOut[i] || l.trim() !== '' || i === 0 || kept[i - 1].trim() !== '');
2280
- return { text: out.join('\n'), stamped: true };
2281
- }
2282
- /**
2283
- * The register's rows against the population the payload derived: every
2284
- * practice of `partition.population` (remainder plus both derived arms) has
2285
- * exactly one row, and no row names anything else. Deleting a row is how a
2286
- * hard practice disappears from a review; this makes that a reported problem.
2287
- */
2288
- function compareReviewPopulation(rows, partition) {
2289
- const expected = uniqueInOrder([
2290
- ...partition.remainder,
2291
- ...partition.naUniverse.map((r) => r.id),
2292
- ...partition.naNoFile.map((r) => r.id),
2293
- ]);
2294
- const want = new Set(expected.map((id) => quotableId(id)));
2295
- const seen = new Map();
2296
- for (const r of rows) {
2297
- if (typeof r?.id !== 'string' || r.id === '')
2298
- continue;
2299
- seen.set(r.id, (seen.get(r.id) ?? 0) + 1);
2300
- }
2301
- const problems = [];
2302
- for (const id of want) {
2303
- if (!seen.has(id)) {
2304
- problems.push(reviewProblem(REVIEW_PROBLEM_KIND.ROW_POPULATION_MISSING, id, null, `"${id}" is in the population this review derived and has no row: a practice handed to the review ` +
2305
- 'is judged or reproduced, never dropped.'));
2306
- }
2307
- }
2308
- for (const [id, n] of seen) {
2309
- if (!want.has(id)) {
2310
- problems.push(reviewProblem(REVIEW_PROBLEM_KIND.ROW_POPULATION_UNEXPECTED, id, null, `"${safe(id)}" has a row but is not in the population this review derived, so its verdict judges ` +
2311
- 'something the review was not handed.'));
2312
- }
2313
- if (n > 1) {
2314
- problems.push(reviewProblem(REVIEW_PROBLEM_KIND.ROW_DUPLICATE, id, null, `"${safe(id)}" carries ${n} rows; each practice carries exactly one verdict.`));
2315
- }
2316
- }
2317
- return problems;
2318
- }
2319
2067
  module.exports = {
2320
2068
  // The three frozen vocabularies. These ARE the contract 06-04/06-06/06-08 and
2321
2069
  // 06-10 serialize; nothing downstream re-derives a token.
@@ -2351,9 +2099,4 @@ module.exports = {
2351
2099
  // `cmdVaultAudit` states about its own leaf: a second composition is a second
2352
2100
  // place the report can disagree with the derivation it claims to report.
2353
2101
  buildReviewPayload,
2354
- // The artifact the agent fills in, and the three lines only code writes.
2355
- REVIEW_UNJUDGED_VERDICT,
2356
- renderReviewScaffold,
2357
- stampReviewLines,
2358
- compareReviewPopulation,
2359
2102
  };