44 lines
7.9 KiB
JavaScript
44 lines
7.9 KiB
JavaScript
#!/usr/bin/env node
|
|
// The INT suite of the acceptance registry: the master edition's 18 integration gates (docs/plans/igneum-2.0-master/traceability.json,
|
|
// integration_gates: id, requirement, status, source) as cases, requirement verbatim as title and accept, status NOT RUN, source R1,
|
|
// the owner per the coordinator's crosswalk (8 October 2026, 20:1x UK). Generated, never by hand; --check refuses drift.
|
|
// node tools/ci/int-suite.mjs --traceability <json> [--write | --check] node tools/ci/int-suite.mjs --self-test
|
|
import fs from 'node:fs'; import path from 'node:path';
|
|
const args = process.argv.slice(2); const arg = (n) => { const i = args.indexOf(n); return i >= 0 ? args[i + 1] : undefined; };
|
|
const ROOT = process.env.TEST_RECORD_ROOT || path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', '..');
|
|
const REG = process.env.TEST_REGISTRY || path.join(ROOT, 'docs/plans/igneum-2.0-test-registry.json');
|
|
const MAP = process.env.TEST_MAP || path.join(ROOT, 'tools/ci/test-map.json');
|
|
export const OWNERS = { // the coordinator's crosswalk, 8 October 2026 20:1x UK
|
|
'INT-01': 'proving lane (a6e8f84588b809d62)', 'INT-02': 'proving lane (a6e8f84588b809d62)', 'INT-17': 'proving lane (a6e8f84588b809d62)',
|
|
'INT-03': 'pool lane (a1c484c48a62948c2)', 'INT-04': 'pool lane (a1c484c48a62948c2)', 'INT-15': 'pool lane (a1c484c48a62948c2)', 'INT-16': 'pool lane (a1c484c48a62948c2)',
|
|
'INT-05': 'node lane (a283f5f0d364ceef0)', 'INT-06': 'node lane (a283f5f0d364ceef0)',
|
|
'INT-07': 'CI steward with the hash lane (a690540514aa453d7) and the worker lane (a9e87343f008e0edd)', 'INT-08': 'CI steward with the hash lane (a690540514aa453d7) and the worker lane (a9e87343f008e0edd)', 'INT-10': 'CI steward with the hash lane (a690540514aa453d7) and the worker lane (a9e87343f008e0edd)',
|
|
'INT-09': 'app lane', 'INT-11': 'app lane', 'INT-12': 'proving lane (a6e8f84588b809d62) and fleet lane (ac055d60427caab99)',
|
|
'INT-13': 'Ember lane (a04fe3451877e2ff0) with the app lane', 'INT-14': 'Ember lane (a04fe3451877e2ff0) with the app lane', 'INT-18': 'research lane (ad6a2bd47d4a46105)' };
|
|
export const DEFINITIONS = { // the master's definitions carried beside the requirement (never the accept text)
|
|
'INT-07': 'V6-12 (R1 p. 213): the release manifest is signed and binds lockfile hashes, kernel and host binaries, supported devices and drivers, the prover server patch, memory thresholds and tuner identity, with end-to-end artifact authentication and a clean-install accepted-and-paid test on an independent machine with no unpublished files, symlinks or shell overrides.' };
|
|
export function suite(tr) {
|
|
const tests = (tr.integration_gates || []).map((g) => ({ id: g.id, title: g.requirement, ...(DEFINITIONS[g.id] ? { definition: DEFINITIONS[g.id] } : {}), setup: `Integration gate ${g.id} of the master edition (${g.source}).`, steps: [g.requirement], accept: g.requirement,
|
|
evidence: 'The run record of the gate as its owner lane records it through tools/ci/test-record.mjs.', priority: 'P0', profile: 'P00', cadence: 'Every release candidate', method: 'Automated + independent review', status: 'NOT RUN',
|
|
source: ['R1'], gate: 'Integration gates closed', owner: OWNERS[g.id] || 'unassigned', manual_page: null, owner_lane: OWNERS[g.id] || 'unassigned', run_status: 'NOT RUN', master_status: g.status }));
|
|
return { code: 'INT', title: 'INT: the master edition\'s integration gates (R1, the full-system review)', source: 'docs/plans/igneum-2.0-master/traceability.json integration_gates', gate: 'Integration gates closed', owner: 'the owner lanes per the coordinator\'s crosswalk', fixtures: ['F0', 'F5'], summary: `${tests.length} integration gates; each reads NOT RUN until its owner lane records a run`, tests };
|
|
}
|
|
export function merge(reg, s) { const old = (reg.suites || []).find((x) => x.code === s.code); const live = new Map((old?.tests || []).map((t) => [t.id, t]));
|
|
for (const t of s.tests) { const o = live.get(t.id); if (o) for (const k of ['run_status', 'run_id', 'evidence_path', 'updated', 'evidence_record', 'evidence_records', 'blocked_on', 'method_recorded', 'in_progress_since', 'deferral_note', 'approvals']) if (k in o) t[k] = o[k]; }
|
|
if (old?.notes) s.notes = old.notes; reg.suites = [...(reg.suites || []).filter((x) => x.code !== s.code), s]; return reg; }
|
|
const canon = (o) => JSON.stringify(o, (k, v) => (v && typeof v === 'object' && !Array.isArray(v)) ? Object.fromEntries(Object.keys(v).sort().map((x) => [x, v[x]])) : v);
|
|
function mapReasons(map, s) { const mapped = new Set(Object.values(map.cells || {}).flatMap((c) => c.cases || [])); map.not_run = map.not_run || {}; let n = 0;
|
|
for (const t of s.tests) { if (mapped.has(t.id)) { delete map.not_run[t.id]; continue; } map.not_run[t.id] = `${t.id} (R1 integration gate): the gate's harness is the owner lane's (${t.owner_lane}); not yet named in the map`; n++; } return n; }
|
|
if (args.includes('--self-test')) {
|
|
let fails = 0; const tr = { integration_gates: [{ id: 'INT-01', requirement: 'r one', status: 'PROPOSED / NOT RUN', source: 'R1' }, { id: 'INT-05', requirement: 'r five', status: 'x', source: 'R1' }, { id: 'INT-07', requirement: 'r seven', status: 'x', source: 'R1' }] };
|
|
const s = suite(tr); if (!(s.tests[2].definition && /V6-12/.test(s.tests[2].definition) && s.tests[2].accept === 'r seven')) { console.log('self-test failed: INT-07 does not carry V6-12 as a definition beside its verbatim requirement'); fails = 1; }
|
|
if (!(s.tests.length === 3 && s.tests[0].id === 'INT-01' && s.tests[0].accept === 'r one' && /proving/.test(s.tests[0].owner_lane) && /node/.test(s.tests[1].owner_lane) && s.tests[0].run_status === 'NOT RUN' && s.tests[0].source[0] === 'R1')) { console.log('self-test failed: the INT cases are not shaped from the gates with the crosswalk owners'); fails = 1; }
|
|
const reg = { suites: [{ code: 'INT', notes: [{ text: 'n' }], tests: [{ id: 'INT-01', run_status: 'NOT RUN', in_progress_since: 't', evidence_records: { 'c:x': { decision: 'PASS' } }, run_id: 'r' }] }] }; const m = merge(reg, s); const i = m.suites.find((x) => x.code === 'INT');
|
|
if (!(i.tests[0].in_progress_since === 't' && i.tests[0].evidence_records?.['c:x']?.decision === 'PASS' && i.tests[1].run_status === 'NOT RUN' && i.notes?.length === 1)) { console.log('self-test failed: regenerating lost live fields or notes'); fails = 1; }
|
|
const map = { cells: { c: { cases: ['INT-01'] } }, not_run: {} }; if (!(mapReasons(map, s) === 2 && !map.not_run['INT-01'] && /node lane/.test(map.not_run['INT-05']) && /CI steward/.test(map.not_run['INT-07']))) { console.log('self-test failed: the map reasons'); fails = 1; }
|
|
if (!fails) console.log('self-test passed: one INT case per integration gate, the requirement verbatim, source R1, NOT RUN, the owner from the crosswalk; regenerating keeps live fields and notes; unmapped gates get a NOT RUN reason naming the owner'); process.exit(fails);
|
|
}
|
|
const tr = JSON.parse(fs.readFileSync(arg('--traceability'), 'utf8')); const s = suite(tr); const reg = JSON.parse(fs.readFileSync(REG, 'utf8'));
|
|
if (args.includes('--check')) { const cur = (reg.suites || []).find((x) => x.code === 'INT'); const want = merge(JSON.parse(JSON.stringify(reg)), s).suites.find((x) => x.code === 'INT'); if (canon(cur) !== canon(want)) { console.error('int-suite: the registry\'s INT suite differs from the generator\'s output; run --write and commit'); process.exit(1); } console.log(`int-suite: the INT suite matches the master edition (${s.tests.length} gates)`); process.exit(0); }
|
|
if (args.includes('--write')) { fs.writeFileSync(REG, JSON.stringify(merge(reg, s), null, 2) + '\n'); let n = 0; if (fs.existsSync(MAP)) { const map = JSON.parse(fs.readFileSync(MAP, 'utf8')); n = mapReasons(map, s); fs.writeFileSync(MAP, JSON.stringify(map, null, 2) + '\n'); } console.log(`int-suite: INT written, ${s.tests.length} gates; ${n} NOT RUN reasons in the map`); process.exit(0); }
|
|
console.error('usage: int-suite.mjs --traceability <json> --write|--check | --self-test'); process.exit(2);
|