@haposoft/cafekit 0.18.0 → 0.18.2

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 (162) hide show
  1. package/README.md +12 -1
  2. package/bin/__tests__/claude-settings.test.js +123 -0
  3. package/bin/__tests__/codex-ask-guard.test.js +123 -0
  4. package/bin/__tests__/codex-ask-projection.test.js +51 -0
  5. package/bin/__tests__/codex-hooks-ownership.test.js +80 -1
  6. package/bin/__tests__/codex-hooks.test.js +938 -237
  7. package/bin/__tests__/codex-native-hook-boundaries.test.js +136 -0
  8. package/bin/__tests__/codex-native.test.js +222 -149
  9. package/bin/__tests__/codex-projection-parity.test.js +314 -0
  10. package/bin/__tests__/codex-quiet-hooks.test.js +170 -0
  11. package/bin/__tests__/codex-research-projection.test.js +70 -0
  12. package/bin/__tests__/codex-specs-projection.test.js +76 -0
  13. package/bin/__tests__/develop-contract.test.js +102 -1362
  14. package/bin/__tests__/docs-sync-on-demand.test.js +25 -20
  15. package/bin/__tests__/inspect-paths.test.js +183 -0
  16. package/bin/__tests__/installer-safety.test.js +335 -0
  17. package/bin/__tests__/legacy-authority-decoupled.test.js +14 -19
  18. package/bin/__tests__/legacy-hooks-retired.test.js +18 -8
  19. package/bin/__tests__/omp-bridge.test.js +66 -12
  20. package/bin/__tests__/omp-hooks.test.js +11 -5
  21. package/bin/__tests__/orca-session.test.js +7 -0
  22. package/bin/__tests__/package-inventory.test.js +1 -395
  23. package/bin/__tests__/privacy-paths.test.js +317 -0
  24. package/bin/__tests__/spec-narrowing.test.js +30 -134
  25. package/bin/__tests__/specs-v2-execution-closeout.test.js +0 -372
  26. package/bin/__tests__/test-runtime-dependency-closure.cjs +1 -2
  27. package/bin/__tests__/usage-hook-retired.test.js +5 -3
  28. package/bin/__tests__/validator-grounding.test.js +17 -2929
  29. package/bin/install.js +25 -0
  30. package/bin/lib/codex-install.js +134 -12
  31. package/bin/lib/context.js +1 -1
  32. package/bin/lib/managed-writer.js +2 -3
  33. package/bin/phases/claude-runtime.js +91 -25
  34. package/bin/phases/claude-settings.js +22 -1
  35. package/bin/phases/codex-hooks.js +96 -33
  36. package/bin/phases/codex-runtime.js +45 -49
  37. package/bin/phases/copy-payload.js +11 -28
  38. package/bin/phases/omp-runtime.js +6 -3
  39. package/package.json +2 -3
  40. package/src/claude/CLAUDE.md +4 -0
  41. package/src/claude/agents/code-auditor.md +9 -6
  42. package/src/claude/agents/debugger.md +3 -9
  43. package/src/claude/agents/docs-keeper.md +4 -7
  44. package/src/claude/agents/git-ops.md +2 -4
  45. package/src/claude/agents/implementer.md +6 -7
  46. package/src/claude/agents/inspector.md +2 -0
  47. package/src/claude/agents/project-manager.md +2 -2
  48. package/src/claude/agents/spec-maker.md +4 -4
  49. package/src/claude/agents/test-runner.md +8 -11
  50. package/src/claude/agents/ui-ux-designer.md +0 -1
  51. package/src/claude/gitignore +3 -0
  52. package/src/claude/hooks/__tests__/installed-root.test.js +111 -0
  53. package/src/claude/hooks/__tests__/precompact.test.js +206 -10
  54. package/src/claude/hooks/__tests__/privacy-block.test.js +27 -0
  55. package/src/claude/hooks/__tests__/session-writeenv.test.js +25 -0
  56. package/src/claude/hooks/__tests__/spec-gate.test.js +311 -340
  57. package/src/claude/hooks/__tests__/spec-state-touched.test.js +230 -8
  58. package/src/claude/hooks/__tests__/state.test.js +207 -7
  59. package/src/claude/hooks/__tests__/update-check.test.js +88 -0
  60. package/src/claude/hooks/agent.cjs +7 -6
  61. package/src/claude/hooks/inspect-block.cjs +15 -63
  62. package/src/claude/hooks/lib/inspect-paths.cjs +304 -0
  63. package/src/claude/hooks/lib/parser.cjs +1 -1
  64. package/src/claude/hooks/lib/privacy-command-analysis.cjs +191 -17
  65. package/src/claude/hooks/lib/privacy-paths.cjs +313 -0
  66. package/src/claude/hooks/lib/project-root.cjs +15 -0
  67. package/src/claude/hooks/lib/rules-reservation.cjs +36 -0
  68. package/src/claude/hooks/lib/spec-touch.cjs +67 -0
  69. package/src/claude/hooks/lib/update-check.cjs +77 -0
  70. package/src/claude/hooks/precompact.cjs +8 -15
  71. package/src/claude/hooks/privacy-block.cjs +23 -205
  72. package/src/claude/hooks/rules.cjs +6 -28
  73. package/src/claude/hooks/session.cjs +34 -117
  74. package/src/claude/hooks/spec-gate.cjs +17 -89
  75. package/src/claude/hooks/spec-state.cjs +89 -96
  76. package/src/claude/hooks/state.cjs +96 -51
  77. package/src/claude/migration-manifest.json +20 -8
  78. package/src/claude/output-styles/coding-level-0-eli5.md +9 -9
  79. package/src/claude/output-styles/coding-level-1-junior.md +3 -3
  80. package/src/claude/output-styles/coding-level-2-mid.md +3 -3
  81. package/src/claude/output-styles/coding-level-3-senior.md +2 -2
  82. package/src/claude/output-styles/coding-level-4-lead.md +5 -5
  83. package/src/claude/output-styles/coding-level-5-god.md +6 -6
  84. package/src/claude/rules/ai-dev-rules.md +1 -2
  85. package/src/claude/rules/hook-protocols.md +1 -1
  86. package/src/claude/rules/workflow.md +1 -1
  87. package/src/claude/runtime.json +0 -3
  88. package/src/claude/scripts/generate-skill-catalog.cjs +2 -2
  89. package/src/claude/scripts/provenance.cjs +14 -0
  90. package/src/claude/scripts/spec-receipt.cjs +0 -114
  91. package/src/claude/scripts/spec-resolver.cjs +41 -457
  92. package/src/claude/scripts/workflow-policy.cjs +3 -1801
  93. package/src/claude/settings/settings.json +1 -1
  94. package/src/claude/skills/agent-browser/SKILL.md +1 -1
  95. package/src/claude/skills/ai-multimodal/scripts/check_setup.py +8 -68
  96. package/src/claude/skills/ai-multimodal/scripts/gemini_batch_process.py +6 -28
  97. package/src/claude/skills/chrome-devtools/SKILL.md +14 -31
  98. package/src/claude/skills/code-review/SKILL.md +25 -108
  99. package/src/claude/skills/code-review/references/spec-compliance-review.md +4 -7
  100. package/src/claude/skills/develop/SKILL.md +22 -70
  101. package/src/claude/skills/develop/references/subagent-patterns.md +0 -6
  102. package/src/claude/skills/git/SKILL.md +1 -1
  103. package/src/claude/skills/git/references/worktree-blueprint.md +1 -1
  104. package/src/claude/skills/research/SKILL.md +24 -55
  105. package/src/claude/skills/sync/SKILL.md +12 -34
  106. package/src/claude/skills/sync/references/rebind-and-audit.md +2 -2
  107. package/src/claude/skills/test/SKILL.md +39 -123
  108. package/src/claude/skills/test/references/execution-strategy.md +11 -22
  109. package/src/claude/skills/ui-ux/SKILL.md +1 -1
  110. package/src/claude/skills/ui-ux-pro-max/SKILL.md +24 -79
  111. package/src/claude/skills/web-testing/SKILL.md +2 -20
  112. package/src/codex/AGENTS.md +8 -0
  113. package/src/codex/hooks/ask-guard.cjs +54 -0
  114. package/src/codex/hooks/inspect-block.cjs +11 -7
  115. package/src/codex/hooks/lib/inspect-paths.cjs +1 -199
  116. package/src/codex/hooks/lib/privacy-paths.cjs +1 -0
  117. package/src/codex/hooks/lib/privacy-state.cjs +98 -16
  118. package/src/codex/hooks/lib/rules-reservation.cjs +38 -0
  119. package/src/codex/hooks/lib/secret-keywords.cjs +1 -0
  120. package/src/codex/hooks/lib/spec-receipt.cjs +2 -30
  121. package/src/codex/hooks/lib/spec-session-touch.cjs +100 -0
  122. package/src/codex/hooks/lib/spec-touch.cjs +3 -0
  123. package/src/codex/hooks/lib/spec-utils.cjs +6 -45
  124. package/src/codex/hooks/lib/update-check.cjs +1 -0
  125. package/src/codex/hooks/precompact.cjs +39 -0
  126. package/src/codex/hooks/privacy-approval.cjs +28 -17
  127. package/src/codex/hooks/privacy-block.cjs +9 -215
  128. package/src/codex/hooks/rules.cjs +4 -30
  129. package/src/codex/hooks/secret-output-guardrail.cjs +1 -1
  130. package/src/codex/hooks/session.cjs +47 -11
  131. package/src/codex/hooks/spec-gate.cjs +32 -74
  132. package/src/codex/hooks/spec-state.cjs +68 -46
  133. package/src/codex/hooks/state.cjs +25 -4
  134. package/src/codex/hooks.json +39 -7
  135. package/src/codex/rules/hook-protocols.md +13 -6
  136. package/src/common/AGENTS.md +4 -10
  137. package/src/omp/extensions/cafekit-bridge.mjs +12 -4
  138. package/src/omp/hooks/privacy-block.cjs +24 -206
  139. package/bin/__tests__/change-firewall.semantic-firewall.test.js +0 -2133
  140. package/bin/__tests__/finalizer-pass-gate.semantic-firewall.test.js +0 -342
  141. package/bin/__tests__/release-preflight.semantic-firewall.test.js +0 -586
  142. package/bin/__tests__/review-receipt-c2.semantic-firewall.test.js +0 -378
  143. package/bin/__tests__/specs-v2-policy-and-scaffold.test.js +0 -665
  144. package/bin/__tests__/specs-v2-validator-grounder-contract.test.js +0 -1132
  145. package/src/claude/hooks/lib/context.cjs +0 -527
  146. package/src/claude/hooks/lib/detect.cjs +0 -474
  147. package/src/claude/scripts/change-firewall.cjs +0 -1748
  148. package/src/claude/scripts/spec-authoring-digest.cjs +0 -151
  149. package/src/claude/scripts/spec-authoring-validation.cjs +0 -126
  150. package/src/claude/scripts/spec-final-state.cjs +0 -290
  151. package/src/claude/scripts/spec-ground.cjs +0 -650
  152. package/src/claude/scripts/spec-readiness.cjs +0 -425
  153. package/src/claude/scripts/spec-scaffold.cjs +0 -2317
  154. package/src/claude/scripts/spec-semantic-model.cjs +0 -396
  155. package/src/claude/scripts/validate-spec-output.cjs +0 -3914
  156. package/src/claude/skills/code-review/references/verification-gate.md +0 -41
  157. package/src/claude/skills/specs/templates/design.md +0 -82
  158. package/src/claude/skills/specs/templates/requirements-init.md +0 -8
  159. package/src/claude/skills/specs/templates/requirements.md +0 -42
  160. package/src/claude/skills/specs/templates/spec-state.json +0 -56
  161. package/src/claude/skills/specs/templates/task.md +0 -47
  162. package/src/codex/rules/state-sync.md +0 -60
@@ -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
- });