@haposoft/cafekit 0.16.0-rc.4 → 0.16.0-rc.5

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 (28) hide show
  1. package/bin/__tests__/change-firewall.semantic-firewall.test.js +2134 -0
  2. package/bin/__tests__/codex-hooks.test.js +136 -64
  3. package/bin/__tests__/develop-contract.test.js +55 -17
  4. package/bin/__tests__/finalizer-pass-gate.semantic-firewall.test.js +342 -0
  5. package/bin/__tests__/package-inventory.test.js +29 -11
  6. package/bin/__tests__/release-preflight.semantic-firewall.test.js +578 -0
  7. package/bin/__tests__/review-receipt-c2.semantic-firewall.test.js +378 -0
  8. package/bin/__tests__/specs-v2-execution-closeout.test.js +59 -12
  9. package/bin/__tests__/specs-v2-policy-and-scaffold.test.js +78 -14
  10. package/bin/__tests__/specs-v2-validator-grounder-contract.test.js +222 -42
  11. package/bin/__tests__/validator-grounding.test.js +103 -4
  12. package/package.json +5 -2
  13. package/src/claude/hooks/__tests__/completion-authority.test.js +31 -1
  14. package/src/claude/hooks/__tests__/privacy-block.test.js +111 -29
  15. package/src/claude/hooks/privacy-block.cjs +38 -14
  16. package/src/claude/scripts/change-firewall.cjs +1748 -0
  17. package/src/claude/scripts/spec-authoring-digest.cjs +151 -0
  18. package/src/claude/scripts/spec-authoring-validation.cjs +126 -0
  19. package/src/claude/scripts/spec-final-state.cjs +46 -0
  20. package/src/claude/scripts/spec-ground.cjs +46 -10
  21. package/src/claude/scripts/spec-readiness.cjs +324 -17
  22. package/src/claude/scripts/spec-scaffold.cjs +268 -28
  23. package/src/claude/scripts/validate-spec-output.cjs +262 -10
  24. package/src/claude/skills/specs/references/review.md +14 -2
  25. package/src/claude/skills/specs/templates/spec-state.json +14 -2
  26. package/src/codex/hooks/lib/privacy-state.cjs +23 -22
  27. package/src/codex/hooks/privacy-approval.cjs +7 -3
  28. package/src/codex/hooks/privacy-block.cjs +45 -22
@@ -11,6 +11,27 @@ const PACKAGE_ROOT = path.resolve(__dirname, '../..');
11
11
  const SCAFFOLD = path.join(PACKAGE_ROOT, 'src/claude/scripts/spec-scaffold.cjs');
12
12
  const VALIDATOR = path.join(PACKAGE_ROOT, 'src/claude/scripts/validate-spec-output.cjs');
13
13
  const GROUNDER = path.join(PACKAGE_ROOT, 'src/claude/scripts/spec-ground.cjs');
14
+ const AUTHORING_VALIDATOR = path.join(PACKAGE_ROOT, 'src/claude/scripts/spec-authoring-validation.cjs');
15
+
16
+ // C2 SemanticReviewReceipt (R1-01, frozen): a minimal but exact-shape completed
17
+ // receipt for fixtures that need `ready_for_implementation` true.
18
+ function completedSemanticReview({ reviewedCriteria, counterexamples }) {
19
+ return {
20
+ status: 'completed',
21
+ semantic_digest: null,
22
+ verdict: 'PASS',
23
+ lifecycle_disposition: 'CONTINUE',
24
+ findings: [],
25
+ unresolved_decisions: [],
26
+ graph_coverage: [
27
+ 'criterion_local', 'cross_criterion', 'runtime_path', 'assumption_provenance', 'compatibility_migration',
28
+ ].map((surface) => ({ surface, covered: true, notes: 'Fixture coverage for structural contract test.' })),
29
+ repair_round: 0,
30
+ reviewed_criteria: reviewedCriteria,
31
+ counterexamples,
32
+ reviewer_evidence: { assurance: 'Routine', summary: 'Fixture reviewer evidence for structural contract test.' },
33
+ };
34
+ }
14
35
 
15
36
  function exec(root, script, args) {
16
37
  return spawnSync(process.execPath, [script, ...args], { cwd: root, encoding: 'utf8' });
@@ -115,15 +136,17 @@ The transition moves rejected input to failure and valid input to enabled; recov
115
136
  const promoted = exec(root, SCAFFOLD, ['feature', '--sync-semantic-model']);
116
137
  assert.equal(promoted.status, 0, output(promoted));
117
138
  const specPath = path.join(specDir, 'spec.json');
139
+ if (!ready) return { root, specDir, specPath };
140
+ // C16/D13: only spec-authoring-validation.cjs may flip authoring.* to
141
+ // validated and write the matching receipt (I21/R3.9) — run the real
142
+ // coordinator instead of hand-writing those fields.
143
+ const coordinated = exec(root, AUTHORING_VALIDATOR, [specDir]);
144
+ assert.equal(coordinated.status, 0, output(coordinated));
118
145
  const spec = JSON.parse(fs.readFileSync(specPath, 'utf8'));
119
- if (ready) {
120
- spec.authoring.requirements = 'validated';
121
- spec.authoring.design = 'validated';
122
- spec.authoring.tasks = 'validated';
123
- spec.validation.status = 'completed';
124
- spec.validation.semantic_review.status = 'completed';
125
- spec.validation.semantic_review.reviewed_criteria = ['R1.1', 'R1.2'];
126
- spec.validation.semantic_review.counterexamples = [{
146
+ spec.validation.status = 'completed';
147
+ spec.validation.semantic_review = completedSemanticReview({
148
+ reviewedCriteria: ['R1.1', 'R1.2'],
149
+ counterexamples: [{
127
150
  criterion: 'R1.1', case_kind: 'failure',
128
151
  scenario: 'The service receives an invalid input object without the required discriminator.',
129
152
  expected: 'The service returns rejected state and never emits enabled true.',
@@ -133,13 +156,13 @@ The transition moves rejected input to failure and valid input to enabled; recov
133
156
  scenario: 'The implementation reports enabled state without producing the verifier-owned artifact.',
134
157
  expected: 'Verification fails because the separate proof artifact is absent.',
135
158
  decision_refs: ['D1', 'I1', 'C1'], verification_ref: 'V1',
136
- }];
137
- fs.writeFileSync(specPath, `${JSON.stringify(spec, null, 2)}\n`);
138
- const digest = exec(root, VALIDATOR, [specDir, '--semantic-digest']);
139
- assert.equal(digest.status, 0, output(digest));
140
- spec.validation.semantic_review.semantic_digest = digest.stdout.trim();
141
- spec.ready_for_implementation = true;
142
- }
159
+ }],
160
+ });
161
+ fs.writeFileSync(specPath, `${JSON.stringify(spec, null, 2)}\n`);
162
+ const digest = exec(root, VALIDATOR, [specDir, '--semantic-digest']);
163
+ assert.equal(digest.status, 0, output(digest));
164
+ spec.validation.semantic_review.semantic_digest = digest.stdout.trim();
165
+ spec.ready_for_implementation = true;
143
166
  fs.writeFileSync(specPath, `${JSON.stringify(spec, null, 2)}\n`);
144
167
  return { root, specDir, specPath };
145
168
  }
@@ -155,9 +178,18 @@ function refreshSemanticDigest(fixture) {
155
178
  const previous = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
156
179
  const promoted = exec(fixture.root, SCAFFOLD, ['feature', '--sync-semantic-model']);
157
180
  assert.equal(promoted.status, 0, output(promoted));
181
+ // C16/D13: authoring.* and its receipt are never hand-restored — only
182
+ // spec-authoring-validation.cjs may write them (I21). Re-run it best-effort
183
+ // over the current (possibly just-edited) bytes; a fixture that
184
+ // deliberately broke grounding/validation after the initial ready fixture
185
+ // leaves authoring at scaffold's own draft reset instead, which is fine —
186
+ // the caller is exercising a different failure, not readiness itself.
187
+ const wasValidated = ['requirements', 'design', 'research', 'tasks']
188
+ .some((field) => previous.authoring[field] === 'validated');
189
+ if (wasValidated) exec(fixture.root, AUTHORING_VALIDATOR, [fixture.specDir]);
158
190
  const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
159
- state.authoring = previous.authoring;
160
- state.validation = previous.validation;
191
+ state.validation.status = previous.validation.status;
192
+ state.validation.semantic_review = previous.validation.semantic_review;
161
193
  state.validation.semantic_review.semantic_digest = null;
162
194
  state.ready_for_implementation = false;
163
195
  fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
@@ -212,30 +244,41 @@ function makeTasklessReadyFixture() {
212
244
  delete state.task_files;
213
245
  delete state.task_registry;
214
246
  state.coordination = { boundaries: [] };
215
- state.authoring.requirements = 'validated';
216
- state.authoring.design = 'validated';
247
+ // Dropping task topology requires the taskless authoring state exactly:
248
+ // scaffold's closed-world check refuses task_files/task_registry omission
249
+ // paired with anything but authoring.tasks = 'absent'.
217
250
  state.authoring.tasks = 'absent';
218
- state.validation.status = 'completed';
219
- state.validation.semantic_review.status = 'completed';
220
- state.validation.semantic_review.reviewed_criteria = ['R1.1', 'R1.2'];
221
- state.validation.semantic_review.counterexamples = [{
222
- criterion: 'R1.1', case_kind: 'failure',
223
- scenario: 'The service receives invalid input without the required discriminator.',
224
- expected: 'The service returns rejected state and never emits enabled true.',
225
- decision_refs: ['D1', 'I1', 'C1'], verification_ref: 'V1',
226
- }, {
227
- criterion: 'R1.2', case_kind: 'adversarial',
228
- scenario: 'The subject reports success without producing the design-owned proof artifact.',
229
- expected: 'Verification fails because the separate taskless proof evidence is absent.',
230
- decision_refs: ['D1', 'I1', 'C1'], verification_ref: 'V1',
231
- }];
232
251
  fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
233
252
  const promoted = exec(fixture.root, SCAFFOLD, ['feature', '--sync-semantic-model']);
234
253
  assert.equal(promoted.status, 0, output(promoted));
235
- refreshSemanticDigest(fixture);
236
- const promotedState = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
237
- promotedState.ready_for_implementation = true;
238
- fs.writeFileSync(fixture.specPath, `${JSON.stringify(promotedState, null, 2)}\n`);
254
+ // C16/D13: only spec-authoring-validation.cjs may flip authoring.* to
255
+ // validated and write the matching receipt (I21/R3.9) — run the real
256
+ // coordinator instead of hand-writing those fields.
257
+ const coordinated = exec(fixture.root, AUTHORING_VALIDATOR, [fixture.specDir]);
258
+ assert.equal(coordinated.status, 0, output(coordinated));
259
+ const validatedState = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
260
+ validatedState.validation.status = 'completed';
261
+ validatedState.validation.semantic_review = completedSemanticReview({
262
+ reviewedCriteria: ['R1.1', 'R1.2'],
263
+ counterexamples: [{
264
+ criterion: 'R1.1', case_kind: 'failure',
265
+ scenario: 'The service receives invalid input without the required discriminator.',
266
+ expected: 'The service returns rejected state and never emits enabled true.',
267
+ decision_refs: ['D1', 'I1', 'C1'], verification_ref: 'V1',
268
+ }, {
269
+ criterion: 'R1.2', case_kind: 'adversarial',
270
+ scenario: 'The subject reports success without producing the design-owned proof artifact.',
271
+ expected: 'Verification fails because the separate taskless proof evidence is absent.',
272
+ decision_refs: ['D1', 'I1', 'C1'], verification_ref: 'V1',
273
+ }],
274
+ });
275
+ fs.writeFileSync(fixture.specPath, `${JSON.stringify(validatedState, null, 2)}\n`);
276
+ const digest = exec(fixture.root, VALIDATOR, [fixture.specDir, '--semantic-digest']);
277
+ assert.equal(digest.status, 0, output(digest));
278
+ const readyState = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
279
+ readyState.validation.semantic_review.semantic_digest = digest.stdout.trim();
280
+ readyState.ready_for_implementation = true;
281
+ fs.writeFileSync(fixture.specPath, `${JSON.stringify(readyState, null, 2)}\n`);
239
282
  return fixture;
240
283
  }
241
284
 
@@ -519,17 +562,46 @@ test('research authoring is an exact pointer-file state machine and participates
519
562
  } finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
520
563
  }
521
564
 
522
- const fixture = makeFixture({ ready: true });
565
+ // Production lifecycle order (I15/R3.7): research is added and left draft
566
+ // BEFORE readiness exists, the coordinator proves it clean, and only then
567
+ // does the semantic reviewer compute a digest and the finalizer promote —
568
+ // never the reverse. Start from the plain (not yet ready) fixture so the
569
+ // coordinator never has to run against an already-completed, now-stale
570
+ // review (that ordering is out of the coordinator's own contract).
571
+ const fixture = makeFixture();
523
572
  try {
524
573
  const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
525
- state.authoring.research = 'validated';
526
574
  state.research = 'research.md';
575
+ state.authoring.research = 'draft';
527
576
  fs.writeFileSync(path.join(fixture.specDir, 'research.md'), canonicalResearch21());
528
577
  fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
578
+ // C16/D13: only spec-authoring-validation.cjs may flip authoring.research
579
+ // (and every other non-absent stage) to validated and write the matching
580
+ // receipt (I21/R3.9).
581
+ const coordinated = exec(fixture.root, AUTHORING_VALIDATOR, [fixture.specDir]);
582
+ assert.equal(coordinated.status, 0, output(coordinated));
583
+ const validated = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
584
+ validated.validation.status = 'completed';
585
+ validated.validation.semantic_review = completedSemanticReview({
586
+ reviewedCriteria: ['R1.1', 'R1.2'],
587
+ counterexamples: [{
588
+ criterion: 'R1.1', case_kind: 'failure',
589
+ scenario: 'The service receives an invalid input object without the required discriminator.',
590
+ expected: 'The service returns rejected state and never emits enabled true.',
591
+ decision_refs: ['D1', 'I1', 'C1'], verification_ref: 'V1',
592
+ }, {
593
+ criterion: 'R1.2', case_kind: 'adversarial',
594
+ scenario: 'The implementation reports enabled state without producing the verifier-owned artifact.',
595
+ expected: 'Verification fails because the separate proof artifact is absent.',
596
+ decision_refs: ['D1', 'I1', 'C1'], verification_ref: 'V1',
597
+ }],
598
+ });
599
+ fs.writeFileSync(fixture.specPath, `${JSON.stringify(validated, null, 2)}\n`);
529
600
  const digest = exec(fixture.root, VALIDATOR, [fixture.specDir, '--semantic-digest']);
530
601
  assert.equal(digest.status, 0, output(digest));
531
- state.validation.semantic_review.semantic_digest = digest.stdout.trim();
532
- fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
602
+ validated.validation.semantic_review.semantic_digest = digest.stdout.trim();
603
+ validated.ready_for_implementation = true;
604
+ fs.writeFileSync(fixture.specPath, `${JSON.stringify(validated, null, 2)}\n`);
533
605
  assert.equal(exec(fixture.root, VALIDATOR, [fixture.specDir]).status, 0);
534
606
  fs.appendFileSync(path.join(fixture.specDir, 'research.md'), '\nA second grounded conclusion changes the authored research artifact.\n');
535
607
  const stale = exec(fixture.root, VALIDATOR, [fixture.specDir]);
@@ -677,10 +749,118 @@ test('typed boundaries require exact non-empty resource maps and participation b
677
749
  } finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
678
750
  });
679
751
 
752
+ test('same-target writers cannot hide outside declared coordination pairs', () => {
753
+ const fixture = makeFixture();
754
+ try {
755
+ const thirdPath = 'tasks/task-R1-03-shadow.md';
756
+ const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
757
+ fs.writeFileSync(
758
+ path.join(fixture.specDir, thirdPath),
759
+ task('R1-03', 'Shadow writer', 'src/entry.js', 'modify', 'none', '', 'subject'),
760
+ );
761
+ state.task_files.push(thirdPath);
762
+ state.task_registry[thirdPath] = {
763
+ ...state.task_registry['tasks/task-R1-01-entry.md'],
764
+ id: 'R1-03',
765
+ title: 'Shadow writer',
766
+ dependencies: [],
767
+ };
768
+ state.coordination.boundaries.push({
769
+ id: 'B-OWN-SHADOW',
770
+ type: 'ownership',
771
+ tasks: ['R1-02', 'R1-03'],
772
+ write_sets: {
773
+ 'R1-02': ['src/service.js', 'artifacts/R1-02.json'],
774
+ 'R1-03': ['src/entry.js', 'artifacts/R1-03.json'],
775
+ },
776
+ });
777
+ fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
778
+
779
+ const result = exec(fixture.root, VALIDATOR, [fixture.specDir]);
780
+ assert.equal(result.status, 1, output(result));
781
+ assert.match(
782
+ output(result),
783
+ /R1-01 and R1-03 both hold a write anchor for src\/entry\.js with no dependency boundary connecting them/,
784
+ );
785
+ } finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
786
+ });
787
+
788
+ test('schema 2.1 grounding follows actual readiness and task execution phase', () => {
789
+ const fixture = makeFixture();
790
+ try {
791
+ const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
792
+ const taskPath = 'tasks/task-R1-01-entry.md';
793
+ state.task_registry[taskPath] = {
794
+ ...state.task_registry[taskPath],
795
+ status: 'blocked',
796
+ blocker: 'Waiting for a concrete external prerequisite.',
797
+ last_updated_at: '2026-08-17T00:00:00Z',
798
+ };
799
+ fs.writeFileSync(
800
+ path.join(fixture.specDir, taskPath),
801
+ fs.readFileSync(path.join(fixture.specDir, taskPath), 'utf8').replace('**Status:** pending', '**Status:** blocked'),
802
+ );
803
+ fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
804
+ const blocked = exec(fixture.root, VALIDATOR, [fixture.specDir]);
805
+ assert.equal(blocked.status, 0, output(blocked));
806
+
807
+ state.task_registry[taskPath] = {
808
+ ...state.task_registry[taskPath],
809
+ status: 'in_progress',
810
+ blocker: null,
811
+ started_at: '2026-08-17T00:00:00Z',
812
+ last_updated_at: '2026-08-17T00:00:00Z',
813
+ };
814
+ fs.writeFileSync(
815
+ path.join(fixture.specDir, taskPath),
816
+ fs.readFileSync(path.join(fixture.specDir, taskPath), 'utf8').replace('**Status:** blocked', '**Status:** in_progress'),
817
+ );
818
+ fs.writeFileSync(path.join(fixture.root, 'artifacts/R1-01.json'), '{}\n');
819
+ fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
820
+ const materialized = exec(fixture.root, GROUNDER, [fixture.specDir, '--root', fixture.root]);
821
+ assert.equal(materialized.status, 0, output(materialized));
822
+ } finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
823
+ });
824
+
825
+ test('schema 2.1 grounding permits a missing planned read only through its direct create dependency', () => {
826
+ const fixture = makeFixture();
827
+ try {
828
+ const producerPath = path.join(fixture.specDir, 'tasks/task-R1-01-entry.md');
829
+ const consumerPath = path.join(fixture.specDir, 'tasks/task-R1-02-verify.md');
830
+ fs.writeFileSync(producerPath, fs.readFileSync(producerPath, 'utf8').replace(
831
+ '| A-R1-01-02 | artifact | `artifacts/R1-01.json` | verifier | write | create |',
832
+ '| A-R1-01-02 | artifact | `artifacts/R1-01.json` | verifier | write | create |\n| A-R1-01-03 | file | `artifacts/planned-output.json` | owner | write | create |',
833
+ ));
834
+ fs.writeFileSync(consumerPath, fs.readFileSync(consumerPath, 'utf8').replace(
835
+ '| A-R1-02-02 | artifact | `artifacts/R1-02.json` | verifier | write | create |',
836
+ '| A-R1-02-02 | artifact | `artifacts/R1-02.json` | verifier | write | create |\n| A-R1-02-03 | file | `artifacts/planned-output.json` | consumer | read | read |',
837
+ ).replace('- none\n\n## Verification Plan', '- tasks/task-R1-01-entry.md\n\n## Verification Plan'));
838
+ const state = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
839
+ state.coordination.boundaries[0].write_sets['R1-01'].push('artifacts/planned-output.json');
840
+ state.coordination.boundaries.push({
841
+ id: 'B-PLANNED-READ', type: 'dependency', producer: 'R1-01', consumer: 'R1-02',
842
+ deliverable: 'artifacts/planned-output.json',
843
+ });
844
+ state.task_registry['tasks/task-R1-02-verify.md'].dependencies = ['tasks/task-R1-01-entry.md'];
845
+ fs.writeFileSync(fixture.specPath, `${JSON.stringify(state, null, 2)}\n`);
846
+ const synced = exec(fixture.root, SCAFFOLD, ['feature', '--sync-semantic-model']);
847
+ assert.equal(synced.status, 0, output(synced));
848
+ const grounded = exec(fixture.root, GROUNDER, [fixture.specDir, '--root', fixture.root]);
849
+ assert.equal(grounded.status, 0, output(grounded));
850
+
851
+ const withoutDependency = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
852
+ withoutDependency.task_registry['tasks/task-R1-02-verify.md'].dependencies = [];
853
+ fs.writeFileSync(fixture.specPath, `${JSON.stringify(withoutDependency, null, 2)}\n`);
854
+ const refused = exec(fixture.root, GROUNDER, [fixture.specDir, '--root', fixture.root]);
855
+ assert.equal(refused.status, 1, output(refused));
856
+ assert.match(output(refused), /read target not found.*artifacts\/planned-output\.json/);
857
+ } finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
858
+ });
859
+
680
860
  test('schema 2.1 rejects unknown and dead machine fields outside the explicit adapter', () => {
681
861
  const cases = [
682
862
  ['top-level', (state) => { state.dead_machine_field = true; }, /dead_machine_field: unknown or legacy field is not canonical in closed schema 2\.1/],
683
- ['validation', (state) => { state.validation.legacy_digest = 'inert'; }, /validation: fields must be exactly status, semantic_review/],
863
+ ['validation', (state) => { state.validation.legacy_digest = 'inert'; }, /validation: fields must be exactly status, semantic_review, semantic_review_history, authoring_validation/],
684
864
  ['scope-lock', (state) => { state.scope_lock.legacy_mode = true; }, /scope_lock: fields must be exactly/],
685
865
  ];
686
866
  for (const [name, mutate, expected] of cases) {
@@ -11,6 +11,7 @@ const ROOT = path.resolve(__dirname, '../..');
11
11
  const VALIDATOR = path.join(ROOT, 'src/claude/scripts/validate-spec-output.cjs');
12
12
  const GROUNDING = path.join(ROOT, 'src/claude/scripts/spec-ground.cjs');
13
13
  const SCAFFOLD = path.join(ROOT, 'src/claude/scripts/spec-scaffold.cjs');
14
+ const AUTHORING_VALIDATION = path.join(ROOT, 'src/claude/scripts/spec-authoring-validation.cjs');
14
15
  const RESOLVER = path.join(ROOT, 'src/claude/scripts/spec-resolver.cjs');
15
16
  const POLICY = require(path.join(ROOT, 'src/claude/scripts/workflow-policy.cjs'));
16
17
  const SPEC_RESOLVER = require(RESOLVER);
@@ -483,19 +484,36 @@ The transition records success, failure, and recovery states.
483
484
  path.dirname(path.dirname(specDir)),
484
485
  );
485
486
  assert.equal(promoted.status, 0, output(promoted));
487
+ // I21: `validated` is never hand-authored here. The real C16 coordinator is
488
+ // the only writer, and it flips the enums only after its own clean
489
+ // validator+grounder pass, writing the matching digest receipt in the same
490
+ // atomic replace. Routing the fixture through it keeps this helper honest and
491
+ // exercises the production path end to end.
492
+ const authored = run(AUTHORING_VALIDATION, [specDir, '--root', fixtureRoot], fixtureRoot);
493
+ assert.equal(authored.status, 0, output(authored));
486
494
  state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
487
- state.authoring.requirements = 'validated';
488
- state.authoring.design = 'validated';
489
- state.authoring.tasks = state.task_files?.length ? 'validated' : 'absent';
495
+ assert.equal(state.authoring.requirements, 'validated');
496
+ assert.equal(state.authoring.design, 'validated');
490
497
  state.validation.status = 'completed';
491
498
  state.validation.semantic_review = {
492
- status: 'completed', semantic_digest: null, reviewed_criteria: criteria,
499
+ status: 'completed', semantic_digest: null, verdict: 'PASS', lifecycle_disposition: 'CONTINUE',
500
+ findings: [], unresolved_decisions: [],
501
+ graph_coverage: [
502
+ 'criterion_local', 'cross_criterion', 'runtime_path',
503
+ 'assumption_provenance', 'compatibility_migration',
504
+ ].map((surface) => ({ surface, covered: true, notes: `${surface} reviewed against the fixture topology.` })),
505
+ repair_round: 0,
506
+ reviewed_criteria: criteria,
493
507
  counterexamples: criteria.map((criterion, index) => ({
494
508
  criterion, case_kind: index % 2 === 0 ? 'failure' : 'adversarial',
495
509
  scenario: `Task ${index + 1} receives a distinct malformed status payload at its declared boundary.`,
496
510
  expected: `Task ${index + 1} rejects that payload without publishing its distinct success outcome.`,
497
511
  decision_refs: ['D1', 'I1', 'C1'], verification_ref: 'V1',
498
512
  })),
513
+ reviewer_evidence: {
514
+ assurance: 'Routine',
515
+ summary: 'Fixture reviewer confirmed every criterion, counterexample, and reachability anchor.',
516
+ },
499
517
  };
500
518
  write(statePath, JSON.stringify(state, null, 2));
501
519
  state.validation.semantic_review.semantic_digest = semanticDigest(specDir);
@@ -3109,3 +3127,84 @@ test('benchmark validate handles empty and invalid evidence as not-ready/invalid
3109
3127
  fs.rmSync(root, { recursive: true, force: true });
3110
3128
  }
3111
3129
  });
3130
+
3131
+ test('D12: semantic-firewall test discovery finds R0-01\'s required basenames in the real checkout', () => {
3132
+ const DISCOVERY = require(path.join(ROOT, 'scripts/semantic-firewall-test-discovery.cjs'));
3133
+ const testsDir = path.join(ROOT, 'bin/__tests__');
3134
+ const discovered = DISCOVERY.discoverSemanticFirewallTests(testsDir);
3135
+ assert.ok(discovered.includes('change-firewall.semantic-firewall.test.js'), 'change-firewall.semantic-firewall.test.js must be discovered');
3136
+ assert.ok(discovered.includes('release-preflight.semantic-firewall.test.js'), 'release-preflight.semantic-firewall.test.js must be discovered');
3137
+ assert.deepEqual(discovered, [...discovered].sort(), 'discovery must return sorted basenames');
3138
+
3139
+ const requiredOk = DISCOVERY.assertRequiredSemanticTests(testsDir, [
3140
+ 'change-firewall.semantic-firewall.test.js',
3141
+ 'release-preflight.semantic-firewall.test.js',
3142
+ ]);
3143
+ assert.equal(requiredOk.ok, true);
3144
+ assert.deepEqual(requiredOk.missing, []);
3145
+
3146
+ const requiredMissing = DISCOVERY.assertRequiredSemanticTests(testsDir, ['does-not-exist.semantic-firewall.test.js']);
3147
+ assert.equal(requiredMissing.ok, false);
3148
+ assert.deepEqual(requiredMissing.missing, ['does-not-exist.semantic-firewall.test.js']);
3149
+ });
3150
+
3151
+ test('D12: run-skill-self-tests.mjs sentinel exits nonzero immediately when a required semantic-firewall basename is missing', () => {
3152
+ const RUNNER = path.join(ROOT, 'scripts/run-skill-self-tests.mjs');
3153
+ const result = spawnSync(process.execPath, [
3154
+ RUNNER, '--require-semantic-test', 'does-not-exist.semantic-firewall.test.js',
3155
+ ], { cwd: ROOT, encoding: 'utf8', timeout: 15000 });
3156
+ assert.notEqual(result.status, 0);
3157
+ assert.match(`${result.stdout}${result.stderr}`, /required semantic-firewall test not discovered: does-not-exist\.semantic-firewall\.test\.js/);
3158
+ });
3159
+
3160
+ test('R0-01 owned change-firewall contract surface (D1/C15) is reachable: every named export and owned anchor target exists', () => {
3161
+ const CHANGE_FIREWALL = require(path.join(ROOT, 'src/claude/scripts/change-firewall.cjs'));
3162
+ const requiredExports = [
3163
+ 'loadBaselineAuthority', 'assertBaselineUsable', 'bootstrapBaseline', 'advanceBaseline',
3164
+ 'deriveProtectedChangedPaths', 'computeProtectedTreeDigest', 'computeProposalDigest',
3165
+ 'computeCandidateDigest', 'assertHotspotChangeAllowed', 'createFreezeManifest',
3166
+ 'verifyFreezeManifest', 'invalidateFreeze', 'NO_CHANGE_ATTESTATION_DIGEST',
3167
+ 'loadFailureLedger', 'assertFailureLedgerUsable', 'openFailure', 'classifyFailure',
3168
+ 'reclassifyFailure', 'resolveFailure', 'computeFailureLedgerDigest', 'deriveFailureState',
3169
+ 'NO_FAILURES_ATTESTATION_DIGEST', 'createChangeFirewall', 'assertLegacyBridgeArtifact',
3170
+ ];
3171
+ for (const name of requiredExports) {
3172
+ assert.ok(Object.prototype.hasOwnProperty.call(CHANGE_FIREWALL, name), `change-firewall.cjs must export ${name}`);
3173
+ }
3174
+
3175
+ const ownedAnchorTargets = [
3176
+ 'src/claude/scripts/change-firewall.cjs',
3177
+ 'src/claude/scripts/spec-authoring-digest.cjs',
3178
+ 'benchmarks/change-proposal.schema.json',
3179
+ 'benchmarks/change-firewall-freeze.schema.json',
3180
+ 'benchmarks/release-baseline.schema.json',
3181
+ 'benchmarks/migration-manifest.json',
3182
+ 'benchmarks/bootstrap-attestation.schema.json',
3183
+ 'benchmarks/benchmark-failure-ledger.schema.json',
3184
+ 'scripts/release-preflight.mjs',
3185
+ 'scripts/benchmark-workflow.mjs',
3186
+ 'scripts/semantic-firewall-test-discovery.cjs',
3187
+ 'scripts/run-skill-self-tests.mjs',
3188
+ 'bin/__tests__/change-firewall.semantic-firewall.test.js',
3189
+ 'bin/__tests__/release-preflight.semantic-firewall.test.js',
3190
+ 'package.json',
3191
+ '.gitignore',
3192
+ ];
3193
+ for (const relative of ownedAnchorTargets) {
3194
+ assert.ok(fs.existsSync(path.join(ROOT, relative)), `owned anchor target must exist: ${relative}`);
3195
+ }
3196
+ });
3197
+
3198
+ test('D11/R1.14: the legacy-bridge precondition artifact is reachable and change-firewall.cjs asserts its exact schema', () => {
3199
+ const CHANGE_FIREWALL = require(path.join(ROOT, 'src/claude/scripts/change-firewall.cjs'));
3200
+ // The bridge artifact lives under the monorepo root's specs/ (change-firewall.cjs
3201
+ // resolves its own production root the same way, via git from its own file
3202
+ // location) -- ROOT here is packages/spec, one level below that root.
3203
+ const MONOREPO_ROOT = path.resolve(ROOT, '..', '..');
3204
+ const bridgePath = path.join(MONOREPO_ROOT, 'specs/cafekit-semantic-eval-firewall/reports/bootstrap-legacy-bridge-review.json');
3205
+ assert.ok(fs.existsSync(bridgePath), 'the legacy-bridge artifact must exist before any R0-01 change-firewall assertion runs');
3206
+ const parsed = CHANGE_FIREWALL.assertLegacyBridgeArtifact();
3207
+ assert.equal(parsed.schema_version, '1');
3208
+ assert.equal(parsed.verdict, 'PASS');
3209
+ assert.equal(parsed.blocking_count, 0);
3210
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haposoft/cafekit",
3
- "version": "0.16.0-rc.4",
3
+ "version": "0.16.0-rc.5",
4
4
  "description": "Native spec-driven workflows for Claude Code, Codex CLI, and OpenCode. Bundles CafeKit skills, runtime hooks, agents, and installer scaffolding.",
5
5
  "author": "Haposoft <nghialt@haposoft.com>",
6
6
  "license": "MIT",
@@ -12,7 +12,10 @@
12
12
  "test": "node scripts/run-skill-self-tests.mjs",
13
13
  "test:package": "node --test bin/__tests__/package-inventory.test.js",
14
14
  "test:secrets": "node --test bin/__tests__/secret-scanner.test.js",
15
- "test:policy": "node --test bin/__tests__/develop-contract.test.js"
15
+ "test:policy": "node --test bin/__tests__/develop-contract.test.js",
16
+ "release:freeze": "node scripts/benchmark-workflow.mjs freeze",
17
+ "prepack": "node scripts/release-preflight.mjs",
18
+ "prepublishOnly": "node scripts/release-preflight.mjs"
16
19
  },
17
20
  "files": [
18
21
  "bin",
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const assert = require('node:assert/strict');
4
+ const crypto = require('node:crypto');
4
5
  const fs = require('node:fs');
5
6
  const os = require('node:os');
6
7
  const path = require('node:path');
@@ -14,6 +15,7 @@ const POLICY = require(path.join(ROOT, 'claude/scripts/workflow-policy.cjs'));
14
15
  const PROVENANCE = require(path.join(ROOT, 'claude/scripts/provenance.cjs'));
15
16
  const AUTHORITY_CHECK = require(path.join(ROOT, 'claude/hooks/completion-authority-check.cjs'));
16
17
  const RESOLVER = require(path.join(ROOT, 'claude/scripts/spec-resolver.cjs'));
18
+ const SEMANTIC_MODEL = require(path.join(ROOT, 'claude/scripts/spec-semantic-model.cjs'));
17
19
  const { copyClaudeTestRuntime } = require(path.join(ROOT, '../bin/__tests__/test-runtime-dependency-closure.cjs'));
18
20
  const FEATURE = 'authority-fixture';
19
21
  const TASK = 'tasks/closeout.md';
@@ -810,9 +812,37 @@ test('canonical final-state decision rejects every stale mutation class before e
810
812
  fs.mkdirSync(featureDir, { recursive: true });
811
813
  const specFile = path.join(featureDir, 'spec.json');
812
814
  const digest = `sha256:${'a'.repeat(64)}`;
815
+ // Canonical terminal shape: a completed C2 receipt that is PASS/CONTINUE
816
+ // with zero blocking items, plus the C13 history entry that records it and
817
+ // binds the same semantic digest, the receipt's own canonical digest, and
818
+ // the attempt index the receipt reports as repair_round.
819
+ const terminalReview = {
820
+ status: 'completed', semantic_digest: digest, verdict: 'PASS',
821
+ lifecycle_disposition: 'CONTINUE', findings: [], unresolved_decisions: [],
822
+ graph_coverage: [], repair_round: 0, reviewed_criteria: [], counterexamples: [],
823
+ reviewer_evidence: null,
824
+ };
813
825
  const baseSpec = {
814
826
  schema_version: '2.1', feature_name: 'final-state-matrix', ready_for_implementation: true,
815
- validation: { status: 'completed', semantic_review: { status: 'completed', semantic_digest: digest } },
827
+ validation: {
828
+ status: 'completed',
829
+ semantic_review: terminalReview,
830
+ semantic_review_history: {
831
+ lineage_id: `sha256:${'c'.repeat(64)}`,
832
+ entries: [{
833
+ sequence: 0,
834
+ semantic_digest: digest,
835
+ review_receipt_digest: `sha256:${crypto.createHash('sha256')
836
+ .update(SEMANTIC_MODEL.stableJson(terminalReview), 'utf8').digest('hex')}`,
837
+ verdict: 'PASS',
838
+ lifecycle_disposition: 'CONTINUE',
839
+ blocking_count: 0,
840
+ attempt_index: 0,
841
+ review_epoch: 0,
842
+ }],
843
+ },
844
+ authoring_validation: null,
845
+ },
816
846
  workflow_policy: POLICY.canonicalWorkflowPolicySnapshot({ planning_depth: 'Compact', assurance_level: 'Routine' }),
817
847
  };
818
848
  fs.writeFileSync(specFile, JSON.stringify(baseSpec));