473 lines
30 KiB
JavaScript
473 lines
30 KiB
JavaScript
// The /acceptance page (8 October 2026): the Test and Acceptance Standard 1.0, case by case, rendered from ONE file,
|
||
// docs/plans/igneum-2.0-test-registry.json (128 cases in 16 suites, the profiles P00 onward, the fixtures F0 onward).
|
||
//
|
||
// One source of truth for both renders. Every function below is a plain, self-contained function declaration: site/build.mjs
|
||
// imports them to write the page at build time, and runtimeSource() prints the same functions (Function.prototype.toString)
|
||
// into the page's inline classic script, which polls /api/acceptance every 60 seconds and re-renders the page from the
|
||
// registry on the public git host when a case status or the registry's source_sha256 moves. Nothing here may reference a
|
||
// module-level binding: each function carries its own constants, so the printed copy runs unchanged in the browser.
|
||
//
|
||
// The states are the standard's words (section 02, execution rules): NOT RUN, BLOCKED, FAIL, PASS, plus RUNNING while a
|
||
// packet is being produced, and EXCLUDED for an excluded optional feature (never PASS, never counted as an achievement).
|
||
// A gate reads FAIL if any case it depends on is FAIL, else BLOCKED if any is BLOCKED, else RUNNING if any is RUNNING, else
|
||
// PASS only when every case is PASS, else NOT RUN. The page is a checklist, never a completion dashboard: no percentage.
|
||
|
||
// the build-time constants the founder's approval of 8 October 2026 sets, used only where the registry does not carry the
|
||
// approval block yet (reg.approval {by, at, word, profiles {P00..P16}, rules_in_force}); the approver is always printed as
|
||
// "the founder", never a name. The P04 line is the coordinator's verbatim wording and is never softened.
|
||
function overrides() {
|
||
return {
|
||
approval: { word: 'APPROVED AS PROPOSED', date: '2026-10-08' },
|
||
profiles: { all: 'APPROVED AS PROPOSED', P13: 'DEFERRED' },
|
||
notes: { P04: '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' },
|
||
};
|
||
}
|
||
|
||
// a profile's approval word: the registry's approval block, else the profile's own status when it carries a decision, else
|
||
// the build-time constant
|
||
function profileWord(reg, id) {
|
||
var a = reg && reg.approval && reg.approval.profiles;
|
||
if (a && a[id]) return String(a[id]).toUpperCase();
|
||
var raw = String(reg && reg.profiles && reg.profiles[id] && reg.profiles[id].status || '');
|
||
if (/DEFERRED/i.test(raw)) return 'DEFERRED';
|
||
if (/APPROVED/i.test(raw) && !/REQUIRES APPROVAL/i.test(raw)) return raw.toUpperCase();
|
||
var o = overrides().profiles;
|
||
return o[id] || o.all;
|
||
}
|
||
|
||
// an anchor's opening, without its closing bracket: the target escaped; the attribute name is split so the site's link
|
||
// check (which reads every href in a page, scripts included) does not take this concatenation for a literal target
|
||
function linkOpen(u) {
|
||
return '<a ' + 'href' + '="' + esc(u) + '"';
|
||
}
|
||
|
||
function esc(s) {
|
||
return String(s == null ? '' : s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||
}
|
||
|
||
// registry text as served: no em dash (a comma), no spaced hyphen as a dash (a comma), escaped
|
||
function clean(s) {
|
||
return esc(String(s == null ? '' : s).replace(/\s*\u2014\s*/g, ', ').replace(/\s+-\s+/g, ', ').replace(/,\s*,/g, ','));
|
||
}
|
||
|
||
function fmtDate(iso) {
|
||
var m = /^(\d{4})-(\d{2})-(\d{2})/.exec(String(iso || ''));
|
||
if (!m) return clean(iso);
|
||
var MON = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
||
return Number(m[3]) + ' ' + MON[Number(m[2]) - 1] + ' ' + m[1];
|
||
}
|
||
|
||
// a time as UK wall time (the site's rule: times read in UK time); a value that is not a time is shown as written
|
||
function fmtWhen(v) {
|
||
if (!v) return '';
|
||
var d = new Date(v);
|
||
if (isNaN(d.getTime()) || !/\d{4}-\d{2}-\d{2}T/.test(String(v))) return clean(v);
|
||
try {
|
||
return esc(d.toLocaleString('en-GB', { timeZone: 'Europe/London', day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false })) + ' UK';
|
||
} catch (e) { return esc(d.toISOString().slice(0, 16).replace('T', ' ')) + ' UTC'; }
|
||
}
|
||
|
||
// a case's state in the standard's words; an unknown word is BLOCKED (P00: unknown values are BLOCKED, not defaults)
|
||
function normStatus(s) {
|
||
var t = String(s == null ? '' : s).trim().toUpperCase().replace(/[_-]+/g, ' ');
|
||
if (t === '' || t === 'NOT RUN' || t === 'NOTRUN') return 'NOT RUN';
|
||
if (t === 'PASS' || t === 'PASSED') return 'PASS';
|
||
if (t === 'FAIL' || t === 'FAILED') return 'FAIL';
|
||
if (t === 'BLOCKED') return 'BLOCKED';
|
||
if (t === 'RUNNING' || t === 'IN PROGRESS') return 'RUNNING';
|
||
if (t === 'EXCLUDED') return 'EXCLUDED';
|
||
if (t === 'DEFERRED') return 'DEFERRED';
|
||
return 'BLOCKED';
|
||
}
|
||
|
||
// the run state: run_status, else the design status (the registry before the run fields existed), else result.status
|
||
function caseStatus(t) {
|
||
if (!t) return 'NOT RUN';
|
||
if (t.run_status != null) return normStatus(t.run_status);
|
||
return normStatus(t.status != null ? t.status : (t.result && t.result.status));
|
||
}
|
||
|
||
// the evidence link a case carries once it has run: evidence_path (a repository path, linked on the public git host), else
|
||
// evidence_url, evidence_link or result.evidence(_url); https or site-relative only
|
||
function caseEvidenceUrl(t) {
|
||
if (!t) return null;
|
||
var p = t.evidence_path;
|
||
if (typeof p === 'string' && /^[A-Za-z0-9._\/-]+$/.test(p) && p.indexOf('..') < 0) return 'https://git.igneum.network/igneum-network/igneum/src/branch/master/' + p.replace(/^\/+/, '');
|
||
var r = t.result || {};
|
||
var cands = [t.evidence_url, t.evidence_link, r.evidence_url, r.evidence_link, r.evidence];
|
||
for (var i = 0; i < cands.length; i++) {
|
||
var u = cands[i];
|
||
if (typeof u === 'string' && (/^https:\/\/[^\s"'<>]+$/.test(u) || /^\/[^\s"'<>]*$/.test(u))) return u;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
function caseLastRun(t) {
|
||
var r = (t && t.result) || {};
|
||
return (t && (t.updated || t.last_run || t.last_run_at || t.run_at)) || r.at || r.run_at || r.date || null;
|
||
}
|
||
|
||
function profilesOf(t) {
|
||
return String(t && t.profile || '').split(/;\s*/).map(function (x) { return x.trim(); }).filter(Boolean);
|
||
}
|
||
|
||
function allCases(reg) {
|
||
var out = [];
|
||
(reg && reg.suites || []).forEach(function (s) { (s.tests || []).forEach(function (t) { out.push(t); }); });
|
||
return out;
|
||
}
|
||
|
||
function tally(cases) {
|
||
var c = { total: 0, PASS: 0, FAIL: 0, BLOCKED: 0, RUNNING: 0, 'NOT RUN': 0, DEFERRED: 0, EXCLUDED: 0 };
|
||
cases.forEach(function (t) { c.total++; c[caseStatus(t)]++; });
|
||
return c;
|
||
}
|
||
|
||
// the counts, in the standard's words; deferred and excluded cases are counted apart and never as passes
|
||
function countStatuses(reg) {
|
||
return tally(allCases(reg));
|
||
}
|
||
|
||
// a gate or suite state: FAIL, else BLOCKED, else RUNNING, else PASS when every case (excluded ones aside) passed, else NOT RUN;
|
||
// a deferred case is not a pass, so it holds its gate at NOT RUN
|
||
function stateOf(cases) {
|
||
var st = cases.map(caseStatus).filter(function (s) { return s !== 'EXCLUDED'; });
|
||
if (!st.length) return 'NOT RUN';
|
||
if (st.indexOf('FAIL') >= 0) return 'FAIL';
|
||
if (st.indexOf('BLOCKED') >= 0) return 'BLOCKED';
|
||
if (st.indexOf('RUNNING') >= 0) return 'RUNNING';
|
||
if (st.every(function (s) { return s === 'PASS'; })) return 'PASS';
|
||
return 'NOT RUN';
|
||
}
|
||
|
||
// the standard's section 04: G1 to G5 keep the plan's names (the deliverables D1 to D5 by number); G0, technical readiness,
|
||
// commercial evidence and the contender decision are its proposed execution controls
|
||
function gateDefs() {
|
||
return [
|
||
{ id: 'G0', name: 'Freeze', d: 'Release identity', consequence: 'No formal run or public pass before approval.' },
|
||
{ id: 'G1', name: 'Baseline', d: 'D1', consequence: 'No validated hardware claim without reproduction.' },
|
||
{ id: 'G2', name: 'Experiments', d: 'D2', consequence: 'No improvement claim from a negative hypothesis.' },
|
||
{ id: 'G3', name: 'Adversary', d: 'D3', consequence: 'No broad resistance claim from one weak design.' },
|
||
{ id: 'G4', name: 'Coexistence', d: 'D4', consequence: 'No durability claim based on assumed chip expiry.' },
|
||
{ id: 'G5', name: 'No rescue', d: 'D5', consequence: 'No no-rescue claim from a founder-supported demo.' },
|
||
{ id: 'TR', name: 'Technical readiness', d: 'Execution control', consequence: 'No mainnet-ready claim with missing enforcement or safety.' },
|
||
{ id: 'CE', name: 'Commercial evidence', d: 'Execution control', consequence: 'Devnet activity is insufficient.' },
|
||
{ id: 'CD', name: 'Leadership-contender decision', d: 'Execution control', consequence: 'Supports a scoped contention assessment, not a guaranteed rank.' },
|
||
];
|
||
}
|
||
|
||
// a suite's "gate" string to the gates it names: "G0 / all gates" names G0 and every gate; "G2 / G3" names both; the
|
||
// readiness, commercial and leadership words name the execution controls; the contender decision rests on all of them
|
||
function gatesOf(gate) {
|
||
var g = String(gate || ''), out = [];
|
||
var re = /\bG([0-5])\b/g, m;
|
||
while ((m = re.exec(g))) if (out.indexOf('G' + m[1]) < 0) out.push('G' + m[1]);
|
||
if (/all gates/i.test(g)) ['G0', 'G1', 'G2', 'G3', 'G4', 'G5', 'TR', 'CE'].forEach(function (x) { if (out.indexOf(x) < 0) out.push(x); });
|
||
if (/technical readiness|operator readiness/i.test(g) && out.indexOf('TR') < 0) out.push('TR');
|
||
if (/commercial/i.test(g) && out.indexOf('CE') < 0) out.push('CE');
|
||
if (out.indexOf('CD') < 0) out.push('CD');
|
||
return out;
|
||
}
|
||
|
||
function stateClass(s) {
|
||
return { 'PASS': 'pass', 'FAIL': 'fail', 'BLOCKED': 'blocked', 'RUNNING': 'running', 'NOT RUN': 'notrun', 'DEFERRED': 'deferred', 'EXCLUDED': 'excluded' }[s] || 'notrun';
|
||
}
|
||
|
||
function chipHtml(state) {
|
||
var s = normStatus(state);
|
||
return '<span class="acc-chip s-' + stateClass(s) + '" data-state="' + s + '">' + s + '</span>';
|
||
}
|
||
|
||
// the thin segmented bar: one segment per state, its width the count of cases in that state; the numbers sit beside it
|
||
function barHtml(c, label) {
|
||
var order = ['PASS', 'FAIL', 'BLOCKED', 'RUNNING', 'NOT RUN', 'DEFERRED', 'EXCLUDED'];
|
||
var segs = order.filter(function (o) { return c[o] > 0; }).map(function (o) {
|
||
return '<span class="seg g-' + stateClass(o) + '" style="flex-grow:' + c[o] + '" title="' + c[o] + ' ' + o + '"></span>';
|
||
}).join('');
|
||
return '<div class="acc-bar" role="img" aria-label="' + esc(label || '') + '">' + segs + '</div>';
|
||
}
|
||
|
||
// the counts as one line, in the coordinator's words: passed under the standard, running with team evidence, failed and blocked
|
||
// when non-zero, not run, deferred
|
||
function tallyText(c) {
|
||
var parts = [c.PASS + ' passed under the standard', c.RUNNING + ' running with team evidence'];
|
||
if (c.FAIL) parts.push(c.FAIL + ' failed');
|
||
if (c.BLOCKED) parts.push(c.BLOCKED + ' blocked');
|
||
parts.push(c['NOT RUN'] + ' not run');
|
||
parts.push(c.DEFERRED + ' deferred');
|
||
if (c.EXCLUDED) parts.push(c.EXCLUDED + ' excluded');
|
||
return c.total + ' cases: ' + parts.join(', ');
|
||
}
|
||
|
||
function tallyHtml(c) {
|
||
var rows = [['PASS', 'pass'], ['FAIL', 'fail'], ['BLOCKED', 'blocked'], ['RUNNING', 'running'], ['NOT RUN', 'not run'], ['DEFERRED', 'deferred']];
|
||
if (c.EXCLUDED) rows.push(['EXCLUDED', 'excluded']);
|
||
return '<ul class="acc-legend">' + rows.map(function (r) {
|
||
return '<li><i class="sw g-' + stateClass(r[0]) + '" aria-hidden="true"></i><b>' + c[r[0]] + '</b> ' + r[1] + '</li>';
|
||
}).join('') + '</ul>';
|
||
}
|
||
|
||
// the header's status words: the approval (the registry's approval block, else its status string when it says APPROVED, else the
|
||
// build-time constant), the deferred profiles, and NOT EXECUTED while no case has run
|
||
function statusWords(reg) {
|
||
var raw = String(reg && reg.status || ''), a = (reg && reg.approval) || {}, o = overrides();
|
||
var c = countStatuses(reg);
|
||
var fileApproved = !!a.word || (/APPROVED/i.test(raw) && !/REQUIRES APPROVAL/i.test(raw));
|
||
// the served header carries the standard's form of the decision, never the approver's own sentence (the registry's word field
|
||
// holds the founder's words verbatim): an approval reads APPROVED AS PROPOSED, anything else the field's first word in capitals
|
||
var word = a.word ? (/approve/i.test(String(a.word)) ? 'APPROVED AS PROPOSED' : String(a.word).split(/[.;,]/)[0].toUpperCase()) : o.approval.word;
|
||
var dm = /(\d{4}-\d{2}-\d{2})/.exec(String(a.at || '')) || (fileApproved ? /(\d{4}-\d{2}-\d{2})/.exec(String(reg.approved_date || reg.date || '')) : null);
|
||
var date = dm ? dm[1] : o.approval.date;
|
||
var deferred = Object.keys(reg && reg.profiles || {}).filter(function (id) { return /DEFERRED/.test(profileWord(reg, id)); });
|
||
var ran = c.PASS + c.FAIL + c.BLOCKED + c.RUNNING;
|
||
return { raw: raw, word: word, date: date, fromFile: fileApproved, deferred: deferred, execution: ran ? null : 'NOT EXECUTED' };
|
||
}
|
||
|
||
// the header line, verbatim in the coordinator's words with the counts read from the file
|
||
function headLine(reg) {
|
||
var w = statusWords(reg), c = countStatuses(reg);
|
||
var title = String(reg.title || 'Test and Acceptance Standard').replace(/^IGNEUM 2\.0\s*-\s*/i, '');
|
||
var defs = w.deferred.map(function (id) {
|
||
var t = reg.profiles[id] && reg.profiles[id].title ? String(reg.profiles[id].title).toLowerCase() : '';
|
||
return id + (t ? ' (' + t + ')' : '') + ' DEFERRED';
|
||
});
|
||
return title + ' ' + (reg.version || '') + ': ' + w.word + ' by the founder, ' + fmtDate(w.date).replace(/&/g, '&') + '; ' + (defs.length ? defs.join('; ') + '; ' : '') + tallyText(c);
|
||
}
|
||
|
||
function headHtml(reg) {
|
||
var title = String(reg.title || 'Test and Acceptance Standard').replace(/^IGNEUM 2\.0\s*-\s*/i, '');
|
||
var w = statusWords(reg), c = countStatuses(reg);
|
||
return '<section class="page-hero acc-hero"><div class="container">'
|
||
+ '<div class="eyebrow"><span class="line"></span>Igneum 2.0 acceptance</div>'
|
||
+ '<h1 class="acc-title">' + clean(title) + ' ' + clean(reg.version) + '</h1>'
|
||
+ '<p class="lead">Every case of the standard, shown as it is run, in the standard’s own words. A checklist, never a completion score: a gate passes only when every case it depends on has passed.</p>'
|
||
+ '<p class="acc-basis"><span>Basis</span> ' + clean(reg.basis) + '. ' + (reg.manual_page_count ? 'The standard runs to ' + esc(reg.manual_page_count) + ' pages. ' : '') + 'Registry dated ' + fmtDate(reg.date) + '.</p>'
|
||
+ '<div class="acc-tally" id="acc-tally">'
|
||
+ '<div class="acc-words"><span class="acc-word approved">' + esc(w.word) + ' ' + esc(fmtDate(w.date).toUpperCase()) + '</span>'
|
||
+ w.deferred.map(function (id) { return '<span class="acc-word deferred">' + esc(id) + ' DEFERRED</span>'; }).join('')
|
||
+ (w.execution ? '<span class="acc-word exec">' + esc(w.execution) + '</span>' : '') + '</div>'
|
||
+ '<p class="acc-line">' + clean(headLine(reg)) + '</p>'
|
||
+ barHtml(c, tallyText(c)) + tallyHtml(c)
|
||
+ '</div>'
|
||
+ '</div></section>';
|
||
}
|
||
|
||
function gateCardHtml(def, reg, small) {
|
||
var suites = (reg.suites || []).filter(function (s) { return gatesOf(s.gate).indexOf(def.id) >= 0; });
|
||
var cases = [];
|
||
suites.forEach(function (s) { (s.tests || []).forEach(function (t) { cases.push(t); }); });
|
||
var st = stateOf(cases), c = tally(cases);
|
||
var list = suites.map(function (s) {
|
||
return '<li>' + linkOpen('#suite-' + s.code) + '>' + esc(s.code) + '</a><span class="acc-gs-n">' + (s.tests || []).length + ' cases</span>' + chipHtml(stateOf(s.tests || [])) + '</li>';
|
||
}).join('');
|
||
return '<article class="acc-gate st-' + stateClass(st) + (small ? ' small' : '') + '" id="gate-' + def.id + '" data-gate-state="' + st + '">'
|
||
+ '<div class="acc-gate-top"><span class="acc-gate-id">' + (/^G[0-5]$/.test(def.id) ? def.id : '') + '</span>' + chipHtml(st) + '</div>'
|
||
+ '<h3>' + esc(def.name) + '</h3>'
|
||
+ '<p class="acc-gate-d">' + esc(def.d) + '</p>'
|
||
+ '<p class="acc-gate-c">' + esc(def.consequence) + '</p>'
|
||
+ barHtml(c, def.name + ': ' + tallyText(c))
|
||
+ '<p class="acc-gate-n">' + esc(tallyText(c)) + '</p>'
|
||
+ (list ? '<ul class="acc-gs">' + list + '</ul>' : '<p class="acc-gate-n">No suite names this gate.</p>')
|
||
+ '</article>';
|
||
}
|
||
|
||
function gatesHtml(reg) {
|
||
var d = gateDefs();
|
||
var g0 = d.filter(function (x) { return x.id === 'G0'; })[0];
|
||
var five = d.filter(function (x) { return /^G[1-5]$/.test(x.id); });
|
||
var tracks = d.filter(function (x) { return /^(TR|CE|CD)$/.test(x.id); });
|
||
return '<section class="section acc-gates-sec" id="gates"><div class="container">'
|
||
+ '<div class="eyebrow"><span class="line"></span>The gates</div>'
|
||
+ '<h2>Five gates, and the freeze before them.</h2>'
|
||
+ '<p class="acc-sub">A gate reads NOT RUN until every case it depends on has run, RUNNING while any is running, BLOCKED if any is blocked, FAIL if any fails, and PASS only when every case passes. No gate is weighted into an average.</p>'
|
||
+ '<div class="acc-g0">' + gateCardHtml(g0, reg, true) + '</div>'
|
||
+ '<div class="acc-five">' + five.map(function (x) { return gateCardHtml(x, reg, false); }).join('') + '</div>'
|
||
+ '<h3 class="acc-tracks-h">The three named gates</h3>'
|
||
+ '<div class="acc-tracks">' + tracks.map(function (x) { return gateCardHtml(x, reg, true); }).join('') + '</div>'
|
||
+ '</div></section>';
|
||
}
|
||
|
||
function caseHtml(t) {
|
||
var st = caseStatus(t), ev = caseEvidenceUrl(t), run = caseLastRun(t), er = t.evidence_record;
|
||
var steps = (t.steps || []).map(function (s) { return '<li>' + clean(s) + '</li>'; }).join('');
|
||
var meta = [];
|
||
if (t.method) meta.push('<div><dt>Method</dt><dd>' + clean(t.method) + '</dd></div>');
|
||
if (t.cadence) meta.push('<div><dt>Cadence</dt><dd>' + clean(t.cadence) + '</dd></div>');
|
||
if (t.owner || t.owner_lane) meta.push('<div><dt>Owner</dt><dd>' + clean(t.owner_lane || t.owner) + '</dd></div>');
|
||
if (t.run_id) meta.push('<div><dt>Run</dt><dd>' + clean(t.run_id) + '</dd></div>');
|
||
if (t.run_status != null && t.status) meta.push('<div><dt>Design status</dt><dd>' + clean(t.status) + '</dd></div>');
|
||
if (t.manual_page) meta.push('<div><dt>Standard page</dt><dd>' + esc(t.manual_page) + '</dd></div>');
|
||
if (t.source && t.source.length) meta.push('<div><dt>Plan pages</dt><dd>' + esc([].concat(t.source).join(', ')) + '</dd></div>');
|
||
var rec = '';
|
||
if (er && typeof er === 'object') {
|
||
var F = [['what_was_run', 'What was run'], ['run_by', 'Run by'], ['under_the_standard', 'Under the standard'], ['pass_or_fail_today', 'Pass or fail today']];
|
||
var rows = F.filter(function (f) { return er[f[0]] != null && er[f[0]] !== ''; }).map(function (f) { return '<div><dt>' + f[1] + '</dt><dd>' + clean(er[f[0]]) + '</dd></div>'; });
|
||
if (rows.length) rec = '<h4>Evidence record of the run</h4><dl class="acc-meta acc-rec">' + rows.join('') + '</dl>';
|
||
}
|
||
return '<details class="acc-case" id="case-' + esc(t.id) + '" data-case="' + esc(t.id) + '" data-state="' + st + '">'
|
||
+ '<summary>'
|
||
+ '<span class="c-id">' + esc(t.id) + '</span>'
|
||
+ '<span class="c-title">' + clean(t.title) + '</span>'
|
||
+ '<span class="c-pri"><span class="lbl">Priority </span>' + clean(t.priority) + '</span>'
|
||
+ '<span class="c-prof"><span class="lbl">Profile </span>' + (profilesOf(t).map(function (p) { return esc(p); }).join(', ') || 'none') + '</span>'
|
||
+ '<span class="c-st">' + chipHtml(st) + '</span>'
|
||
+ '<span class="c-ev"><span class="lbl">Evidence </span>' + (ev ? linkOpen(ev) + (/^https:/.test(ev) ? ' rel="noopener"' : '') + '>record</a>' : '<span class="none">none yet</span>') + '</span>'
|
||
+ '<span class="c-run"><span class="lbl">Last run </span>' + (run ? fmtWhen(run) : '<span class="none">none yet</span>') + '</span>'
|
||
+ '<span class="c-caret" aria-hidden="true"></span>'
|
||
+ '</summary>'
|
||
+ '<div class="acc-panel"><blockquote>'
|
||
+ '<h4>Setup</h4><p>' + clean(t.setup) + '</p>'
|
||
+ '<h4>Steps</h4><ol>' + steps + '</ol>'
|
||
+ '<h4>Accept</h4><p>' + clean(t.accept) + '</p>'
|
||
+ '<h4>Evidence the case requires</h4><p>' + clean(t.evidence || 'not stated') + '</p>'
|
||
+ '</blockquote>'
|
||
+ rec
|
||
+ (meta.length ? '<dl class="acc-meta">' + meta.join('') + '</dl>' : '')
|
||
+ '</div>'
|
||
+ '</details>';
|
||
}
|
||
|
||
function suiteHtml(s, i) {
|
||
var cases = s.tests || [], c = tally(cases);
|
||
var gates = gatesOf(s.gate).filter(function (g) { return /^G[0-5]$/.test(g); });
|
||
return '<section class="section acc-suite" id="suite-' + esc(s.code) + '"><div class="container">'
|
||
+ '<div class="acc-suite-head">'
|
||
+ '<div class="acc-suite-code"><span class="n">' + (i + 1 < 10 ? '0' : '') + (i + 1) + '</span>' + esc(s.code) + '</div>'
|
||
+ '<div class="acc-suite-t"><h2>' + clean(s.title) + '</h2>' + (s.summary ? '<p class="acc-sub">' + clean(s.summary) + '</p>' : '') + '</div>'
|
||
+ '<div class="acc-suite-st">' + chipHtml(stateOf(cases)) + '</div>'
|
||
+ '</div>'
|
||
+ '<dl class="acc-facts">'
|
||
+ '<div><dt>Owner</dt><dd>' + clean(s.owner || 'not named') + '</dd></div>'
|
||
+ '<div><dt>Gate</dt><dd>' + clean(s.gate) + (gates.length ? ' <span class="acc-gl">' + gates.map(function (g) { return linkOpen('#gate-' + g) + '>' + g + '</a>'; }).join(' ') + '</span>' : '') + '</dd></div>'
|
||
+ (s.fixtures ? '<div><dt>Fixtures</dt><dd>' + clean(s.fixtures) + '</dd></div>' : '')
|
||
+ (s.source && s.source.length ? '<div><dt>Plan pages</dt><dd>' + esc([].concat(s.source).join(', ')) + '</dd></div>' : '')
|
||
+ '</dl>'
|
||
+ '<div class="acc-suite-bar">' + barHtml(c, s.code + ': ' + tallyText(c)) + '<span>' + esc(tallyText(c)) + '</span></div>'
|
||
+ '<div class="acc-cases">'
|
||
+ '<div class="acc-cases-h" aria-hidden="true"><span>Case</span><span>Title</span><span>Priority</span><span>Profile</span><span>Status</span><span>Evidence</span><span>Last run</span><span></span></div>'
|
||
+ cases.map(caseHtml).join('')
|
||
+ '</div>'
|
||
+ '</div></section>';
|
||
}
|
||
|
||
function profileHtml(id, p, reg) {
|
||
var o = overrides(), raw = String(p.status || ''), word = profileWord(reg, id);
|
||
var used = allCases(reg).filter(function (t) { return profilesOf(t).indexOf(id) >= 0; }).length;
|
||
var deferred = /DEFERRED/.test(word), cls = deferred ? 'deferred' : (/APPROVED/.test(word) ? 'approved' : 'proposed');
|
||
var text = deferred ? 'Deferred by the founder' : (/APPROVED AS PROPOSED/.test(word) ? 'Approved as proposed' : (/APPROVED/.test(word) ? 'Approved' : clean(word)));
|
||
var note = o.notes[id];
|
||
return '<article class="acc-prof ' + cls + '" id="profile-' + esc(id) + '">'
|
||
+ '<div class="acc-prof-top"><span class="acc-prof-id">' + esc(id) + '</span><span class="acc-pstate ' + cls + '">' + text + '</span></div>'
|
||
+ '<h3>' + clean(p.title) + '</h3>'
|
||
+ '<ol class="acc-req">' + (p.requirements || []).map(function (r) { return '<li>' + clean(r) + '</li>'; }).join('') + '</ol>'
|
||
+ (note ? '<p class="acc-note">' + chipHtml('FAIL') + ' <span>' + esc(note) + '</span></p>' : '')
|
||
+ '<p class="acc-prof-n">Cited by ' + used + ' case' + (used === 1 ? '' : 's') + '. ' + (raw ? 'The profile’s own line in the registry: ' + clean(raw) + '.' : '') + '</p>'
|
||
+ '</article>';
|
||
}
|
||
|
||
function profilesHtml(reg) {
|
||
var ps = reg.profiles || {};
|
||
var ids = Object.keys(ps);
|
||
return '<section class="section acc-profiles" id="profiles"><div class="container">'
|
||
+ '<div class="eyebrow"><span class="line"></span>Profiles</div>'
|
||
+ '<h2>The numbers each case is held to.</h2>'
|
||
+ '<p class="acc-sub">' + ids.length + ' profiles, P00 to P' + (ids.length - 1 < 10 ? '0' : '') + (ids.length - 1) + '. A profile is frozen before the confirmatory run; weakening a target after a failure creates a different claim.</p>'
|
||
+ '<div class="acc-prof-grid">' + ids.map(function (id) { return profileHtml(id, ps[id], reg); }).join('') + '</div>'
|
||
+ '</div></section>';
|
||
}
|
||
|
||
function fixturesHtml(reg) {
|
||
var fs = reg.fixtures || [];
|
||
if (!fs.length) return '';
|
||
return '<section class="section acc-fixtures" id="fixtures"><div class="container">'
|
||
+ '<div class="eyebrow"><span class="line"></span>Fixtures</div>'
|
||
+ '<h2>What every run is built on.</h2>'
|
||
+ '<div class="acc-fix-grid">' + fs.map(function (f) {
|
||
return '<article class="acc-fix" id="fixture-' + esc(f.id) + '"><span class="acc-fix-id">' + esc(f.id) + '</span><h3>' + clean(f.title) + '</h3><p>' + clean(f.contents) + '</p></article>';
|
||
}).join('') + '</div>'
|
||
+ '</div></section>';
|
||
}
|
||
|
||
function footHtml(reg) {
|
||
var rules = reg.approval && reg.approval.rules_in_force;
|
||
var rulesHtml = '';
|
||
if (Array.isArray(rules) && rules.length) rulesHtml = '<h3>Rules in force</h3><ul class="acc-rules">' + rules.map(function (r) { return '<li>' + clean(typeof r === 'string' ? r : JSON.stringify(r)) + '</li>'; }).join('') + '</ul>';
|
||
else if (typeof rules === 'string' && rules) rulesHtml = '<h3>Rules in force</h3><p>' + clean(rules) + '</p>';
|
||
return '<section class="section acc-foot" id="notes"><div class="container"><div class="acc-foot-card">'
|
||
+ '<div class="eyebrow"><span class="line"></span>What a full pass means</div>'
|
||
+ (reg.all_pass_note ? '<p class="acc-allpass">' + clean(reg.all_pass_note) + '</p>' : '')
|
||
+ (reg.scope_note ? '<p>' + clean(reg.scope_note) + '</p>' : '')
|
||
+ rulesHtml
|
||
+ '<p class="acc-never" data-never-served>Never served: guaranteed chip death, a universal ASIC-efficiency ceiling, chip-arrival probabilities, guaranteed profits, Ethereum security by compatibility, privacy from ZK, a numerical rank.</p>'
|
||
+ '<p class="acc-src"><b>Source:</b> <code>docs/plans/igneum-2.0-test-registry.json</code>, version ' + clean(reg.version) + ', dated ' + fmtDate(reg.date) + (reg.source_sha256 ? ', plan sha256 <code>' + esc(String(reg.source_sha256).slice(0, 16)) + '</code>' : '') + '. <span id="acc-live">This copy was written when the page was built; the page checks the registry on the public git host every 60 seconds.</span></p>'
|
||
+ '</div></div></section>';
|
||
}
|
||
|
||
function suiteNavHtml(reg) {
|
||
return '<nav class="acc-jump container" aria-label="Suites"><a href="#gates">Gates</a>' + (reg.suites || []).map(function (s) {
|
||
return linkOpen('#suite-' + s.code) + '>' + esc(s.code) + '</a>';
|
||
}).join('') + '<a href="#profiles">Profiles</a><a href="#fixtures">Fixtures</a></nav>';
|
||
}
|
||
|
||
// the page body inside #acc-root (the same at build and at runtime)
|
||
function renderBody(reg) {
|
||
return headHtml(reg) + suiteNavHtml(reg) + gatesHtml(reg)
|
||
+ (reg.suites || []).map(function (s, i) { return suiteHtml(s, i); }).join('')
|
||
+ profilesHtml(reg) + fixturesHtml(reg) + footHtml(reg);
|
||
}
|
||
|
||
// a key that moves when any case's state, evidence or run time moves
|
||
function statusKey(reg) {
|
||
return String(reg && reg.source_sha256 || '') + '|' + String(reg && reg.status || '') + '|' + allCases(reg).map(function (t) {
|
||
return t.id + ':' + caseStatus(t) + ':' + (caseEvidenceUrl(t) || '') + ':' + (caseLastRun(t) || '');
|
||
}).join(',');
|
||
}
|
||
|
||
// the runtime: read the build-time copy, poll /api/acceptance every 60 seconds, re-render in place when the key moves,
|
||
// keeping the open cases open and the focus where it was
|
||
function boot() {
|
||
var root = document.getElementById('acc-root'), data = document.getElementById('acceptance-data');
|
||
if (!root || !data) return;
|
||
var cur; try { cur = JSON.parse(data.textContent); } catch (e) { return; }
|
||
var key = statusKey(cur);
|
||
function live(text) { var l = document.getElementById('acc-live'); if (l) l.textContent = text; }
|
||
function apply(reg) {
|
||
var open = [].map.call(root.querySelectorAll('details[open]'), function (d) { return d.id; });
|
||
var f = document.activeElement, fid = f && f.closest && f.closest('details') ? f.closest('details').id : null;
|
||
root.innerHTML = renderBody(reg);
|
||
open.forEach(function (id) { var d = document.getElementById(id); if (d) d.open = true; });
|
||
if (fid) { var s = document.querySelector('#' + fid + ' > summary'); if (s) s.focus({ preventScroll: true }); }
|
||
}
|
||
function poll() {
|
||
if (document.hidden) return;
|
||
fetch('/api/acceptance', { cache: 'no-store' }).then(function (r) { return r.json(); }).then(function (d) {
|
||
if (!d || !d.ok || !d.registry || !d.registry.suites) return;
|
||
var k = statusKey(d.registry);
|
||
if (k !== key) { cur = d.registry; key = k; apply(cur); }
|
||
live('Checked against ' + (d.source || 'the git host') + ' at ' + fmtWhen(d.fetched_at).replace(/&/g, '&') + '; the page re-reads it every 60 seconds.');
|
||
}).catch(function () { /* the build-time copy stands */ });
|
||
}
|
||
setTimeout(poll, 4000);
|
||
setInterval(poll, 60000);
|
||
document.addEventListener('visibilitychange', function () { if (!document.hidden) poll(); });
|
||
}
|
||
|
||
// the page's <main> content: the rendered body in #acc-root, the registry as data, the runtime script
|
||
function renderAcceptance(reg) {
|
||
return '<div id="acc-root">' + renderBody(reg) + '</div>\n'
|
||
+ '<script type="application/json" id="acceptance-data">' + JSON.stringify(reg).replace(/</g, '\\u003c').replace(/\u2014/g, '\\u2014') + '</script>\n'
|
||
+ '<script>\n' + runtimeSource() + '\n</script>';
|
||
}
|
||
|
||
// the inline classic script: the same functions, printed, then boot()
|
||
function runtimeSource() {
|
||
var fns = [overrides, profileWord, linkOpen, esc, clean, fmtDate, fmtWhen, normStatus, caseStatus, caseEvidenceUrl, caseLastRun, profilesOf, allCases, tally, countStatuses, stateOf, gateDefs, gatesOf,
|
||
stateClass, chipHtml, barHtml, tallyText, tallyHtml, statusWords, headLine, headHtml, gateCardHtml, gatesHtml, caseHtml, suiteHtml, profileHtml, profilesHtml, fixturesHtml,
|
||
footHtml, suiteNavHtml, renderBody, statusKey, boot];
|
||
return '/* /acceptance runtime: printed at build from site/lib/acceptance-render.mjs (one source for both renders) */\n(function(){\n'
|
||
+ fns.map(function (f) { return String(f); }).join('\n') + '\nif (document.readyState === "loading") document.addEventListener("DOMContentLoaded", boot); else boot();\n})();';
|
||
}
|
||
|
||
export { overrides, profileWord, linkOpen, profilesOf, tally, stateClass, headLine, esc, clean, fmtDate, fmtWhen, normStatus, caseStatus, caseEvidenceUrl, caseLastRun, allCases, countStatuses, stateOf, gateDefs, gatesOf,
|
||
chipHtml, barHtml, tallyText, statusWords, gateCardHtml, gatesHtml, caseHtml, suiteHtml, profilesHtml, fixturesHtml, footHtml, renderBody, statusKey,
|
||
renderAcceptance, runtimeSource };
|