397 lines
49 KiB
JavaScript
397 lines
49 KiB
JavaScript
#!/usr/bin/env node
|
|
// proba-conformance.mjs: the probe of the conformance suite itself. Three states per row: VERDE (as required), ROSU (not), STRICAT /
|
|
// NEMASURAT (the row could not be measured: counted, never passed); INFO rows are measurements printed beside the gates, not counted. Exit 0 all green, 1 a red or broken row, 2 only unmeasured rows.
|
|
//
|
|
// node proba-conformance.mjs [--online] [--acceptate] the suite is derived, whole, single-place, two implementations pass it,
|
|
// and malformed artifacts are refused by name (--acceptate lists what a
|
|
// verifier still accepts when damaged: the optional and the unread)
|
|
// node proba-conformance.mjs --control-negativ [--online] [--doar <rule id prefix>]
|
|
// every rule planted out of its verifier, one at a time: the vectors that
|
|
// name the rule, and only they, must fail; then the HALVES of the device
|
|
// verifier (one condition of a compound check planted out: exactly the
|
|
// vectors written for it must fail); plus the controls of the runner,
|
|
// of the coverage gate and of the rule derivation
|
|
// node proba-conformance.mjs --pe-loc a MEASUREMENT, not a gate: each check site planted out alone; how many
|
|
// of the sites the vectors reach
|
|
//
|
|
// The independent implementation needs Python 3 (AERE_CONFORMANCE_PYTHON=<command>, default python3 then python); the envelope
|
|
// vectors need the AIP-23 reference verifier (AERE_VERIFY_PROOF=<verify-proof.mjs>, with `npm install` done beside it); --online
|
|
// reads testnet 28001 (nothing is sent).
|
|
import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { spawn, spawnSync } from 'node:child_process'; import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
import { deriva, hartaPublica, VERIFICATOARE, MODULE, VERIFY_PROOF, FINALITY_SITES, NEJUDECATE_IDENTITY, incarca, marcajul } from './reguli.mjs';
|
|
import { incarcaVectorii, ruleazaSuita } from './conformance.mjs';
|
|
import { aplica, plicuriKinds } from './genereaza-vectori.mjs';
|
|
import { PLANTARI as PLANTARI_DEVICE } from '../device-identity/plantari-device.mjs';
|
|
|
|
const AICI = path.dirname(fileURLToPath(import.meta.url));
|
|
const a = process.argv.slice(2); const online = a.includes('--online'); const control = a.includes('--control-negativ'); const peLoc = a.includes('--pe-loc');
|
|
const REF = `node "${path.join(AICI, 'implementare-aere.mjs')}"`;
|
|
let verzi = 0, rosii = 0, nemas = 0;
|
|
const rand = (stare, text) => { console.log(` [${stare.padEnd(9)}] ${text}`); if (stare === 'VERDE') verzi++; else if (stare === 'ROSU') rosii++; else if (stare !== 'INFO') nemas++; };
|
|
const cer = (cond, text, detaliu = '') => rand(cond ? 'VERDE' : 'ROSU', text + (detaliu ? ': ' + detaliu : ''));
|
|
const canon = (x) => (x === null || typeof x !== 'object' ? JSON.stringify(x) : Array.isArray(x) ? '[' + x.map(canon).join(',') + ']' : '{' + Object.keys(x).sort().map((k) => JSON.stringify(k) + ':' + canon(x[k])).join(',') + '}');
|
|
/** the JSON paths at which two values differ (a subtree added, removed or of another type is one path) */
|
|
function difera(x, y, p = []) {
|
|
if (canon(x) === canon(y)) return [];
|
|
const ob = (v) => v !== null && typeof v === 'object';
|
|
if (!ob(x) || !ob(y) || Array.isArray(x) !== Array.isArray(y)) return [p];
|
|
return [...new Set([...Object.keys(x), ...Object.keys(y)])].flatMap((k) => (k in x && k in y ? difera(x[k], y[k], [...p, Array.isArray(x) ? Number(k) : k]) : [[...p, Array.isArray(x) ? Number(k) : k]]));
|
|
}
|
|
const prefix = (pre, p) => pre.length <= p.length && pre.every((k, i) => String(k) === String(p[i]));
|
|
|
|
if (!fs.existsSync(VERIFY_PROOF)) { console.log('NEMASURAT: the AIP-23 reference verifier is not at ' + VERIFY_PROOF + ' (AERE_VERIFY_PROOF=<verify-proof.mjs>)'); process.exit(2); }
|
|
const S = incarcaVectorii(); if (S.eroare) { console.log('ROSU: the vector set is not whole: ' + S.eroare); process.exit(1); }
|
|
const vectori = S.vectori; const HARTA = hartaPublica(); const TOATE = Object.keys(HARTA);
|
|
const cuRegula = (r, lista = vectori) => lista.filter((v) => v.expected.failed.includes(r)).map((v) => v.id);
|
|
/** coverage: every rule has a vector that expects it; returns the rules without one */
|
|
const neacoperite = (reguli, lista = vectori) => reguli.filter((r) => !cuRegula(r, lista).length);
|
|
const python = () => { for (const c of [process.env.AERE_CONFORMANCE_PYTHON, 'python3', 'python'].filter(Boolean)) { const r = spawnSync(c, ['--version'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }); if (r.status === 0 && /Python 3/.test(r.stdout + r.stderr)) return c; } return null; };
|
|
const numar = (s, st) => s.rezultate.filter((r) => r.stare === st);
|
|
|
|
// ---------- planting ----------
|
|
const PLANT = "|| String(1) === '1' /*PLANTAT*/";
|
|
/** exact-once plantings of the reference verifier (a published program without markers): rule -> [text, planted text]. The rule is
|
|
* taken out whole: its level then passes whatever it is shown. */
|
|
const PLANT_VP = {
|
|
form: ["const formOk = obj(env) && obj(env.statement)", "const formOk = String(1) === '1' || obj(env) && obj(env.statement)"],
|
|
integrity: ["const ok = h === sh.toLowerCase();", "const ok = h === sh.toLowerCase() || String(1) === '1';"],
|
|
signature: [" const s = env.signature;\n", " const s = env.signature;\n if (String(1) === '1') return level('signature', 'PASSED', 'planted out');\n"],
|
|
finality: ["env.notarization ? 'FAILED' : 'ABSENT'", "(env.notarization && String(1) === '2') ? 'FAILED' : 'ABSENT'"],
|
|
};
|
|
/**
|
|
* The halves of the device verifier: the name of a planting of its negative control (plantari-device.mjs: one condition of a
|
|
* compound check taken out, or two checks that guard the same thing taken out together) -> the device-bundle vectors that must
|
|
* fail under it, and only they. The whole-rule control above cannot see these: with a rule taken out whole, every vector of the
|
|
* rule fails, whichever half it was written for.
|
|
*/
|
|
const JUMATATI_DEVICE = {
|
|
'one half of the hybrid signature is enough': ['sold-once.firmware-signature'],
|
|
'the type of the device key is not checked': ['attestation-only.attestation-key', 'manufacture-only.device-key'],
|
|
'the device key is accepted in any base64 text (its canonical form is not required)': ['attestation-only.attestation-key-not-canonical', 'manufacture-only.device-key-not-canonical'],
|
|
'a hybrid signature of a link is accepted in any base64 text': ['chain-only.transfer-signature-not-canonical'],
|
|
'the attestation signature is accepted in any base64 text': ['sold-once.attestation-signature-not-canonical'],
|
|
"the manufacturer's signature is accepted in any base64 text": ['manufacture-only.manufacture-signature-not-canonical'],
|
|
'identities are compared by their keys alone (the id half of the comparison taken out)': ['manufacture-only.manufacture-holder-other-id'],
|
|
'identities are compared by their id alone (the keys half of the comparison taken out)': ['manufacture-only.manufacture-holder-other-keys'],
|
|
'a manufacture credential that has not begun is accepted': ['manufacture-only.manufacture-window-not-yet'],
|
|
'an expired manufacture credential is accepted': ['manufacture-only.manufacture-window'],
|
|
'a recall list of another manufacturer is believed': ['recall-list-of-another-manufacturer.partial'],
|
|
'an expired recall list is read as current': ['recall-list-expired.partial', 'recall-list-unreadable-beside-a-clean-one.partial'],
|
|
'a recall list whose validity is not a time is dropped in silence': ['recall-list-unreadable-beside-a-clean-one.partial'],
|
|
'a hardware root in the manufacture credential passes in silence': ['manufacture-hardware-root.partial'],
|
|
'the id of who signed a release is not derived from its keys': ['release-only.firmware-signer-id'],
|
|
'with two firmware policies the loosest floor holds': ['sold-once.firmware-rollback-second-policy'],
|
|
'a retraction is lifted by a policy that does not list it': ['sold-once.firmware-retracted-second-policy'],
|
|
'a retraction written in upper case is not seen': ['sold-once.firmware-retracted-upper-case'],
|
|
'a firmware policy of a stranger is believed': ['sold-once.firmware-signer'],
|
|
'a firmware policy of another model is believed': ['policy-of-another-model.partial'],
|
|
'a firmware policy of a version this verifier does not read is dropped in silence': ['policy-of-another-version-beside-a-clean-one.partial'],
|
|
'an expired firmware policy is read as current': ['policy-expired.partial', 'policy-validity-unreadable-beside-a-clean-one.partial'],
|
|
'a firmware policy that has not begun is read as current': ['policy-not-yet.partial'],
|
|
'a retracted entry that is not a digest is read as nothing retracted': ['policy-unreadable-beside-a-clean-one.partial', 'policy-unreadable.partial'],
|
|
'a floor that is not a whole number is read as a floor': ['policy-floor-unreadable-beside-a-clean-one.partial'],
|
|
'a policy whose validity is not a time is dropped in silence': ['policy-validity-unreadable-beside-a-clean-one.partial'],
|
|
'a policy that cannot be read passes in silence': ['policy-floor-unreadable-beside-a-clean-one.partial', 'policy-of-another-version-beside-a-clean-one.partial', 'policy-unreadable-beside-a-clean-one.partial', 'policy-unreadable.partial', 'policy-validity-unreadable-beside-a-clean-one.partial'],
|
|
'the Proof of Software attestation is not required to list the image': ['with-software.firmware-software'],
|
|
'the Proof of Software attestation is not required to be the one named': ['with-software.firmware-software-not-the-one-named'],
|
|
'the id of who GIVES a transfer is not derived from its keys': ['chain-unrooted.transfer-ids-giver'],
|
|
'the id of who RECEIVES a transfer is not derived from its keys': ['chain-only.transfer-ids'],
|
|
'the position of a transfer is not required': ['chain-only.transfer-prev-position'],
|
|
'a transfer is not required to name the link before it': ['chain-only.transfer-prev', 'sold-once.manufacture-signature-and-chain'],
|
|
'a transfer may be dated in the future': ['chain-only.transfer-time-future'],
|
|
'a transfer may be dated before the link before it': ['chain-only.transfer-time'],
|
|
'the id of who grants an authorization is not derived from its keys': ['grant-only.authorization-owner-id'],
|
|
"an authorization that has not begun on the verifier's clock is accepted": ['sold-once.authorization-window-begins-after-tolerance'],
|
|
"an authorization that ended on the verifier's clock is accepted": ['sold-once.authorization-window-ended-between'],
|
|
'an authorization that had not begun when the device attested is accepted': ['sold-once.authorization-window-after-attestation'],
|
|
'an authorization that ends before the time of the attestation is accepted': ['sold-once.authorization-window-ends-before-attestation'],
|
|
'the authorization is not judged at the time of the attestation at all': ['sold-once.authorization-window-after-attestation', 'sold-once.authorization-window-ends-before-attestation'],
|
|
'the beginning of an authorization is not judged at all': ['sold-once.authorization-window-after-attestation', 'sold-once.authorization-window-begins-after-tolerance', 'sold-once.authorization-window-not-yet'],
|
|
"the manufacturer's retirement, and the owner's, are lifted like a suspension": ['bought-back.status-retired-first-ownership', 'resold.status-retired-by-manufacturer', 'retired-then-active.status-retired', 'sold-once.status-retired', 'sold-once.status-retired-by-owner', 'sold-once.status-retired-by-owner-no-head', 'sold-once.status-retired-date-not-a-time', 'sold-once.status-retired-dated-just-ahead'],
|
|
"the manufacturer's records stop counting once the device is sold": ['resold.status-retired-by-manufacturer', 'retired-then-active.status-retired', 'sold-once.status-retired', 'sold-once.status-retired-date-not-a-time', 'sold-once.status-retired-dated-just-ahead', 'status-record-of-another-version.partial', 'suspension-lifted.status-suspended-by-manufacturer'],
|
|
'anyone can write a status record': ['retired-by-a-stranger.valid', 'status-of-a-stranger.partial'],
|
|
'a record of a PREVIOUS owner binds the owner of now': ['resold-after-retirement.valid', 'resold-after-suspension.valid', 'resold-seller-retires-after.valid'],
|
|
"the owner's retirement and suspension count only under the current ownership head": ['bought-back.status-retired-first-ownership', 'bought-back.status-suspended-first-ownership', 'sold-once.status-retired-by-owner-no-head'],
|
|
'an "active" record of the owner is believed under any ownership head': ['bought-back-active-of-first-ownership.partial'],
|
|
'a record of who may give it that cannot be read is ignored (the registry is read as clean)': ['status-record-of-another-version.partial'],
|
|
'a record of another version, or with a state the verifier does not know, is read as one of this version': ['status-record-of-another-version.partial'],
|
|
'a record of another device is applied to this one': ['retirement-of-another-device.valid'],
|
|
'DOUBLE: a status record under an id its keys do not derive is applied (the derivation in the status loop AND the id half of the comparison, both out)': ['manufacture-only.manufacture-holder-other-id', 'retired-under-an-id-not-derived.valid'],
|
|
"a suspension is lifted by another signer's later record": ['suspension-lifted.status-suspended-by-manufacturer'],
|
|
'a suspension expires with its record': ['sold-once.status-suspended-record-expired'],
|
|
'an expired "active" record is believed': ['active-record-expired.partial'],
|
|
'of two records with one number the laxer holds': ['sold-once.status-suspended-same-number'],
|
|
'a record dated in the future is applied': ['retirement-not-yet-in-effect.valid'],
|
|
"a record dated just ahead of the verifier's clock is given no tolerance": ['sold-once.status-retired-dated-just-ahead'],
|
|
'a retirement whose date cannot be read is dropped': ['sold-once.status-retired-date-not-a-time'],
|
|
'the clone check compares the device id alone (the key half taken out)': ['sold-once.attestation-device-claimed-id'],
|
|
'an attestation dated in the future is accepted up to the allowed age': ['sold-once.freshness-ahead'],
|
|
'an attestation dated in the future is accepted, however far': ['sold-once.freshness-ahead'],
|
|
'an old attestation is accepted, however old': ['sold-once.freshness'],
|
|
'an attestation dated ahead is given no tolerance': ['attested-just-ahead.valid', 'sold-once.authorization-window-begins-after-tolerance', 'sold-once.authorization-window-ends-before-attestation'],
|
|
'the sequence the device attests is not compared with the release': ['sold-once.attestation-firmware-sequence'],
|
|
'the digest the device attests is not compared with the release': ['sold-once.attestation-firmware-digest'],
|
|
'a hardware root in the attestation passes in silence': ['hardware-root.partial'],
|
|
'the envelope is not bound to this attestation (its hash)': ['witnessed.witness-envelope-of-another'],
|
|
'the envelope is not bound to this device (its id)': ['witnessed.witness-envelope-other-device'],
|
|
'the envelope is not required to hash to its statementHash': ['witnessed.witness-envelope'],
|
|
'a claim of notarization passes for a reading': ['notarization-not-read.partial', 'reading-of-another-envelope.partial'],
|
|
'a reading of another envelope is taken for this one': ['reading-of-another-envelope.partial'],
|
|
'a reading whose time is in milliseconds is taken for one in seconds': ['reading-in-milliseconds.partial'],
|
|
'a reading that cannot be read passes in silence': ['reading-in-milliseconds.partial'],
|
|
'a bundle of another version is read': ['sold-once.form-version'],
|
|
'transfers that are not a list are read': ['sold-once.form-transfers-not-a-list'],
|
|
};
|
|
/** Writes the planted copy of a rule's verifier beside the original; returns { copie, env } or { stricat }. `loc` plants one site only. */
|
|
function planteaza(verifier, sursa, loc = null) {
|
|
const d = VERIFICATOARE[verifier];
|
|
if (d.sursa === 'verify-proof') {
|
|
const src = fs.readFileSync(VERIFY_PROOF, 'utf8'); const [de, cu] = PLANT_VP[sursa] || []; const n = de ? src.split(de).length - 1 : 0;
|
|
if (n !== 1) return { stricat: `the planting anchor of ${sursa} appears ${n} times in the reference verifier` };
|
|
const copie = path.join(path.dirname(VERIFY_PROOF), `.plantat-conf-${process.pid}-${sursa}.mjs`); fs.writeFileSync(copie, src.replace(de, () => cu));
|
|
return { copie, env: { AERE_VERIFY_PROOF: copie, AERE_CONF_VERIFY_PROOF_DEP: VERIFY_PROOF } };
|
|
}
|
|
const p = fileURLToPath(MODULE[d.sursa].url); const src = fs.readFileSync(p, 'utf8'); const ap = marcajul(d.sursa);
|
|
if (ap) { // the rule is named where the verifier refuses: its one refusal helper, in the copy, drops the refusals of this rule
|
|
const text = ap.planteaza(src, sursa); if (text === null || loc !== null) return { stricat: loc !== null ? 'no single sites to plant' : 'the refusal helper is not where the planting expects it' };
|
|
const copie = path.join(path.dirname(p), `_plantat-conf-${process.pid}-${sursa}.mjs`); fs.writeFileSync(copie, text); return { copie, env: { [MODULE[d.sursa].env]: copie } };
|
|
}
|
|
const bucati = src.split(`/*PAZA:${sursa}*/`); const n = bucati.length - 1;
|
|
if (n < 1) return { stricat: `no /*PAZA:${sursa}*/ marker in ${path.basename(p)}` };
|
|
if (loc !== null && loc >= n) return { stricat: 'site ' + loc + ' of ' + n };
|
|
const text = loc === null ? bucati.join(PLANT) : bucati.map((b, i) => (i === bucati.length - 1 ? b : b + (i === loc ? PLANT : `/*PAZA:${sursa}*/`))).join('');
|
|
const copie = path.join(path.dirname(p), `_plantat-conf-${process.pid}-${sursa}${loc === null ? '' : '-' + loc}.mjs`); fs.writeFileSync(copie, text);
|
|
return { copie, env: { [MODULE[d.sursa].env]: copie } };
|
|
}
|
|
/** the suite, run as a separate program (several plantings run side by side) */
|
|
function suitaAsync(env, { online: on = false, doar = null, artefacte = null } = {}) {
|
|
return new Promise((gata) => {
|
|
const args = [path.join(AICI, 'conformance.mjs'), '--impl', REF, '--json']; if (on) args.push('--online'); if (doar) args.push('--only', doar); if (artefacte) args.push('--artifacts', artefacte.join(','));
|
|
const c = spawn(process.execPath, args, { env: { ...process.env, ...env }, stdio: ['ignore', 'pipe', 'pipe'] }); let out = '', err = '';
|
|
c.stdout.on('data', (d) => { out += d; }); c.stderr.on('data', (d) => { err += d; });
|
|
c.on('close', () => { try { gata({ rezultate: JSON.parse(out).results }); } catch { gata({ eroare: (out + err).trim().split('\n').slice(-1)[0].slice(0, 200) }); } });
|
|
});
|
|
}
|
|
async function inParalel(sarcini, n = 4) { const rez = new Array(sarcini.length); let k = 0; await Promise.all(Array.from({ length: Math.min(n, sarcini.length) }, async () => { while (k < sarcini.length) { const i = k++; rez[i] = await sarcini[i](); } })); return rez; }
|
|
const ramase = () => [...Object.values(MODULE).map((m) => path.dirname(fileURLToPath(m.url))), path.dirname(VERIFY_PROOF)].flatMap((d) => fs.readdirSync(d).filter((f) => /^[._]plantat-conf-/.test(f)).map((f) => path.join(d, f)));
|
|
|
|
/**
|
|
* Every path of every offline vector that is not INVALID, removed or replaced by null, a string, a number, a list, an object, and given
|
|
* to the module verifiers directly. Left out, and said: the policy of a payment (a policy that cannot be defined is refused by
|
|
* definePolicy by throwing, by design: nothing is judged under it), the configuration of an identity verifier (the issuers it trusts,
|
|
* and the two lists themselves: a configuration that cannot be read makes it throw, by design; the entries of the lists are damaged like
|
|
* everything else), the public task of a market job (each replay costs seconds), and
|
|
* the ai-trust bundle (its verifier runs the reference verifier, a program, several times per case).
|
|
* @returns {{ cazuri: number, aruncate: string[], acceptate: string[] }} acceptate = the paths (without array positions) whose damage was accepted
|
|
*/
|
|
async function malformate() {
|
|
const R = await incarca('risc'), C = await incarca('chitanta'), P = await incarca('piata'), B = await incarca('baza'), SC = await incarca('sidecar'), AR = await incarca('arbore'), L = await incarca('ledger'), ID = await incarca('identity'), DV = await incarca('device'); const clona = (x) => JSON.parse(JSON.stringify(x));
|
|
const J = { 'payment-risk': (i) => R.judeca(i.policy, i.inputs).decision === 'ALLOW', 'payment-receipt': (i) => C.verificaChitanta(i.receipt, { policy: i.policy, inputs: i.inputs, tx: i.tx, receipt: i.txReceipt }).verdict === 'VALID', 'market-timeline': (i) => P.judeca(i).verdict === 'VALID',
|
|
'dataset-record': (i) => B.verificaInregistrare(i.record, i.commitment).ok, 'dataset-answer': (i) => B.verificaRaspuns(i.answer, i.commitment).verdict === 'VALID', 'dataset-consistency': (i) => B.verificaConsistentaVersiuni(i.proof, i.older, i.newer).ok,
|
|
// the verifiers enrolled after the first four (2026-10-05), called the way the adapter calls them; ai-trust is not here: each case would run the reference verifier several times
|
|
'audit-log': (i) => (i.head !== undefined ? SC.verificaFataDeCap(i.log, i.head, { semnatar: i.signer || null }) : SC.verificaJurnal(i.log)).ok,
|
|
'audit-inclusion': (i) => AR.verificaIncludere(i.proof, { cap: i.head || null, semnatar: i.signer || null }).ok,
|
|
'audit-consistency': (i) => AR.verificaConsistenta(i.proof, { vechi: i.older, nou: i.newer, semnatar: i.signer || null }).ok,
|
|
'agent-ledger': (i) => { const o = { policy: i.policy, policyHash: i.policyHash, anchors: i.anchors || [], notBefore: i.notBefore === undefined ? null : i.notBefore, revocations: i.revocations || [] }; if (i.maxTime !== undefined) o.maxTime = i.maxTime; return L.verifyLedger(i.ledger, o).ok; },
|
|
// identity (2026-10-05): the presentation and everything handed with it; the verifier's own configuration is left out below
|
|
'identity-presentation': (i) => { const o = { now: i.now }; for (const k of ['audience', 'nonce', 'trustedIssuers', 'statusLists', 'revocations', 'maxAgeS']) if (i[k] !== undefined) o[k] = i[k]; const r = ID.verifyPresentation(i.presentation, o); return r.valid && !r.unjudged.some((c) => NEJUDECATE_IDENTITY[c]); }, // accepted = what the suite would call VALID, not PARTIAL
|
|
// a device bundle (2026-10-05) and everything handed with it; the verifier's own configuration (its clock, the manufacturers it trusts, the lists themselves) is left out below
|
|
'device-bundle': (i) => { const o = { now: i.now }; for (const k of ['nonce', 'audience', 'trustedManufacturers', 'statusLists', 'firmwarePolicies', 'statusRecords', 'softwareAttestation', 'anchors', 'maxAgeS']) if (i[k] !== undefined) o[k] = i[k]; return DV.verifyDevice(i.bundle, o).verdict === 'VALID'; } };
|
|
const cai = (x, p = []) => (x !== null && typeof x === 'object' ? [p, ...Object.keys(x).flatMap((k) => cai(x[k], [...p, Array.isArray(x) ? Number(k) : k]))] : [p]).filter((q) => q.length);
|
|
const VAL = [null, 'x', 7, [], {}]; let cazuri = 0; const aruncate = [], acceptate = [], sarite = [];
|
|
// a vector whose input is larger than MARE characters is not damaged path by path: thousands of paths, each judged over the whole input
|
|
// (a chain of 64 transfers), would take longer than everything else here together. It is counted and named, not passed in silence.
|
|
const MARE = 300000;
|
|
for (const v of vectori) {
|
|
if (!J[v.artifact] || v.expected.verdict === 'INVALID' || v.requires.length) continue;
|
|
if (JSON.stringify(v.input).length > MARE) { sarite.push(v.id); continue; }
|
|
const inp = clona(v.input); if (v.artifact === 'market-timeline') delete inp.spec;
|
|
for (const p of cai(inp)) { if (p[0] === 'policy' && v.artifact.startsWith('payment-')) continue;
|
|
if (v.artifact === 'identity-presentation' && (p[0] === 'trustedIssuers' || (p.length === 1 && (p[0] === 'statusLists' || p[0] === 'revocations')))) continue;
|
|
if (v.artifact === 'device-bundle' && (['trustedManufacturers', 'now', 'maxAgeS'].includes(p[0]) || (p.length === 1 && ['statusLists', 'firmwarePolicies', 'statusRecords', 'anchors'].includes(p[0])))) continue;
|
|
for (const op of ['remove', 0, 1, 2, 3, 4]) { const c = clona(inp); let o = c; for (const k of p.slice(0, -1)) o = o[k]; const k = p[p.length - 1];
|
|
if (op === 'remove') { if (Array.isArray(o)) o.splice(k, 1); else delete o[k]; } else { if (JSON.stringify(o[k]) === JSON.stringify(VAL[op])) continue; o[k] = clona(VAL[op]); }
|
|
cazuri++; try { if (J[v.artifact](c)) acceptate.push(v.artifact + ':' + p.filter((x) => typeof x !== 'number').join('.')); } catch (e) { aruncate.push(v.id + ' ' + p.join('.') + ' <- ' + (op === 'remove' ? 'removed' : JSON.stringify(VAL[op])) + ': ' + String(e.message).slice(0, 60)); } } }
|
|
}
|
|
return { cazuri, aruncate, acceptate, sarite, MARE };
|
|
}
|
|
|
|
/**
|
|
* The reference verifier is a PROGRAM: it is given files. Every path of two envelopes (a tree head signed with ML-DSA-65; the recorded
|
|
* envelope with its notarization claim) removed or replaced by null, a string, a number, a list, an object, and files that are not
|
|
* envelopes at all (null, a number, a string, true, lists, an empty object, text that is not JSON, an empty file), each written to a
|
|
* file and given to the program. NO public endpoint is contacted: the program goes to the chain whenever a file claims a notarization
|
|
* (to the default endpoint of the chain the claim names, mainnet when it names none it knows), so a file that still carries a
|
|
* `notarization` is run with a closed local endpoint and an empty local manifest directory, and its finality level comes out not
|
|
* measured. A case has a verdict when the program prints JSON with a verdict and its levels.
|
|
* @returns {{ cazuri: number, faraVerdict: string[], valide: string[] }} valide = the damaged files it still calls VALID
|
|
*/
|
|
async function plicuriMalformate(vp = VERIFY_PROOF) {
|
|
const clona = (x) => JSON.parse(JSON.stringify(x)); const cai = (x, p = []) => (x !== null && typeof x === 'object' ? [p, ...Object.keys(x).flatMap((k) => cai(x[k], [...p, Array.isArray(x) ? Number(k) : k]))] : [p]).filter((q) => q.length);
|
|
const baze = ['envelope/signed-head.valid', 'envelope/notarized-claim.partial'].map((id) => { const v = vectori.find((x) => x.id === id); if (!v) throw new Error('the vector ' + id + ' is gone'); return v.input.envelope; });
|
|
const VAL = [null, 'x', 7, [], {}]; const fisiere = [];
|
|
baze.forEach((env, b) => { for (const p of cai(env)) for (const op of ['remove', 0, 1, 2, 3, 4]) { const c = clona(env); let o = c; for (const k of p.slice(0, -1)) o = o[k]; const k = p[p.length - 1];
|
|
if (op === 'remove') { if (Array.isArray(o)) o.splice(k, 1); else delete o[k]; } else { if (JSON.stringify(o[k]) === JSON.stringify(VAL[op])) continue; o[k] = clona(VAL[op]); }
|
|
fisiere.push([`envelope ${b + 1}: ${p.join('.')} <- ${op === 'remove' ? 'removed' : JSON.stringify(VAL[op])}`, JSON.stringify(c)]); } });
|
|
for (const [n, t] of [['null', 'null'], ['a number', '7'], ['a string', '"x"'], ['true', 'true'], ['an empty list', '[]'], ['a list with the envelope inside', JSON.stringify([baze[0]])], ['an empty object', '{}'], ['text that is not JSON', JSON.stringify(baze[0]).slice(0, 60)], ['an empty file', '']]) fisiere.push(['the file holds ' + n, t]);
|
|
const T = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-conf-plic-')); const faraVerdict = [], valide = []; const gol = path.join(T, 'no-manifests'); fs.mkdirSync(gol);
|
|
const MORT = ['--rpc', 'http://127.0.0.1:9', '--manifests', gol]; const pretinde = (t) => { try { const j = JSON.parse(t); return !!j && typeof j === 'object' && !!j.notarization; } catch { return false; } };
|
|
try {
|
|
await inParalel(fisiere.map(([nume, text], k) => () => new Promise((gata) => { const f = path.join(T, k + '.json'); fs.writeFileSync(f, text);
|
|
const c = spawn(process.execPath, [vp, f, '--json', ...(pretinde(text) ? MORT : [])], { stdio: ['ignore', 'pipe', 'pipe'] }); let out = ''; const t = setTimeout(() => c.kill(), 60000); c.stdout.on('data', (d) => { out += d; });
|
|
c.on('close', (cod) => { clearTimeout(t); let j = null; try { j = JSON.parse(out); } catch { /* no verdict */ } if (!j || typeof j.verdict !== 'string' || !Array.isArray(j.levels) || !j.levels.length) faraVerdict.push(nume + ' (exit ' + cod + ')'); else if (j.verdict === 'VALID') valide.push(nume); gata(); }); })), 8);
|
|
} finally { fs.rmSync(T, { recursive: true, force: true }); }
|
|
return { cazuri: fisiere.length, faraVerdict, valide };
|
|
}
|
|
|
|
if (!control && !peLoc) {
|
|
console.log('A. the rules and the vectors are derived, and nothing is typed in their place');
|
|
const D = await deriva();
|
|
cer(!D.probleme.length && !D.nemasurat.length, `the rules derive from the code of ${Object.keys(D.tabel.verifiers).length} verifiers: declared == marked == mapped (${TOATE.length} rules)`, [...D.probleme, ...D.nemasurat].join(' | '));
|
|
const rj = path.join(AICI, 'REGULI.json'); cer(fs.existsSync(rj) && fs.readFileSync(rj, 'utf8').replace(/\r\n/g, '\n') === JSON.stringify(D.tabel, null, 1) + '\n', 'REGULI.json is what the code derives now');
|
|
const g = spawnSync(process.execPath, [path.join(AICI, 'genereaza-vectori.mjs'), '--verifica'], { encoding: 'utf8' });
|
|
cer(g.status === 0, 'the vectors on disk are what the generator derives now, byte for byte', g.status === 0 ? g.stdout.trim().split('\n').slice(-1)[0] : (g.stdout + g.stderr).trim().split('\n').slice(-3).join(' | '));
|
|
const dev = path.join(AICI, '..', 'aere-proof-protocol', 'genereaza-vectori-kinds.mjs');
|
|
if (fs.existsSync(dev)) { const G = await import(pathToFileURL(dev).href); const lor = new Map(G.totiVectorii().map((x) => [x.nume, canon(x.plic)])); const ale = plicuriKinds();
|
|
cer(ale.length === lor.size && ale.every(([k, p]) => lor.get(k) === canon(p)), `the ${ale.length} kind envelopes are the published AIP-23 kind vectors (same producers, same fixed inputs)`); }
|
|
else rand('INFO', 'the comparison with the published AIP-23 kind vectors is made in the development repository (their generator is not part of this copy)');
|
|
|
|
console.log('B. coverage and single place');
|
|
const fara = neacoperite(TOATE); cer(!fara.length, `every rule a verifier names has a vector that expects it (${TOATE.length} of ${TOATE.length})`, fara.length ? 'without a vector: ' + fara.join(', ') : '');
|
|
const artifacte = Object.values(VERIFICATOARE).flatMap((v) => v.artifacts); const faraValid = artifacte.filter((x) => !vectori.some((v) => v.artifact === x && v.expected.verdict === 'VALID'));
|
|
cer(!faraValid.length, `every artifact kind has a VALID vector (${artifacte.length} kinds)`, faraValid.join(', '));
|
|
const kinds = plicuriKinds().map(([k]) => k); const kLipsa = kinds.filter((k) => !['valid', 'form', 'integrity'].every((s) => vectori.some((v) => v.id === `envelope/${k}.${s}`)));
|
|
cer(!kLipsa.length, `every AIP-23 proof kind (${kinds.length}) has its VALID vector and one INVALID vector per offline envelope rule`, kLipsa.join(', '));
|
|
let unLoc = 0, prod = 0, alte = 0; const rele = [];
|
|
for (const v of vectori) {
|
|
if (!v.base) { if (v.expected.verdict === 'INVALID') rele.push(v.id + ': INVALID without a base'); continue; }
|
|
const b = vectori.find((x) => x.id === v.base); if (!b) { rele.push(v.id + ': base missing'); continue; }
|
|
if (b.expected.verdict === 'INVALID') rele.push(v.id + ': its base is INVALID');
|
|
if (v.mutation && v.mutation.op) { let m; try { m = aplica(b.input, v.mutation); } catch (e) { rele.push(v.id + ': ' + e.message); continue; } if (canon(m) !== canon(v.input)) rele.push(v.id + ': the input is not the base with the one operation applied'); else if (v.expected.verdict === 'INVALID') unLoc++; else alte++; }
|
|
else if (v.mutation && v.mutation.producer) { const dif = difera(b.input, v.input); const afara = dif.filter((p) => !v.mutation.changed.some((c) => prefix(c, p))); const neatinse = v.mutation.changed.filter((c) => !dif.some((p) => prefix(c, p)));
|
|
if (!dif.length || afara.length || neatinse.length) rele.push(v.id + ': differs outside what it declares (' + afara.map((p) => p.join('.')).join(', ') + ') or declares paths that did not move (' + neatinse.map((p) => p.join('.')).join(', ') + ')'); else prod++; }
|
|
else rele.push(v.id + ': a base without a mutation');
|
|
}
|
|
const inv = vectori.filter((v) => v.expected.verdict === 'INVALID').length;
|
|
cer(!rele.length && unLoc + prod === inv, `every INVALID vector (${inv}) is its base with ONE operation applied (${unLoc}), or one change at the producer with the paths that moved declared (${prod}); ${alte} more vectors that are not INVALID are also one operation from their base`, rele.slice(0, 4).join(' | '));
|
|
const multe = vectori.filter((v) => v.expected.failed.length > 1); const fin = cuRegula('envelope.finality');
|
|
rand('INFO', `measured, not a gate: ${multe.length} vector(s) expect more than one rule (rules that guard the same field: ${multe.map((v) => v.id.split('/')[1]).join(', ')}); of the ${FINALITY_SITES.length} refusal sites of the reference verifier's finality level the vectors reach ${fin.length ? FINALITY_SITES.filter((x) => x.reachable).length : 0} (the others need an endpoint that lies)`);
|
|
|
|
console.log('C. two implementations, one runner');
|
|
const r1 = ruleazaSuita({ impl: REF, online });
|
|
if (r1.eroare) rand('STRICAT', 'the reference verifiers: ' + r1.eroare);
|
|
else { const p = numar(r1, 'PASS').length, deRulat = vectori.filter((v) => online || !v.requires.length).length;
|
|
cer(p === deRulat && !numar(r1, 'FAIL').length && !numar(r1, 'BROKEN').length && !numar(r1, 'NOT_COVERED').length, `the published verifiers behind the one verdict form pass ${p} of ${deRulat} vectors${online ? ' (testnet 28001 read)' : ' (offline)'}`, [...numar(r1, 'FAIL'), ...numar(r1, 'BROKEN')].slice(0, 3).map((x) => x.id + ' ' + x.detaliu).join(' | '));
|
|
if (!online) rand('NEMASURAT', `${numar(r1, 'SKIPPED_ONLINE').length} vectors need testnet 28001 and were not run (--online)`); }
|
|
const py = python();
|
|
if (!py) rand('NEMASURAT', 'the independent implementation needs Python 3 (AERE_CONFORMANCE_PYTHON=<command>)');
|
|
else { const r2 = ruleazaSuita({ impl: `${py} "${path.join(AICI, 'implementare-integritate.py')}"`, online: false });
|
|
if (r2.eroare) rand('STRICAT', 'the independent implementation: ' + r2.eroare);
|
|
else { const p = numar(r2, 'PASS').length, nc = numar(r2, 'NOT_COVERED').length; const revendica = vectori.filter((v) => r2.caps.artifacts.includes(v.artifact) && v.exercises.every((x) => r2.caps.rules.includes(x)) && !v.requires.length).length;
|
|
cer(p === revendica && p > 0 && !numar(r2, 'FAIL').length && !numar(r2, 'BROKEN').length && p + nc === vectori.length, `an independent implementation in another language (${r2.caps.name}) passes all ${p} vectors it claims; the other ${nc} are reported NOT COVERED, never passed`, [...numar(r2, 'FAIL'), ...numar(r2, 'BROKEN')].slice(0, 3).map((x) => x.id + ' ' + x.detaliu).join(' | ')); } }
|
|
|
|
console.log('D. a malformed artifact is refused by name, never thrown at the reader');
|
|
{ const m = await malformate();
|
|
cer(m.cazuri > 1000 && !m.aruncate.length, `${m.cazuri} malformed inputs (every path removed or replaced) given to the module verifiers: each got a verdict, none threw` + (m.sarite.length ? `; NOT damaged this way: ${m.sarite.length} vector(s) whose input is larger than ${m.MARE} characters (${m.sarite.join(', ')})` : ''), m.aruncate.slice(0, 3).join(' | ') + (m.aruncate.length > 3 ? ' ... ' + m.aruncate.length + ' in all' : ''));
|
|
const cai = [...new Set(m.acceptate)].sort(); const peFel = {}; for (const c of cai) { const k = c.split(':')[0]; peFel[k] = (peFel[k] || 0) + 1; }
|
|
rand('INFO', `measured, not a gate: ${m.acceptate.length} of them were still accepted, at ${cai.length} distinct paths (${Object.entries(peFel).map(([k, n]) => k + ' ' + n).join(', ')}): parts a verifier treats as optional (the transaction facts of a receipt, the envelope of a timeline) and fields no rule reads; --acceptate lists them`);
|
|
if (a.includes('--acceptate')) for (const c of cai) console.log(' ' + c); }
|
|
{ const m = await plicuriMalformate();
|
|
cer(m.cazuri > 100 && !m.faraVerdict.length, `${m.cazuri} malformed envelope files (every path of a signed head and of a notarized envelope removed or replaced; files that hold null, a number, a string, true, a list, an empty object, text that is not JSON, nothing) given to the reference verifier, the program: each got a verdict`, m.faraVerdict.slice(0, 3).join(' | ') + (m.faraVerdict.length > 3 ? ' ... ' + m.faraVerdict.length + ' in all' : ''));
|
|
rand('INFO', `measured, not a gate: ${m.valide.length} of those files the reference verifier still calls VALID (an envelope without its signature or without its notarization claim is an unsigned, unnotarized envelope; the fields v and kind beside the statement are not read). No public endpoint was contacted: a file that still claims a notarization was given a closed local endpoint, and its finality is not measured; --acceptate lists them`);
|
|
if (a.includes('--acceptate')) for (const c of m.valide.sort()) console.log(' ' + c); }
|
|
} else if (control) {
|
|
console.log('CONTROL: every rule planted out of its verifier, one at a time; the vectors that expect the rule, and only they, must fail');
|
|
// Under a planting the suite runs the vectors of every artifact kind that can load the planted verifier (the kinds whose vectors
|
|
// exercise a rule of that verifier); the other kinds never load it: the adapter reaches a planted copy only through that verifier's
|
|
// own variable, and a verifier that another one imports is imported from its real file.
|
|
const atinse = (verif) => [...new Set(vectori.filter((x) => x.exercises.some((r) => HARTA[r] && HARTA[r].verifier === verif)).map((x) => x.artifact))];
|
|
// a rule whose vectors are ALL chain vectors can be controlled only with --online; with --online a rule that has chain vectors is run with them
|
|
const toate = []; for (const [v, d] of Object.entries(VERIFICATOARE)) for (const [src, [id]] of Object.entries(d.reguli)) { const ai = vectori.filter((x) => x.expected.failed.includes(id)); toate.push({ v, src, id, numaiOnline: ai.length > 0 && ai.every((x) => x.requires.length), online: online && ai.some((x) => x.requires.length) }); }
|
|
const filtru = a.includes('--doar') ? a[a.indexOf('--doar') + 1] : null; const cazuri = toate.filter((c) => !filtru || c.id.startsWith(filtru));
|
|
if (filtru) console.log(` (FILTERED: --doar ${filtru}: ${cazuri.length} of ${toate.length} rules; the others are NOT controlled by this run)`);
|
|
const deRulat = cazuri.filter((c) => !c.numaiOnline || online); let bune = 0;
|
|
try {
|
|
const rez = await inParalel(deRulat.map((c) => async () => { const p = planteaza(c.v, c.src); if (p.stricat) return { stricat: p.stricat }; try { return await suitaAsync(p.env, { online: c.online, artefacte: atinse(c.v) }); } finally { fs.rmSync(p.copie, { force: true }); } }));
|
|
deRulat.forEach((c, k) => {
|
|
const r = rez[k]; const asteptate = cuRegula(c.id, vectori.filter((v) => c.online || !v.requires.length)).sort();
|
|
if (r.stricat || r.eroare) return rand('STRICAT', `${c.id}: ${r.stricat || r.eroare}`);
|
|
const cazute = r.rezultate.filter((x) => x.stare === 'FAIL').map((x) => x.id).sort(); const stricate = r.rezultate.filter((x) => x.stare === 'BROKEN').map((x) => x.id);
|
|
const ok = asteptate.length > 0 && cazute.join() === asteptate.join() && !stricate.length; if (ok) bune++;
|
|
rand(ok ? 'VERDE' : stricate.length ? 'STRICAT' : 'ROSU', `${c.id} planted out: ${cazute.length} vector(s) fail` + (ok ? `, exactly the ${asteptate.length} that expect it` : `; expected exactly [${asteptate.join(', ')}], failed [${cazute.join(', ')}]` + (stricate.length ? `, broken [${stricate.join(', ')}]` : '')));
|
|
});
|
|
} finally { for (const f of ramase()) fs.rmSync(f, { force: true }); }
|
|
for (const c of cazuri.filter((x) => x.numaiOnline && !online)) rand('NEMASURAT', `${c.id}: its vectors need testnet 28001 (--online)`);
|
|
if (filtru) rand('NEMASURAT', `${toate.length - cazuri.length} rules left out by --doar`);
|
|
// A rule taken out WHOLE says nothing about each condition of a check made of several. For the device verifier the conditions are
|
|
// planted out one at a time (the plantings are those of its own negative control, read from its plantari-device.mjs, not typed
|
|
// again here): the vectors written for that condition, and only they, must fail.
|
|
console.log('CONTROL of the halves (device verifier): one condition of a compound check planted out; exactly the vectors written for it must fail');
|
|
if (filtru && !filtru.startsWith('device')) rand('NEMASURAT', `the ${Object.keys(JUMATATI_DEVICE).length} halves of the device verifier left out by --doar`);
|
|
else {
|
|
const p = fileURLToPath(MODULE.device.url); const src = fs.readFileSync(p, 'utf8'); const lista = Object.entries(JUMATATI_DEVICE); let buneJ = 0;
|
|
try {
|
|
const rez = await inParalel(lista.map(([nume], k) => async () => {
|
|
const pl = PLANTARI_DEVICE.find((x) => x[0] === nume && x[1] === path.basename(p)); if (!pl) return { stricat: 'no planting of this name in plantari-device.mjs' };
|
|
const de = [pl[2]].flat(), cu = [pl[3]].flat(); let text = src;
|
|
for (const [j, d] of de.entries()) { if (src.split(d).length !== 2 || text.split(d).length !== 2) return { stricat: `pattern ${j + 1} is not in the device verifier exactly once` }; text = text.replace(d, () => cu[j]); }
|
|
const copie = path.join(path.dirname(p), `_plantat-conf-${process.pid}-jum-${k}.mjs`); fs.writeFileSync(copie, text);
|
|
try { return await suitaAsync({ [MODULE.device.env]: copie }, { artefacte: ['device-bundle'] }); } finally { fs.rmSync(copie, { force: true }); }
|
|
}));
|
|
lista.forEach(([nume, ast], k) => {
|
|
const r = rez[k]; const asteptate = ast.map((x) => 'device-bundle/' + x).sort(); const lipsa = asteptate.filter((id) => !vectori.some((v) => v.id === id));
|
|
if (r.stricat || r.eroare || lipsa.length) return rand('STRICAT', `${nume}: ${r.stricat || r.eroare || 'names vectors that are not in the set: ' + lipsa.join(', ')}`);
|
|
const cazute = r.rezultate.filter((x) => x.stare === 'FAIL').map((x) => x.id).sort(); const stricate = r.rezultate.filter((x) => x.stare === 'BROKEN').map((x) => x.id);
|
|
const ok = asteptate.length > 0 && cazute.join() === asteptate.join() && !stricate.length; if (ok) buneJ++;
|
|
rand(ok ? 'VERDE' : stricate.length ? 'STRICAT' : 'ROSU', `${nume}: ${cazute.length} vector(s) fail` + (ok ? `, exactly the ${asteptate.length} written for it (${ast.join(', ')})` : `; expected exactly [${asteptate.join(', ')}], failed [${cazute.join(', ')}]` + (stricate.length ? `, broken [${stricate.join(', ')}]` : '')));
|
|
});
|
|
} finally { for (const f of ramase()) fs.rmSync(f, { force: true }); }
|
|
console.log(`CONTROL of the halves: ${buneJ} of ${lista.length} conditions proven load-bearing through their vectors`);
|
|
}
|
|
console.log('CONTROL of the instruments');
|
|
{ // the runner can fail: an implementation that calls everything VALID
|
|
const stub = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'aere-conf-stub-')), 'stub.mjs');
|
|
fs.writeFileSync(stub, `import fs from 'node:fs'; const a = process.argv.slice(2); const V = JSON.stringify({ verdict: 'VALID', failed: [], levels: [{ level: 'integrity', state: 'PASSED' }, { level: 'authenticity', state: 'NOT_MEASURED' }, { level: 'anchoring', state: 'NOT_MEASURED' }, { level: 'finality', state: 'NOT_MEASURED' }] });\nif (a.includes('--capabilities')) console.log(JSON.stringify({ artifacts: ${JSON.stringify(Object.values(VERIFICATOARE).flatMap((v) => v.artifacts))}, rules: ${JSON.stringify(TOATE)}, online: false, batch: true }));\nelse for (const x of JSON.parse(fs.readFileSync(a[a.indexOf('--batch') + 1], 'utf8'))) console.log(V);\n`);
|
|
const r = ruleazaSuita({ impl: `node "${stub}"` }); fs.rmSync(path.dirname(stub), { recursive: true, force: true });
|
|
const off = vectori.filter((v) => !v.requires.length); const trebuie = off.filter((v) => !(v.expected.verdict === 'VALID' && Object.entries(v.expected.levels).every(([l, s]) => s === (l === 'integrity' ? 'PASSED' : 'NOT_MEASURED')))).map((v) => v.id).sort();
|
|
const cazute = r.eroare ? [] : numar(r, 'FAIL').map((x) => x.id).sort();
|
|
cer(!r.eroare && cazute.join() === trebuie.join() && trebuie.length > 0, `the runner fails an implementation that calls everything VALID on ${trebuie.length} of ${off.length} offline vectors (every one that is not plainly VALID)`, r.eroare || ''); }
|
|
{ const lista = vectori.filter((v) => !v.expected.failed.includes('dataset.order')); const f = neacoperite(TOATE, lista);
|
|
cer(f.join() === 'dataset.order', 'the coverage gate goes red when the vector of one rule is taken away (dataset.order)', f.join(', ')); }
|
|
{ // a rule added to a verifier and not mapped in the suite is refused by the derivation
|
|
const p = fileURLToPath(MODULE.baza.url); const copie = path.join(path.dirname(p), `_plantat-conf-${process.pid}-regula-noua.mjs`); const src = fs.readFileSync(p, 'utf8'); const anc = "'consistenta', 'esantion'];";
|
|
try { if (src.split(anc).length !== 2) rand('STRICAT', 'the anchor of the new-rule planting is not in the dataset module');
|
|
else { fs.writeFileSync(copie, src.replace(anc, "'consistenta', 'esantion', 'noua'];") + '\n/*PAZA:noua*/\n'); const D = await deriva({ cai: { baza: copie } });
|
|
cer(D.probleme.length === 1 && /names rule noua and the suite does not map it/.test(D.probleme[0]), 'a rule added to a verifier and not mapped in the suite is refused by the derivation (a new rule without a vector cannot pass unseen)', D.probleme.join(' | ')); }
|
|
} finally { fs.rmSync(copie, { force: true }); } }
|
|
{ // the malformed-input row can go red: the market judge without its form check and without its catch throws again, and the row counts it
|
|
const p = fileURLToPath(MODULE.piata.url); const copie = path.join(path.dirname(p), `_plantat-conf-${process.pid}-arunca.mjs`); const src = fs.readFileSync(p, 'utf8'); const anc = "catch (e) { return { verdict: 'INVALID', motive: ['malformed timeline: '";
|
|
try { if (src.split(anc).length !== 2) rand('STRICAT', 'the anchor of the throwing planting is not in the market judge');
|
|
else { fs.writeFileSync(copie, src.replace(anc, "catch (e) { throw e; return { verdict: 'INVALID', motive: ['malformed timeline: '").split('/*PAZA:forma*/').join(PLANT)); process.env[MODULE.piata.env] = copie; const m = await malformate(); delete process.env[MODULE.piata.env];
|
|
cer(m.aruncate.length > 0 && m.aruncate.every((x) => x.startsWith('market-timeline/')), `the malformed-input row goes red when a judge throws again (the market judge without its form check and its catch: ${m.aruncate.length} of ${m.cazuri} inputs thrown)`); }
|
|
} finally { delete process.env[MODULE.piata.env]; fs.rmSync(copie, { force: true }); } }
|
|
{ // the malformed-envelope row can go red: the reference verifier without the first half of its form check throws on a file that holds null
|
|
const src = fs.readFileSync(VERIFY_PROOF, 'utf8'); const anc = PLANT_VP.form[0]; const copie = path.join(path.dirname(VERIFY_PROOF), `.plantat-conf-${process.pid}-arunca.mjs`);
|
|
try { if (src.split(anc).length !== 2) rand('STRICAT', 'the anchor of the throwing planting is not in the reference verifier');
|
|
else { fs.writeFileSync(copie, src.replace(anc, () => 'const formOk = obj(env.statement)')); const bun = await plicuriMalformate(), m = await plicuriMalformate(copie);
|
|
cer(!bun.faraVerdict.length && m.faraVerdict.length > 0 && m.faraVerdict.some((x) => x.startsWith('the file holds null')), `the malformed-envelope row goes red when the reference verifier throws again (without the first half of its form check: ${m.faraVerdict.length} of ${m.cazuri} files without a verdict; as published: ${bun.faraVerdict.length})`, m.faraVerdict.slice(0, 2).join(' | ')); }
|
|
} finally { fs.rmSync(copie, { force: true }); } }
|
|
const r = ramase(); cer(!r.length, 'no planted copy is left behind', r.join(', '));
|
|
console.log(`CONTROL: ${bune} of ${cazuri.length} rules proven load-bearing through their vectors`);
|
|
} else {
|
|
console.log('MEASUREMENT (not a gate): each check site planted out alone, the vectors of its artifact kinds run');
|
|
const locuri = []; for (const [v, d] of Object.entries(VERIFICATOARE)) { if (d.sursa === 'verify-proof') continue; const src = fs.readFileSync(fileURLToPath(MODULE[d.sursa].url), 'utf8');
|
|
for (const [s, [id]] of Object.entries(d.reguli)) { const n = src.split(`/*PAZA:${s}*/`).length - 1; for (let k = 0; k < n; k++) locuri.push({ v, src: s, id, k, n, doar: d.artifacts[0].split('-')[0] }); } }
|
|
let atinse = 0;
|
|
try {
|
|
const rez = await inParalel(locuri.map((c) => async () => { const p = planteaza(c.v, c.src, c.k); if (p.stricat) return { stricat: p.stricat }; try { return await suitaAsync(p.env, { doar: c.doar }); } finally { fs.rmSync(p.copie, { force: true }); } }));
|
|
const peRegula = {};
|
|
locuri.forEach((c, k) => { const r = rez[k]; const cazute = r.rezultate ? r.rezultate.filter((x) => x.stare === 'FAIL').length : 0; if (cazute) atinse++; (peRegula[c.id] || (peRegula[c.id] = [])).push(r.stricat || r.eroare ? '?' : cazute ? 'reached' : 'NOT reached'); });
|
|
for (const [id, l] of Object.entries(peRegula)) console.log(` ${id}: ${l.filter((x) => x === 'reached').length} of ${l.length} site(s) reached` + (l.includes('NOT reached') ? ' (sites ' + l.map((x, i) => (x === 'NOT reached' ? i + 1 : null)).filter(Boolean).join(', ') + ' have no vector)' : ''));
|
|
} finally { for (const f of ramase()) fs.rmSync(f, { force: true }); }
|
|
console.log(`SITES: the vectors reach ${atinse} of ${locuri.length} check sites of the module verifiers (the reference verifier's own sites are counted in REGULI.json)`);
|
|
process.exit(0);
|
|
}
|
|
console.log(`\nconformance suite${control ? ' (control)' : ''}: ${verzi} VERDE, ${rosii} ROSU, ${nemas} NEMASURAT/STRICAT`);
|
|
process.exitCode = rosii ? 1 : nemas ? 2 : 0;
|