igneum/site/lib/acceptance-render.test.mjs
igneum-labs aa50875704 /acceptance: the Test and Acceptance Standard 1.0, case by case, rendered from docs/plans/igneum-2.0-test-registry.json
The page shows each of the 128 cases in 16 suites as it is run, in the standard's words (NOT RUN, RUNNING, PASS, FAIL,
BLOCKED, DEFERRED): a header with the approval line and a thin segmented count bar (never a percentage), G0 and the five
gates G1 to G5 plus the three named gates as cards whose state follows the standard's rule, one section per suite with an
expandable row per case (setup, steps, accept, required evidence, the run's evidence record when present), the profiles
(approved as proposed, P13 deferred, the P04 line read as a FAIL to work against), the fixtures, and the closing notes.

site/lib/acceptance-render.mjs is the one renderer: build.mjs imports it for the build-time page and prints the same functions
into the page's inline script, which polls /api/acceptance (site/api/acceptance.mjs: the registry from git.igneum.network
master, cached 60 s, ok:false with status 200 on failure) and re-renders in place when a case state or source_sha256 moves.
Hooks: the Learn panel, the nav check, the sitemap, capture.sh, the og card row, a lead link on /scorecard, the facts rows 1
to 7 name the case that will prove each. The never-served check strips the plan's own marked never-served list. Node tests
(known-failed first) run in the gate; the registry and the standard text land with the page.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 17:40:41 +00:00

89 lines
6.6 KiB
JavaScript

// node --test site/lib/acceptance-render.test.mjs: the /acceptance renderer (8 October 2026). Known-failed first: a registry
// with one FAIL case must render a molten FAIL chip and read FAIL on the gate that case belongs to; then a clean fixture of
// 128 cases counts 128 NOT RUN, the runtime copy printed into the page renders the same bytes as the build, and the API
// decodes the git host's base64 reply and answers ok:false (status 200) when the host fails.
import test from 'node:test';
import assert from 'node:assert/strict';
import * as R from './acceptance-render.mjs';
import { createHandler, decode } from '../api/acceptance.mjs';
// a clean fixture in the registry's shape: 16 suites of 8 cases, every case NOT RUN
function fixture() {
const gates = ['G0 / all gates', 'G1 / G2', 'G2 / technical readiness', 'G3', 'G5 / G2', 'G4', 'Technical readiness', 'Technical readiness / G5',
'Technical readiness / commercial track', 'G4 / G5', 'G5 / technical readiness', 'Technical readiness / claimed options', 'G5', 'Operator readiness / G5',
'Commercial evidence', 'Leadership-contender decision'];
const codes = ['GOV', 'GPU', 'POW', 'ADV', 'ROT', 'ECO', 'EVM', 'ZKP', 'CAP', 'INC', 'FIN', 'VER', 'OPS', 'UX', 'COM', 'LEAD'];
return {
title: 'IGNEUM 2.0 - Test and Acceptance Standard', version: '1.0', date: '2026-10-08', status: 'PROPOSED - NOT EXECUTED', basis: 'the plan',
suites: codes.map((code, i) => ({ code, title: code + ' suite', gate: gates[i], owner: 'a lead', fixtures: 'F0', summary: 'One line.',
tests: Array.from({ length: 8 }, (_, j) => ({ id: `${code}-0${j + 1}`, title: 'A case', setup: 's', steps: ['a', 'b'], accept: 'x', evidence: 'e', priority: 'GATE', profile: 'P01; P04', status: 'NOT RUN' })) })),
profiles: { P04: { title: 'Scoped specialist-competition target', requirements: ['R_E at most 1.5.'], status: 'PROPOSED - REQUIRES APPROVAL' }, P13: { title: 'Maintenance continuity', requirements: ['r'], status: 'PROPOSED - REQUIRES APPROVAL' } },
fixtures: [{ id: 'F0', title: 'Manifest', contents: 'c' }],
source_sha256: 'ab', scope_note: 'Scope.', all_pass_note: 'All pass.',
};
}
test('known-failed: one FAIL case renders a molten FAIL chip and a FAIL gate', () => {
const reg = fixture();
reg.suites[3].tests[2].run_status = 'FAIL'; // ADV-03, suite gate "G3"
const html = R.renderBody(reg);
assert.match(html, /<details class="acc-case" id="case-ADV-03" data-case="ADV-03" data-state="FAIL">[\s\S]*?<span class="acc-chip s-fail" data-state="FAIL">FAIL<\/span>/);
assert.match(html, /<article class="acc-gate st-fail" id="gate-G3" data-gate-state="FAIL">/);
assert.match(html, /<article class="acc-gate st-notrun" id="gate-G1" data-gate-state="NOT RUN">/);
assert.equal(R.countStatuses(reg).FAIL, 1);
assert.match(R.headLine(reg), /1 failed/);
});
test('a clean fixture counts 128 NOT RUN and every gate reads NOT RUN', () => {
const reg = fixture();
const c = R.countStatuses(reg);
assert.equal(c.total, 128); assert.equal(c['NOT RUN'], 128); assert.equal(c.PASS + c.FAIL + c.BLOCKED + c.RUNNING + c.DEFERRED, 0);
const html = R.renderBody(reg);
for (const g of ['G0', 'G1', 'G2', 'G3', 'G4', 'G5']) assert.match(html, new RegExp(`id="gate-${g}" data-gate-state="NOT RUN"`));
assert.match(R.headLine(reg), /^Test and Acceptance Standard 1\.0: APPROVED AS PROPOSED by the founder, 8 October 2026; P13 \(maintenance continuity\) DEFERRED; 128 cases: 0 passed under the standard, 0 running with team evidence, 128 not run, 0 deferred$/);
assert.match(html, /NOT EXECUTED/);
assert.doesNotMatch(html, /percent|%/);
});
test('the gate rule: FAIL over BLOCKED over RUNNING; PASS only when every case passes', () => {
const t = (s) => ({ run_status: s });
assert.equal(R.stateOf([t('PASS'), t('PASS')]), 'PASS');
assert.equal(R.stateOf([t('PASS'), t('NOT RUN')]), 'NOT RUN');
assert.equal(R.stateOf([t('PASS'), t('RUNNING')]), 'RUNNING');
assert.equal(R.stateOf([t('RUNNING'), t('BLOCKED')]), 'BLOCKED');
assert.equal(R.stateOf([t('BLOCKED'), t('FAIL')]), 'FAIL');
assert.equal(R.stateOf([t('PASS'), t('DEFERRED')]), 'NOT RUN');
assert.equal(R.normStatus('something new'), 'BLOCKED');
assert.deepEqual(R.gatesOf('G0 / all gates').slice(0, 6), ['G0', 'G1', 'G2', 'G3', 'G4', 'G5']);
assert.deepEqual(R.gatesOf('G2 / G3'), ['G2', 'G3', 'CD']);
});
test('run fields: run_status over status, evidence_path linked on the git host, updated as the last run, no em dash served', () => {
const reg = fixture();
Object.assign(reg.suites[1].tests[0], { run_status: 'PASS', evidence_path: 'docs/runs/gpu-01.md', updated: '2026-10-08T18:05:00Z', title: 'A case \u2014 with a dash' });
const html = R.renderBody(reg);
assert.match(html, /href="https:\/\/git\.igneum\.network\/igneum-network\/igneum\/src\/branch\/master\/docs\/runs\/gpu-01\.md"/);
assert.match(html, /8 Oct 2026, 19:05 UK/);
assert.doesNotMatch(R.renderAcceptance(reg), /\u2014/);
assert.match(html, /id="profile-P04"[\s\S]*?R_E target at most 1\.5 at the same node and one node ahead; today's placed bracket 1\.5x to 2\.1x: FAIL/);
assert.match(html, /id="profile-P13"[\s\S]*?Deferred by the founder/);
});
test('the runtime copy printed into the page renders the same body as the build', () => {
const reg = fixture(); reg.suites[0].tests[0].run_status = 'RUNNING';
const src = R.runtimeSource().replace('(function(){', 'return (function(){').replace(/if \(document\.readyState[^\n]*\n\}\)\(\);$/, 'return renderBody;\n})();');
const renderBody = new Function(src)();
assert.equal(renderBody(reg), R.renderBody(reg));
});
test('the API decodes the git host reply and keeps ok:false on failure', async () => {
const reg = fixture();
assert.equal(decode({ content: Buffer.from(JSON.stringify(reg)).toString('base64'), encoding: 'base64' }).suites.length, 16);
const res = () => { const r = { headers: {}, code: 0, body: null }; r.setHeader = (k, v) => { r.headers[k] = v; }; r.status = (c) => { r.code = c; return r; }; r.json = (b) => { r.body = b; return r; }; return r; };
const ok = createHandler({ fetchImpl: async () => ({ ok: true, json: async () => ({ content: Buffer.from(JSON.stringify(reg)).toString('base64'), encoding: 'base64' }) }) });
const a = res(); await ok({ method: 'GET' }, a);
assert.equal(a.code, 200); assert.equal(a.body.ok, true); assert.equal(a.body.source, 'git.igneum.network master'); assert.equal(a.body.registry.suites.length, 16);
const bad = createHandler({ fetchImpl: async () => ({ ok: false, status: 502 }) });
const b = res(); await bad({ method: 'GET' }, b);
assert.equal(b.code, 200); assert.equal(b.body.ok, false); assert.match(b.body.reason, /502/);
});