fix(quality): anchor quantified registry populations
ci/woodpecker/pr/ci Pipeline was successful

This commit is contained in:
2026-08-01 12:39:49 -05:00
parent 32b490a712
commit fbb6191298
14 changed files with 594 additions and 37 deletions
+65 -4
View File
@@ -23,6 +23,20 @@ import { spawnSync } from 'node:child_process';
import { verifyHistory } from './gate-history.mjs';
const COPY_SKIP = new Set(['.git', '.mosaic-test-work', '.next', '.turbo', 'coverage', 'dist']);
const POPULATION_CRITERION_IDS = new Set([
'RM02-EVIDENCE-SUBJECT-BINDING',
'RM02-TYPE-STRICT-SCHEMA',
'RM02-NONEMPTY-ANCHORED-QUANTIFICATION',
]);
const REQUIRED_GATE_INVENTORY = new Map([
['quality-typecheck', 'package.json'],
['quality-lint', 'package.json'],
['quality-format', 'package.json'],
['checkout-preflight', 'scripts/preflight.mjs'],
['ci-queue-wait', 'packages/mosaic/framework/tools/git/ci-queue-wait.sh'],
['hook-pre-commit', '.husky/pre-commit'],
['hook-pre-push', '.husky/pre-push'],
]);
function parseArgs(argv) {
const options = {
@@ -382,7 +396,14 @@ function validateEnvironment(environment, label, failures) {
}
}
function validateClosedSchema(manifest, failures) {
function validateClosedSchema(manifest, failures, { fixtureProfile = false } = {}) {
if (!fixtureProfile) {
for (const population of ['criteria', 'gates', 'proseClaims', 'compatibilityScenarios']) {
if (!Array.isArray(manifest[population]) || manifest[population].length === 0) {
failures.push(`${population} population must be non-empty and anchored before evaluation`);
}
}
}
if (manifest.schemaVersion !== 1)
failures.push(`unsupported schemaVersion ${String(manifest.schemaVersion)}`);
rejectUnknownKeys(
@@ -450,6 +471,7 @@ function validateClosedSchema(manifest, failures) {
'source',
'meaningChanges',
'caseRefs',
'gateRefs',
]),
`criterion ${criterion.id}`,
failures,
@@ -475,6 +497,12 @@ function validateClosedSchema(manifest, failures) {
);
}
}
if (POPULATION_CRITERION_IDS.has(criterion.id) && criterion.gateRefs === undefined) {
failures.push(`${criterion.id}: gateRefs population binding is required`);
}
if (criterion.gateRefs !== undefined) {
validateStringArray(criterion.gateRefs, `criterion ${criterion.id}.gateRefs`, failures);
}
if (!Array.isArray(criterion.caseRefs) || criterion.caseRefs.length === 0) {
failures.push(`${criterion.id}: no declared exercising cases`);
} else {
@@ -535,6 +563,16 @@ function validateClosedSchema(manifest, failures) {
validateFixture(scenario.fixture, `${scenario.id}.fixture`, failures);
}
rejectDuplicateIds(manifest.gates, 'gate', failures);
if (!fixtureProfile) {
for (const [requiredId, requiredSource] of REQUIRED_GATE_INVENTORY) {
const registered = (manifest.gates ?? []).find((gate) => gate?.id === requiredId);
if (!registered || registered.source !== requiredSource) {
failures.push(
`gates population is not anchored: required ${requiredId} at ${requiredSource}`,
);
}
}
}
for (const gate of manifest.gates ?? []) {
rejectUnknownKeys(
gate,
@@ -546,12 +584,19 @@ function validateClosedSchema(manifest, failures) {
'inertMutation',
'cases',
'discoveryAliases',
'evidenceSubject',
]),
`gate ${gate.id}`,
failures,
);
requireString(gate.id, `gate ${gate.id}.id`, failures);
requireString(gate.source, `gate ${gate.id}.source`, failures);
requireString(gate.evidenceSubject, `gate ${gate.id}.evidenceSubject`, failures);
if (gate.evidenceSubject !== gate.id) {
failures.push(
`gate ${gate.id}: evidence subject ${String(gate.evidenceSubject)} does not match gate id`,
);
}
rejectDuplicateIds(gate.cases, `case in gate ${gate.id}`, failures);
if (!Array.isArray(gate.invocation) || gate.invocation.length === 0) {
failures.push(`${gate.id}: exact invocation is missing`);
@@ -657,8 +702,8 @@ function validateClosedSchema(manifest, failures) {
}
}
function validateStructure(manifest, failures) {
validateClosedSchema(manifest, failures);
function validateStructure(manifest, failures, options = {}) {
validateClosedSchema(manifest, failures, options);
const criteria = new Map((manifest.criteria ?? []).map((criterion) => [criterion.id, criterion]));
const boundCriteria = new Set();
const negativeBoundCriteria = new Set();
@@ -686,6 +731,22 @@ function validateStructure(manifest, failures) {
}
}
const registeredGateIds = new Set((manifest.gates ?? []).map((gate) => gate.id));
for (const criterion of criteria.values()) {
if (criterion.gateRefs === undefined) continue;
const referenced = new Set(criterion.gateRefs);
for (const gateId of registeredGateIds) {
if (!referenced.has(gateId)) {
failures.push(`${criterion.id}: gate population binding is missing ${gateId}`);
}
}
for (const gateId of referenced) {
if (!registeredGateIds.has(gateId)) {
failures.push(`${criterion.id}: gate population binding references unknown gate ${gateId}`);
}
}
}
for (const claim of manifest.proseClaims ?? []) {
if (!criteria.has(claim.criterionId)) {
failures.push(`GATE-CLAIM:${claim.id} references unknown criterion ${claim.criterionId}`);
@@ -996,7 +1057,7 @@ export async function verifyRegistry(options) {
const manifestPath = path.resolve(options.root, options.manifest);
const manifest = JSON.parse(await readFile(manifestPath, 'utf8'));
validateStructure(manifest, failures);
validateStructure(manifest, failures, options);
if (options.structureOnly) return { failures, manifest, observations };
async function collectPhaseFailure(label, action) {