@haposoft/cafekit 0.18.0 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/__tests__/codex-hooks.test.js +169 -236
- package/bin/__tests__/codex-native.test.js +54 -114
- package/bin/__tests__/develop-contract.test.js +101 -1361
- package/bin/__tests__/legacy-authority-decoupled.test.js +14 -19
- package/bin/__tests__/package-inventory.test.js +1 -395
- package/bin/__tests__/spec-narrowing.test.js +30 -134
- package/bin/__tests__/specs-v2-execution-closeout.test.js +0 -372
- package/bin/__tests__/test-runtime-dependency-closure.cjs +1 -2
- package/bin/__tests__/validator-grounding.test.js +17 -2929
- package/bin/lib/managed-writer.js +2 -3
- package/bin/phases/codex-runtime.js +6 -3
- package/bin/phases/copy-payload.js +13 -28
- package/bin/phases/omp-runtime.js +6 -3
- package/package.json +2 -3
- package/src/claude/agents/code-auditor.md +9 -6
- package/src/claude/agents/debugger.md +3 -9
- package/src/claude/agents/docs-keeper.md +4 -7
- package/src/claude/agents/git-ops.md +2 -4
- package/src/claude/agents/implementer.md +6 -7
- package/src/claude/agents/inspector.md +2 -0
- package/src/claude/agents/project-manager.md +2 -2
- package/src/claude/agents/spec-maker.md +4 -4
- package/src/claude/agents/test-runner.md +8 -11
- package/src/claude/agents/ui-ux-designer.md +0 -1
- package/src/claude/hooks/__tests__/spec-gate.test.js +170 -330
- package/src/claude/hooks/__tests__/spec-state-touched.test.js +26 -0
- package/src/claude/hooks/spec-gate.cjs +8 -63
- package/src/claude/hooks/spec-state.cjs +47 -38
- package/src/claude/migration-manifest.json +12 -7
- package/src/claude/output-styles/coding-level-0-eli5.md +9 -9
- package/src/claude/output-styles/coding-level-1-junior.md +3 -3
- package/src/claude/output-styles/coding-level-2-mid.md +3 -3
- package/src/claude/output-styles/coding-level-3-senior.md +2 -2
- package/src/claude/output-styles/coding-level-4-lead.md +5 -5
- package/src/claude/output-styles/coding-level-5-god.md +6 -6
- package/src/claude/rules/ai-dev-rules.md +1 -2
- package/src/claude/rules/workflow.md +1 -1
- package/src/claude/scripts/spec-receipt.cjs +0 -114
- package/src/claude/scripts/spec-resolver.cjs +41 -457
- package/src/claude/scripts/workflow-policy.cjs +3 -1801
- package/src/claude/skills/agent-browser/SKILL.md +1 -1
- package/src/claude/skills/chrome-devtools/SKILL.md +14 -31
- package/src/claude/skills/code-review/SKILL.md +25 -108
- package/src/claude/skills/code-review/references/spec-compliance-review.md +4 -7
- package/src/claude/skills/develop/SKILL.md +22 -70
- package/src/claude/skills/develop/references/subagent-patterns.md +0 -6
- package/src/claude/skills/research/SKILL.md +24 -55
- package/src/claude/skills/sync/SKILL.md +12 -34
- package/src/claude/skills/sync/references/rebind-and-audit.md +2 -2
- package/src/claude/skills/test/SKILL.md +39 -123
- package/src/claude/skills/test/references/execution-strategy.md +11 -22
- package/src/claude/skills/ui-ux/SKILL.md +1 -1
- package/src/claude/skills/ui-ux-pro-max/SKILL.md +24 -79
- package/src/claude/skills/web-testing/SKILL.md +2 -20
- package/src/codex/hooks/lib/spec-receipt.cjs +2 -30
- package/src/codex/hooks/lib/spec-utils.cjs +6 -45
- package/src/codex/hooks/spec-gate.cjs +11 -68
- package/src/codex/hooks/spec-state.cjs +37 -41
- package/src/common/AGENTS.md +4 -10
- package/bin/__tests__/change-firewall.semantic-firewall.test.js +0 -2133
- package/bin/__tests__/finalizer-pass-gate.semantic-firewall.test.js +0 -342
- package/bin/__tests__/release-preflight.semantic-firewall.test.js +0 -586
- package/bin/__tests__/review-receipt-c2.semantic-firewall.test.js +0 -378
- package/bin/__tests__/specs-v2-policy-and-scaffold.test.js +0 -665
- package/bin/__tests__/specs-v2-validator-grounder-contract.test.js +0 -1132
- package/src/claude/hooks/lib/context.cjs +0 -527
- package/src/claude/hooks/lib/detect.cjs +0 -474
- package/src/claude/scripts/change-firewall.cjs +0 -1748
- package/src/claude/scripts/spec-authoring-digest.cjs +0 -151
- package/src/claude/scripts/spec-authoring-validation.cjs +0 -126
- package/src/claude/scripts/spec-final-state.cjs +0 -290
- package/src/claude/scripts/spec-ground.cjs +0 -650
- package/src/claude/scripts/spec-readiness.cjs +0 -425
- package/src/claude/scripts/spec-scaffold.cjs +0 -2317
- package/src/claude/scripts/spec-semantic-model.cjs +0 -396
- package/src/claude/scripts/validate-spec-output.cjs +0 -3914
- package/src/claude/skills/code-review/references/verification-gate.md +0 -41
- package/src/claude/skills/specs/templates/design.md +0 -82
- package/src/claude/skills/specs/templates/requirements-init.md +0 -8
- package/src/claude/skills/specs/templates/requirements.md +0 -42
- package/src/claude/skills/specs/templates/spec-state.json +0 -56
- package/src/claude/skills/specs/templates/task.md +0 -47
|
@@ -1,378 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// R1-01 owned proof: frozen C2 SemanticReviewReceipt (verdict vs
|
|
4
|
-
// lifecycle_disposition, structural-only checks per R3.4), C13
|
|
5
|
-
// SemanticReviewHistory (exact keys, whole-lineage digest dedupe), and C16
|
|
6
|
-
// AuthoringValidationReceipt (the sole spec-authoring-validation.cjs atomic
|
|
7
|
-
// writer, fail-closed freshness in validate-spec-output.cjs).
|
|
8
|
-
|
|
9
|
-
const assert = require('node:assert/strict');
|
|
10
|
-
const crypto = require('node:crypto');
|
|
11
|
-
const fs = require('node:fs');
|
|
12
|
-
const os = require('node:os');
|
|
13
|
-
const path = require('node:path');
|
|
14
|
-
const { spawnSync } = require('node:child_process');
|
|
15
|
-
const test = require('node:test');
|
|
16
|
-
|
|
17
|
-
const PACKAGE_ROOT = path.resolve(__dirname, '../..');
|
|
18
|
-
const SCAFFOLD = path.join(PACKAGE_ROOT, 'src/claude/scripts/spec-scaffold.cjs');
|
|
19
|
-
const VALIDATOR = path.join(PACKAGE_ROOT, 'src/claude/scripts/validate-spec-output.cjs');
|
|
20
|
-
const AUTHORING_VALIDATOR = path.join(PACKAGE_ROOT, 'src/claude/scripts/spec-authoring-validation.cjs');
|
|
21
|
-
|
|
22
|
-
function exec(root, script, args) {
|
|
23
|
-
return spawnSync(process.execPath, [script, ...args], { cwd: root, encoding: 'utf8' });
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function output(result) { return `${result.stdout}\n${result.stderr}`; }
|
|
27
|
-
|
|
28
|
-
function writeSpec(specPath, spec) {
|
|
29
|
-
fs.writeFileSync(specPath, `${JSON.stringify(spec, null, 2)}\n`);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function readSpec(specPath) {
|
|
33
|
-
return JSON.parse(fs.readFileSync(specPath, 'utf8'));
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function task(id, title, ownedPath, action, criterion, taskRole) {
|
|
37
|
-
return `# Task ${id}: ${title}
|
|
38
|
-
**Status:** pending
|
|
39
|
-
|
|
40
|
-
## Outcome
|
|
41
|
-
Deliver observable ${title.toLowerCase()} behavior through the declared entrypoint.
|
|
42
|
-
|
|
43
|
-
## Scope
|
|
44
|
-
- **In scope:** Exact behavior for ${ownedPath}.
|
|
45
|
-
- **Out of scope:** Unrelated runtime behavior.
|
|
46
|
-
|
|
47
|
-
## Anchors and Ownership
|
|
48
|
-
| ID | Type | Target | Role | Access | Action |
|
|
49
|
-
|---|---|---|---|---|---|
|
|
50
|
-
| A-${id}-01 | file | \`${ownedPath}\` | owner | write | ${action} |
|
|
51
|
-
| A-${id}-02 | artifact | \`artifacts/${id}.json\` | verifier | write | create |
|
|
52
|
-
|
|
53
|
-
## Changes
|
|
54
|
-
${criterion ? `- [ ] Implement the owned behavior. _Requirements: ${criterion.slice(1)}_` : '- [ ] Verify the subject implementation through the canonical proof boundary.'}
|
|
55
|
-
|
|
56
|
-
## Acceptance
|
|
57
|
-
${criterion ? `- **${criterion}:** The command returns the concrete expected state.` : '- The verifier produces an observable proof artifact without claiming the subject acceptance criterion.'}
|
|
58
|
-
|
|
59
|
-
## Dependencies
|
|
60
|
-
- none
|
|
61
|
-
|
|
62
|
-
## Verification Plan
|
|
63
|
-
- **Verification ref:** V1
|
|
64
|
-
- **Task role:** ${taskRole}
|
|
65
|
-
- **Command:** \`node --test test/service.test.js\`
|
|
66
|
-
- **Expected:** Exit code 0 and exact state enabled.
|
|
67
|
-
- **Negative path:** Invalid input returns the specified failure state.
|
|
68
|
-
- **Reachability:** \`src/entry.js\`
|
|
69
|
-
`;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// A ready, coordinator-validated fixture. C16's own receipt is produced by
|
|
73
|
-
// the real spec-authoring-validation.cjs coordinator, never hand-written, so
|
|
74
|
-
// every test here starts from a byte-accurate receipt.
|
|
75
|
-
function makeReadyFixture() {
|
|
76
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-c2-'));
|
|
77
|
-
fs.mkdirSync(path.join(root, 'src'), { recursive: true });
|
|
78
|
-
fs.mkdirSync(path.join(root, 'test'), { recursive: true });
|
|
79
|
-
fs.mkdirSync(path.join(root, 'artifacts'), { recursive: true });
|
|
80
|
-
fs.writeFileSync(path.join(root, 'src/entry.js'), 'module.exports = { enabled: true };\n');
|
|
81
|
-
fs.writeFileSync(path.join(root, 'src/service.js'), 'module.exports = { enabled: true };\n');
|
|
82
|
-
fs.writeFileSync(path.join(root, 'test/service.test.js'), 'require("node:test")("service",()=>{});\n');
|
|
83
|
-
fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ scripts: { test: 'node --test' } }));
|
|
84
|
-
const boundaries = [{
|
|
85
|
-
id: 'B-OWN', type: 'ownership', tasks: ['R1-01', 'R1-02'],
|
|
86
|
-
write_sets: {
|
|
87
|
-
'R1-01': ['src/entry.js', 'artifacts/R1-01.json'],
|
|
88
|
-
'R1-02': ['src/service.js', 'artifacts/R1-02.json'],
|
|
89
|
-
},
|
|
90
|
-
}, {
|
|
91
|
-
id: 'B-V', type: 'proof', subject: 'R1-01', verifier: 'R1-02',
|
|
92
|
-
verification_ref: 'V1', artifact_anchor: 'A-R1-02-02',
|
|
93
|
-
}];
|
|
94
|
-
const scaffold = exec(root, SCAFFOLD, ['feature', '--tasks', 'R1-01-entry,R1-02-verify', '--boundaries', JSON.stringify(boundaries)]);
|
|
95
|
-
assert.equal(scaffold.status, 0, scaffold.stderr);
|
|
96
|
-
const specDir = path.join(root, 'specs/feature');
|
|
97
|
-
fs.writeFileSync(path.join(specDir, 'requirements.md'), `# Requirements
|
|
98
|
-
|
|
99
|
-
## Requirements
|
|
100
|
-
### Requirement 1: Entry behavior
|
|
101
|
-
- **R1.1**: When valid input arrives, the entry shall return enabled state.
|
|
102
|
-
- **R1.2**: The verifier shall produce a separate observable proof artifact for the enabled state.
|
|
103
|
-
`);
|
|
104
|
-
fs.writeFileSync(path.join(root, 'src/design-boundary.js'), 'module.exports = {};\n');
|
|
105
|
-
fs.writeFileSync(path.join(specDir, 'design.md'), `# Design
|
|
106
|
-
|
|
107
|
-
## Architecture
|
|
108
|
-
The entry delegates to the service and emits a verifier-owned artifact.
|
|
109
|
-
|
|
110
|
-
## Typed Anchors
|
|
111
|
-
| ID | Type | Target | Role | Access | Action |
|
|
112
|
-
|---|---|---|---|---|---|
|
|
113
|
-
| A-D-01 | file | \`src/design-boundary.js\` | design boundary | read | read |
|
|
114
|
-
|
|
115
|
-
## Canonical Contracts & Invariants
|
|
116
|
-
### D1 — Entry decision
|
|
117
|
-
The entry always returns enabled true on valid input.
|
|
118
|
-
### I1 — Invalid-state invariant
|
|
119
|
-
Invalid input never returns enabled state.
|
|
120
|
-
### C1 — Result contract
|
|
121
|
-
The result contains an enabled boolean.
|
|
122
|
-
|
|
123
|
-
## Verification Definitions
|
|
124
|
-
- **V1**: Criteria R1.1; Owner R1-01; Proof criteria R1.2; Proof owner R1-02; Evidence anchor A-R1-02-02; Decision refs D1, I1, C1; Method command \`node --test test/service.test.js\`; Expected exit 0 with enabled state and verifier artifact; Negative/failure invalid input returns rejected state without an enabled result; Reachability/grounding entrypoint \`src/entry.js\` via A-D-01, A-R1-02-02.
|
|
125
|
-
`);
|
|
126
|
-
fs.writeFileSync(path.join(specDir, 'tasks/task-R1-01-entry.md'), task('R1-01', 'Entry', 'src/entry.js', 'modify', 'R1.1', 'subject'));
|
|
127
|
-
fs.writeFileSync(path.join(specDir, 'tasks/task-R1-02-verify.md'), task('R1-02', 'Verify', 'src/service.js', 'modify', 'R1.2', 'verifier'));
|
|
128
|
-
const promoted = exec(root, SCAFFOLD, ['feature', '--sync-semantic-model']);
|
|
129
|
-
assert.equal(promoted.status, 0, output(promoted));
|
|
130
|
-
const specPath = path.join(specDir, 'spec.json');
|
|
131
|
-
const coordinated = exec(root, AUTHORING_VALIDATOR, [specDir]);
|
|
132
|
-
assert.equal(coordinated.status, 0, output(coordinated));
|
|
133
|
-
const spec = readSpec(specPath);
|
|
134
|
-
spec.validation.status = 'completed';
|
|
135
|
-
spec.validation.semantic_review = {
|
|
136
|
-
status: 'completed',
|
|
137
|
-
semantic_digest: null,
|
|
138
|
-
verdict: 'PASS',
|
|
139
|
-
lifecycle_disposition: 'CONTINUE',
|
|
140
|
-
findings: [],
|
|
141
|
-
unresolved_decisions: [],
|
|
142
|
-
graph_coverage: [
|
|
143
|
-
'criterion_local', 'cross_criterion', 'runtime_path', 'assumption_provenance', 'compatibility_migration',
|
|
144
|
-
].map((surface) => ({ surface, covered: true, notes: 'Fixture coverage.' })),
|
|
145
|
-
repair_round: 0,
|
|
146
|
-
reviewed_criteria: ['R1.1', 'R1.2'],
|
|
147
|
-
counterexamples: [{
|
|
148
|
-
criterion: 'R1.1', case_kind: 'failure',
|
|
149
|
-
scenario: 'The entry receives an invalid input object without the required discriminator.',
|
|
150
|
-
expected: 'The entry returns rejected state and never emits enabled true.',
|
|
151
|
-
decision_refs: ['D1', 'I1', 'C1'], verification_ref: 'V1',
|
|
152
|
-
}, {
|
|
153
|
-
criterion: 'R1.2', case_kind: 'adversarial',
|
|
154
|
-
scenario: 'The implementation reports enabled state without producing the verifier-owned artifact.',
|
|
155
|
-
expected: 'Verification fails because the separate proof artifact is absent.',
|
|
156
|
-
decision_refs: ['D1', 'I1', 'C1'], verification_ref: 'V1',
|
|
157
|
-
}],
|
|
158
|
-
reviewer_evidence: { assurance: 'Routine', summary: 'Fixture reviewer evidence for structural contract test.' },
|
|
159
|
-
};
|
|
160
|
-
writeSpec(specPath, spec);
|
|
161
|
-
const digest = exec(root, VALIDATOR, [specDir, '--semantic-digest']);
|
|
162
|
-
assert.equal(digest.status, 0, output(digest));
|
|
163
|
-
const refreshed = readSpec(specPath);
|
|
164
|
-
refreshed.validation.semantic_review.semantic_digest = digest.stdout.trim();
|
|
165
|
-
refreshed.ready_for_implementation = true;
|
|
166
|
-
writeSpec(specPath, refreshed);
|
|
167
|
-
return { root, specDir, specPath };
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function cleanup(fixture) {
|
|
171
|
-
fs.rmSync(fixture.root, { recursive: true, force: true });
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function sha256Tag(bytes) {
|
|
175
|
-
return `sha256:${crypto.createHash('sha256').update(bytes).digest('hex')}`;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
test('C2 receipt freezes the exact key set with separate verdict and lifecycle_disposition', () => {
|
|
179
|
-
const fixture = makeReadyFixture();
|
|
180
|
-
try {
|
|
181
|
-
const clean = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
182
|
-
assert.equal(clean.status, 0, output(clean));
|
|
183
|
-
// Snapshot the known-clean baseline once; every mutation below clones it
|
|
184
|
-
// fresh so one case's leftover mutation never contaminates the next.
|
|
185
|
-
const baseline = readSpec(fixture.specPath);
|
|
186
|
-
|
|
187
|
-
const missingLifecycle = JSON.parse(JSON.stringify(baseline));
|
|
188
|
-
missingLifecycle.validation.semantic_review.lifecycle_disposition = null;
|
|
189
|
-
writeSpec(fixture.specPath, missingLifecycle);
|
|
190
|
-
const result1 = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
191
|
-
assert.equal(result1.status, 1, output(result1));
|
|
192
|
-
assert.match(output(result1), /lifecycle_disposition: must be CONTINUE or BLOCKED when completed/);
|
|
193
|
-
|
|
194
|
-
const extraKey = JSON.parse(JSON.stringify(baseline));
|
|
195
|
-
extraKey.validation.semantic_review.reviewer_note = 'not a canonical C2 field';
|
|
196
|
-
writeSpec(fixture.specPath, extraKey);
|
|
197
|
-
const result2 = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
198
|
-
assert.equal(result2.status, 1, output(result2));
|
|
199
|
-
assert.match(output(result2), /semantic_review: schema 2\.1 fields must be exactly/);
|
|
200
|
-
|
|
201
|
-
const conflated = JSON.parse(JSON.stringify(baseline));
|
|
202
|
-
conflated.validation.semantic_review.verdict = 'FAIL';
|
|
203
|
-
conflated.validation.semantic_review.lifecycle_disposition = 'CONTINUE';
|
|
204
|
-
writeSpec(fixture.specPath, conflated);
|
|
205
|
-
const result3 = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
206
|
-
// R3.4: structural validator enforces shape/type/enum/cardinality only —
|
|
207
|
-
// it does not judge whether a FAIL+CONTINUE pairing is a meaningful
|
|
208
|
-
// review outcome (that policy belongs to the R1-02 finalizer).
|
|
209
|
-
assert.equal(result3.status, 0, output(result3));
|
|
210
|
-
} finally { cleanup(fixture); }
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
test('sync migrates an exact legacy review into a fresh C2/C13/C16 lifecycle even when the semantic model is unchanged', () => {
|
|
214
|
-
const fixture = makeReadyFixture();
|
|
215
|
-
try {
|
|
216
|
-
const legacy = readSpec(fixture.specPath);
|
|
217
|
-
legacy.validation = {
|
|
218
|
-
status: 'completed',
|
|
219
|
-
semantic_review: {
|
|
220
|
-
status: 'completed',
|
|
221
|
-
semantic_digest: legacy.validation.semantic_review.semantic_digest,
|
|
222
|
-
reviewed_criteria: legacy.validation.semantic_review.reviewed_criteria,
|
|
223
|
-
counterexamples: legacy.validation.semantic_review.counterexamples,
|
|
224
|
-
},
|
|
225
|
-
};
|
|
226
|
-
legacy.ready_for_implementation = true;
|
|
227
|
-
writeSpec(fixture.specPath, legacy);
|
|
228
|
-
|
|
229
|
-
const migrated = exec(fixture.root, SCAFFOLD, ['feature', '--sync-semantic-model']);
|
|
230
|
-
assert.equal(migrated.status, 0, output(migrated));
|
|
231
|
-
const next = readSpec(fixture.specPath);
|
|
232
|
-
assert.equal(next.ready_for_implementation, false);
|
|
233
|
-
assert.equal(next.validation.status, 'not-run');
|
|
234
|
-
assert.deepEqual(Object.keys(next.validation).sort(), [
|
|
235
|
-
'authoring_validation', 'semantic_review', 'semantic_review_history', 'status',
|
|
236
|
-
]);
|
|
237
|
-
assert.equal(next.validation.semantic_review.status, 'not-run');
|
|
238
|
-
assert.deepEqual(next.validation.semantic_review_history.entries, []);
|
|
239
|
-
assert.equal(next.validation.authoring_validation, null);
|
|
240
|
-
assert.equal(next.authoring.requirements, 'draft');
|
|
241
|
-
assert.equal(next.authoring.design, 'draft');
|
|
242
|
-
assert.equal(next.authoring.tasks, 'draft');
|
|
243
|
-
} finally { cleanup(fixture); }
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
test('C2 findings/unresolved_decisions/graph_coverage/reviewer_evidence enforce exact shape', () => {
|
|
247
|
-
const fixture = makeReadyFixture();
|
|
248
|
-
try {
|
|
249
|
-
const badFinding = readSpec(fixture.specPath);
|
|
250
|
-
badFinding.validation.semantic_review.findings = [{
|
|
251
|
-
id: 'F1', severity: 'Severe', location: 'design.md', summary: 'A concrete finding summary.', blocking: false, disposition: 'open',
|
|
252
|
-
}];
|
|
253
|
-
writeSpec(fixture.specPath, badFinding);
|
|
254
|
-
const result1 = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
255
|
-
assert.equal(result1.status, 1, output(result1));
|
|
256
|
-
assert.match(output(result1), /findings\[0\]\.severity: must be Critical, High, Medium, or Low/);
|
|
257
|
-
|
|
258
|
-
const badCoverage = readSpec(fixture.specPath);
|
|
259
|
-
badCoverage.validation.semantic_review.graph_coverage = badCoverage.validation.semantic_review.graph_coverage.slice(0, 4);
|
|
260
|
-
writeSpec(fixture.specPath, badCoverage);
|
|
261
|
-
const result2 = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
262
|
-
assert.equal(result2.status, 1, output(result2));
|
|
263
|
-
assert.match(output(result2), /graph_coverage: missing required surface/);
|
|
264
|
-
|
|
265
|
-
const badEvidence = readSpec(fixture.specPath);
|
|
266
|
-
badEvidence.validation.semantic_review.reviewer_evidence = { assurance: 'Overkill', summary: 'A concrete reviewer summary.' };
|
|
267
|
-
writeSpec(fixture.specPath, badEvidence);
|
|
268
|
-
const result3 = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
269
|
-
assert.equal(result3.status, 1, output(result3));
|
|
270
|
-
assert.match(output(result3), /reviewer_evidence\.assurance: must be Routine, Elevated, or Strict/);
|
|
271
|
-
} finally { cleanup(fixture); }
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
test('C13 semantic_review_history freezes exact keys and dedupes across the whole lineage', () => {
|
|
275
|
-
const fixture = makeReadyFixture();
|
|
276
|
-
try {
|
|
277
|
-
const spec = readSpec(fixture.specPath);
|
|
278
|
-
const receiptDigestA = sha256Tag(Buffer.from('receipt-a'));
|
|
279
|
-
const receiptDigestB = sha256Tag(Buffer.from('receipt-b'));
|
|
280
|
-
const semanticDigest = spec.validation.semantic_review.semantic_digest;
|
|
281
|
-
spec.validation.semantic_review_history.entries = [
|
|
282
|
-
{
|
|
283
|
-
sequence: 0, semantic_digest: semanticDigest, review_receipt_digest: receiptDigestA,
|
|
284
|
-
verdict: 'FAIL', lifecycle_disposition: 'CONTINUE', blocking_count: 1, attempt_index: 0, review_epoch: 0,
|
|
285
|
-
},
|
|
286
|
-
{
|
|
287
|
-
sequence: 1, semantic_digest: semanticDigest, review_receipt_digest: receiptDigestB,
|
|
288
|
-
verdict: 'PASS', lifecycle_disposition: 'CONTINUE', blocking_count: 0, attempt_index: 1, review_epoch: 0,
|
|
289
|
-
},
|
|
290
|
-
];
|
|
291
|
-
writeSpec(fixture.specPath, spec);
|
|
292
|
-
const clean = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
293
|
-
assert.equal(clean.status, 0, output(clean));
|
|
294
|
-
// Snapshot the known-clean 2-entry baseline once; every mutation below
|
|
295
|
-
// clones it fresh so one case's leftover mutation never contaminates the
|
|
296
|
-
// next (each case must independently isolate the single defect it proves).
|
|
297
|
-
const baseline = readSpec(fixture.specPath);
|
|
298
|
-
|
|
299
|
-
const missingEpoch = JSON.parse(JSON.stringify(baseline));
|
|
300
|
-
delete missingEpoch.validation.semantic_review_history.entries[0].review_epoch;
|
|
301
|
-
writeSpec(fixture.specPath, missingEpoch);
|
|
302
|
-
const result1 = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
303
|
-
assert.equal(result1.status, 1, output(result1));
|
|
304
|
-
assert.match(output(result1), /entries\[0\]: fields must be exactly/);
|
|
305
|
-
|
|
306
|
-
// Whole-lineage dedupe: a third entry replaying entry 0's digest is a
|
|
307
|
-
// replay even though entry 0 is not the latest entry.
|
|
308
|
-
const replay = JSON.parse(JSON.stringify(baseline));
|
|
309
|
-
replay.validation.semantic_review_history.entries.push({
|
|
310
|
-
sequence: 2, semantic_digest: semanticDigest, review_receipt_digest: receiptDigestA,
|
|
311
|
-
verdict: 'PASS', lifecycle_disposition: 'CONTINUE', blocking_count: 0, attempt_index: 2, review_epoch: 0,
|
|
312
|
-
});
|
|
313
|
-
writeSpec(fixture.specPath, replay);
|
|
314
|
-
const result2 = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
315
|
-
assert.equal(result2.status, 1, output(result2));
|
|
316
|
-
assert.match(output(result2), /review_receipt_digest: replays an existing entry's digest anywhere in the lineage/);
|
|
317
|
-
|
|
318
|
-
const badLineage = JSON.parse(JSON.stringify(baseline));
|
|
319
|
-
badLineage.validation.semantic_review_history.lineage_id = sha256Tag(Buffer.from('wrong'));
|
|
320
|
-
writeSpec(fixture.specPath, badLineage);
|
|
321
|
-
const result3 = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
322
|
-
assert.equal(result3.status, 1, output(result3));
|
|
323
|
-
assert.match(output(result3), /lineage_id: must be sha256 of feature_name and created_at/);
|
|
324
|
-
} finally { cleanup(fixture); }
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
test('C16 coordinator is the sole atomic writer: clean pass writes, a failing pass leaves spec.json untouched', () => {
|
|
328
|
-
const fixture = makeReadyFixture();
|
|
329
|
-
try {
|
|
330
|
-
const before = readSpec(fixture.specPath);
|
|
331
|
-
assert.equal(before.authoring.requirements, 'validated');
|
|
332
|
-
assert.equal(before.authoring.design, 'validated');
|
|
333
|
-
assert.equal(before.authoring.tasks, 'validated');
|
|
334
|
-
assert.ok(before.validation.authoring_validation);
|
|
335
|
-
assert.match(before.validation.authoring_validation.requirements.digest, /^sha256:[a-f0-9]{64}$/);
|
|
336
|
-
|
|
337
|
-
// Re-running the coordinator on already-fresh bytes is a clean no-op pass.
|
|
338
|
-
const rerun = exec(fixture.root, AUTHORING_VALIDATOR, [fixture.specDir]);
|
|
339
|
-
assert.equal(rerun.status, 0, output(rerun));
|
|
340
|
-
|
|
341
|
-
// Break grounding (dangling design anchor target) so the coordinator's
|
|
342
|
-
// own validate+ground pass fails; spec.json bytes must stay identical.
|
|
343
|
-
const designPath = path.join(fixture.specDir, 'design.md');
|
|
344
|
-
fs.writeFileSync(designPath, fs.readFileSync(designPath, 'utf8').replace(
|
|
345
|
-
'| A-D-01 | file | `src/design-boundary.js` | design boundary | read | read |',
|
|
346
|
-
'| A-D-01 | file | `src/does-not-exist.js` | design boundary | read | read |',
|
|
347
|
-
));
|
|
348
|
-
const beforeBytes = fs.readFileSync(fixture.specPath, 'utf8');
|
|
349
|
-
const failing = exec(fixture.root, AUTHORING_VALIDATOR, [fixture.specDir]);
|
|
350
|
-
assert.notEqual(failing.status, 0, output(failing));
|
|
351
|
-
const afterBytes = fs.readFileSync(fixture.specPath, 'utf8');
|
|
352
|
-
assert.equal(afterBytes, beforeBytes, 'a failed coordinator run must leave spec.json completely unchanged');
|
|
353
|
-
} finally { cleanup(fixture); }
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
test('C16/I20 fail-closed freshness: an edit after validation reverts the stage to draft-equivalent structurally', () => {
|
|
357
|
-
const fixture = makeReadyFixture();
|
|
358
|
-
try {
|
|
359
|
-
const clean = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
360
|
-
assert.equal(clean.status, 0, output(clean));
|
|
361
|
-
|
|
362
|
-
// Edit requirements.md after the coordinator already ran; do not rerun the
|
|
363
|
-
// coordinator. authoring.requirements still reads validated, but its
|
|
364
|
-
// receipt digest no longer matches the current bytes.
|
|
365
|
-
fs.appendFileSync(path.join(fixture.specDir, 'requirements.md'), '\nA later edit changes the authored requirement text.\n');
|
|
366
|
-
const stale = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
367
|
-
assert.equal(stale.status, 1, output(stale));
|
|
368
|
-
assert.match(output(stale), /spec\.json\.authoring\.requirements: reads validated but validation\.authoring_validation.*digest-mismatched/);
|
|
369
|
-
|
|
370
|
-
// An absent receipt with a validated reading is refused identically.
|
|
371
|
-
const missingReceipt = readSpec(fixture.specPath);
|
|
372
|
-
missingReceipt.validation.authoring_validation = null;
|
|
373
|
-
writeSpec(fixture.specPath, missingReceipt);
|
|
374
|
-
const absent = exec(fixture.root, VALIDATOR, [fixture.specDir]);
|
|
375
|
-
assert.equal(absent.status, 1, output(absent));
|
|
376
|
-
assert.match(output(absent), /spec\.json\.authoring\.requirements: reads validated but validation\.authoring_validation is absent/);
|
|
377
|
-
} finally { cleanup(fixture); }
|
|
378
|
-
});
|