fix(quality): prove criterion binding semantics

This commit is contained in:
2026-08-01 09:58:47 -05:00
committed by f10-coder
parent d119635265
commit 8b1b873056
9 changed files with 414 additions and 79 deletions
+71 -3
View File
@@ -30,6 +30,7 @@ function baseManifest() {
claimType: 'integrity',
source: 'fixture',
meaningChanges: [],
caseRefs: ['meta-fixture/rejects-bad-input'],
},
],
compatibilityScenarios: [],
@@ -72,10 +73,18 @@ async function writeManifest(root, manifest) {
await writeFile(path.join(root, 'gates', 'gates.manifest.json'), `${JSON.stringify(manifest)}\n`);
}
function verify(root) {
function verify(root, extraArgs = []) {
return spawnSync(
process.execPath,
[verifier, '--root', root, '--manifest', 'gates/gates.manifest.json', '--skip-history'],
[
verifier,
'--root',
root,
'--manifest',
'gates/gates.manifest.json',
'--skip-history',
...extraArgs,
],
{ cwd: root, encoding: 'utf8', env: { ...process.env, HOME: os.homedir() } },
);
}
@@ -260,6 +269,61 @@ test('a required-versus-actual delta without a tracked owner is rejected', async
assert.match(output(result), /meta-fixture.*delta.*tracked owner/i);
});
test('moving criterion bindings to unrelated cases is rejected', async () => {
const root = await fixture('semantic-misbinding');
await writeGate(root);
const manifest = baseManifest();
manifest.criteria.push({
id: 'META-CRIT-2',
originalText: 'The fixture reports the second rejection reason.',
currentText: 'The fixture reports the second rejection reason.',
claimType: 'integrity',
source: 'fixture',
meaningChanges: [],
caseRefs: ['meta-fixture/rejects-second-input'],
});
manifest.gates[0].cases.push({
...manifest.gates[0].cases[0],
id: 'rejects-second-input',
criterionIds: ['META-CRIT-1'],
});
manifest.gates[0].cases[0].criterionIds = ['META-CRIT-2'];
await writeManifest(root, manifest);
const result = verify(root);
assert.notEqual(result.status, 0);
assert.match(output(result), /META-CRIT-1.*rejects-bad-input.*not bound/i);
assert.match(output(result), /META-CRIT-2.*rejects-second-input.*not bound/i);
});
test('moving meaning and prose criteria to an unrelated type error is rejected', async () => {
const root = await fixture('real-manifest-misbinding');
const manifest = JSON.parse(
await readFile(path.join(process.cwd(), 'gates', 'gates.manifest.json'), 'utf8'),
);
for (const gate of manifest.gates) {
for (const gateCase of gate.cases) {
gateCase.criterionIds = gateCase.criterionIds.filter(
(id) => !['RM02-MEANING-PROVENANCE', 'RM02-PROSE-CONTROL'].includes(id),
);
}
}
const typeError = manifest.gates
.find((gate) => gate.id === 'quality-typecheck')
.cases.find((gateCase) => gateCase.id === 'type-error');
typeError.criterionIds.push('RM02-MEANING-PROVENANCE', 'RM02-PROSE-CONTROL');
await writeManifest(root, manifest);
const result = verify(root, ['--structure-only']);
assert.notEqual(result.status, 0);
assert.match(output(result), /RM02-MEANING-PROVENANCE.*missing-meaning-provenance.*not bound/i);
assert.match(output(result), /RM02-PROSE-CONTROL.*prose-claim-misbinding.*not bound/i);
assert.match(
output(result),
/RM02-(?:MEANING-PROVENANCE|PROSE-CONTROL).*undeclared exercising case quality-typecheck\/type-error/i,
);
});
test('a criterion with no bound case is rejected', async () => {
const root = await fixture('unbound-criterion');
await writeGate(root);
@@ -335,6 +399,7 @@ test('compatibility scenarios execute referenced conditions as one construction'
reasonPattern: 'SECOND_REASON',
environment: { SECOND_REASON: 'SECOND_REASON' },
});
manifest.criteria[0].caseRefs.push('meta-fixture/second-condition');
manifest.gates[0].cases[0].fixture = {
writeFiles: [{ path: 'conditions/first', content: 'present\n' }],
};
@@ -427,7 +492,10 @@ test('deployment drift meta-control fails if the shared comparator is made inert
const root = await fixture('deployment-comparator-inert');
await writeGate(root);
await mkdir(path.join(root, 'deployed'), { recursive: true });
await copyFile(path.join(root, 'gates', 'meta-fixture.sh'), path.join(root, 'deployed', 'meta-fixture.sh'));
await copyFile(
path.join(root, 'gates', 'meta-fixture.sh'),
path.join(root, 'deployed', 'meta-fixture.sh'),
);
const manifest = baseManifest();
manifest.gates[0].deployment = { kind: 'file', path: 'deployed/meta-fixture.sh' };
await writeManifest(root, manifest);