#!/usr/bin/env node // implementare-aere.mjs: the published verifiers behind the one verdict form (verdict.mjs). This file holds NO verification rule of // its own: an AIP-23 envelope is judged by the reference verifier (verify-proof.mjs, run as the program it is), a payment by the // risk module, a receipt by the receipt verifier, a market timeline by the market judge, a dataset record, answer, consistency proof // or sample by the dataset module. What it adds is the translation: the rule ids those verifiers name -> the public rule ids and // the level of each (reguli.mjs), and the composition (a receipt or a timeline that carries an AIP-23 envelope gets that envelope // judged too, so one verdict covers the artifact). // // node implementare-aere.mjs --capabilities what this implementation judges (artifacts, rules, online) // node implementare-aere.mjs [--online] one artifact: { artifact, input } (a vector file is one); verdict on stdout // node implementare-aere.mjs --batch [{ file, online }]; one verdict per line, in order // // Offline (no --online) nothing here opens a connection: an envelope that claims a notarization is judged without the claim and the // verdict says so (PARTIAL: claim-not-checked). With --online the envelope goes to the reference verifier with the chain's endpoint. // Exit: 0 VALID, 1 INVALID, 2 PARTIAL, 3 the artifact could not be judged at all (not a verdict). import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { spawn } from 'node:child_process'; import { VERIFICATOARE, FINALITY_SITES, VERIFY_PROOF, incarca } from './reguli.mjs'; import { acumulator, pica, trece, nemasurat, verdictUnic } from './verdict.mjs'; const RPC = { 2800: process.env.AERE_CONFORMANCE_RPC_2800 || 'https://rpc.aere.network', 28001: process.env.AERE_CONFORMANCE_RPC_28001 || 'https://testnet-rpc.aere.network' }; // a verifier that takes the reference verifier as a DEPENDENCY (the trust chain judges its envelopes with it) gets the real one even // while the planting control substitutes a planted copy for the envelopes this file judges itself const VERIFY_PROOF_DEP = process.env.AERE_CONF_VERIFY_PROOF_DEP || VERIFY_PROOF; const LANT = (n) => (n === 28001 ? 'chain 28001, a testnet' : 'chain ' + n); const ARTIFACTE = Object.values(VERIFICATOARE).flatMap((v) => v.artifacts); const toateRegulile = Object.values(VERIFICATOARE).flatMap((v) => Object.values(v.reguli).map((r) => r[0])); function ruleaza(cmd, args, timeout) { return new Promise((gata) => { const c = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'] }); let out = '', err = ''; const t = setTimeout(() => c.kill(), timeout); c.stdout.on('data', (d) => { out += d; }); c.stderr.on('data', (d) => { err += d; }); c.on('error', (e) => { clearTimeout(t); gata({ code: null, out, err: String(e.message) }); }); c.on('close', (code) => { clearTimeout(t); gata({ code, out, err }); }); }); } /** The envelope, judged by the reference verifier; its levels land in the accumulator under the public rule ids. */ async function plic(acc, env, { online, chain, raw }) { if (!fs.existsSync(VERIFY_PROOF)) throw new Error('the AIP-23 reference verifier is not at ' + VERIFY_PROOF + ' (AERE_VERIFY_PROOF=)'); const claim = env && typeof env === 'object' ? env.notarization : null; let judecat = env; if (!online && claim) { judecat = { ...env }; delete judecat.notarization; } const f = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'aere-conf-')), 'envelope.json'); fs.writeFileSync(f, typeof raw === 'string' ? raw : JSON.stringify(judecat === undefined ? null : judecat)); // `raw`: the text of a file that is not JSON const lant = Number(chain || (claim && claim.chainId) || 28001); const args = [VERIFY_PROOF, f, '--json']; if (online) { if (!RPC[lant]) throw new Error('no endpoint known for chain ' + lant); args.push('--chain', String(lant), '--rpc', RPC[lant]); } let r; try { r = await ruleaza(process.execPath, args, online ? 240000 : 60000); } finally { fs.rmSync(path.dirname(f), { recursive: true, force: true }); } let j; try { j = JSON.parse(r.out); } catch { throw new Error('the reference verifier gave no verdict (exit ' + r.code + '): ' + (r.err || r.out).trim().split('\n').slice(-1)[0].slice(0, 160)); } const nivel = (n) => (j.levels || []).find((x) => x.level === n); const R = VERIFICATOARE['aip23-envelope'].reguli; for (const n of ['form', 'integrity']) { const x = nivel(n); if (!x) continue; if (x.state === 'FAILED') pica(acc, 'integrity', R[n][0], x.detail); else trece(acc, 'integrity', 'envelope ' + n); } const s = nivel('signature'); if (!s) nemasurat(acc, 'authenticity', 'not-reached', 'the envelope failed before its signature was looked at'); else if (s.state === 'PASSED') trece(acc, 'authenticity', s.detail); else if (s.state === 'FAILED') pica(acc, 'authenticity', R.signature[0], s.detail); else nemasurat(acc, 'authenticity', s.state === 'ABSENT' ? 'absent' : 'unmeasurable', s.detail); const fin = nivel('finality'); const ambele = (cause, reason) => { nemasurat(acc, 'anchoring', cause, reason); nemasurat(acc, 'finality', cause, reason); }; if (!online && claim) ambele('claim-not-checked', 'the envelope claims a notarization on chain ' + (claim.chainId || '?') + '; this run was offline, the claim was not checked'); else if (!fin) ambele('not-reached', 'the envelope failed before its notarization was looked at'); else if (fin.state === 'PASSED') { trece(acc, 'anchoring', fin.detail); trece(acc, 'finality', 'the post-quantum certificate of the covering anchor verified (' + LANT(lant) + '; the finality is that chain\'s)'); } else if (fin.state === 'FAILED') { const loc = FINALITY_SITES.filter((x) => x.re.test(fin.detail)); if (loc.length !== 1) throw new Error('the reference verifier refused finality at a site this suite does not classify: ' + fin.detail.slice(0, 120)); pica(acc, loc[0].level, R.finality[0], fin.detail); if (loc[0].level === 'anchoring') nemasurat(acc, 'finality', 'not-applicable', 'nothing is anchored, so no certificate covers it'); else nemasurat(acc, 'anchoring', 'not-reached', 'the anchor certificate did not verify, the notary state under it was not read'); } else ambele(fin.state === 'ABSENT' ? 'absent' : fin.state === 'PENDING' ? 'pending' : 'unmeasurable', fin.detail); return j; } const faraPlic = (acc) => { for (const l of ['authenticity', 'anchoring', 'finality']) nemasurat(acc, l, 'not-judged', 'this artifact carries no AIP-23 envelope; its commitment is signed or anchored through one, judged as the artifact `envelope`'); }; /** The rules a module named, as public ids at their levels; `text` is the module's own reasons. */ function reguli(acc, verifier, surse, motive, titlu) { const R = VERIFICATOARE[verifier].reguli; const text = (motive || []).filter(Boolean).join('; '); for (const s of surse) { if (!R[s]) throw new Error(`${verifier} named a rule this suite does not map: ${s}`); pica(acc, R[s][1], R[s][0], text); } if (!surse.length) trece(acc, 'integrity', titlu); } async function judeca(artifact, input, online) { const acc = acumulator(); if (artifact === 'envelope') await plic(acc, input.envelope, { online, chain: input.chain, raw: input.raw }); else if (artifact === 'payment-risk') { const M = await incarca('risc'); const v = M.judeca(input.policy, input.inputs); if ((v.decision === 'ALLOW') !== (v.rules.length === 0)) throw new Error('the risk module refused without naming a rule'); reguli(acc, 'payment-risk', v.rules, v.reasons, 'the declared policy allows this payment'); faraPlic(acc); } else if (artifact === 'payment-receipt') { const M = await incarca('chitanta'); const r = M.verificaChitanta(input.receipt, { policy: input.policy, inputs: input.inputs, tx: input.tx, receipt: input.txReceipt }); if ((r.verdict === 'VALID') !== (r.reguli.length === 0)) throw new Error('the receipt verifier refused without naming a rule'); reguli(acc, 'payment-receipt', r.reguli, r.motive, 'no rule of the receipt verifier refused' + (r.chainChecked ? ' (transaction facts supplied)' : ' (no transaction facts supplied)')); await plic(acc, input.receipt && input.receipt.envelope, { online, chain: input.chain }); } else if (artifact === 'market-timeline') { // the envelope first: WHEN it was notarized is the reading of the reference verifier under the anchor (its finality level's // `certified`), handed to the judge. A `certified` the artifact brings itself is dropped: that reading is never the artifact's. const M = await incarca('piata'); const { certified: adus, ...cronologie } = input; let certified = null; if (input.envelope) { const j = await plic(acc, input.envelope, { online, chain: input.chainId }); const f = (j.levels || []).find((x) => x.level === 'finality'); if (f && f.state === 'PASSED' && f.certified) certified = { ...f.certified, statementHash: input.envelope.statementHash }; } else faraPlic(acc); const r = M.judeca(certified ? { ...cronologie, certified } : cronologie); if (r.verdict === 'INCOMPLETE') nemasurat(acc, 'integrity', 'incomplete', 'the timeline is not terminal (' + (r.status || 'no job or no events') + ')'); else { if ((r.verdict === 'VALID') !== (r.reguli.length === 0)) throw new Error('the market judge refused without naming a rule'); reguli(acc, 'market-timeline', r.reguli, r.motive, 'no rule of the market judge refused (status ' + r.status + ')'); } } else if (artifact === 'audit-log' || artifact === 'audit-inclusion' || artifact === 'audit-consistency') { // an audit log against its own chain and, when given, an attested head; an inclusion or a consistency proof against tree heads. // The checks of a head's signature are the sidecar's whichever file calls them: their rules are mapped there. const S = VERIFICATOARE['audit-log'].reguli, T = VERIFICATOARE['audit-tree'].reguli; let r, semnat = null; if (artifact === 'audit-log') { const M = await incarca('sidecar'); r = input.head !== undefined ? M.verificaFataDeCap(input.log, input.head, { semnatar: input.signer || null }) : M.verificaJurnal(input.log); if (r.ok && input.head !== undefined) semnat = r.semnatDe; } else { const M = await incarca('arbore'); r = artifact === 'audit-inclusion' ? M.verificaIncludere(input.proof, { cap: input.head || null, semnatar: input.signer || null }) : M.verificaConsistenta(input.proof, { vechi: input.older, nou: input.newer, semnatar: input.signer || null }); if (r.ok) semnat = r.semnatDe || r.semnatNou || null; } if (r.ok) { trece(acc, 'integrity', 'no rule of the audit log verifier refused'); if (semnat) trece(acc, 'authenticity', 'the head is signed with ML-DSA-65 by key ' + String(semnat).slice(0, 18) + (input.signer ? '.., the expected signer' : '.. (not compared with an expected signer)')); else nemasurat(acc, 'authenticity', 'absent', 'no signed head'); } else { const m = (artifact === 'audit-log' ? S[r.regula] : T[r.regula] || S[r.regula]); if (!m) throw new Error('the audit log verifier refused without a rule this suite maps: ' + r.regula); pica(acc, m[1], m[0], r.motiv); nemasurat(acc, m[1] === 'integrity' ? 'authenticity' : 'integrity', 'not-reached', 'the verifier stopped at its first refusal'); } for (const l of ['anchoring', 'finality']) nemasurat(acc, l, 'not-judged', 'an audit head is anchored by notarizing its envelope, judged as the artifact `envelope`'); } else if (artifact === 'agent-ledger') { // an agent's signed ledger, verified as a stranger would: the policy re-hashed, the identity bound to the key, every signature, the // chain, and every decision re-derived by replaying the policy const M = await incarca('ledger'); const R = VERIFICATOARE['agent-ledger'].reguli; const o = { policy: input.policy, policyHash: input.policyHash, anchors: input.anchors || [], notBefore: input.notBefore === undefined ? null : input.notBefore, revocations: input.revocations || [] }; if (input.maxTime !== undefined) o.maxTime = input.maxTime; const r = M.verifyLedger(input.ledger, o); if (r.ok) { trece(acc, 'integrity', `the ledger verifies under its policy: ${r.seq} entries, ${r.allowedPayments} allowed payments, time ${r.time.lowerBound}`); trece(acc, 'authenticity', 'the agent id is derived from its ML-DSA-65 key and every entry is signed by it'); } else { const m = R[r.rule]; if (!m) throw new Error('the ledger verifier refused without a rule this suite maps: ' + r.rule); pica(acc, m[1], m[0], r.error); nemasurat(acc, m[1] === 'integrity' ? 'authenticity' : 'integrity', 'not-reached', 'the verifier stopped at its first refusal'); } for (const l of ['anchoring', 'finality']) nemasurat(acc, l, 'not-judged', 'a ledger head is anchored by notarizing its envelope, judged as the artifact `envelope`'); } else if (artifact === 'ai-trust-bundle') { // the trust chain of an AI result, judged by its own verifier (which runs the reference verifier on each envelope and the agents' // and the audit log's verifiers on theirs). Offline, a notarization claimed by the attestation, the runtime record or the audit // head is taken out of the copy judged (those envelopes are not hashed into the ledger) and reported as not checked. const M = await incarca('aitrust'); const b = JSON.parse(JSON.stringify(input.bundle === undefined ? null : input.bundle)); let claim = false; const ob = (x) => !!x && typeof x === 'object'; const lant = Number((ob(b) && ob(b.witness) && b.witness.chainId) || input.chain || 28001); if (!online && ob(b)) { for (const e of [ob(b.version) && b.version.attestation, ob(b.runtime) && b.runtime.record, ob(b.runtime) && b.runtime.head]) if (ob(e) && e.notarization) { delete e.notarization; claim = true; } if (ob(b.provenance) && ob(b.provenance.proofOfAi) && b.provenance.proofOfAi.notarization) { nemasurat(acc, 'integrity', 'claim-not-checked', 'the Proof of AI envelope claims a notarization: this bundle is judged only with the chain (--online)'); return verdictUnic(artifact, acc); } } if (online && !RPC[lant]) throw new Error('no endpoint known for chain ' + lant); const v = await M.verifica(b, { verifyProof: VERIFY_PROOF_DEP, rpc: online ? RPC[lant] : null, chain: online ? lant : null }); const R = VERIFICATOARE['ai-trust'].reguli; const st = v.levels, det = v.details; for (const l of Object.keys(st)) if (st[l] === 'FAILED') { const r = R[det[l].reason]; if (!r) throw new Error('ai-trust named a rule this suite does not map: ' + det[l].reason); pica(acc, r[1], r[0], det[l].detail); } const INT = ['version', 'runtime', 'permissions', 'provenance']; const nem = INT.find((l) => st[l] === 'UNMEASURED'); if (nem) nemasurat(acc, 'integrity', 'unmeasurable', nem + ': ' + det[nem].reason); else if (st.form === 'PASSED' && st.permissions === 'PASSED' && st.provenance === 'PASSED') trece(acc, 'integrity', 'the links hold: ' + INT.map((l) => l + ' ' + st[l]).join(', ')); if (st.identity === 'PASSED') trece(acc, 'authenticity', det.identity.detail); else if (st.identity !== 'FAILED') nemasurat(acc, 'authenticity', 'not-reached', 'the identity level was not reached'); const ambele = (cause, reason) => { nemasurat(acc, 'anchoring', cause, reason); nemasurat(acc, 'finality', cause, reason); }; if (st.witness === 'PASSED') { trece(acc, 'anchoring', det.witness.detail); trece(acc, 'finality', 'the post-quantum certificate of the covering anchor verified (' + LANT(lant) + '; the finality is that chain\'s)'); } else if (st.witness === 'FAILED') nemasurat(acc, 'finality', 'not-applicable', 'no head of this ledger is anchored'); else if (st.witness === 'UNMEASURED') ambele(online ? 'unmeasurable' : 'claim-not-checked', online ? String(det.witness.detail).slice(0, 200) : 'the bundle brings notarized heads; this run was offline, they were not read'); else if (st.form === 'PASSED') ambele('unwitnessed', 'no witness: every time in this bundle is the statement of whoever holds the keys'); if (claim) nemasurat(acc, 'anchoring', 'claim-not-checked', 'an envelope of the bundle claims a notarization; this run was offline, the claim was not checked'); } else if (artifact.startsWith('dataset-')) { const M = await incarca('baza'); let r; if (artifact === 'dataset-record') r = M.verificaInregistrare(input.record, input.commitment); else if (artifact === 'dataset-answer') { r = M.verificaRaspuns(input.answer, input.commitment); r.ok = r.verdict === 'VALID'; } else if (artifact === 'dataset-consistency') r = M.verificaConsistentaVersiuni(input.proof, input.older, input.newer); else if (artifact === 'dataset-sample') r = await M.esantioneaza({ url: 'vector://served', commitment: input.commitment, k: input.k, seed: input.seed, ia: async (u) => (input.served || {})[u.split('/').pop()] || null }); else throw new Error('unknown artifact ' + artifact); if (r.ok !== (r.reguli.length === 0)) throw new Error('the dataset module refused without naming a rule'); reguli(acc, 'dataset', r.reguli, r.motive, 'no rule of the dataset module refused'); faraPlic(acc); } else throw new Error('unknown artifact ' + artifact); return verdictUnic(artifact, acc); } async function unul(file, online) { let j; try { j = JSON.parse(fs.readFileSync(file, 'utf8')); } catch (e) { return { error: 'the file cannot be read: ' + e.message }; } if (!j || !ARTIFACTE.includes(j.artifact) || !j.input) return { error: 'not { artifact, input } with a known artifact (' + ARTIFACTE.join(', ') + ')' }; try { return await judeca(j.artifact, j.input, online); } catch (e) { return { error: String(e.message).split('\n')[0].slice(0, 300) }; } } const a = process.argv.slice(2); if (a.includes('--capabilities')) console.log(JSON.stringify({ v: 1, kind: 'aere-conformance-capabilities', name: 'aere reference verifiers (JavaScript)', artifacts: ARTIFACTE, rules: toateRegulile.sort(), online: true, batch: true })); else if (a.includes('--batch')) { const lista = JSON.parse(fs.readFileSync(a[a.indexOf('--batch') + 1], 'utf8')); const rez = new Array(lista.length); let urm = 0; const lucrator = async () => { while (urm < lista.length) { const k = urm++; rez[k] = await unul(lista[k].file, !!lista[k].online); } }; await Promise.all(Array.from({ length: Math.min(6, lista.length) }, lucrator)); for (const r of rez) console.log(JSON.stringify(r)); } else { const file = a.find((x) => !x.startsWith('--')); if (!file) { console.error('usage: node implementare-aere.mjs --capabilities | [--online] | --batch '); process.exitCode = 3; } else { const r = await unul(file, a.includes('--online')); console.log(JSON.stringify(r, null, 1)); process.exitCode = r.error ? 3 : r.verdict === 'VALID' ? 0 : r.verdict === 'INVALID' ? 1 : 2; } }