@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.
- package/bin/__tests__/change-firewall.semantic-firewall.test.js +2134 -0
- package/bin/__tests__/codex-hooks.test.js +136 -64
- package/bin/__tests__/develop-contract.test.js +55 -17
- package/bin/__tests__/finalizer-pass-gate.semantic-firewall.test.js +342 -0
- package/bin/__tests__/package-inventory.test.js +29 -11
- package/bin/__tests__/release-preflight.semantic-firewall.test.js +578 -0
- package/bin/__tests__/review-receipt-c2.semantic-firewall.test.js +378 -0
- package/bin/__tests__/specs-v2-execution-closeout.test.js +59 -12
- package/bin/__tests__/specs-v2-policy-and-scaffold.test.js +78 -14
- package/bin/__tests__/specs-v2-validator-grounder-contract.test.js +222 -42
- package/bin/__tests__/validator-grounding.test.js +103 -4
- package/package.json +5 -2
- package/src/claude/hooks/__tests__/completion-authority.test.js +31 -1
- package/src/claude/hooks/__tests__/privacy-block.test.js +111 -29
- package/src/claude/hooks/privacy-block.cjs +38 -14
- package/src/claude/scripts/change-firewall.cjs +1748 -0
- package/src/claude/scripts/spec-authoring-digest.cjs +151 -0
- package/src/claude/scripts/spec-authoring-validation.cjs +126 -0
- package/src/claude/scripts/spec-final-state.cjs +46 -0
- package/src/claude/scripts/spec-ground.cjs +46 -10
- package/src/claude/scripts/spec-readiness.cjs +324 -17
- package/src/claude/scripts/spec-scaffold.cjs +268 -28
- package/src/claude/scripts/validate-spec-output.cjs +262 -10
- package/src/claude/skills/specs/references/review.md +14 -2
- package/src/claude/skills/specs/templates/spec-state.json +14 -2
- package/src/codex/hooks/lib/privacy-state.cjs +23 -22
- package/src/codex/hooks/privacy-approval.cjs +7 -3
- 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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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.
|
|
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
|
-
|
|
216
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
|
|
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
|
-
|
|
532
|
-
|
|
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
|
|
488
|
-
state.authoring.design
|
|
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,
|
|
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.
|
|
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: {
|
|
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));
|