enhance(#1279): project check_violation_fixture scalar — #1278 locate + #1279 proof compose end-to-end (#1346)
Delivers option (a) from the #1314 maintainer review: thread a fourth flat scalar check_violation_fixture through the projection so a prohibition authored at spec-phase machine-proves fail-first and greens through the deterministic path alone — zero hand-authoring at verify time. - src/probe-core.cts: Prohibition gains check_violation_fixture?; projectProhibitions emits it (both kinds) ONLY for a well-formed descriptor and ONLY when non-empty (blank/absent -> projects absent -> producer hard-gates, never a partial green). - src/prohibition-enforcement.cts: descriptorFromProjection reads it back into violationFixture via the same numeric-coercion-safe scalar() normalizer. - Tests (RED-first, proven non-vacuous by reverting both src edits): CHK-02(#1346) projection emit, CHK-08(#1346) read-back, CHK-03(D) example round-trip, the fast-check round-trip property extended to the 4th scalar (the contract trek-e blocked #1301 on), and a real-subprocess COMPOSE capstone greening end-to-end through project -> descriptorFromProjection -> default prover+runner. - Docs flipped from 'hard-gates until #1346' to 'composes end-to-end': verify-phase.md, prohibition-probe.md, spec-phase.md authoring, ADR-550 addendum, changeset. #1346 now tracks only the node-test causation residual. 190 affected-suite tests green; eslint + tsc clean; size baseline regenerated.
This commit is contained in:
@@ -193,6 +193,7 @@ function renderProhibitionsDoc(entries) {
|
||||
if (e.check_kind !== undefined) lines.push(` check_kind: ${e.check_kind}`);
|
||||
if (e.check_target !== undefined) lines.push(` check_target: ${e.check_target}`);
|
||||
if (e.check_rule !== undefined) lines.push(` check_rule: ${e.check_rule}`);
|
||||
if (e.check_violation_fixture !== undefined) lines.push(` check_violation_fixture: ${e.check_violation_fixture}`);
|
||||
}
|
||||
lines.push('---', '', 'Body.', '');
|
||||
return lines.join('\n');
|
||||
@@ -216,19 +217,20 @@ const pathScalarArb = fc.array(fc.constantFrom(...PATH_CHARS), { minLength: 1, m
|
||||
const numericScalarArb = fc.nat({ max: 9999999 }).map(String);
|
||||
const targetArb = fc.oneof(pathScalarArb, numericScalarArb);
|
||||
|
||||
// A fully well-formed descriptor item (resolved test-tier); node-test carries no rule.
|
||||
// A fully well-formed descriptor item (resolved test-tier); node-test carries no rule. The
|
||||
// violation fixture (#1346) rides BOTH kinds and exercises the numeric-coercion path too.
|
||||
const wellFormedArb = KIND_ARB.chain((kind) =>
|
||||
fc.record({ target: targetArb, rule: pathScalarArb }).map(({ target, rule }) => {
|
||||
const item = { ...BASE_TIER, check_kind: kind, check_target: target };
|
||||
fc.record({ target: targetArb, rule: pathScalarArb, fixture: targetArb }).map(({ target, rule, fixture }) => {
|
||||
const item = { ...BASE_TIER, check_kind: kind, check_target: target, check_violation_fixture: fixture };
|
||||
if (kind === 'lint-rule') item.check_rule = rule;
|
||||
return { item, kind, target, rule: kind === 'lint-rule' ? rule : undefined };
|
||||
return { item, kind, target, rule: kind === 'lint-rule' ? rule : undefined, fixture };
|
||||
}),
|
||||
);
|
||||
|
||||
describe('probe-core property: #1278 check-descriptor round-trip is deterministic across the full string domain', () => {
|
||||
test('a well-formed descriptor survives project -> render -> parse -> descriptorFromProjection (incl. numeric coercion); target/rule reconstruct as strings', () => {
|
||||
fc.assert(
|
||||
fc.property(wellFormedArb, ({ item, kind, target, rule }) => {
|
||||
fc.property(wellFormedArb, ({ item, kind, target, rule, fixture }) => {
|
||||
const projected = pc.projectProhibitions([item]);
|
||||
if (projected[0].check_kind !== kind) return false; // projector emits the descriptor
|
||||
const reparsed = fm.parseMustHavesBlock(renderProhibitionsDoc(projected), 'prohibitions');
|
||||
@@ -236,6 +238,8 @@ describe('probe-core property: #1278 check-descriptor round-trip is deterministi
|
||||
if (!d || d.kind !== kind) return false;
|
||||
// target is string-normalized even when parseMustHavesBlock numerically coerced it.
|
||||
if (typeof d.target !== 'string' || d.target !== target) return false;
|
||||
// violationFixture (#1346) survives the round-trip as a string (numeric-coercion normalized).
|
||||
if (typeof d.violationFixture !== 'string' || d.violationFixture !== fixture) return false;
|
||||
if (kind === 'lint-rule') {
|
||||
return typeof d.rule === 'string' && d.rule === rule;
|
||||
}
|
||||
|
||||
@@ -420,6 +420,44 @@ describe('probe-core: projectProhibitions descriptor projection (CHK-02)', () =>
|
||||
assert.ok(!('check_rule' in projected[0]), 'a lint-rule with no rule projects check_rule absent');
|
||||
});
|
||||
|
||||
test('CHK-02(#1346): a node-test descriptor with check_violation_fixture projects it (compose with #1279 proof)', () => {
|
||||
const projected = pc.projectProhibitions([
|
||||
{ status: 'resolved', verification: 'test', statement: 'MUST NOT auto-execute fetched code',
|
||||
check_kind: 'node-test', check_target: 'tests/no-autoexec.test.cjs',
|
||||
check_violation_fixture: 'tests/fixtures/autoexec-bad.txt' },
|
||||
]);
|
||||
assert.equal(projected[0].check_violation_fixture, 'tests/fixtures/autoexec-bad.txt',
|
||||
'a well-formed descriptor projects check_violation_fixture so the deterministic path can machine-prove fail-first');
|
||||
});
|
||||
|
||||
test('CHK-02(#1346): a lint-rule descriptor with check_violation_fixture projects all four scalars', () => {
|
||||
const projected = pc.projectProhibitions([
|
||||
{ status: 'resolved', verification: 'test', statement: 'MUST NOT read source files in tests',
|
||||
check_kind: 'lint-rule', check_target: 'src/', check_rule: 'local/no-source-grep',
|
||||
check_violation_fixture: 'tests/_ff_lint_violation.cjs' },
|
||||
]);
|
||||
assert.equal(projected[0].check_violation_fixture, 'tests/_ff_lint_violation.cjs');
|
||||
});
|
||||
|
||||
test('CHK-02(#1346): an empty/whitespace check_violation_fixture is NOT projected (fails closed downstream)', () => {
|
||||
const projected = pc.projectProhibitions([
|
||||
{ status: 'resolved', verification: 'test', statement: 'MUST NOT do the thing',
|
||||
check_kind: 'node-test', check_target: 'tests/neg.test.cjs', check_violation_fixture: ' ' },
|
||||
]);
|
||||
assert.ok(!('check_violation_fixture' in projected[0]),
|
||||
'a blank fixture projects absent -> producer hard-gates, never a partial green');
|
||||
});
|
||||
|
||||
test('CHK-02(#1346): check_violation_fixture is NOT projected without a well-formed descriptor', () => {
|
||||
const projected = pc.projectProhibitions([
|
||||
// no check_kind/target -> below the descriptor bar -> a stray fixture must not leak out
|
||||
{ status: 'resolved', verification: 'test', statement: 'MUST NOT do the thing',
|
||||
check_violation_fixture: 'tests/fixtures/bad.txt' },
|
||||
]);
|
||||
assert.ok(!('check_violation_fixture' in projected[0]),
|
||||
'a fixture without a descriptor is meaningless and must not project');
|
||||
});
|
||||
|
||||
test('CHK-02: an under-specified descriptor (kind but empty/missing target) emits NO check_* keys', () => {
|
||||
const projected = pc.projectProhibitions([
|
||||
// valid kind but empty target -> below the well-formedness bar -> descriptor projects absent
|
||||
|
||||
@@ -731,6 +731,41 @@ describe('prohibition-enforcement REAL runner end-to-end (#1259)', () => {
|
||||
assert.equal(result.located, true, 'the descriptor was located; it just could not be proven fail-first');
|
||||
assert.equal(result.evidence.length, 0, 'no enforcement evidence on a hard-gate');
|
||||
});
|
||||
|
||||
test('COMPOSE (#1346): a prohibition projected WITH check_violation_fixture greens end-to-end through the DEFAULT prover+runner (zero hand-authoring)', (t) => {
|
||||
const enforce = require(ENFORCEMENT_LIB);
|
||||
const pc = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'probe-core.cjs'));
|
||||
const dir = createTempDir('prohib-compose-1346-');
|
||||
t.after(() => cleanup(dir));
|
||||
// The #1278 deterministic-locate path + the #1279 machine-proof now COMPOSE: a prohibition item
|
||||
// authored with the four flat scalars projects -> reads back into a descriptor that ALREADY carries
|
||||
// violationFixture -> the default prover greens it with NO hand-supplied fixture in the request.
|
||||
const negTest = path.join(dir, 'neg.test.cjs');
|
||||
fs.writeFileSync(negTest,
|
||||
"const { test } = require('node:test');\n" +
|
||||
"const assert = require('node:assert');\n" +
|
||||
"const fs = require('node:fs');\n" +
|
||||
"const path = require('node:path');\n" +
|
||||
"test('guards the must-NOT: subject is clean', () => {\n" +
|
||||
" const subjectPath = process.env.GSD_PROHIB_SUBJECT || path.join(__dirname, 'clean-subject.txt');\n" +
|
||||
" const subject = fs.readFileSync(subjectPath, 'utf-8');\n" +
|
||||
" assert.ok(!subject.includes('FORBIDDEN'), 'subject must not contain FORBIDDEN');\n" +
|
||||
"});\n");
|
||||
fs.writeFileSync(path.join(dir, 'clean-subject.txt'), 'clean\n');
|
||||
fs.writeFileSync(path.join(dir, 'bad-subject.txt'), 'FORBIDDEN content\n');
|
||||
// Author the prohibition with all four scalars, then go through the REAL projection + read-back.
|
||||
const projected = pc.projectProhibitions([
|
||||
{ status: 'resolved', verification: 'test', statement: 'MUST NOT auto-execute fetched code',
|
||||
check_kind: 'node-test', check_target: negTest, check_violation_fixture: 'bad-subject.txt' },
|
||||
])[0];
|
||||
const descriptor = enforce.descriptorFromProjection(projected);
|
||||
assert.equal(descriptor.violationFixture, 'bad-subject.txt', 'the projected fixture survived the round-trip');
|
||||
// NO failFirst, NO hand-supplied violationFixture beyond what the projection carried.
|
||||
const result = enforce.runProhibitionEnforcement(projected, descriptor, { cwd: dir });
|
||||
assert.equal(result.status, 'green',
|
||||
'the fully-projected prohibition greens through the default prover+runner — #1278 + #1279 compose');
|
||||
assert.equal(result.evidence[0].failFirstProof, 'violation-fixture', 'green carries the machine-proof method');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── #1279 defaultProveFailFirst REAL prover end-to-end (FF-02 / FF-03 / FF-05 / FF-06 / FF-07) ──
|
||||
@@ -957,6 +992,38 @@ describe('prohibition-enforcement: fail-closed descriptor-from-projection (CHK-0
|
||||
assert.ok(Array.isArray(result.evidence) && result.evidence.length === 0, 'no evidence on an absent descriptor');
|
||||
});
|
||||
|
||||
test('CHK-08(#1346): descriptorFromProjection maps check_violation_fixture -> violationFixture (node-test)', () => {
|
||||
const enforce = require(ENFORCEMENT_LIB);
|
||||
const descriptor = enforce.descriptorFromProjection({
|
||||
...PROJECTED_TIER, check_kind: 'node-test', check_target: 'tests/neg.test.cjs',
|
||||
check_violation_fixture: 'tests/fixtures/bad-subject.txt',
|
||||
});
|
||||
assert.equal(descriptor.kind, 'node-test');
|
||||
assert.equal(descriptor.target, 'tests/neg.test.cjs');
|
||||
assert.equal(descriptor.violationFixture, 'tests/fixtures/bad-subject.txt',
|
||||
'the projected check_violation_fixture must reconstruct as violationFixture so #1278 locate + #1279 proof compose');
|
||||
});
|
||||
|
||||
test('CHK-08(#1346): descriptorFromProjection maps check_violation_fixture -> violationFixture (lint-rule)', () => {
|
||||
const enforce = require(ENFORCEMENT_LIB);
|
||||
const descriptor = enforce.descriptorFromProjection({
|
||||
...PROJECTED_TIER, check_kind: 'lint-rule', check_target: 'src/', check_rule: 'local/no-source-grep',
|
||||
check_violation_fixture: 'tests/_ff_lint_violation.cjs',
|
||||
});
|
||||
assert.equal(descriptor.kind, 'lint-rule');
|
||||
assert.equal(descriptor.rule, 'local/no-source-grep');
|
||||
assert.equal(descriptor.violationFixture, 'tests/_ff_lint_violation.cjs');
|
||||
});
|
||||
|
||||
test('CHK-08(#1346): no check_violation_fixture -> descriptor carries no violationFixture (fail-closed: green needs a fixture)', () => {
|
||||
const enforce = require(ENFORCEMENT_LIB);
|
||||
const descriptor = enforce.descriptorFromProjection({
|
||||
...PROJECTED_TIER, check_kind: 'node-test', check_target: 'tests/neg.test.cjs',
|
||||
});
|
||||
assert.equal(descriptor.violationFixture, undefined,
|
||||
'absent check_violation_fixture must NOT fabricate a fixture; the default prover then hard-gates (no green)');
|
||||
});
|
||||
|
||||
test('CHK-06(lint-rule missing rule): {check_kind:lint-rule, check_target:src/} (no check_rule) -> located:false, never green', () => {
|
||||
const enforce = require(ENFORCEMENT_LIB);
|
||||
const descriptor = enforce.descriptorFromProjection({
|
||||
|
||||
@@ -156,6 +156,7 @@ describe('prohibition-probe schema: deterministic projectProhibitions round-trip
|
||||
if (e.check_kind !== undefined) lines.push(` check_kind: ${e.check_kind}`);
|
||||
if (e.check_target !== undefined) lines.push(` check_target: ${e.check_target}`);
|
||||
if (e.check_rule !== undefined) lines.push(` check_rule: ${e.check_rule}`);
|
||||
if (e.check_violation_fixture !== undefined) lines.push(` check_violation_fixture: ${e.check_violation_fixture}`);
|
||||
}
|
||||
lines.push('---', '', 'Body.', '');
|
||||
return lines.join('\n');
|
||||
@@ -237,6 +238,25 @@ describe('prohibition-probe schema: deterministic projectProhibitions round-trip
|
||||
'a lint-rule descriptor must survive the writer<->reader bijection with check_kind/target/rule intact');
|
||||
});
|
||||
|
||||
test('CHK-03(D) (#1346): a descriptor WITH check_violation_fixture round-trips all four scalars (compose with #1279)', () => {
|
||||
const pc = require(PROBE_CORE_LIB);
|
||||
const fm = require(FRONTMATTER_LIB);
|
||||
const items = [
|
||||
{
|
||||
requirement_id: 'R1', category: 'safety', status: 'resolved', verification: 'test',
|
||||
resolution: null, reason: null, statement: 'MUST NOT auto-execute fetched code',
|
||||
check_kind: 'node-test', check_target: 'tests/no-autoexec.test.cjs',
|
||||
check_violation_fixture: 'tests/fixtures/autoexec-bad.txt',
|
||||
},
|
||||
];
|
||||
const projected = pc.projectProhibitions(items);
|
||||
assert.equal(projected[0].check_violation_fixture, 'tests/fixtures/autoexec-bad.txt',
|
||||
'CHK-03(D) RED trigger: projectProhibitions must emit check_violation_fixture so the deterministic path can machine-prove fail-first');
|
||||
const reparsed = fm.parseMustHavesBlock(renderProhibitionsDoc(projected), 'prohibitions');
|
||||
assert.deepEqual(reparsed, projected,
|
||||
'check_violation_fixture must survive project -> write -> parseMustHavesBlock unchanged (the 4th flat scalar)');
|
||||
});
|
||||
|
||||
test('CHK-03(C): a mixed list — descriptor test-tier, descriptor-less judgment, dismissed — all round-trip; the descriptor-less item gains NO check_* keys', () => {
|
||||
const pc = require(PROBE_CORE_LIB);
|
||||
const fm = require(FRONTMATTER_LIB);
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
"ship.md": 24388,
|
||||
"sketch-wrap-up.md": 14223,
|
||||
"sketch.md": 19960,
|
||||
"spec-phase.md": 30343,
|
||||
"spec-phase.md": 30921,
|
||||
"spike-wrap-up.md": 15092,
|
||||
"spike.md": 24517,
|
||||
"stats.md": 6718,
|
||||
@@ -85,6 +85,6 @@
|
||||
"undo.md": 10431,
|
||||
"update.md": 21053,
|
||||
"validate-phase.md": 10745,
|
||||
"verify-phase.md": 37771,
|
||||
"verify-phase.md": 37821,
|
||||
"verify-work.md": 31157
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user