#!/usr/bin/env node 'use strict'; // AERE Quantum Security Control Plane - CONSOLA (B1 milestone 3): "planul de control care le uneste". Ingera ce produc uneltele - un // buraf de la sidecar-ul B3 (jurnal de audit + dovezi) si, optional, un plan de migrare de la control-plane si executia lui - si scoate // O SINGURA stare verificabila a unei desfasurari. // // PRINCIPIUL, si e chiar valoarea: consola NU se increde in ce spune despre sine burafu. Recalculeaza ea insasi lantul de hash-uri // (reia verificaJurnal din sidecar) si integritatea FIECARUI plic (sha256(JSON.stringify(statement)) == statementHash). Un host rau // care preda un buraf cu `chainOk:true` peste un jurnal manipulat e prins aici. Finalitatea pe lant (notarizarea capului) e in afara // consolei offline: sidecar-ul o da cu `verify-log --attested` si verificatorul AIP-23. Iesirea e in engleza (forma 2, 2026-09-29). // // node consola.mjs --bundle b.json [--plan plan.json] [--execution execution.json] [--json] // iesire 0 = verdict OK (lant intreg SI toate plicurile integre); 1 = ceva stricat (BROKEN); 2 = nu s-a putut rula. import fs from 'node:fs'; import path from 'node:path'; import crypto from 'node:crypto'; import { fileURLToPath, pathToFileURL } from 'node:url'; const AICI = path.dirname(fileURLToPath(import.meta.url)); const RAD = path.resolve(AICI, '..', '..'); const sha256 = (b) => '0x' + crypto.createHash('sha256').update(b).digest('hex'); // sidecar-ul: langa planul de control intr-o copie publica (../verify-layer), sau in depozitul de dezvoltare (tools/aere-verify-layer) export function caleaSidecarului() { const c = [path.join(AICI, '..', 'verify-layer', 'sidecar.mjs'), path.join(RAD, 'tools', 'aere-verify-layer', 'sidecar.mjs')]; return c.find((p) => fs.existsSync(p)) || c[c.length - 1]; } /** * Reverifica un buraf de sidecar independent de ce declara el. * @returns {object} starea consolei */ export async function evalueaza({ bundle, plan = null, execution = null }) { const { verificaJurnal } = await import(pathToFileURL(caleaSidecarului()).href); const intrari = Array.isArray(bundle.entries) ? bundle.entries : []; // 1) lantul: recalculat, nu citit din bundle.chainOk const lant = verificaJurnal(intrari); // 2) integritatea fiecarui plic (tamper-evident, fara retea) const plicuriRele = []; for (const e of intrari) { const p = e && e.proof; const okForma = p && p.statement && typeof p.statementHash === 'string' && /^0x[0-9a-fA-F]{64}$/.test(p.statementHash); const okInt = okForma && sha256(Buffer.from(JSON.stringify(p.statement), 'utf8')).toLowerCase() === p.statementHash.toLowerCase(); if (!okInt) plicuriRele.push({ seq: e && e.seq, kind: p && (p.kind || (p.statement && p.statement.kind)) || '?', reason: okForma ? 'statementHash != sha256(statement)' : 'invalid envelope form' }); } // 3) minciuna auto-raportata: bundle.chainOk spune altceva decat masuratoarea noastra const minciunaChainOk = typeof bundle.chainOk === 'boolean' && bundle.chainOk !== lant.ok; // 4) planul de migrare (optional, informativ): forma planificatorului (actions/method/urgency). Un plan fara nicio lista // recunoscuta e raportat ca atare, nu ca plan gol (2026-09-27: consola citea alte nume si afisa "0 actiuni" pe un plan real). let migration = null; if (plan) { const items = Array.isArray(plan.actions) ? plan.actions : null; migration = items === null ? { total: null, error: 'the plan has no recognized list (actions)' } : { total: items.length, critical: items.filter((i) => String(i.urgency || '').toUpperCase() === 'CRITICAL').length, auto: items.filter((i) => i.method === 'auto-aere').length, manual: items.filter((i) => i.method === 'manual').length, blocked: items.filter((i) => i.method === 'blocked').length, }; } // 5) executia migrarii (optional): lantul execution.json al executorului se RE-VERIFICA aici, nu se crede sumarul lui let executie = null; if (execution) { const { verificaExecutie } = await import(pathToFileURL(path.join(AICI, 'executa-migrare.mjs')).href); const v = verificaExecutie(execution); const recs = (execution.records || []).map((e) => e && e.record).filter((r) => r && r.ref); executie = { chainOk: v.ok, brokenAtSeq: v.ok ? null : v.seq, reason: v.ok ? null : v.reason, actions: recs.length, ok: recs.filter((r) => r.verdict === 'OK').length, failed: recs.filter((r) => r.verdict === 'FAILED').length }; } const okTot = lant.ok && plicuriRele.length === 0 && !minciunaChainOk && (executie === null || executie.chainOk) && !(migration && migration.total === null); return { v: 2, kind: 'aere-control-plane-console', host: bundle.host || '?', auditChain: { ok: lant.ok, count: lant.count, head: lant.head, brokenAtSeq: lant.rupt, reason: lant.motiv || null }, envelopes: { total: intrari.length, intact: intrari.length - plicuriRele.length, bad: plicuriRele }, selfReportSuspect: minciunaChainOk ? `the bundle declares chainOk=${bundle.chainOk} but the measurement gives ${lant.ok}` : null, migration, execution: executie, verdict: okTot ? 'OK' : 'BROKEN', }; } async function main() { const args = process.argv.slice(2); const get = (f) => { const i = args.indexOf(f); return i >= 0 ? args[i + 1] : null; }; const bf = get('--bundle'); if (!bf) { console.error('usage: node consola.mjs --bundle b.json [--plan plan.json] [--execution execution.json] [--json]'); return 2; } const bundle = JSON.parse(fs.readFileSync(bf, 'utf8')); const pf = get('--plan'); const plan = pf ? JSON.parse(fs.readFileSync(pf, 'utf8')) : null; const xf = get('--execution'); const execution = xf ? JSON.parse(fs.readFileSync(xf, 'utf8')) : null; const st = await evalueaza({ bundle, plan, execution }); if (args.includes('--json')) console.log(JSON.stringify(st, null, 1)); else { console.log(`CONTROL PLANE CONSOLE - host ${st.host}: ${st.verdict}`); console.log(` audit chain: ${st.auditChain.ok ? 'INTACT' : 'BROKEN at seq ' + st.auditChain.brokenAtSeq}, ${st.auditChain.count} entries, head ${st.auditChain.head || '-'}`); console.log(` AIP-23 envelopes: ${st.envelopes.intact}/${st.envelopes.total} intact` + (st.envelopes.bad.length ? ` (bad: ${st.envelopes.bad.map((x) => x.seq).join(',')})` : '')); if (st.selfReportSuspect) console.log(` WARNING: ${st.selfReportSuspect}`); if (st.migration) console.log(st.migration.total === null ? ` PQ migration: ${st.migration.error}` : ` PQ migration: ${st.migration.total} actions (${st.migration.critical} critical, ${st.migration.auto} auto, ${st.migration.manual} manual, ${st.migration.blocked} blocked)`); if (st.execution) console.log(` execution: chain ${st.execution.chainOk ? 'INTACT' : 'BROKEN at seq ' + st.execution.brokenAtSeq + ' (' + st.execution.reason + ')'}, ${st.execution.actions} actions (${st.execution.ok} OK, ${st.execution.failed} FAILED)`); } return st.verdict === 'OK' ? 0 : 1; } if (import.meta.url === pathToFileURL(process.argv[1] || '').href) { main().then((c) => { process.exitCode = c; }).catch((e) => { console.error(e.message); process.exitCode = 2; }); }