aere-quantum/agents/proba-agent-policy.mjs
Aere Network 6e62b1ad1a Add agents: a post-quantum identity, a policy and a signed action ledger for AI agents, with human approval and revocation
An agent gets an ML-DSA-65 identity and a policy (spending per time window, allowed tools and recipients, which actions need human
approval, who may revoke it). Every action it proposes is judged against the policy, signed by the agent and chained; a verifier that
does not trust the agent re-runs the policy over the whole ledger. Approvals and revocations are signed by people with their own
ML-DSA-65 keys. The ledger of an agent under a policy is one ledger: a second history is a branch, and two branches are a proof of
equivocation anyone can check with the public key alone. The README says what the verifier cannot see: entry times are bounded from
below only with a witness (anchors or a start time), and someone who sees one branch cannot know of another.

Tests: policy 23/23 with the AIP-23 reference verifier (21 run without it), ledger 51/51, approval and revocation 39/39, command line
23/23; negative control 25/25.
2026-09-29 21:59:41 +03:00

78 lines
7.1 KiB
JavaScript

'use strict';
// Proba motorului de politica al agentilor (B2 m2), cu CONTROALE NEGATIVE: sub limita permis, peste limita refuzat, unealta/destinatar
// nepermis refuzat, iar plicul de decizie verifica sub AIP-23 si leaga policyHash (o politica schimbata -> alt hash, decizia nu se
// poate atribui altei politici). 2026-09-29 (B-17): hash-ul unei politici PRIMITE se recalculeaza (o politica laxa nu poate purta
// hash-ul celei reale), politica se valideaza, un alt activ decat al limitei e refuzat, actionHash-ul plicului e canonic.
// node proba-agent-policy.mjs -> 0 toate cum trebuia, 1 altfel, 2 fara verificatorul AIP-23 (partea lui NEMASURATA)
// Verificatorul AIP-23: AERE_VERIFY_PROOF=<verify-proof.mjs> sau, in depozitul de dezvoltare, ../aere-proof-protocol/verify.mjs.
import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path';
import { execFileSync } from 'node:child_process'; import { fileURLToPath } from 'node:url';
import { definePolicy, hashPolicy, checkAction, decisionEnvelope, canonical } from './agent-policy.mjs';
import crypto from 'node:crypto';
const AICI = path.dirname(fileURLToPath(import.meta.url));
const VERIFY = process.env.AERE_VERIFY_PROOF || path.resolve(AICI, '..', 'aere-proof-protocol', 'verify.mjs');
const T = fs.mkdtempSync(path.join(os.tmpdir(), 'ap-'));
let ok = 0, rele = 0, sarite = 0;
const cer = (n, c) => { console.log(` [${c ? 'OK ' : 'RAU '}] ${n}`); c ? ok++ : rele++; };
const arunca = (fn) => { try { fn(); return null; } catch (e) { return e.message; } };
const verdict = (p) => { try { return JSON.parse(execFileSync(process.execPath, [VERIFY, p, '--json'], { encoding: 'utf8' })).verdict; } catch (e) { try { return JSON.parse(e.stdout || '').verdict; } catch { return '?'; } } };
try {
const { policy, policyHash } = definePolicy({ agentId: 'agent-1', spend: { amount: '100', windowSeconds: 3600 }, tools: ['retrieval', 'calc'], recipients: ['0xBBBB'] });
cer('policyHash e digest 0x+64', /^0x[0-9a-f]{64}$/.test(policyHash));
// unelte
cer('unealta permisa -> allowed', checkAction(policy, { kind: 'tool', tool: 'retrieval', at: 1 }).allowed === true);
cer('CONTROL: unealta nepermisa -> denied, cu motivul in engleza', /is not in the allowed list/.test(checkAction(policy, { kind: 'tool', tool: 'shell', at: 1 }).reason));
// cheltuiala
cer('plata sub limita, destinatar permis -> allowed', checkAction(policy, { kind: 'payment', to: '0xbbbb', amount: '40', at: 1000 }, [{ amount: '30', at: 999 }]).allowed === true);
const peste = checkAction(policy, { kind: 'payment', to: '0xbbbb', amount: '80', at: 1000 }, [{ amount: '30', at: 999 }]);
cer(`CONTROL: plata care depaseste limita in fereastra -> denied (${peste.reason})`, peste.allowed === false && /over the limit: 110 > 100/.test(peste.reason));
cer('CONTROL: destinatar nepermis -> denied', checkAction(policy, { kind: 'payment', to: '0xCCCC', amount: '10', at: 1000 }, []).allowed === false);
cer('cheltuiala veche (in afara ferestrei) nu conteaza', checkAction(policy, { kind: 'payment', to: '0xbbbb', amount: '90', at: 100000 }, [{ amount: '90', at: 1 }]).allowed === true);
// activul: implicit al limitei; altul e refuzat, nu adunat
cer('plata cu activul politicii (AERE) scris explicit -> allowed', checkAction(policy, { kind: 'payment', to: '0xbbbb', amount: '10', asset: 'AERE', at: 1 }).allowed === true);
const altActiv = checkAction(policy, { kind: 'payment', to: '0xbbbb', amount: '10', asset: 'USDC', at: 1 });
cer(`CONTROL: plata in alt activ decat al limitei -> denied (${altActiv.reason})`, !altActiv.allowed && /not the policy's asset/.test(altActiv.reason));
// validarea politicii
cer('CONTROL: spend.amount care nu e intreg -> politica refuzata', /spend.amount/.test(arunca(() => definePolicy({ agentId: 'a', spend: { amount: '1e9', windowSeconds: 60 } })) || ''));
cer('CONTROL: fereastra zero -> politica refuzata', /windowSeconds/.test(arunca(() => definePolicy({ agentId: 'a', spend: { amount: '1', windowSeconds: 0 } })) || ''));
// hashPolicy: hash-ul unei politici PRIMITE se recalculeaza
const dinFisier = JSON.parse(JSON.stringify(policy));
cer('hashPolicy pe politica citita dintr-un fisier = hash-ul definePolicy', hashPolicy(dinFisier).policyHash === policyHash);
const reordonata = Object.fromEntries(Object.entries(dinFisier).reverse());
cer('hashPolicy nu depinde de ordinea cheilor din fisier', hashPolicy(reordonata).policyHash === policyHash);
const laxa = { ...dinFisier, spend: { ...dinFisier.spend, amount: '1000000' } };
cer('CONTROL: o politica LAXA are alt hash (nu poate purta hash-ul celei reale)', hashPolicy(laxa).policyHash !== policyHash);
cer('CONTROL: un camp necunoscut in politica -> refuzat', /unknown policy field/.test(arunca(() => hashPolicy({ ...dinFisier, bypass: true })) || ''));
cer('politicile de dinainte de 2026-09-29 pastreaza hash-ul (forma normala neschimbata)',
definePolicy({ agentId: 'agent-7', spend: { amount: '100', windowSeconds: 3600, asset: 'AERE' }, tools: ['search'], recipients: null }).policyHash === '0xc7d983a9886a0899bd6f3f09fde526514b0075f8f6abe77a1886405d86048c27');
// plic de decizie
const act = { kind: 'payment', to: '0xbbbb', amount: '80', at: 1000 };
const dec = checkAction(policy, act, [{ amount: '30', at: 999 }]);
const env = decisionEnvelope({ policyHash, action: act, decision: dec, createdAt: '2026-09-26T09:00:00Z' });
cer('plicul leaga policyHash', env.statement.policyHash === policyHash);
cer('plicul spune allowed=false (decizia reala)', env.statement.allowed === false);
const inversa = { at: 1000, amount: '80', to: '0xbbbb', kind: 'payment' };
cer('actionHash e canonic: aceeasi actiune cu cheile in alta ordine -> acelasi digest', decisionEnvelope({ policyHash, action: inversa, decision: dec, createdAt: '2026-09-26T09:00:00Z' }).statement.actionHash === env.statement.actionHash);
cer('actionHash = sha256 peste JSON-ul canonic al actiunii (reproductibil de un strain)', env.statement.actionHash === '0x' + crypto.createHash('sha256').update(canonical(act)).digest('hex'));
if (fs.existsSync(VERIFY)) {
const f = path.join(T, 'dec.json'); fs.writeFileSync(f, JSON.stringify(env, null, 1));
cer('plicul de decizie verifica sub AIP-23', verdict(f) === 'VALID');
const rau = JSON.parse(JSON.stringify(env)); rau.statement.allowed = true; const f2 = path.join(T, 'rau.json'); fs.writeFileSync(f2, JSON.stringify(rau));
cer('CONTROL: plicul cu decizia rescrisa nu mai verifica sub AIP-23', verdict(f2) !== 'VALID');
} else { sarite += 2; console.log(` [SARIT] plicul sub AIP-23 (2 probe): verificatorul nu e la ${VERIFY}; AERE_VERIFY_PROOF=<verify-proof.mjs>`); }
// o politica schimbata -> alt hash (decizia nu se poate atribui altei politici)
const alt = definePolicy({ agentId: 'agent-1', spend: { amount: '1000000', windowSeconds: 3600 }, tools: ['retrieval'], recipients: ['0xBBBB'] });
cer('CONTROL: politica cu alta limita -> alt policyHash', alt.policyHash !== policyHash);
} finally { fs.rmSync(T, { recursive: true, force: true }); }
console.log(`\nagent-policy: ${ok}/${ok + rele} cum trebuia${sarite ? `, ${sarite} NEMASURATE (fara verificatorul AIP-23)` : ''}`);
process.exitCode = rele ? 1 : (sarite ? 2 : 0);