#!/usr/bin/env node 'use strict'; // AERE Quantum Security Control Plane - JUDECATA ORGANIZATIEI (B2, roadmap master punctele 41 si 43; 2026-10-20). Planul de control // "toate domeniile intr-un loc": o organizatie aduce un PACHET DE DOVEZI (prezentari de identitate, plicuri AIP-23, lanturi de incredere AI, // chitante de plata, jurnale de masini, starea Shield, raportul de conformitate PQ), iar aici fiecare artefact e judecat de VERIFICATORUL // PUBLICAT al felului lui, si se scoate un verdict pe domeniu si un rezumat care nu e mai tare decat cel mai slab domeniu cerut. // // DE CE UN MODUL VECIN, NU O COMANDA NOUA IN consola.mjs: consola are un contract publicat (iesirea `aere-control-plane-console` forma 2, // steagurile si codurile ei) pe care se sprijina proba ei, vizorul web si poarta publicarii; judecata organizatiei are alta intrare (un // pachet + contextul celui care se bazeaza pe el), alta iesire, si cheama alte programe. Domeniul Shield e judecat CHIAR de consola.mjs // (evalueaza), deci regulile ei traiesc intr-un singur loc. // // DE CE PRIN ADAPTORUL SUITEI DE CONFORMITATE: tools/aere-conformance/implementare-aere.mjs (publicat in aere-quantum/conformance/) pune // deja cele saptesprezece verificatoare publicate in aceeasi forma de verdict, fara nicio regula a lui; un al doilea adaptor scris aici ar // diverge in tacere de primul. Il chemam ca PROGRAM (--batch), deci "verificatorul nu porneste" se vede ca atare si iese NEMASURAT. // // PRINCIPIILE, si sunt valoarea modulului: // 1. niciun verdict din pachet nu e crezut: se recalculeaza; un verdict scris in pachet care difera de masuratoare e numit ca suspect; // 2. intrarile VERIFICATORULUI (ceasul, emitentii si producatorii de incredere, audienta, nonce-ul, cheia emitentului aleasa, citirile // proprii ale lantului) sunt ale celui care se bazeaza pe pachet, nu ale organizatiei: vin din CONTEXT; una luata din pachet nu mai // poate da VALID (organizatia si-ar alege singura increderea); ceasul nu se ia NICIODATA din pachet; // 3. un domeniu fara artefacte e NOT_PRESENTED, nu verde; un verificator care nu porneste e NOT_MEASURED, nu INVALID si nu VALID; // 4. reteaua (finalitatea pe lant) se citeste numai cu --online; fara el, artefactele care o cer sunt numite si verdictul spune ca nu a masurat. // // node organizatie.mjs --pack pack.json [--context context.json] [--require shield,identity,...] [--at ] [--online] [--json] // iesire 0 = rezumat VALID; 1 = INVALID; 2 = NOT_MEASURED (sau nu s-a putut rula); 3 = PARTIAL. Iesirea e in engleza. import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import crypto from 'node:crypto'; import { spawn } from 'node:child_process'; import { fileURLToPath, pathToFileURL } from 'node:url'; const AICI = path.dirname(fileURLToPath(import.meta.url)); const RAD = path.resolve(AICI, '..', '..'); const sha256 = (s) => '0x' + crypto.createHash('sha256').update(s).digest('hex'); const obiect = (x) => !!x && typeof x === 'object' && !Array.isArray(x); export const VERSIUNE = 'aere-organization-judgement/1 (2026-10-20)'; export const PACK_KIND = 'aere-organization-evidence-pack'; export const CONTEXT_KIND = 'aere-organization-context'; export const OUT_KIND = 'aere-organization-judgement'; /** Domeniile, in ordinea in care se arata. "cloud" nu e un domeniu al lui: ce intorc produsele Cloud se judeca in domeniul artefactului. */ export const DOMENII = [ { id: 'shield', title: 'Security and post-quantum migration (Shield)' }, { id: 'identity', title: 'Identity' }, { id: 'compliance', title: 'Compliance' }, { id: 'ai', title: 'AI and agents' }, { id: 'payments', title: 'Payments and settlement' }, { id: 'devices', title: 'Machines' }, { id: 'audit', title: 'Audit logs' }, { id: 'proofs', title: 'Proofs and data' }, ]; const ID_DOMENII = DOMENII.map((d) => d.id); /** felul artefactului -> domeniul. Felurile suitei de conformitate trebuie sa fie TOATE aici (proba o deriva din reguli.mjs); doua sunt ale * planului de control insusi: starea Shield (judecata de consola.mjs) si raportul de conformitate PQ (raport-conformitate.mjs + plicul lui). */ export const DOMENIUL = { 'shield-console': 'shield', 'identity-presentation': 'identity', 'sdjwt-presentation': 'identity', 'sdjwt-vc-presentation': 'identity', 'compliance-presentation': 'compliance', 'travel-rule-binding': 'compliance', 'travel-rule-message': 'compliance', 'travel-rule-receipt': 'compliance', 'pq-compliance-report': 'compliance', 'ai-trust-bundle': 'ai', 'agent-ledger': 'ai', 'payment-risk': 'payments', 'payment-receipt': 'payments', 'market-timeline': 'payments', 'device-bundle': 'devices', 'device-operation-log': 'devices', 'device-reputation': 'devices', 'audit-log': 'audit', 'audit-inclusion': 'audit', 'audit-consistency': 'audit', envelope: 'proofs', 'dataset-record': 'proofs', 'dataset-answer': 'proofs', 'dataset-consistency': 'proofs', 'dataset-sample': 'proofs', }; export const FELURI_PROPRII = ['shield-console', 'pq-compliance-report']; /** Cine aduce fiecare intrare a verificatorului, pe fel. Cheile sunt EXACT cele pe care le citeste adaptorul suitei (proba le deriva din * sursa lui si din cei 791 de vectori, si cade pe o cheie neclasificata): * artifact numai din pachet (artefactul organizatiei); * stapled semnat de un tert (o lista de stare, o revocare, o politica de firmware): din pachet SI din context, unite (listele * CONTEXTULUI intai, apoi ale pachetului: un verificator judeca numai primele N); o valoare care nu e lista: contextul castiga; * verifier alegerea celui care verifica (increderea, provocarea, asteptarile, citirile lui proprii): din context; daca lipseste * acolo si o aduce pachetul, se foloseste, dar artefactul nu mai poate fi VALID (`self-supplied`); * verifier-only numai din context; din pachet se arunca, numit (lantul de citit, cheile KEM private ale beneficiarului, plafonul de timp); * clock ceasul: contextul pe artefact sau pe fel, apoi --at, apoi `now` global al contextului, apoi ceasul masinii; din pachet NICIODATA. * Revizia B2 (2026-10-21): nu mai exista fel "tot inputul e artefactul". Cronologia de piata si reputatia erau date intregi verificatorului, * deci organizatia isi alegea singura ARBITRUL pietei (constants.arbiter: o rezolvare a disputei de catre ea insasi iesea VALID, iar constantele * aduse de cel care verifica erau ignorate) si FAPTELE din care se calculeaza reputatia (fapte inventate, fara jurnal, iesea VALID). Cheile pe * care le citeste modulul insusi se deriva acum din sursa lui (proba) si se clasifica pe nume, la fel ca toate celelalte; '*': A ramane numai * pentru o cheie pe care modulul NU o citeste pe nume (un membru in plus), ca modulul care cere forma exacta sa o refuze in continuare. */ const A = 'artifact', S = 'stapled', V = 'verifier', O = 'verifier-only', C = 'clock'; const AUDIT = { log: A, head: A, proof: A, newer: A, older: V, signer: V }; const SDJWT = { sdJwt: A, now: C, issuerKey: V, issuerAlg: V, classicalHalf: V, audience: V, nonce: V, expectedIssuer: V, requireKeyBinding: V, requireExp: V, maxAgeS: V, expectedTyp: V, expectedVct: V, statusListJwt: S, statusListKey: V, statusListAlg: V, requireStatus: V }; const TRAVEL = { binding: A, presentation: A, message: A, receipt: A, now: C, registries: V, originatorId: V, nonce: V, me: V, seen: V, beneficiaryId: V, maxAgeS: V, statusLists: S, kem: O }; const DATASET = { record: A, commitment: A, answer: A, proof: A, newer: A, served: A, older: V, k: V, seed: V }; export const INTRARI = { envelope: { envelope: A, raw: A, chain: O }, 'payment-risk': { policy: A, inputs: A }, 'payment-receipt': { receipt: A, policy: A, inputs: A, tx: V, txReceipt: V, chain: O }, // constants: ARBITER si ARBITRATION_TIMEOUT citite de cel care verifica din contractul pietei in care are incredere; chainId: lantul pe care se // citeste plicul executiei (ca `chain` la plic); certified: citirea de sub ancora, pe care adaptorul o face el insusi 'market-timeline': { chainId: O, market: A, jobId: A, constants: V, job: A, events: A, result: A, spec: A, envelope: A, certified: O, '*': A }, 'ai-trust-bundle': { bundle: A, chain: O }, 'audit-log': AUDIT, 'audit-inclusion': AUDIT, 'audit-consistency': AUDIT, 'agent-ledger': { ledger: A, policy: A, policyHash: V, anchors: V, notBefore: V, revocations: S, maxTime: O }, 'identity-presentation': { presentation: A, now: C, audience: V, nonce: V, trustedIssuers: V, maxAgeS: V, request: V, statusLists: S, revocations: S }, 'device-bundle': { bundle: A, softwareAttestation: A, now: C, nonce: V, audience: V, trustedManufacturers: V, anchors: V, maxAgeS: V, statusLists: S, firmwarePolicies: S, statusRecords: S }, 'sdjwt-presentation': SDJWT, 'sdjwt-vc-presentation': SDJWT, 'travel-rule-binding': TRAVEL, 'travel-rule-message': TRAVEL, 'travel-rule-receipt': TRAVEL, 'compliance-presentation': { presentation: A, now: C, policy: V, audience: V, nonce: V, request: V, statusLists: S, revocations: S }, 'device-operation-log': { log: A, now: C, clockSkewS: V, trustedManufacturers: V, readings: V, beacons: V, otherRecords: V, statusLists: S, statusRecords: S }, // facts: ce a DERIVAT verificatorul de operare din jurnal (citirea celui care verifica); params si claimed sunt afirmatia (fereastra ei si cifrele) 'device-reputation': { facts: V, params: A, claimed: A, '*': A }, 'dataset-record': DATASET, 'dataset-answer': DATASET, 'dataset-consistency': DATASET, 'dataset-sample': DATASET, 'shield-console': { bundle: A, plan: A, execution: A }, 'pq-compliance-report': { report: A, envelope: A }, }; /** Regulile planului de control pentru felurile lui proprii (restul regulilor sunt ale verificatoarelor, numite de suita). */ export const REGULI_PROPRII = { 'shield.bundle-form': ['integrity', 'the Shield state carries a sidecar bundle (kind aere-verify-layer-bundle) with at least one entry'], 'shield.audit-chain': ['integrity', 'the audit chain recomputed from its entries is intact (the console does not read chainOk from the bundle)'], 'shield.envelope-integrity': ['integrity', 'every AIP-23 envelope in the bundle: sha256 of its statement is its statementHash'], 'shield.self-report': ['integrity', 'what the bundle declares about its own chain (chainOk) is what the measurement gives'], 'shield.execution-chain': ['integrity', 'the migration execution chain (execution.json) recomputed is intact'], 'shield.plan-form': ['integrity', 'the migration plan has the planner\'s list of actions'], 'pqreport.binding': ['integrity', 'the compliance report is the one bound to the AIP-23 compliance envelope (canonical sha256 == evidenceHash)'], // revizia B2, 2026-10-21: plicul nu e semnat, deci legarea singura lasa un rezumat rescris peste constatarile lui; si o executie rupta de planul ei 'pqreport.derivation': ['integrity', 'the report is what the report generator gives for its own findings, date, risk and CNSA choice, and the envelope\'s statement is the one it writes'], 'shield.execution-plan': ['integrity', 'the execution presented with a plan is the executor\'s run of that plan: one record per automatic action, in the plan\'s order, between its start and end records, and the end record counts them'], }; /** Felurile pe care adaptorul le judeca CITIND LANTUL cu --online (plicul AIP-23 al lor); proba le deriva din sursa adaptorului. Ceilalti (o * masina, un jurnal de operare) isi judeca notarizarea pe CITIRILE celui care verifica (anchors, readings), aduse in context: --online nu le ajuta. */ export const CITESC_LANTUL = ['envelope', 'payment-receipt', 'market-timeline', 'ai-trust-bundle', 'pq-compliance-report']; // ---- unde stau celelalte piese: langa planul de control intr-o copie publica (../conformance), sau in depozitul de dezvoltare (tools/) function dosarulSuitei() { const c = [path.join(AICI, '..', 'conformance'), path.join(RAD, 'tools', 'aere-conformance')]; return c.find((d) => fs.existsSync(path.join(d, 'implementare-aere.mjs'))) || c[c.length - 1]; } export const caleaAdaptorului = () => (process.env.AERE_ORG_ADAPTOR ? path.resolve(process.env.AERE_ORG_ADAPTOR) : path.join(dosarulSuitei(), 'implementare-aere.mjs')); /** Forma verdictului (verdict.mjs al suitei) si tabla verificatoarelor (reguli.mjs), incarcate o data; o lipsa intoarce motivul, nu arunca. */ let SUITA = null; async function suita() { if (SUITA) return SUITA; const d = dosarulSuitei(); try { const V = await import(pathToFileURL(path.join(d, 'verdict.mjs')).href); let R = null; try { R = await import(pathToFileURL(path.join(d, 'reguli.mjs')).href); } catch { R = null; } SUITA = { ok: true, V, R }; } catch (e) { SUITA = { ok: false, motiv: `the verdict form of the conformance suite is not at ${d} (${String(e.message).split('\n')[0]})` }; } return SUITA; } function verificatorul(fel, R) { if (fel === 'shield-console') return { id: 'control-plane-console', module: 'control-plane/consola.mjs' }; if (fel === 'pq-compliance-report') return { id: 'compliance-report', module: 'control-plane/raport-conformitate.mjs, and verify-proof.mjs (AIP-23 reference verifier) for its envelope' }; if (!R || !R.VERIFICATOARE) return { id: '?', module: '?' }; const [id, v] = Object.entries(R.VERIFICATOARE).find(([, x]) => x.artifacts.includes(fel)) || []; if (!v) return { id: '?', module: '?' }; let module = v.sursa === 'verify-proof' ? 'verify-proof.mjs (AIP-23 reference verifier)' : '?'; if (R.MODULE && R.MODULE[v.sursa]) module = fileURLToPath(R.MODULE[v.sursa].url).split(/[\\/]/).slice(-2).join('/'); return { id, module, through: 'conformance/implementare-aere.mjs' }; } // ---- adaptorul suitei, chemat ca program: un verdict pe rand, in ordine; orice altceva e "nu a pornit" pentru TOATE artefactele lotului function ruleaza(cmd, args, timeoutMs, env) { return new Promise((gata) => { let out = '', err = '', c, expirat = false; try { c = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'], env }); } catch (e) { gata({ code: null, out, err: String(e.message) }); return; } const t = setTimeout(() => { expirat = true; c.kill(); }, timeoutMs); 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, expirat }); }); }); } const NIVELURI = ['integrity', 'authenticity', 'anchoring', 'finality']; /** * Un verdict al suitei e crezut ca VERDICT numai daca are forma ei si verdictul lui e CHIAR cel pe care regula suitei (verdict.mjs) il da pentru * nivelurile lui: INVALID <=> o regula refuzata (si `failed` = regulile nivelurilor); VALID numai cu integritatea PASSED si fara o afirmatie * nemasurata (o cauza din `cauzePartiale`). Revizia B2 (2026-10-21): pana azi un "VALID" cu integritatea nemasurata trecea drept VALID. */ export function formaVerdictului(j, cauzePartiale) { if (!(obiect(j) && ['VALID', 'INVALID', 'PARTIAL'].includes(j.verdict) && Array.isArray(j.failed) && Array.isArray(j.levels) && j.levels.length === 4)) return false; if (!j.levels.every((l, i) => obiect(l) && l.level === NIVELURI[i] && ['PASSED', 'FAILED', 'NOT_MEASURED'].includes(l.state) && Array.isArray(l.rules) && l.rules.every((x) => typeof x === 'string') && (l.state === 'FAILED') === (l.rules.length > 0) && (l.state !== 'NOT_MEASURED' || typeof l.cause === 'string'))) return false; if (!Array.isArray(cauzePartiale)) return false; const failed = [...new Set(j.levels.flatMap((l) => l.rules))].sort(); const partial = j.levels.some((l) => l.state === 'NOT_MEASURED' && cauzePartiale.includes(l.cause)); const v = failed.length ? 'INVALID' : partial || j.levels[0].state !== 'PASSED' ? 'PARTIAL' : 'VALID'; return j.verdict === v && JSON.stringify([...j.failed].sort()) === JSON.stringify(failed); } /** Un lot catre adaptor: { linii, motiv } (motiv = de ce lotul nu a dat cate un rand pe artefact) */ async function unLot(items, { adaptor, online, timeoutS }) { const T = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-org-')); try { const lista = items.map((x, k) => { const f = path.join(T, k + '.json'); fs.writeFileSync(f, JSON.stringify({ artifact: x.artifact, input: x.input })); return { file: f, online: !!online }; }); const lf = path.join(T, 'list.json'); fs.writeFileSync(lf, JSON.stringify(lista)); const r = await ruleaza(process.execPath, [adaptor, '--batch', lf], timeoutS * 1000, process.env); const linii = r.out.split('\n').filter((l) => l.trim()); const coada = (r.err || '').trim().split('\n').slice(-1)[0].slice(0, 200); const motiv = linii.length === items.length ? null : `the adapter gave ${linii.length} verdict(s) for ${items.length} artifact(s) (${r.expirat ? `it did not finish in ${timeoutS} s` : 'exit ' + r.code})${coada ? ': ' + coada : ''}`; return { linii, motiv }; } finally { fs.rmSync(T, { recursive: true, force: true }); } } /** Randurile unui lot, citite in ORDINEA artefactelor: un verdict dat pentru alt fel decat cel cerut in locul lui inseamna ca ordinea raspunsurilor * nu se poate crede, deci niciun raspuns al lotului (revizia B2: un adaptor care raspundea invers dadea artefactului stricat verdictul altuia). */ function citesteLotul(linii, items, cauzePartiale) { const js = linii.map((l) => { try { return JSON.parse(l); } catch { return undefined; } }); const k = js.findIndex((j, i) => obiect(j) && typeof j.verdict === 'string' && j.artifact !== items[i].artifact); if (k >= 0) return items.map(() => ({ error: `the adapter answered for ${JSON.stringify(js[k].artifact)} in the place of ${items[k].artifact} (position ${k}): the order of its answers cannot be trusted, so none is` })); return js.map((j) => { if (j === undefined) return { error: 'the adapter gave a line that is not JSON' }; if (formaVerdictului(j, cauzePartiale)) return j; return { error: obiect(j) && typeof j.error === 'string' ? 'the verifier gave no verdict: ' + j.error : 'the adapter gave something that is not a verdict of the suite\'s form (or a verdict its own levels do not give)' }; }); } async function adaptorul(items, { adaptor, online, timeoutS, cauzePartiale }) { if (!items.length) return []; if (!fs.existsSync(adaptor)) return items.map(() => ({ error: `the conformance adapter is not at ${adaptor}: no verifier was run` })); const o = { adaptor, online, timeoutS }; const lot = await unLot(items, o); if (!lot.motiv) return citesteLotul(lot.linii, items, cauzePartiale); if (items.length === 1) return [{ error: lot.motiv }]; // un lot care nu s-a terminat (un artefact care opreste sau tine adaptorul pe loc) nu are voie sa ia cu el verdictele celorlalte: fiecare artefact // e judecat atunci SINGUR, cu acelasi plafon de timp (revizia B2: un esantion lent adus de pachet facea NOT_MEASURED tot lotul) const rez = new Array(items.length); let urm = 0; await Promise.all(Array.from({ length: Math.min(2, items.length) }, async () => { while (urm < items.length) { const k = urm++; const u = await unLot([items[k]], o); const unu = u.motiv ? { error: u.motiv } : citesteLotul(u.linii, [items[k]], cauzePartiale)[0]; rez[k] = unu.error ? { error: `the batch of ${items.length} did not complete (${lot.motiv}); judged alone: ${unu.error}` } : unu; } })); return rez; } // ---- intrarile unui artefact: ce e al organizatiei, ce e semnat de un tert, ce e al celui care verifica, ceasul const CEAS_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/; export function rezolvaIntrarile(fel, intrare, ctx, ceas) { const cls = INTRARI[fel]; const note = { fromContext: [], selfSupplied: [], dropped: [], ignored: [], contextIgnored: [], stapled: {}, now: null }; if (!obiect(cls)) throw new Error(`no classification of the inputs of ${fel}`); // fara prototip: o cheie `__proto__` din pachet ramane o cheie (si ajunge la verificator ca atare), nu schimba obiectul const input = Object.create(null); // cine castiga o intrare a verificatorului adusa de amandoi: contextul (o singura hotarare, citita de ambele bucle) const castigaContextul = (k) => Object.hasOwn(ctx, k); // '*': la felurile pe care modulul le citeste INTREGI (si le cere exact forma), o cheie necunoscuta e a artefactului si ajunge la el; cheile pe // care le citeste sunt toate numite, deci '*' nu poate acoperi una a celui care verifica (proba o cere) const clasa = (k) => (Object.hasOwn(cls, k) ? cls[k] : Object.hasOwn(cls, '*') ? cls['*'] : null); for (const [k, v] of Object.entries(intrare)) { const c = clasa(k); if (c === null) note.ignored.push(k); else if (c === A) input[k] = v; else if (c === S) { input[k] = v; note.stapled[k] = 'pack'; } else if (c === V) { if (!castigaContextul(k)) { input[k] = v; note.selfSupplied.push(k); } } else note.dropped.push(k); // verifier-only si clock: o valoare din pachet nu se foloseste niciodata } for (const [k, v] of Object.entries(ctx)) { const c = clasa(k); if (c === V && !castigaContextul(k)) continue; if (c === V || c === O || c === C) { input[k] = v; note.fromContext.push(k); } else if (c === S) { // listele CELUI CARE VERIFICA intai: verificatoarele judeca numai primele N elemente ale unei liste (64 de liste de rechemare la o masina), // iar cu listele pachetului in fata, 64 de liste curate aduse de organizatie impingeau rechemarea adusa de cel care verifica afara din // fereastra si INVALID devenea PARTIAL (revizia B2, 2026-10-21, masurat pe vectorul sold-once.manufacture-revoked) if (Array.isArray(input[k]) && Array.isArray(v)) { input[k] = [...v, ...input[k]]; note.stapled[k] = 'context+pack'; } else { input[k] = v; note.stapled[k] = 'context'; } } else note.contextIgnored.push(k); // un context nu poate scrie artefactul organizatiei } if (cls.now === C) { if (Object.hasOwn(input, 'now') && note.fromContext.includes('now')) note.now = { value: input.now, source: 'context' }; else { input.now = ceas.now; note.now = { value: ceas.now, source: ceas.source }; } } return { input, note }; } // ---- felurile proprii: starea Shield (consola.mjs) si raportul de conformitate PQ /** Executia (execution.json al executorului) e rularea ACESTUI plan? null daca da, altfel motivul. Executorul scrie un rand `start` cu numarul de * actiuni ale planului, cate un rand pentru fiecare actiune `auto-aere`, in ordinea planului (si cand nu are consimtamant: SKIPPED), si un rand `end` * care le numara (executa-migrare.mjs, `executa`). */ export function legaturaExecutiei(plan, execution) { const recs = (Array.isArray(execution) ? execution : execution.records).map((e) => (obiect(e) ? e.record : null)); const start = recs[0], end = recs[recs.length - 1]; if (!(obiect(start) && start.type === 'start')) return 'the execution does not begin with the executor\'s start record'; if (recs.length < 2 || !(obiect(end) && end.type === 'end')) return 'the execution does not end with the executor\'s end record (cut short, or not written by the executor)'; const actiuni = plan.actions, auto = actiuni.filter((a) => obiect(a) && a.method === 'auto-aere').map((a) => a.ref); if (start.actionsInPlan !== actiuni.length) return `the execution says its plan had ${start.actionsInPlan} action(s); the plan presented with it has ${actiuni.length}`; const facute = recs.slice(1, -1); if (!facute.every((r) => obiect(r) && typeof r.ref === 'string')) return 'a record between start and end is not an action record'; const refs = facute.map((r) => r.ref); if (JSON.stringify(refs) !== JSON.stringify(auto)) { const lipsa = auto.filter((x) => !refs.includes(x)), straine = refs.filter((x) => !auto.includes(x)); return 'the execution is not the run of the plan presented with it: ' + [lipsa.length ? `no record for ${lipsa.slice(0, 3).join(', ')}${lipsa.length > 3 ? ` and ${lipsa.length - 3} more` : ''}` : '', straine.length ? `records for actions the plan does not have automatic: ${straine.slice(0, 3).join(', ')}${straine.length > 3 ? ` and ${straine.length - 3} more` : ''}` : '', !lipsa.length && !straine.length ? 'the records are not one per automatic action in the plan\'s order' : ''].filter(Boolean).join('; '); } const s = end.summary, n = (v) => facute.filter((r) => r.verdict === v).length; if (!(obiect(s) && s.total === facute.length && s.ok === n('OK') && s.failed === n('FAILED') && s.dryRun === n('DRY-RUN') && s.skipped === n('SKIPPED-no-consent') && s.notExecutable === actiuni.length - auto.length)) return 'the end record of the execution does not count its own records'; return null; } async function judecaShield(input, V) { const acc = V.acumulator(); const b = input.bundle; if (!(obiect(b) && b.kind === 'aere-verify-layer-bundle' && Array.isArray(b.entries) && b.entries.length > 0)) { V.pica(acc, 'integrity', 'shield.bundle-form', 'not a sidecar bundle with entries (kind aere-verify-layer-bundle, entries[] not empty)'); for (const l of ['authenticity', 'anchoring', 'finality']) V.nemasurat(acc, l, 'not-reached', 'the bundle could not be read'); return { v: V.verdictUnic('shield-console', acc), details: null }; } const { evalueaza } = await import(pathToFileURL(path.join(AICI, 'consola.mjs')).href); const st = await evalueaza({ bundle: b, plan: input.plan === undefined ? null : input.plan, execution: input.execution === undefined ? null : input.execution }); if (!st.auditChain.ok) V.pica(acc, 'integrity', 'shield.audit-chain', `the audit chain is broken at seq ${st.auditChain.brokenAtSeq}: ${st.auditChain.reason}`); if (st.envelopes.bad.length) V.pica(acc, 'integrity', 'shield.envelope-integrity', st.envelopes.bad.map((x) => `seq ${x.seq} (${x.kind}): ${x.reason}`).join('; ')); if (st.selfReportSuspect) V.pica(acc, 'integrity', 'shield.self-report', st.selfReportSuspect); if (st.execution && !st.execution.chainOk) V.pica(acc, 'integrity', 'shield.execution-chain', `the execution chain is broken at seq ${st.execution.brokenAtSeq}: ${st.execution.reason}`); if (st.migration && st.migration.total === null) V.pica(acc, 'integrity', 'shield.plan-form', st.migration.error); // executia si planul prezentate impreuna trebuie sa fie aceeasi rulare (revizia B2: o executie din care lipsea o actiune, sau executia altui plan, // iesea VALID langa planul acesta); numai cand amandoua se pot citi, altfel au deja regula lor if (st.execution && st.execution.chainOk && st.migration && st.migration.total !== null) { const m = legaturaExecutiei(input.plan, input.execution); if (m) V.pica(acc, 'integrity', 'shield.execution-plan', m); } V.trece(acc, 'integrity', `the console recomputed the audit chain (${st.auditChain.count} entries, head ${st.auditChain.head || '-'}) and ${st.envelopes.total} envelope digest(s)` + (st.migration && st.migration.total !== null ? `; plan: ${st.migration.total} action(s)` : '') + (st.execution ? `; execution: ${st.execution.actions} action(s)` : '')); // ce poarta burafu si consola NU judeca: o semnatura, o notarizare. Purtate, sunt o afirmatie nemasurata aici (PARTIAL) const semnate = b.entries.filter((e) => obiect(e) && obiect(e.proof) && e.proof.signature).map((e) => e.seq); const notarizate = b.entries.filter((e) => obiect(e) && obiect(e.proof) && e.proof.notarization).map((e) => e.seq); if (semnate.length) V.nemasurat(acc, 'authenticity', 'claim-not-checked', `entries ${semnate.join(',')} carry a signature, which the console does not judge: present each as an \`envelope\` artifact`); else V.nemasurat(acc, 'authenticity', 'not-judged', 'no entry carries a signature; a signed head is judged as an `audit-log` artifact with its head'); const fin = notarizate.length ? ['claim-not-checked', `entries ${notarizate.join(',')} claim a notarization, which the console does not read: present each as an \`envelope\` artifact and judge with --online`] : ['not-judged', 'the head is anchored by notarizing its envelope, judged as the artifact `envelope`']; for (const l of ['anchoring', 'finality']) V.nemasurat(acc, l, fin[0], fin[1]); return { v: V.verdictUnic('shield-console', acc), details: { console: { auditChain: { ok: st.auditChain.ok, count: st.auditChain.count, head: st.auditChain.head }, envelopes: { total: st.envelopes.total, intact: st.envelopes.intact }, migration: st.migration, execution: st.execution } }, }; } /** Un verdict al suitei, pus inapoi intr-un acumulator (ca sa i se adauge o regula si sa se refaca verdictul in aceeasi forma). */ function inAcumulator(V, acc, v) { for (const l of v.levels) { if (l.state === 'FAILED') for (const r of l.rules) V.pica(acc, l.level, r, l.reason); else if (l.state === 'NOT_MEASURED') V.nemasurat(acc, l.level, l.cause, l.reason); else V.trece(acc, l.level, l.reason); } } // ---- judecata const RANG = { INVALID: 0, NOT_MEASURED: 1, NOT_PRESENTED: 1, PARTIAL: 2, VALID: 3 }; export const celMaiSlab = (vs) => vs.reduce((a, b) => (RANG[b] < RANG[a] ? b : a)); const ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/; const nemasuratArtefact = (baza, motiv) => ({ ...baza, verdict: 'NOT_MEASURED', verifierVerdict: null, failed: [], levels: null, error: motiv }); function citesteContextul(context) { if (context == null) return { ok: true, global: {}, byKind: {}, byArtifact: {}, now: null, require: null }; if (!obiect(context) || context.kind !== CONTEXT_KIND) return { ok: false, motiv: `the context is not an ${CONTEXT_KIND}` }; for (const k of ['byKind', 'byArtifact']) if (context[k] !== undefined && !(obiect(context[k]) && Object.values(context[k]).every(obiect))) return { ok: false, motiv: `context.${k} must map names to objects` }; if (context.now !== undefined && !(typeof context.now === 'string' && CEAS_RE.test(context.now))) return { ok: false, motiv: 'context.now must be an RFC 3339 time' }; if (context.require !== undefined && !(Array.isArray(context.require) && context.require.length && context.require.every((d) => ID_DOMENII.includes(d)))) return { ok: false, motiv: `context.require must list domains among ${ID_DOMENII.join(', ')}` }; return { ok: true, byKind: context.byKind || {}, byArtifact: context.byArtifact || {}, now: context.now || null, require: context.require || null }; } /** * Judeca un pachet de dovezi al unei organizatii. * @param {object} pack pachetul (aere-organization-evidence-pack) * @param {object} [o.context] contextul celui care se bazeaza pe pachet (aere-organization-context): ceas, incredere, provocari, cerinte * @param {string[]} [o.require] domeniile cerute (peste context.require); implicit TOATE * @param {string} [o.at] ceasul celui care verifica (RFC 3339), daca nu il da contextul; implicit ceasul masinii * @param {boolean} [o.online] citeste lantul pentru finalitate (prin adaptor si verificatorul de referinta); implicit NU */ export async function judecaPachetul(pack, { context = null, require = null, at = null, online = false, adaptor = caleaAdaptorului(), timeoutS = null, packSha256 = null } = {}) { if (require !== null && !(Array.isArray(require) && require.length && require.every((d) => ID_DOMENII.includes(d)))) throw new Error(`--require must list domains among ${ID_DOMENII.join(', ')}`); if (at !== null && !(typeof at === 'string' && CEAS_RE.test(at))) throw new Error('--at must be an RFC 3339 time'); const ctx = citesteContextul(context); const ceas = at ? { now: at, source: '--at' } : ctx.ok && ctx.now ? { now: ctx.now, source: 'context' } : { now: new Date().toISOString(), source: 'this machine' }; const cerute = require ? { domains: require, source: '--require' } : ctx.ok && ctx.require ? { domains: ctx.require, source: 'context' } : { domains: ID_DOMENII, source: 'default: every domain' }; const iesire = { v: 1, kind: OUT_KIND, version: VERSIUNE, organization: obiect(pack) && typeof pack.organization === 'string' ? pack.organization : null, pack: { sha256: packSha256, createdAt: obiect(pack) && typeof pack.createdAt === 'string' ? pack.createdAt : null, artifacts: obiect(pack) && Array.isArray(pack.artifacts) ? pack.artifacts.length : null }, clock: ceas, network: { online: !!online, needed: [], readings: [], unmeasured: [], finality: [] }, required: cerute, summary: null, domains: [], artifacts: [], unjudged: [], selfReport: { ignored: [], suspect: [] }, }; const opreste = (motiv) => { iesire.domains = DOMENII.map((d) => ({ domain: d.id, title: d.title, verdict: 'NOT_PRESENTED', artifacts: [], failed: [], reason: 'nothing was judged' })); iesire.summary = { verdict: 'NOT_MEASURED', weakest: [], notPresented: [], outsideRequired: {}, reason: motiv }; return iesire; }; if (!ctx.ok) return opreste('the context cannot be read: ' + ctx.motiv); if (!(obiect(pack) && pack.kind === PACK_KIND && Array.isArray(pack.artifacts))) return opreste(`the pack cannot be read: not an ${PACK_KIND} with an artifacts list`); const S0 = await suita(); if (!S0.ok) return opreste(S0.motiv); const { V, R } = S0; // 1) forma fiecarui artefact, domeniul lui, intrarile rezolvate const rezultate = []; const deJudecat = []; const vazute = new Set(); const pePozitie = new Map(); pack.artifacts.forEach((a, k) => { const fel = obiect(a) && typeof a.artifact === 'string' ? a.artifact : null; const id = obiect(a) && typeof a.id === 'string' && ID_RE.test(a.id) ? a.id : null; const domeniu = fel && Object.hasOwn(DOMENIUL, fel) ? DOMENIUL[fel] : null; if (!domeniu) { iesire.unjudged.push({ id: id || `#${k}`, artifact: fel, reason: fel ? `no verifier here judges the kind ${fel}` : 'the artifact names no kind' }); return; } const baza = { id: id || `#${k}`, artifact: fel, domain: domeniu, verifier: verificatorul(fel, R), inputSha256: sha256(JSON.stringify(obiect(a) ? (a.input ?? null) : null)) }; const nm = (motiv) => { const r = nemasuratArtefact(baza, motiv); rezultate.push(r); pePozitie.set(k, r); }; if (!id) { nm('the artifact has no id (letters, digits, . _ : / -, at most 128)'); return; } if (vazute.has(id)) { nm(`the id ${id} appears more than once in the pack: only its first artifact is judged`); return; } vazute.add(id); if (!obiect(a.input)) { nm('input is not an object'); return; } const cx = { ...(obiect(ctx.byKind[fel]) ? ctx.byKind[fel] : {}), ...(obiect(ctx.byArtifact[id]) ? ctx.byArtifact[id] : {}) }; const { input, note } = rezolvaIntrarile(fel, a.input, cx, ceas); const r = { ...baza, inputs: note }; rezultate.push(r); pePozitie.set(k, r); deJudecat.push({ r, input }); }); // 2) un singur lot catre adaptor (felurile suitei si plicul raportului PQ), apoi felurile proprii const lot = []; for (const x of deJudecat) { if (x.r.artifact === 'shield-console') continue; if (x.r.artifact === 'pq-compliance-report') { x.plic = lot.length; lot.push({ artifact: 'envelope', input: { envelope: x.input.envelope === undefined ? null : x.input.envelope } }); continue; } x.lot = lot.length; lot.push({ artifact: x.r.artifact, input: x.input }); } const verdicte = await adaptorul(lot, { adaptor, online, timeoutS: timeoutS || (online ? 1800 : 600), cauzePartiale: V.CAUZE_PARTIALE }); for (const x of deJudecat) { let v = null, eroare = null; try { if (x.r.artifact === 'shield-console') { const s = await judecaShield(x.input, V); v = s.v; if (s.details) x.r.details = s.details; } else if (x.r.artifact === 'pq-compliance-report') { const { verifyReport, verificaDerivarea } = await import(pathToFileURL(path.join(AICI, 'raport-conformitate.mjs')).href); const leg = obiect(x.input.report) ? verifyReport(x.input.report, x.input.envelope) : { ok: false, reason: 'the artifact carries no report' }; const pv = verdicte[x.plic]; if (pv.error && leg.ok) eroare = 'the envelope of the report was not judged: ' + pv.error; else { const acc = V.acumulator(); if (pv.error) for (const l of NIVELURI) V.nemasurat(acc, l, 'not-reached', 'the envelope of the report was not judged: ' + pv.error); else inAcumulator(V, acc, pv); if (!leg.ok) V.pica(acc, 'integrity', 'pqreport.binding', leg.reason); else { V.trece(acc, 'integrity', 'the report is the one bound to the envelope (canonical sha256 == evidenceHash)'); // legat nu inseamna facut de generator: plicul nu e semnat, deci un rezumat rescris si legat din nou trecea (revizia B2, 2026-10-21) const dv = verificaDerivarea(x.input.report, x.input.envelope); if (dv.ok === false) V.pica(acc, 'integrity', 'pqreport.derivation', dv.reason); else if (dv.ok === null) V.nemasurat(acc, 'integrity', 'unmeasurable', dv.reason); else V.trece(acc, 'integrity', 'the report is what the report generator gives for its own findings, and the envelope\'s statement is the one it writes'); } v = V.verdictUnic('pq-compliance-report', acc); if (obiect(x.input.report)) x.r.details = { report: { organization: x.input.report.organization ?? null, date: x.input.report.date ?? null, finding: obiect(x.input.report.summary) ? x.input.report.summary.verdict ?? null : null } }; } } else { const a = verdicte[x.lot]; if (a.error) eroare = a.error; else v = a; } } catch (e) { eroare = 'the verifier could not run: ' + String(e.message).split('\n')[0].slice(0, 200); } if (eroare) { Object.assign(x.r, { verdict: 'NOT_MEASURED', verifierVerdict: null, failed: [], levels: null, error: eroare }); continue; } Object.assign(x.r, { verifierVerdict: v.verdict, verdict: v.verdict, failed: v.failed, levels: v.levels }); // intrarile verificatorului aduse de organizatie: verificatorul a spus VALID, dar pe increderea aleasa de ea; nu e VALID aici const proprii = x.r.inputs.selfSupplied; if (v.verdict === 'VALID' && proprii.length) { x.r.verdict = 'PARTIAL'; x.r.capped = `the verifier's own inputs (${proprii.join(', ')}) came from the organization's pack, not from the relying party's context: the verifier said VALID on trust the organization chose itself`; } } // 3) reteaua: ce a ramas nemasurat fiindca nu s-a citit lantul (consola nu citeste lantul nici online). Revizia B2 (2026-10-21), trei lucruri: // - raportul PQ isi are nivelurile de la plicul lui, citit de adaptor ca orice plic: pana azi lipsea din `needed`; // - o masina sau un jurnal de operare isi judeca notarizarea pe CITIRILE celui care verifica (context: anchors, readings), nu pe lant: --online // nu le ajuta, deci stau in `readings`, nu in `needed` (pana azi rezumatul spunea "(no --online)" si cu --online dat); // - finalitatea citita isi numeste lantul, si un testnet e spus ca testnet (adaptorul il scrie in motivul nivelului; o forma necunoscuta: chain null) for (const r of rezultate) { if (!r.levels || r.artifact === 'shield-console') continue; const lantul = r.levels.filter((l) => l.level === 'anchoring' || l.level === 'finality' || r.artifact === 'ai-trust-bundle'); if (lantul.some((l) => l.state === 'NOT_MEASURED' && l.cause === 'claim-not-checked')) (CITESC_LANTUL.includes(r.artifact) ? iesire.network.needed : iesire.network.readings).push(r.id); if (online && r.levels.some((l) => (l.level === 'anchoring' || l.level === 'finality') && l.state === 'NOT_MEASURED' && l.cause === 'unmeasurable')) iesire.network.unmeasured.push(r.id); const fin = r.levels.find((l) => l.level === 'finality'); if (fin && fin.state === 'PASSED') { const m = /\(chain (\d+)(, a testnet)?; the finality is that chain's\)/.exec(String(fin.reason || '')); iesire.network.finality.push({ id: r.id, chain: m ? Number(m[1]) : null, testnet: m ? !!m[2] : null }); } } // 4) domeniile: fiecare cat cel mai slab artefact al lui; fara artefacte, NOT_PRESENTED iesire.artifacts = rezultate; iesire.domains = DOMENII.map((d) => { const ale = rezultate.filter((r) => r.domain === d.id); if (!ale.length) return { domain: d.id, title: d.title, verdict: 'NOT_PRESENTED', artifacts: [], failed: [], reason: 'no artifact of this domain in the pack' }; const verdict = celMaiSlab(ale.map((r) => r.verdict)); const slabe = ale.filter((r) => r.verdict === verdict).map((r) => r.id); return { domain: d.id, title: d.title, verdict, artifacts: ale.map((r) => r.id), failed: [...new Set(ale.flatMap((r) => r.failed))].sort(), reason: `${ale.length} artifact(s); the weakest (${verdict}): ${slabe.join(', ')}` }; }); // 5) rezumatul: nu e mai tare decat cel mai slab domeniu CERUT; un domeniu cerut si neprezentat e NOT_MEASURED pentru rezumat const cer = iesire.domains.filter((d) => cerute.domains.includes(d.domain)); const vs = cer.map((d) => (d.verdict === 'NOT_PRESENTED' ? 'NOT_MEASURED' : d.verdict)); let verdict = celMaiSlab(vs); const weakest = cer.filter((d, i) => RANG[vs[i]] === RANG[verdict]).map((d) => d.domain); const notPresented = cer.filter((d) => d.verdict === 'NOT_PRESENTED').map((d) => d.domain); const motive = [`${cer.length} required domain(s) (${cerute.source}); weakest: ${weakest.join(', ')}`]; if (notPresented.length) motive.push(`not presented: ${notPresented.join(', ')}`); if (iesire.unjudged.length && verdict === 'VALID') { verdict = 'PARTIAL'; weakest.length = 0; motive.push(`${iesire.unjudged.length} artifact(s) of the pack no verifier here judges: the pack claims more than was measured`); } else if (iesire.unjudged.length) motive.push(`${iesire.unjudged.length} artifact(s) not judged (unknown kind)`); const outsideRequired = Object.fromEntries(iesire.domains.filter((d) => !cerute.domains.includes(d.domain) && d.verdict !== 'NOT_PRESENTED').map((d) => [d.domain, d.verdict])); if (Object.values(outsideRequired).includes('INVALID')) motive.push(`INVALID outside the required domains: ${Object.entries(outsideRequired).filter(([, x]) => x === 'INVALID').map(([d]) => d).join(', ')}`); const N = iesire.network; if (N.needed.length) motive.push(`${N.needed.length} artifact(s) claim a notarization that was not read${online ? '' : ' (no --online)'}`); if (N.readings.length) motive.push(`${N.readings.length} artifact(s) claim a notarization that only the relying party's own readings check (the context's anchors or readings; --online does not read them)`); if (N.unmeasured.length) motive.push(`the chain could not be read for ${N.unmeasured.length} artifact(s)`); const pePlasa = N.finality.filter((f) => f.testnet === true), neNumit = N.finality.filter((f) => f.chain === null); if (pePlasa.length) motive.push(`${pePlasa.length} artifact(s) are final only on a testnet (chain ${[...new Set(pePlasa.map((f) => f.chain))].join(', ')})`); if (neNumit.length) motive.push(`${neNumit.length} artifact(s) are final on a chain the verifier did not name`); iesire.summary = { verdict, weakest, notPresented, outsideRequired, reason: motive.join('; ') }; // 6) verdictele scrise de pachet despre el insusi: ignorate; cele care difera de masuratoare sunt numite const spune = (unde, x, masurat) => { if (typeof x !== 'string') return; iesire.selfReport.ignored.push({ where: unde, says: x }); if (x !== masurat) iesire.selfReport.suspect.push({ where: unde, says: x, measured: masurat }); }; spune('pack.verdict', pack.verdict, verdict); if (obiect(pack.summary)) spune('pack.summary.verdict', pack.summary.verdict, verdict); pack.artifacts.forEach((a, k) => { const r = pePozitie.get(k); if (!obiect(a) || !r) return; spune(`artifact ${r.id}: verdict`, a.verdict, r.verdict); if (obiect(a.expected)) spune(`artifact ${r.id}: expected.verdict`, a.expected.verdict, r.verdict); }); return iesire; } export const codulIesirii = (v) => ({ VALID: 0, INVALID: 1, NOT_MEASURED: 2, PARTIAL: 3 }[v] ?? 2); function text(o) { const L = []; L.push(`ORGANIZATION JUDGEMENT${o.organization ? ' - ' + o.organization : ''}: ${o.summary.verdict}`); L.push(` ${o.summary.reason}`); L.push(` pack ${o.pack.sha256 || '-'}, ${o.pack.artifacts ?? '?'} artifact(s); clock ${o.clock.now} (${o.clock.source}); network: ${o.network.online ? 'read (--online)' : 'not read'}` + (o.network.needed.length ? `; needed by ${o.network.needed.join(', ')}` : '') + (o.network.readings.length ? `; the relying party's readings needed by ${o.network.readings.join(', ')}` : '') + (o.network.unmeasured.length ? `; unmeasured for ${o.network.unmeasured.join(', ')}` : '') + (o.network.finality.length ? `; final on ${o.network.finality.map((f) => `${f.chain === null ? 'an unnamed chain' : 'chain ' + f.chain + (f.testnet ? ' (a testnet)' : '')} (${f.id})`).join(', ')}` : '')); for (const d of o.domains) L.push(` ${d.domain.padEnd(11)} ${(d.verdict === 'NOT_PRESENTED' ? 'NOT PRESENTED' : d.verdict).padEnd(13)} ${d.artifacts.length ? d.artifacts.length + ' artifact(s)' + (d.failed.length ? '; refused by ' + d.failed.join(', ') : '') : ''}${o.required.domains.includes(d.domain) ? '' : ' (not required)'}`); const rele = o.artifacts.filter((r) => r.verdict !== 'VALID'); if (rele.length) L.push(' artifacts not VALID:'); for (const r of rele) { const nm = (r.levels || []).filter((l) => l.state !== 'PASSED').map((l) => `${l.level} ${l.state}${l.cause ? ' (' + l.cause + ')' : ''}`).join(', '); L.push(` ${r.id} (${r.artifact}): ${r.verdict}` + (r.failed.length ? ` - refused by ${r.failed.join(', ')}` : '') + (r.error ? ` - ${r.error}` : '') + (r.capped ? ` - ${r.capped}` : '') + (nm && !r.failed.length && !r.capped ? ` - ${nm}` : '')); } for (const r of o.artifacts) { const n = r.inputs; if (!n) continue; const parti = []; if (n.dropped.length) parti.push(`dropped from the pack: ${n.dropped.join(', ')}`); if (n.ignored.length) parti.push(`not an input of the verifier: ${n.ignored.join(', ')}`); if (n.contextIgnored.length) parti.push(`context keys ignored: ${n.contextIgnored.join(', ')}`); if (parti.length) L.push(` ${r.id}: ${parti.join('; ')}`); } for (const u of o.unjudged) L.push(` not judged: ${u.id} (${u.artifact || '?'}): ${u.reason}`); for (const s of o.selfReport.suspect) L.push(` WARNING: the pack says ${s.where} = ${s.says}; measured ${s.measured} (ignored)`); return L.join('\n'); } async function main() { const a = process.argv.slice(2); const get = (f) => { const i = a.indexOf(f); return i >= 0 ? a[i + 1] : null; }; const pf = get('--pack'); if (!pf) { console.error('usage: node organizatie.mjs --pack pack.json [--context context.json] [--require shield,identity,...] [--at ] [--online] [--json]'); return 2; } let brut; try { brut = fs.readFileSync(pf); } catch (e) { console.error('the pack cannot be read: ' + e.message); return 2; } let pack; try { pack = JSON.parse(brut.toString('utf8')); } catch (e) { pack = { kind: 'not JSON: ' + e.message }; } const cf = get('--context'); let context = null; if (cf) { try { context = JSON.parse(fs.readFileSync(cf, 'utf8')); } catch (e) { console.error('the context cannot be read: ' + e.message); return 2; } } const rq = get('--require'); const o = await judecaPachetul(pack, { context, require: rq ? rq.split(',').map((x) => x.trim()).filter(Boolean) : null, at: get('--at'), online: a.includes('--online'), packSha256: sha256(brut) }); console.log(a.includes('--json') ? JSON.stringify(o, null, 1) : text(o)); return codulIesirii(o.summary.verdict); } if (import.meta.url === pathToFileURL(process.argv[1] || '').href) { main().then((c) => { process.exitCode = c; }).catch((e) => { console.error(e.message); process.exitCode = 2; }); }