@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,342 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const test = require('node:test');
|
|
4
|
-
const assert = require('node:assert/strict');
|
|
5
|
-
const fs = require('fs');
|
|
6
|
-
const os = require('os');
|
|
7
|
-
const path = require('path');
|
|
8
|
-
|
|
9
|
-
const { finalizeReadiness } = require('../../src/claude/scripts/spec-readiness.cjs');
|
|
10
|
-
|
|
11
|
-
const SEMANTIC_DIGEST = `sha256:${'a'.repeat(64)}`;
|
|
12
|
-
const FEATURE_NAME = 'cafekit-semantic-eval-firewall';
|
|
13
|
-
const CREATED_AT = '2026-08-17T00:00:00.000Z';
|
|
14
|
-
const AUTHORING_VALIDATION = Object.freeze({
|
|
15
|
-
schema_version: '1',
|
|
16
|
-
requirements: { digest: `sha256:${'b'.repeat(64)}`, validated_at: CREATED_AT },
|
|
17
|
-
design: { digest: `sha256:${'c'.repeat(64)}`, validated_at: CREATED_AT },
|
|
18
|
-
research: { digest: `sha256:${'d'.repeat(64)}`, validated_at: CREATED_AT },
|
|
19
|
-
tasks: { digest: `sha256:${'e'.repeat(64)}`, validated_at: CREATED_AT },
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
function review(verdict = 'PASS', overrides = {}) {
|
|
23
|
-
return {
|
|
24
|
-
verdict,
|
|
25
|
-
findings: [],
|
|
26
|
-
unresolved_decisions: [],
|
|
27
|
-
graph_coverage: [{
|
|
28
|
-
surface: 'criterion_local',
|
|
29
|
-
covered: true,
|
|
30
|
-
notes: 'criterion was reviewed against the canonical semantic model',
|
|
31
|
-
}],
|
|
32
|
-
reviewed_criteria: ['R1.1'],
|
|
33
|
-
counterexamples: [],
|
|
34
|
-
reviewer_evidence: null,
|
|
35
|
-
...overrides,
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function finding(id, blocking = false) {
|
|
40
|
-
return {
|
|
41
|
-
id,
|
|
42
|
-
severity: 'High',
|
|
43
|
-
location: 'spec.json:R1.1',
|
|
44
|
-
summary: 'The semantic review requires an explicit disposition',
|
|
45
|
-
blocking,
|
|
46
|
-
disposition: 'open',
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function decision(id, blocking = false) {
|
|
51
|
-
return {
|
|
52
|
-
id,
|
|
53
|
-
summary: 'The unresolved decision needs an owner and a recorded outcome',
|
|
54
|
-
blocking,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function stableJson(value) {
|
|
59
|
-
if (Array.isArray(value)) return `[${value.map(stableJson).join(',')}]`;
|
|
60
|
-
if (value && typeof value === 'object') {
|
|
61
|
-
return `{${Object.keys(value).sort().map((key) => (
|
|
62
|
-
`${JSON.stringify(key)}:${stableJson(value[key])}`
|
|
63
|
-
)).join(',')}}`;
|
|
64
|
-
}
|
|
65
|
-
return JSON.stringify(value);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function dependencies({ staleAuthoring = false } = {}) {
|
|
69
|
-
const calls = { digest: 0, validate: 0, ground: 0 };
|
|
70
|
-
const expectedAuthoring = JSON.stringify(AUTHORING_VALIDATION);
|
|
71
|
-
return {
|
|
72
|
-
calls,
|
|
73
|
-
semantic: {
|
|
74
|
-
modelFromMarkdown: () => ({ model: { projected: true }, errors: [] }),
|
|
75
|
-
stableJson,
|
|
76
|
-
},
|
|
77
|
-
validator: {
|
|
78
|
-
computeSemanticDigest21: () => {
|
|
79
|
-
calls.digest += 1;
|
|
80
|
-
return { errors: [], digest: SEMANTIC_DIGEST };
|
|
81
|
-
},
|
|
82
|
-
validateSpec: (_directory, candidate) => {
|
|
83
|
-
calls.validate += 1;
|
|
84
|
-
const preserved = JSON.stringify(candidate.validation.authoring_validation) === expectedAuthoring;
|
|
85
|
-
return { errors: staleAuthoring || !preserved ? ['authoring validation is stale'] : [], warnings: [] };
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
grounder: () => {
|
|
89
|
-
calls.ground += 1;
|
|
90
|
-
return { errors: [] };
|
|
91
|
-
},
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function fixture({ identity = { feature_name: FEATURE_NAME, created_at: CREATED_AT },
|
|
96
|
-
semanticReviewHistory, authoringValidation = AUTHORING_VALIDATION } = {}) {
|
|
97
|
-
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-readiness-'));
|
|
98
|
-
const validation = {
|
|
99
|
-
status: 'completed',
|
|
100
|
-
semantic_review: null,
|
|
101
|
-
authoring_validation: authoringValidation,
|
|
102
|
-
};
|
|
103
|
-
if (semanticReviewHistory !== undefined) validation.semantic_review_history = semanticReviewHistory;
|
|
104
|
-
const spec = {
|
|
105
|
-
schema_version: '2.1',
|
|
106
|
-
...identity,
|
|
107
|
-
semantic_model: { projected: false },
|
|
108
|
-
validation,
|
|
109
|
-
ready_for_implementation: false,
|
|
110
|
-
timestamps: {
|
|
111
|
-
init: CREATED_AT,
|
|
112
|
-
requirements_done: CREATED_AT,
|
|
113
|
-
design_done: CREATED_AT,
|
|
114
|
-
validation_done: CREATED_AT,
|
|
115
|
-
},
|
|
116
|
-
updated_at: CREATED_AT,
|
|
117
|
-
};
|
|
118
|
-
fs.writeFileSync(path.join(directory, 'spec.json'), `${JSON.stringify(spec, null, 2)}\n`);
|
|
119
|
-
return { directory, spec };
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function finalize(directory, reviewResult, options = {}) {
|
|
123
|
-
const injected = dependencies(options);
|
|
124
|
-
const result = finalizeReadiness({
|
|
125
|
-
specDir: directory,
|
|
126
|
-
projectRoot: directory,
|
|
127
|
-
reviewResult,
|
|
128
|
-
...injected,
|
|
129
|
-
});
|
|
130
|
-
return { result, calls: injected.calls };
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function cleanup(directory) {
|
|
134
|
-
fs.rmSync(directory, { recursive: true, force: true });
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
test('explicit PASS builds a completed C2 receipt, preserves C16, and is ready', () => {
|
|
138
|
-
const { directory } = fixture();
|
|
139
|
-
try {
|
|
140
|
-
const { result, calls } = finalize(directory, review('PASS'));
|
|
141
|
-
assert.equal(result.spec.ready_for_implementation, true);
|
|
142
|
-
assert.equal(result.spec.validation.semantic_review.status, 'completed');
|
|
143
|
-
assert.equal(result.spec.validation.semantic_review.verdict, 'PASS');
|
|
144
|
-
assert.deepEqual(result.spec.validation.semantic_review.findings, []);
|
|
145
|
-
assert.deepEqual(result.spec.validation.authoring_validation, AUTHORING_VALIDATION);
|
|
146
|
-
assert.match(result.history_entry.review_receipt_digest, /^sha256:[0-9a-f]{64}$/);
|
|
147
|
-
assert.equal(calls.validate, 2);
|
|
148
|
-
assert.equal(calls.ground, 2);
|
|
149
|
-
} finally {
|
|
150
|
-
cleanup(directory);
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
test('explicit FAIL is authoritative even when there are no blockers', () => {
|
|
155
|
-
const { directory } = fixture();
|
|
156
|
-
try {
|
|
157
|
-
const { result } = finalize(directory, review('FAIL'));
|
|
158
|
-
assert.equal(result.spec.ready_for_implementation, false);
|
|
159
|
-
assert.equal(result.history_entry.verdict, 'FAIL');
|
|
160
|
-
assert.equal(result.history_entry.blocking_count, 0);
|
|
161
|
-
assert.equal(result.history_entry.lifecycle_disposition, 'CONTINUE');
|
|
162
|
-
} finally {
|
|
163
|
-
cleanup(directory);
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
test('PASS with blocking findings or decisions is refused', () => {
|
|
168
|
-
for (const overrides of [
|
|
169
|
-
{ findings: [finding('F-BLOCKING', true)] },
|
|
170
|
-
{ unresolved_decisions: [decision('D-BLOCKING', true)] },
|
|
171
|
-
]) {
|
|
172
|
-
const { directory } = fixture();
|
|
173
|
-
try {
|
|
174
|
-
assert.throws(
|
|
175
|
-
() => finalize(directory, review('PASS', overrides)),
|
|
176
|
-
/PASS cannot contain blocking/,
|
|
177
|
-
);
|
|
178
|
-
const persisted = JSON.parse(fs.readFileSync(path.join(directory, 'spec.json'), 'utf8'));
|
|
179
|
-
assert.equal(persisted.validation.semantic_review_history, undefined);
|
|
180
|
-
} finally {
|
|
181
|
-
cleanup(directory);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test('blocking_count counts blocking findings and unresolved decisions', () => {
|
|
187
|
-
const { directory } = fixture();
|
|
188
|
-
try {
|
|
189
|
-
const { result } = finalize(directory, review('FAIL', {
|
|
190
|
-
findings: [finding('F-BLOCKING', true), finding('F-OPEN', false)],
|
|
191
|
-
unresolved_decisions: [decision('D-BLOCKING', true), decision('D-OPEN', false)],
|
|
192
|
-
}));
|
|
193
|
-
assert.equal(result.history_entry.blocking_count, 2);
|
|
194
|
-
} finally {
|
|
195
|
-
cleanup(directory);
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
test('caller digest and derived/control fields are rejected from public input', () => {
|
|
200
|
-
const fields = [
|
|
201
|
-
'review_receipt_digest', 'lifecycle_disposition', 'repair_round', 'review_epoch',
|
|
202
|
-
'attempt_index', 'semantic_review_history', 'lineage', 'readiness',
|
|
203
|
-
'ready_for_implementation', 'validation', 'authoring_validation',
|
|
204
|
-
];
|
|
205
|
-
for (const field of fields) {
|
|
206
|
-
const { directory } = fixture();
|
|
207
|
-
try {
|
|
208
|
-
const invalid = { ...review(), [field]: field };
|
|
209
|
-
assert.throws(() => finalize(directory, invalid), /fields must be exactly/);
|
|
210
|
-
} finally {
|
|
211
|
-
cleanup(directory);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
test('lineage is deterministic and divergent or missing identity is rejected', () => {
|
|
217
|
-
const firstFixture = fixture();
|
|
218
|
-
const secondFixture = fixture();
|
|
219
|
-
const divergentFixture = fixture({
|
|
220
|
-
semanticReviewHistory: { lineage_id: `sha256:${'f'.repeat(64)}`, entries: [] },
|
|
221
|
-
});
|
|
222
|
-
const missingFixture = fixture({ identity: { created_at: CREATED_AT } });
|
|
223
|
-
try {
|
|
224
|
-
const first = finalize(firstFixture.directory, review()).result;
|
|
225
|
-
const second = finalize(secondFixture.directory, review()).result;
|
|
226
|
-
assert.equal(
|
|
227
|
-
first.spec.validation.semantic_review_history.lineage_id,
|
|
228
|
-
second.spec.validation.semantic_review_history.lineage_id,
|
|
229
|
-
);
|
|
230
|
-
assert.match(first.spec.validation.semantic_review_history.lineage_id, /^sha256:[0-9a-f]{64}$/);
|
|
231
|
-
assert.throws(
|
|
232
|
-
() => finalize(divergentFixture.directory, review()),
|
|
233
|
-
/lineage_id diverges/,
|
|
234
|
-
);
|
|
235
|
-
assert.throws(
|
|
236
|
-
() => finalize(missingFixture.directory, review()),
|
|
237
|
-
/feature_name is required/,
|
|
238
|
-
);
|
|
239
|
-
} finally {
|
|
240
|
-
cleanup(firstFixture.directory);
|
|
241
|
-
cleanup(secondFixture.directory);
|
|
242
|
-
cleanup(divergentFixture.directory);
|
|
243
|
-
cleanup(missingFixture.directory);
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
test('replaying a receipt is idempotent across the whole lineage', () => {
|
|
248
|
-
const { directory } = fixture();
|
|
249
|
-
try {
|
|
250
|
-
finalize(directory, review('PASS'));
|
|
251
|
-
const before = fs.readFileSync(path.join(directory, 'spec.json'), 'utf8');
|
|
252
|
-
const replay = finalize(directory, review('PASS')).result;
|
|
253
|
-
const after = fs.readFileSync(path.join(directory, 'spec.json'), 'utf8');
|
|
254
|
-
assert.equal(replay.replayed, true);
|
|
255
|
-
assert.equal(replay.spec.validation.semantic_review_history.entries.length, 1);
|
|
256
|
-
assert.equal(after, before);
|
|
257
|
-
} finally {
|
|
258
|
-
cleanup(directory);
|
|
259
|
-
}
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
test('replaying an earlier receipt dedupes across the whole lineage', () => {
|
|
263
|
-
const { directory } = fixture();
|
|
264
|
-
try {
|
|
265
|
-
finalize(directory, review('PASS', { reviewed_criteria: ['R1.1'] }));
|
|
266
|
-
finalize(directory, review('FAIL', { reviewed_criteria: ['R1.2'] }));
|
|
267
|
-
const specFile = path.join(directory, 'spec.json');
|
|
268
|
-
const beforeReplay = fs.readFileSync(specFile);
|
|
269
|
-
const replay = finalize(directory, review('PASS', { reviewed_criteria: ['R1.1'] })).result;
|
|
270
|
-
const afterReplay = fs.readFileSync(specFile);
|
|
271
|
-
assert.equal(replay.replayed, true);
|
|
272
|
-
assert.equal(replay.history_entry.sequence, 0);
|
|
273
|
-
assert.equal(replay.history_entry.review_epoch, 0);
|
|
274
|
-
assert.equal(replay.history_entry.attempt_index, 0);
|
|
275
|
-
assert.equal(replay.spec.validation.semantic_review_history.entries.length, 2);
|
|
276
|
-
assert.deepEqual(afterReplay, beforeReplay);
|
|
277
|
-
} finally {
|
|
278
|
-
cleanup(directory);
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
test('a PASS starts the next epoch with a reset FAIL attempt index', () => {
|
|
283
|
-
const { directory } = fixture();
|
|
284
|
-
try {
|
|
285
|
-
finalize(directory, review('PASS'));
|
|
286
|
-
finalize(directory, review('FAIL', { reviewed_criteria: ['R1.2'] }));
|
|
287
|
-
finalize(directory, review('PASS', { reviewed_criteria: ['R1.3'] }));
|
|
288
|
-
const next = finalize(directory, review('FAIL', { reviewed_criteria: ['R1.4'] })).result;
|
|
289
|
-
assert.equal(next.history_entry.review_epoch, 2);
|
|
290
|
-
assert.equal(next.history_entry.attempt_index, 0);
|
|
291
|
-
assert.equal(next.history_entry.lifecycle_disposition, 'CONTINUE');
|
|
292
|
-
} finally {
|
|
293
|
-
cleanup(directory);
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
test('the third FAIL is terminal BLOCKED and there is no index 3', () => {
|
|
298
|
-
const { directory } = fixture();
|
|
299
|
-
try {
|
|
300
|
-
finalize(directory, review('FAIL', { reviewed_criteria: ['R1.1'] }));
|
|
301
|
-
finalize(directory, review('FAIL', { reviewed_criteria: ['R1.2'] }));
|
|
302
|
-
const blocked = finalize(directory, review('FAIL', { reviewed_criteria: ['R1.3'] })).result;
|
|
303
|
-
assert.equal(blocked.spec.ready_for_implementation, false);
|
|
304
|
-
assert.equal(blocked.history_entry.attempt_index, 2);
|
|
305
|
-
assert.equal(blocked.history_entry.lifecycle_disposition, 'BLOCKED');
|
|
306
|
-
const specFile = path.join(directory, 'spec.json');
|
|
307
|
-
const beforeReplay = fs.readFileSync(specFile);
|
|
308
|
-
const replay = finalize(directory, review('FAIL', { reviewed_criteria: ['R1.3'] })).result;
|
|
309
|
-
const afterReplay = fs.readFileSync(specFile);
|
|
310
|
-
assert.equal(replay.replayed, true);
|
|
311
|
-
assert.equal(replay.history_entry.attempt_index, 2);
|
|
312
|
-
assert.deepEqual(afterReplay, beforeReplay);
|
|
313
|
-
assert.throws(() => finalize(directory, review('PASS')), /already ends BLOCKED/);
|
|
314
|
-
const persisted = JSON.parse(afterReplay.toString('utf8'));
|
|
315
|
-
assert.deepEqual(
|
|
316
|
-
persisted.validation.semantic_review_history.entries.map((entry) => entry.attempt_index),
|
|
317
|
-
[0, 1, 2],
|
|
318
|
-
);
|
|
319
|
-
} finally {
|
|
320
|
-
cleanup(directory);
|
|
321
|
-
}
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
test('stale C16 authoring validation refuses the append', () => {
|
|
325
|
-
const { directory } = fixture({
|
|
326
|
-
authoringValidation: { ...AUTHORING_VALIDATION, design: { digest: 'stale' } },
|
|
327
|
-
});
|
|
328
|
-
try {
|
|
329
|
-
const specFile = path.join(directory, 'spec.json');
|
|
330
|
-
const before = fs.readFileSync(specFile);
|
|
331
|
-
assert.throws(
|
|
332
|
-
() => finalize(directory, review('PASS'), { staleAuthoring: true }),
|
|
333
|
-
/readiness validation failed/,
|
|
334
|
-
);
|
|
335
|
-
const after = fs.readFileSync(specFile);
|
|
336
|
-
assert.deepEqual(after, before);
|
|
337
|
-
const persisted = JSON.parse(after.toString('utf8'));
|
|
338
|
-
assert.equal(persisted.validation.semantic_review_history, undefined);
|
|
339
|
-
} finally {
|
|
340
|
-
cleanup(directory);
|
|
341
|
-
}
|
|
342
|
-
});
|