@haposoft/cafekit 0.16.7 → 0.18.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 (231) hide show
  1. package/README.md +14 -22
  2. package/bin/__tests__/codex-hooks-ownership.test.js +2 -2
  3. package/bin/__tests__/codex-hooks.test.js +169 -272
  4. package/bin/__tests__/codex-native.test.js +100 -202
  5. package/bin/__tests__/develop-contract.test.js +122 -1413
  6. package/bin/__tests__/docs-sync-on-demand.test.js +93 -0
  7. package/bin/__tests__/grok-envelope.test.js +0 -56
  8. package/bin/__tests__/hook-payload.test.js +4 -6
  9. package/bin/__tests__/legacy-authority-decoupled.test.js +154 -0
  10. package/bin/__tests__/legacy-hooks-retired.test.js +221 -0
  11. package/bin/__tests__/omp-bridge.test.js +20 -17
  12. package/bin/__tests__/omp-hooks.test.js +0 -19
  13. package/bin/__tests__/optional-skill-inventory.test.js +112 -4
  14. package/bin/__tests__/orca-session.test.js +0 -2
  15. package/bin/__tests__/package-inventory.test.js +90 -925
  16. package/bin/__tests__/runtime-dir.test.js +0 -3
  17. package/bin/__tests__/runtime-schema.test.js +3 -2
  18. package/bin/__tests__/skill-routing-source.test.js +27 -15
  19. package/bin/__tests__/spec-narrowing.test.js +30 -161
  20. package/bin/__tests__/specs-v2-execution-closeout.test.js +0 -437
  21. package/bin/__tests__/test-runtime-dependency-closure.cjs +1 -2
  22. package/bin/__tests__/ui-ux-skill.test.js +101 -0
  23. package/bin/__tests__/usage-hook-retired.test.js +114 -0
  24. package/bin/__tests__/validator-grounding.test.js +18 -2949
  25. package/bin/lib/managed-writer.js +2 -3
  26. package/bin/phases/codex-runtime.js +27 -0
  27. package/bin/phases/copy-payload.js +13 -28
  28. package/bin/phases/omp-runtime.js +25 -0
  29. package/package.json +2 -3
  30. package/src/claude/CLAUDE.md +3 -10
  31. package/src/claude/agents/brainstormer.md +7 -3
  32. package/src/claude/agents/code-auditor.md +44 -56
  33. package/src/claude/agents/debugger.md +8 -88
  34. package/src/claude/agents/deployer.md +3 -5
  35. package/src/claude/agents/docs-keeper.md +6 -14
  36. package/src/claude/agents/git-ops.md +2 -4
  37. package/src/claude/agents/implementer.md +12 -16
  38. package/src/claude/agents/inspector.md +4 -3
  39. package/src/claude/agents/project-manager.md +9 -17
  40. package/src/claude/agents/researcher.md +9 -3
  41. package/src/claude/agents/spec-maker.md +15 -15
  42. package/src/claude/agents/test-runner.md +8 -11
  43. package/src/claude/agents/ui-ux-designer.md +0 -1
  44. package/src/claude/hooks/__tests__/fixtures/statusline-default.golden +1 -1
  45. package/src/claude/hooks/__tests__/spec-gate.test.js +244 -333
  46. package/src/claude/hooks/__tests__/spec-state-touched.test.js +160 -0
  47. package/src/claude/hooks/__tests__/state.test.js +68 -0
  48. package/src/claude/hooks/__tests__/statusline.test.js +140 -11
  49. package/src/claude/hooks/agent.cjs +2 -1
  50. package/src/claude/hooks/lib/hook-payload.cjs +2 -2
  51. package/src/claude/hooks/rules.cjs +2 -1
  52. package/src/claude/hooks/session.cjs +0 -1
  53. package/src/claude/hooks/spec-gate.cjs +17 -83
  54. package/src/claude/hooks/spec-state.cjs +142 -39
  55. package/src/claude/hooks/state.cjs +21 -2
  56. package/src/claude/migration-manifest.json +42 -22
  57. package/src/claude/output-styles/coding-level-0-eli5.md +9 -9
  58. package/src/claude/output-styles/coding-level-1-junior.md +3 -3
  59. package/src/claude/output-styles/coding-level-2-mid.md +3 -3
  60. package/src/claude/output-styles/coding-level-3-senior.md +2 -2
  61. package/src/claude/output-styles/coding-level-4-lead.md +5 -5
  62. package/src/claude/output-styles/coding-level-5-god.md +6 -6
  63. package/src/claude/rules/ai-dev-rules.md +2 -3
  64. package/src/claude/rules/hook-protocols.md +8 -15
  65. package/src/claude/rules/manage-docs.md +9 -23
  66. package/src/claude/rules/orchestrator.md +3 -4
  67. package/src/claude/rules/review-audit-self-decision.md +3 -3
  68. package/src/claude/rules/skill-domain-routing.md +1 -3
  69. package/src/claude/rules/skill-workflow-routing.md +4 -5
  70. package/src/claude/rules/state-sync.md +3 -12
  71. package/src/claude/rules/workflow.md +11 -19
  72. package/src/claude/runtime.schema.json +5 -3
  73. package/src/claude/scripts/generate-skill-catalog.cjs +7 -6
  74. package/src/claude/scripts/provenance.cjs +23 -5
  75. package/src/claude/scripts/spec-receipt.cjs +24 -121
  76. package/src/claude/scripts/spec-resolver.cjs +41 -458
  77. package/src/claude/scripts/workflow-policy.cjs +3 -1801
  78. package/src/claude/settings/settings.json +1 -32
  79. package/src/claude/skills/agent-browser/SKILL.md +1 -1
  80. package/src/claude/skills/{question → ask}/SKILL.md +9 -75
  81. package/src/claude/skills/brainstorm/SKILL.md +43 -131
  82. package/src/claude/skills/chrome-devtools/SKILL.md +14 -31
  83. package/src/claude/skills/code-review/SKILL.md +29 -81
  84. package/src/claude/skills/code-review/references/spec-compliance-review.md +4 -8
  85. package/src/claude/skills/debug/SKILL.md +29 -149
  86. package/src/claude/skills/develop/SKILL.md +26 -83
  87. package/src/claude/skills/develop/references/parallel-waves.md +0 -8
  88. package/src/claude/skills/develop/references/quality-gate.md +15 -13
  89. package/src/claude/skills/develop/references/subagent-patterns.md +0 -6
  90. package/src/claude/skills/docs/SKILL.md +2 -2
  91. package/src/claude/skills/docs/references/update-workflow.md +1 -1
  92. package/src/claude/skills/fix/SKILL.md +153 -0
  93. package/src/claude/skills/{hotfix → fix}/references/diagnosis-protocol.md +2 -2
  94. package/src/claude/skills/{hotfix → fix}/references/parallel-patterns.md +2 -2
  95. package/src/claude/skills/{hotfix → fix}/references/prevention-gate.md +3 -3
  96. package/src/claude/skills/{hotfix → fix}/references/review-cycle.md +1 -1
  97. package/src/claude/skills/git/SKILL.md +48 -9
  98. package/src/claude/skills/git/references/commit-protocols.md +35 -16
  99. package/src/claude/skills/git/references/worktree-blueprint.md +42 -6
  100. package/src/claude/skills/orca/SKILL.md +9 -8
  101. package/src/claude/skills/research/SKILL.md +17 -46
  102. package/src/claude/skills/scout/SKILL.md +61 -0
  103. package/src/claude/skills/{inspect → scout}/references/internal-inspection.md +26 -10
  104. package/src/claude/skills/specs/SKILL.md +22 -29
  105. package/src/claude/skills/specs/references/review.md +8 -6
  106. package/src/claude/skills/specs/references/templates.md +34 -41
  107. package/src/claude/skills/sync/SKILL.md +16 -45
  108. package/src/claude/skills/sync/references/rebind-and-audit.md +59 -0
  109. package/src/claude/skills/sync/references/sync-protocols.md +4 -13
  110. package/src/claude/skills/test/SKILL.md +39 -104
  111. package/src/claude/skills/test/references/execution-strategy.md +93 -21
  112. package/src/claude/skills/test/references/failure-triage.md +1 -1
  113. package/src/claude/skills/ui-ux/LICENSE +21 -0
  114. package/src/claude/skills/ui-ux/SKILL.md +497 -0
  115. package/src/claude/skills/ui-ux/references/brand-tokens.md +90 -0
  116. package/src/claude/skills/ui-ux/references/budgets.md +113 -0
  117. package/src/claude/skills/ui-ux/references/checklist.md +271 -0
  118. package/src/claude/skills/ui-ux/references/components/accordion.md +246 -0
  119. package/src/claude/skills/ui-ux/references/components/avatar.md +153 -0
  120. package/src/claude/skills/ui-ux/references/components/banner.md +43 -0
  121. package/src/claude/skills/ui-ux/references/components/breadcrumb.md +201 -0
  122. package/src/claude/skills/ui-ux/references/components/button.md +142 -0
  123. package/src/claude/skills/ui-ux/references/components/card.md +113 -0
  124. package/src/claude/skills/ui-ux/references/components/charts.md +376 -0
  125. package/src/claude/skills/ui-ux/references/components/chat.md +132 -0
  126. package/src/claude/skills/ui-ux/references/components/choice-controls.md +369 -0
  127. package/src/claude/skills/ui-ux/references/components/comment-thread.md +17 -0
  128. package/src/claude/skills/ui-ux/references/components/description-list.md +116 -0
  129. package/src/claude/skills/ui-ux/references/components/empty-state.md +98 -0
  130. package/src/claude/skills/ui-ux/references/components/file-upload.md +107 -0
  131. package/src/claude/skills/ui-ux/references/components/inline-edit.md +128 -0
  132. package/src/claude/skills/ui-ux/references/components/input.md +161 -0
  133. package/src/claude/skills/ui-ux/references/components/list-row.md +56 -0
  134. package/src/claude/skills/ui-ux/references/components/loading.md +198 -0
  135. package/src/claude/skills/ui-ux/references/components/logo.md +147 -0
  136. package/src/claude/skills/ui-ux/references/components/otp-input.md +43 -0
  137. package/src/claude/skills/ui-ux/references/components/quantity-input.md +100 -0
  138. package/src/claude/skills/ui-ux/references/components/range-slider.md +16 -0
  139. package/src/claude/skills/ui-ux/references/components/small-controls.md +340 -0
  140. package/src/claude/skills/ui-ux/references/components/sortable-header.md +86 -0
  141. package/src/claude/skills/ui-ux/references/components/tag-input.md +14 -0
  142. package/src/claude/skills/ui-ux/references/components/timeline.md +38 -0
  143. package/src/claude/skills/ui-ux/references/components/tree.md +30 -0
  144. package/src/claude/skills/ui-ux/references/design-process.md +650 -0
  145. package/src/claude/skills/ui-ux/references/layouts/app-kanban.html +303 -0
  146. package/src/claude/skills/ui-ux/references/layouts/app.md +1369 -0
  147. package/src/claude/skills/ui-ux/references/layouts/form.md +430 -0
  148. package/src/claude/skills/ui-ux/references/layouts/overlay.md +552 -0
  149. package/src/claude/skills/ui-ux/references/layouts/pricing.md +204 -0
  150. package/src/claude/skills/ui-ux/references/locked-rules.md +35 -0
  151. package/src/claude/skills/ui-ux/references/principles.md +408 -0
  152. package/src/claude/skills/ui-ux/references/refactor.md +245 -0
  153. package/src/claude/skills/ui-ux/references/responsive.md +195 -0
  154. package/src/claude/skills/ui-ux/references/review.md +528 -0
  155. package/src/claude/skills/ui-ux/references/rules-color.md +623 -0
  156. package/src/claude/skills/ui-ux/references/rules-form.md +235 -0
  157. package/src/claude/skills/ui-ux/references/rules-state.md +680 -0
  158. package/src/claude/skills/ui-ux/references/rules-type.md +369 -0
  159. package/src/claude/skills/ui-ux/references/styles.md +311 -0
  160. package/src/claude/skills/ui-ux/references/system.md +219 -0
  161. package/src/claude/skills/ui-ux/references/tailwind-v4-traps.md +237 -0
  162. package/src/claude/skills/ui-ux/references/tokens.css +362 -0
  163. package/src/claude/skills/ui-ux/scripts/lint-skill.mjs +216 -0
  164. package/src/claude/skills/ui-ux/scripts/package.json +9 -0
  165. package/src/claude/skills/ui-ux/scripts/probe.mjs +4447 -0
  166. package/src/claude/skills/ui-ux-pro-max/SKILL.md +25 -80
  167. package/src/claude/skills/web-testing/SKILL.md +2 -20
  168. package/src/claude/status.cjs +149 -144
  169. package/src/codex/AGENTS.md +8 -13
  170. package/src/codex/hooks/agent.cjs +2 -1
  171. package/src/codex/hooks/lib/spec-receipt.cjs +4 -32
  172. package/src/codex/hooks/lib/spec-utils.cjs +6 -52
  173. package/src/codex/hooks/rules.cjs +2 -1
  174. package/src/codex/hooks/session.cjs +0 -2
  175. package/src/codex/hooks/spec-gate.cjs +13 -81
  176. package/src/codex/hooks/spec-state.cjs +37 -41
  177. package/src/codex/hooks.json +0 -20
  178. package/src/codex/rules/state-sync.md +3 -12
  179. package/src/common/AGENTS.md +6 -12
  180. package/src/omp/extensions/cafekit-bridge.mjs +6 -6
  181. package/bin/__tests__/change-firewall.semantic-firewall.test.js +0 -2133
  182. package/bin/__tests__/finalizer-pass-gate.semantic-firewall.test.js +0 -342
  183. package/bin/__tests__/release-preflight.semantic-firewall.test.js +0 -586
  184. package/bin/__tests__/review-receipt-c2.semantic-firewall.test.js +0 -378
  185. package/bin/__tests__/specs-v2-policy-and-scaffold.test.js +0 -665
  186. package/bin/__tests__/specs-v2-validator-grounder-contract.test.js +0 -1132
  187. package/src/claude/hooks/__tests__/completion-authority.test.js +0 -990
  188. package/src/claude/hooks/__tests__/semantic-review-authority.test.js +0 -259
  189. package/src/claude/hooks/completion-authority-check.cjs +0 -56
  190. package/src/claude/hooks/completion-authority-state.cjs +0 -527
  191. package/src/claude/hooks/completion-authority.cjs +0 -163
  192. package/src/claude/hooks/lib/context.cjs +0 -616
  193. package/src/claude/hooks/lib/detect.cjs +0 -474
  194. package/src/claude/hooks/semantic-review-authority.cjs +0 -154
  195. package/src/claude/hooks/task-scaffold-guard.cjs +0 -114
  196. package/src/claude/hooks/usage.cjs +0 -197
  197. package/src/claude/scripts/change-firewall.cjs +0 -1748
  198. package/src/claude/scripts/spec-authoring-digest.cjs +0 -151
  199. package/src/claude/scripts/spec-authoring-validation.cjs +0 -126
  200. package/src/claude/scripts/spec-final-state.cjs +0 -290
  201. package/src/claude/scripts/spec-ground.cjs +0 -650
  202. package/src/claude/scripts/spec-readiness.cjs +0 -425
  203. package/src/claude/scripts/spec-scaffold.cjs +0 -2317
  204. package/src/claude/scripts/spec-semantic-model.cjs +0 -396
  205. package/src/claude/scripts/validate-spec-output.cjs +0 -3941
  206. package/src/claude/skills/code-review/references/verification-gate.md +0 -46
  207. package/src/claude/skills/delegate/SKILL.md +0 -124
  208. package/src/claude/skills/delegate/references/codex-delegation.md +0 -71
  209. package/src/claude/skills/delegate/references/grok-delegation.md +0 -84
  210. package/src/claude/skills/hotfix/SKILL.md +0 -289
  211. package/src/claude/skills/inspect/SKILL.md +0 -142
  212. package/src/claude/skills/loop/SKILL.md +0 -114
  213. package/src/claude/skills/loop/references/bounded-loop-protocol.md +0 -104
  214. package/src/claude/skills/loop/references/metric-and-guard-contract.md +0 -76
  215. package/src/claude/skills/route/SKILL.md +0 -94
  216. package/src/claude/skills/route/references/agent-timing.md +0 -46
  217. package/src/claude/skills/route/references/chaining-patterns.md +0 -53
  218. package/src/claude/skills/route/references/task-taxonomy.md +0 -48
  219. package/src/claude/skills/specs/templates/design.md +0 -82
  220. package/src/claude/skills/specs/templates/requirements-init.md +0 -8
  221. package/src/claude/skills/specs/templates/requirements.md +0 -42
  222. package/src/claude/skills/specs/templates/spec-state.json +0 -56
  223. package/src/claude/skills/specs/templates/task.md +0 -47
  224. package/src/codex/hooks/completion-authority-check.cjs +0 -67
  225. package/src/codex/hooks/completion-authority-state.cjs +0 -527
  226. package/src/codex/hooks/completion-authority.cjs +0 -109
  227. package/src/codex/hooks/semantic-review-authority.cjs +0 -130
  228. package/src/codex/hooks/task-scaffold-guard.cjs +0 -63
  229. /package/src/claude/skills/{question → ask}/templates/question.md +0 -0
  230. /package/src/claude/skills/{hotfix → fix}/references/escalation-tactics.md +0 -0
  231. /package/src/claude/skills/{hotfix → fix}/references/workflow-specialized.md +0 -0
@@ -1,342 +0,0 @@
1
- 'use strict';
2
-
3
- const test = require('node:test');
4
- const assert = require('node:assert/strict');
5
- const fs = require('fs');
6
- const os = require('os');
7
- const path = require('path');
8
-
9
- const { finalizeReadiness } = require('../../src/claude/scripts/spec-readiness.cjs');
10
-
11
- const SEMANTIC_DIGEST = `sha256:${'a'.repeat(64)}`;
12
- const FEATURE_NAME = 'cafekit-semantic-eval-firewall';
13
- const CREATED_AT = '2026-08-17T00:00:00.000Z';
14
- const AUTHORING_VALIDATION = Object.freeze({
15
- schema_version: '1',
16
- requirements: { digest: `sha256:${'b'.repeat(64)}`, validated_at: CREATED_AT },
17
- design: { digest: `sha256:${'c'.repeat(64)}`, validated_at: CREATED_AT },
18
- research: { digest: `sha256:${'d'.repeat(64)}`, validated_at: CREATED_AT },
19
- tasks: { digest: `sha256:${'e'.repeat(64)}`, validated_at: CREATED_AT },
20
- });
21
-
22
- function review(verdict = 'PASS', overrides = {}) {
23
- return {
24
- verdict,
25
- findings: [],
26
- unresolved_decisions: [],
27
- graph_coverage: [{
28
- surface: 'criterion_local',
29
- covered: true,
30
- notes: 'criterion was reviewed against the canonical semantic model',
31
- }],
32
- reviewed_criteria: ['R1.1'],
33
- counterexamples: [],
34
- reviewer_evidence: null,
35
- ...overrides,
36
- };
37
- }
38
-
39
- function finding(id, blocking = false) {
40
- return {
41
- id,
42
- severity: 'High',
43
- location: 'spec.json:R1.1',
44
- summary: 'The semantic review requires an explicit disposition',
45
- blocking,
46
- disposition: 'open',
47
- };
48
- }
49
-
50
- function decision(id, blocking = false) {
51
- return {
52
- id,
53
- summary: 'The unresolved decision needs an owner and a recorded outcome',
54
- blocking,
55
- };
56
- }
57
-
58
- function stableJson(value) {
59
- if (Array.isArray(value)) return `[${value.map(stableJson).join(',')}]`;
60
- if (value && typeof value === 'object') {
61
- return `{${Object.keys(value).sort().map((key) => (
62
- `${JSON.stringify(key)}:${stableJson(value[key])}`
63
- )).join(',')}}`;
64
- }
65
- return JSON.stringify(value);
66
- }
67
-
68
- function dependencies({ staleAuthoring = false } = {}) {
69
- const calls = { digest: 0, validate: 0, ground: 0 };
70
- const expectedAuthoring = JSON.stringify(AUTHORING_VALIDATION);
71
- return {
72
- calls,
73
- semantic: {
74
- modelFromMarkdown: () => ({ model: { projected: true }, errors: [] }),
75
- stableJson,
76
- },
77
- validator: {
78
- computeSemanticDigest21: () => {
79
- calls.digest += 1;
80
- return { errors: [], digest: SEMANTIC_DIGEST };
81
- },
82
- validateSpec: (_directory, candidate) => {
83
- calls.validate += 1;
84
- const preserved = JSON.stringify(candidate.validation.authoring_validation) === expectedAuthoring;
85
- return { errors: staleAuthoring || !preserved ? ['authoring validation is stale'] : [], warnings: [] };
86
- },
87
- },
88
- grounder: () => {
89
- calls.ground += 1;
90
- return { errors: [] };
91
- },
92
- };
93
- }
94
-
95
- function fixture({ identity = { feature_name: FEATURE_NAME, created_at: CREATED_AT },
96
- semanticReviewHistory, authoringValidation = AUTHORING_VALIDATION } = {}) {
97
- const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-readiness-'));
98
- const validation = {
99
- status: 'completed',
100
- semantic_review: null,
101
- authoring_validation: authoringValidation,
102
- };
103
- if (semanticReviewHistory !== undefined) validation.semantic_review_history = semanticReviewHistory;
104
- const spec = {
105
- schema_version: '2.1',
106
- ...identity,
107
- semantic_model: { projected: false },
108
- validation,
109
- ready_for_implementation: false,
110
- timestamps: {
111
- init: CREATED_AT,
112
- requirements_done: CREATED_AT,
113
- design_done: CREATED_AT,
114
- validation_done: CREATED_AT,
115
- },
116
- updated_at: CREATED_AT,
117
- };
118
- fs.writeFileSync(path.join(directory, 'spec.json'), `${JSON.stringify(spec, null, 2)}\n`);
119
- return { directory, spec };
120
- }
121
-
122
- function finalize(directory, reviewResult, options = {}) {
123
- const injected = dependencies(options);
124
- const result = finalizeReadiness({
125
- specDir: directory,
126
- projectRoot: directory,
127
- reviewResult,
128
- ...injected,
129
- });
130
- return { result, calls: injected.calls };
131
- }
132
-
133
- function cleanup(directory) {
134
- fs.rmSync(directory, { recursive: true, force: true });
135
- }
136
-
137
- test('explicit PASS builds a completed C2 receipt, preserves C16, and is ready', () => {
138
- const { directory } = fixture();
139
- try {
140
- const { result, calls } = finalize(directory, review('PASS'));
141
- assert.equal(result.spec.ready_for_implementation, true);
142
- assert.equal(result.spec.validation.semantic_review.status, 'completed');
143
- assert.equal(result.spec.validation.semantic_review.verdict, 'PASS');
144
- assert.deepEqual(result.spec.validation.semantic_review.findings, []);
145
- assert.deepEqual(result.spec.validation.authoring_validation, AUTHORING_VALIDATION);
146
- assert.match(result.history_entry.review_receipt_digest, /^sha256:[0-9a-f]{64}$/);
147
- assert.equal(calls.validate, 2);
148
- assert.equal(calls.ground, 2);
149
- } finally {
150
- cleanup(directory);
151
- }
152
- });
153
-
154
- test('explicit FAIL is authoritative even when there are no blockers', () => {
155
- const { directory } = fixture();
156
- try {
157
- const { result } = finalize(directory, review('FAIL'));
158
- assert.equal(result.spec.ready_for_implementation, false);
159
- assert.equal(result.history_entry.verdict, 'FAIL');
160
- assert.equal(result.history_entry.blocking_count, 0);
161
- assert.equal(result.history_entry.lifecycle_disposition, 'CONTINUE');
162
- } finally {
163
- cleanup(directory);
164
- }
165
- });
166
-
167
- test('PASS with blocking findings or decisions is refused', () => {
168
- for (const overrides of [
169
- { findings: [finding('F-BLOCKING', true)] },
170
- { unresolved_decisions: [decision('D-BLOCKING', true)] },
171
- ]) {
172
- const { directory } = fixture();
173
- try {
174
- assert.throws(
175
- () => finalize(directory, review('PASS', overrides)),
176
- /PASS cannot contain blocking/,
177
- );
178
- const persisted = JSON.parse(fs.readFileSync(path.join(directory, 'spec.json'), 'utf8'));
179
- assert.equal(persisted.validation.semantic_review_history, undefined);
180
- } finally {
181
- cleanup(directory);
182
- }
183
- }
184
- });
185
-
186
- test('blocking_count counts blocking findings and unresolved decisions', () => {
187
- const { directory } = fixture();
188
- try {
189
- const { result } = finalize(directory, review('FAIL', {
190
- findings: [finding('F-BLOCKING', true), finding('F-OPEN', false)],
191
- unresolved_decisions: [decision('D-BLOCKING', true), decision('D-OPEN', false)],
192
- }));
193
- assert.equal(result.history_entry.blocking_count, 2);
194
- } finally {
195
- cleanup(directory);
196
- }
197
- });
198
-
199
- test('caller digest and derived/control fields are rejected from public input', () => {
200
- const fields = [
201
- 'review_receipt_digest', 'lifecycle_disposition', 'repair_round', 'review_epoch',
202
- 'attempt_index', 'semantic_review_history', 'lineage', 'readiness',
203
- 'ready_for_implementation', 'validation', 'authoring_validation',
204
- ];
205
- for (const field of fields) {
206
- const { directory } = fixture();
207
- try {
208
- const invalid = { ...review(), [field]: field };
209
- assert.throws(() => finalize(directory, invalid), /fields must be exactly/);
210
- } finally {
211
- cleanup(directory);
212
- }
213
- }
214
- });
215
-
216
- test('lineage is deterministic and divergent or missing identity is rejected', () => {
217
- const firstFixture = fixture();
218
- const secondFixture = fixture();
219
- const divergentFixture = fixture({
220
- semanticReviewHistory: { lineage_id: `sha256:${'f'.repeat(64)}`, entries: [] },
221
- });
222
- const missingFixture = fixture({ identity: { created_at: CREATED_AT } });
223
- try {
224
- const first = finalize(firstFixture.directory, review()).result;
225
- const second = finalize(secondFixture.directory, review()).result;
226
- assert.equal(
227
- first.spec.validation.semantic_review_history.lineage_id,
228
- second.spec.validation.semantic_review_history.lineage_id,
229
- );
230
- assert.match(first.spec.validation.semantic_review_history.lineage_id, /^sha256:[0-9a-f]{64}$/);
231
- assert.throws(
232
- () => finalize(divergentFixture.directory, review()),
233
- /lineage_id diverges/,
234
- );
235
- assert.throws(
236
- () => finalize(missingFixture.directory, review()),
237
- /feature_name is required/,
238
- );
239
- } finally {
240
- cleanup(firstFixture.directory);
241
- cleanup(secondFixture.directory);
242
- cleanup(divergentFixture.directory);
243
- cleanup(missingFixture.directory);
244
- }
245
- });
246
-
247
- test('replaying a receipt is idempotent across the whole lineage', () => {
248
- const { directory } = fixture();
249
- try {
250
- finalize(directory, review('PASS'));
251
- const before = fs.readFileSync(path.join(directory, 'spec.json'), 'utf8');
252
- const replay = finalize(directory, review('PASS')).result;
253
- const after = fs.readFileSync(path.join(directory, 'spec.json'), 'utf8');
254
- assert.equal(replay.replayed, true);
255
- assert.equal(replay.spec.validation.semantic_review_history.entries.length, 1);
256
- assert.equal(after, before);
257
- } finally {
258
- cleanup(directory);
259
- }
260
- });
261
-
262
- test('replaying an earlier receipt dedupes across the whole lineage', () => {
263
- const { directory } = fixture();
264
- try {
265
- finalize(directory, review('PASS', { reviewed_criteria: ['R1.1'] }));
266
- finalize(directory, review('FAIL', { reviewed_criteria: ['R1.2'] }));
267
- const specFile = path.join(directory, 'spec.json');
268
- const beforeReplay = fs.readFileSync(specFile);
269
- const replay = finalize(directory, review('PASS', { reviewed_criteria: ['R1.1'] })).result;
270
- const afterReplay = fs.readFileSync(specFile);
271
- assert.equal(replay.replayed, true);
272
- assert.equal(replay.history_entry.sequence, 0);
273
- assert.equal(replay.history_entry.review_epoch, 0);
274
- assert.equal(replay.history_entry.attempt_index, 0);
275
- assert.equal(replay.spec.validation.semantic_review_history.entries.length, 2);
276
- assert.deepEqual(afterReplay, beforeReplay);
277
- } finally {
278
- cleanup(directory);
279
- }
280
- });
281
-
282
- test('a PASS starts the next epoch with a reset FAIL attempt index', () => {
283
- const { directory } = fixture();
284
- try {
285
- finalize(directory, review('PASS'));
286
- finalize(directory, review('FAIL', { reviewed_criteria: ['R1.2'] }));
287
- finalize(directory, review('PASS', { reviewed_criteria: ['R1.3'] }));
288
- const next = finalize(directory, review('FAIL', { reviewed_criteria: ['R1.4'] })).result;
289
- assert.equal(next.history_entry.review_epoch, 2);
290
- assert.equal(next.history_entry.attempt_index, 0);
291
- assert.equal(next.history_entry.lifecycle_disposition, 'CONTINUE');
292
- } finally {
293
- cleanup(directory);
294
- }
295
- });
296
-
297
- test('the third FAIL is terminal BLOCKED and there is no index 3', () => {
298
- const { directory } = fixture();
299
- try {
300
- finalize(directory, review('FAIL', { reviewed_criteria: ['R1.1'] }));
301
- finalize(directory, review('FAIL', { reviewed_criteria: ['R1.2'] }));
302
- const blocked = finalize(directory, review('FAIL', { reviewed_criteria: ['R1.3'] })).result;
303
- assert.equal(blocked.spec.ready_for_implementation, false);
304
- assert.equal(blocked.history_entry.attempt_index, 2);
305
- assert.equal(blocked.history_entry.lifecycle_disposition, 'BLOCKED');
306
- const specFile = path.join(directory, 'spec.json');
307
- const beforeReplay = fs.readFileSync(specFile);
308
- const replay = finalize(directory, review('FAIL', { reviewed_criteria: ['R1.3'] })).result;
309
- const afterReplay = fs.readFileSync(specFile);
310
- assert.equal(replay.replayed, true);
311
- assert.equal(replay.history_entry.attempt_index, 2);
312
- assert.deepEqual(afterReplay, beforeReplay);
313
- assert.throws(() => finalize(directory, review('PASS')), /already ends BLOCKED/);
314
- const persisted = JSON.parse(afterReplay.toString('utf8'));
315
- assert.deepEqual(
316
- persisted.validation.semantic_review_history.entries.map((entry) => entry.attempt_index),
317
- [0, 1, 2],
318
- );
319
- } finally {
320
- cleanup(directory);
321
- }
322
- });
323
-
324
- test('stale C16 authoring validation refuses the append', () => {
325
- const { directory } = fixture({
326
- authoringValidation: { ...AUTHORING_VALIDATION, design: { digest: 'stale' } },
327
- });
328
- try {
329
- const specFile = path.join(directory, 'spec.json');
330
- const before = fs.readFileSync(specFile);
331
- assert.throws(
332
- () => finalize(directory, review('PASS'), { staleAuthoring: true }),
333
- /readiness validation failed/,
334
- );
335
- const after = fs.readFileSync(specFile);
336
- assert.deepEqual(after, before);
337
- const persisted = JSON.parse(after.toString('utf8'));
338
- assert.equal(persisted.validation.semantic_review_history, undefined);
339
- } finally {
340
- cleanup(directory);
341
- }
342
- });