aere-quantum/agents/control-negativ-aprobare.mjs

89 lines
9.6 KiB
JavaScript

// Controlul negativ al probelor agentilor (politica, registrul, aprobarea si revocarea, linia de comanda): fiecare paznic se strica intr-o COPIE a
// dosarului, proba lui ruleaza pe copie si TREBUIE sa iasa rosie, cu probele chiar rulate (rezumatul lor exista); pe copia neatinsa,
// verde. O plantare al carei tipar nu apare exact o data e un esec al controlului, nu o linie informativa. Trei stari: o proba care nu
// ajunge la rezumat e STRICAT, si se numara esec (rosul ei nu masoara nimic).
// 2026-09-29 (B-17): acopera si paznicii noi - argumentele in aprobare, hash-ul politicii recalculat, antedatarea (la scriere si fata
// de ancore), ancora altei ramuri, sesiunea derivata, dovada de echivocare, reluarea registrului, campul necunoscut, activul, actionHash canonic.
// node control-negativ-aprobare.mjs iesire 0 = martorii verzi si toate plantarile rosii
// Proba politicii foloseste verificatorul AIP-23 (AERE_VERIFY_PROOF sau ../aere-proof-protocol/verify.mjs); fara el, martorul ei iese
// cu 2 (partea AIP-23 NEMASURATA) si se accepta ca martor numai asa, cu zero RAU.
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { spawn } from 'node:child_process';
import { fileURLToPath } from 'node:url';
const AICI = path.dirname(fileURLToPath(import.meta.url));
const DEV_VERIFY = path.resolve(AICI, '..', 'aere-proof-protocol', 'verify.mjs');
const VERIFY = process.env.AERE_VERIFY_PROOF || (fs.existsSync(DEV_VERIFY) ? DEV_VERIFY : '');
const P = { pol: 'proba-agent-policy.mjs', reg: 'proba-agent-ledger.mjs', apr: 'proba-agent-aprobare.mjs', cli: 'proba-agent-cli.mjs' };
const PLANTARI = [
// [nume, fisier, tipar, inlocuire, proba]
['nonce-ul unei aprobari nu mai e unic', 'agent-ledger.mjs', "if (ap && folosite.has(String(ap.nonce))) { respinse.push('nonce already used'); continue; }", '', P.apr],
['un aprobator nenumit in politica numara', 'agent-aprobare.mjs', 'if (!policy.approval.approvers.includes(humanId)) return', 'if (false) return', P.apr],
['semnatura aprobarii nu se mai verifica', 'agent-aprobare.mjs', "if (!sig) return { ok: false, error: 'the approval signature does not verify' };", '', P.apr],
['aprobarea devine scutire de limita', 'agent-policy.mjs', 'if (!d.allowed || !needsApproval(policy, action)) return d;', 'if (needsApproval(policy, action) && (ctx.approvers || []).length >= policy.approval.threshold) return { allowed: true, reason: "approved (exempt)" }; if (!d.allowed || !needsApproval(policy, action)) return d;', P.apr],
['verificatorul ignora revocarile date separat', 'agent-ledger.mjs', 'let revokedAt = ext.revokedAt;', 'let revokedAt = null;', P.apr],
['revocarea nu mai cere proprietarul', 'agent-aprobare.mjs', "if (humanIdFromKey(k) !== policy.owner) return { ok: false, error: 'the revocation is not signed by the owner named in the policy' };", '', P.apr],
['aprobarea nu mai e legata de continutul actiunii', 'agent-aprobare.mjs', 'if (ap.actionHash !== actionHash(action)) return', 'if (false) return', P.apr],
['argumentele uneltei nu mai intra in continutul aprobat', 'agent-aprobare.mjs', 'if (a.args !== undefined) c.argsHash = sha(canonical(a.args));', '', P.apr],
['verificatorul crede hash-ul dat in loc sa il recalculeze', 'agent-ledger.mjs', 'if (!(hashFixat == null || String(hashFixat).toLowerCase() === policyHash /*PAZA:politica*/)) return', 'if (hashFixat != null) policyHash = String(hashFixat).toLowerCase(); if (false) return', P.reg],
['registrul accepta la scriere o intrare antedatata', 'agent-ledger.mjs', 'if (Number(at) < acum - TOLERANTA_S) throw', 'if (false) throw', P.reg],
['ancora nu mai margineste timpul de jos', 'agent-ledger.mjs', 'if (!(!(i > a.seq && atI < a.at - tol) /*PAZA:timp*/)) return', 'if (false) return', P.reg],
['ancora nu mai trebuie sa numeasca o intrare a registrului', 'agent-ledger.mjs', 'if (!(!!ledger.entries[s] && !!a && ledger.entries[s].hash === a.hash /*PAZA:ancora*/)) return', 'if (false) return', P.reg],
['verificatorul nu mai cere sesiunea registrului in intrare', 'agent-ledger.mjs', ' && b.session === ledger.session /*PAZA:corp*/)) return', ' /*PAZA:corp*/)) return', P.reg],
['sesiunea registrului nu mai trebuie sa fie cea derivata', 'agent-ledger.mjs', 'if (!(ledger.session === sessionId(ledger.agentId, policyHash) /*PAZA:sesiune*/)) return', 'if (false) return', P.reg],
['dovada de echivocare fara semnaturile agentului', 'agent-ledger.mjs', "if (!intrareSemnata(e, pub)) return { ok: false, error: 'an entry is not signed by the agent' };", '', P.reg],
['un registru atins se poate relua', 'agent-ledger.mjs', 'if (!v.ok) throw new Error(`agent-ledger: the ledger to resume does not verify', 'if (false) throw new Error(`agent-ledger: the ledger to resume does not verify', P.reg],
['reluarea uita nonce-urile folosite', 'agent-ledger.mjs', 'for (const e of entries) for (const n of nonceuri(e.body.approvals)) folosite.add(n);', '', P.apr],
['reluarea uita revocarea', 'agent-ledger.mjs', 'JSON.parse(JSON.stringify(ledger.entries)), v.revocations.revokedAt);', 'JSON.parse(JSON.stringify(ledger.entries)), null);', P.apr],
['linia de comanda suprascrie o cheie existenta', 'agent-cli.mjs', 'if (fs.existsSync(cheie)) throw new Folosire(', 'if (false) throw new Folosire(', P.cli],
['linia de comanda lasa pe oricine sa aprobe', 'agent-cli.mjs', 'if (!policy.approval || !policy.approval.approvers.includes(human.humanId)) throw', 'if (false) throw', P.cli],
['verify din linia de comanda uita hash-ul numit in fisierul politicii', 'agent-cli.mjs', "const pinned = get('--policy-hash') ?? j.policyHash;", "const pinned = get('--policy-hash');", P.cli],
['record deschide mereu un registru nou in loc sa il reia', 'agent-cli.mjs', 'const L = fs.existsSync(lf) ? resumeLedger(', 'const L = false ? resumeLedger(', P.cli],
['o plata din alt portofel decat cel numit trece', 'agent-policy.mjs', 'if (policy.wallet && String(action.from', 'if (false && String(action.from', P.pol],
['politica cu un camp necunoscut e primita', 'agent-policy.mjs', 'if (necunoscute.length) throw', 'if (false) throw', P.pol],
['o plata in alt activ trece pe langa limita', 'agent-policy.mjs', 'if (action.asset != null && String(action.asset) !== policy.spend.asset) return', 'if (false) return', P.pol],
['actionHash-ul plicului nu mai e canonic', 'agent-policy.mjs', "actionHash: sha256(Buffer.from(canonical(action), 'utf8')),", "actionHash: sha256(Buffer.from(JSON.stringify(action), 'utf8')),", P.pol],
];
const FISIERE = ['agent-policy.mjs', 'agent-ledger.mjs', 'agent-aprobare.mjs', 'agent-cli.mjs', P.pol, P.reg, P.apr, P.cli];
function copie() { const t = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-b2-ctl-')); for (const f of FISIERE) fs.copyFileSync(path.join(AICI, f), path.join(t, f)); return t; }
// 2026-09-29: probele ruleaza cate PARALEL deodata (asincron), ca tot controlul sa incapa in termenul rulatorului comun
const PARALEL = 4;
function ruleaza(t, proba) {
const env = { ...process.env }; if (VERIFY) env.AERE_VERIFY_PROOF = VERIFY; else delete env.AERE_VERIFY_PROOF;
return new Promise((resolve) => {
const c = spawn(process.execPath, [path.join(t, proba)], { env }); let out = '';
const ceas = setTimeout(() => c.kill(), 240000);
c.stdout.on('data', (x) => { out += x; }); c.stderr.on('data', (x) => { out += x; });
c.on('close', (cod) => { clearTimeout(ceas); const m = /agent-(policy|ledger|aprobare|cli): (\d+)\/(\d+)/.exec(out);
resolve({ cod, rulat: !!m, rele: (out.match(/^\s*(RAU\s|\[RAU)/gm) || []).length }); });
});
}
async function inGrup(lucrari) { const rez = new Array(lucrari.length); let i = 0;
await Promise.all(Array.from({ length: PARALEL }, async () => { while (i < lucrari.length) { const k = i++; rez[k] = await lucrari[k](); } })); return rez; }
let esecuri = 0;
const martori = await inGrup(Object.values(P).map((proba) => async () => { const t0 = copie(); try { return [proba, await ruleaza(t0, proba)]; } finally { fs.rmSync(t0, { recursive: true, force: true }); } }));
for (const [proba, m0] of martori) {
const verde = m0.rulat && m0.rele === 0 && (m0.cod === 0 || (m0.cod === 2 && proba === P.pol && !VERIFY));
if (verde) console.log(` OK martorul ${proba}: copia neatinsa verde${m0.cod === 2 ? ' (fara verificatorul AIP-23: partea lui NEMASURATA)' : ''}`);
else { esecuri++; console.log(` RAU martorul ${proba} nu e verde (cod ${m0.cod}, ${m0.rulat ? m0.rele + ' RAU' : 'nu a ajuns la rezumat'})`); }
}
const linii = await inGrup(PLANTARI.map((pl) => async () => {
const [nume, f, din, inl, proba] = pl; let t = null;
try {
// o plantare scrisa gresit (fisier, tipar) e un esec al controlului, nu o cadere a lui
if (!FISIERE.includes(f) || typeof din !== 'string' || !proba) return [false, ` RAU ${nume}: plantarea e scrisa gresit (fisier ${f}, proba ${proba})`];
t = copie(); const fp = path.join(t, f); const src = fs.readFileSync(fp, 'utf8');
if (src.split(din).length !== 2) return [false, ` RAU ${nume}: tiparul nu apare exact o data in ${f}`];
fs.writeFileSync(fp, src.replace(din, inl));
const r = await ruleaza(t, proba);
if (r.rulat && r.cod !== 0 && r.rele > 0) return [true, ` OK ${nume}: ${proba} ROSIE (${r.rele} RAU)`];
return [false, ` RAU ${nume}: ${r.rulat ? `${proba} a ramas verde` : `${proba} nu a ajuns la rezumat (STRICAT)`} (cod ${r.cod})`];
} catch (e) { return [false, ` RAU ${nume}: controlul a cazut pe ea (${String(e.message).slice(0, 80)})`]; }
finally { if (t) fs.rmSync(t, { recursive: true, force: true }); }
}));
for (const [bun, l] of linii) { console.log(l); if (!bun) esecuri++; }
console.log(esecuri ? `RAU: ${esecuri} esecuri ale controlului` : `DOVEDIT: martorii verzi, ${PLANTARI.length} din ${PLANTARI.length} plantari rosii`);
process.exitCode = esecuri ? 1 : 0;