aere-quantum/verify-layer/control-negativ-sidecar.mjs

80 lines
5.9 KiB
JavaScript

// control-negativ-sidecar.mjs (2026-09-29, revizuirea adversariala, pista B): controlul negativ al verificarilor R, C si N din
// proba-sidecar.mjs. Trei stari pe caz: PRINS (proba a ajuns la capat si verificarile numite sunt rosii), SCAPAT, STRICAT (copia nu
// s-a incarcat, ancora nu apare exact o data, sau proba nu a ajuns la capat).
// V0 sidecar-ul de dinainte, din git (sarit si spus daca revizia nu e in istoric) -> R CONTROL x3, N, N CONTROL, C rosii
// P1 lacatul scos (scriitorii nu se mai asteapta) -> C rosu
// P2 capul atestat nu se mai compara cu intrarea count-1 -> R CONTROL (istoria rescrisa) rosu
// P3 lungimea jurnalului nu se mai compara cu count -> R CONTROL (intrari scoase) rosu
// P4 statementHash-ul capului atestat nu se mai reface -> R CONTROL (cap manipulat) rosu
// P5 un rand care nu e JSON arunca din nou (cadere) -> N rosu
// P6 --attested nu se mai cere digest -> N CONTROL rosu
// Copiile stau LANGA original (importul lui proof-kinds e relativ) si se sterg; originalul trebuie sa ramana octet cu octet.
// node control-negativ-sidecar.mjs -> 0 toate prinse, 1 una scapata, 2 STRICAT
import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import { spawnSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
const AICI = path.dirname(fileURLToPath(import.meta.url));
const SRC = path.join(AICI, 'sidecar.mjs');
const PROBA = path.join(AICI, 'proba-sidecar.mjs');
const REV = process.env.AERE_SIDECAR_REV_VECHE || 'b2b08a5e';
const sha = (f) => crypto.createHash('sha256').update(fs.readFileSync(f)).digest('hex');
const inainte = sha(SRC);
let prinse = 0, stricate = 0, total = 0;
const T = {
refacut: 'R CONTROL: acelasi lant refacut fata de capul atestat',
taiat: 'R CONTROL: jurnal taiat sub capul atestat',
capMan: 'R CONTROL: cap atestat manipulat',
necitibil: 'N: rand care nu e JSON',
digest: 'N CONTROL: --attested care nu e digest',
concurent: 'C: doi scriitori concurenti',
};
function caz(id, text, tinte) {
total++;
const copie = path.join(AICI, `.plantat-sidecar-${id}.mjs`);
try {
fs.writeFileSync(copie, text);
const r = spawnSync(process.execPath, [PROBA], { encoding: 'utf8', timeout: 400000, env: { ...process.env, AERE_SIDECAR_MODUL: copie } });
const out = (r.stdout || '') + (r.stderr || '');
if (!/B3 sidecar \(verify layer\): \d+\/\d+ cum trebuia/.test(out) || !out.includes(copie)) { stricate++; console.log(` STRICAT ${id}: proba nu a ajuns la capat pe copie (${out.trim().split('\n').pop()?.slice(0, 140)})`); return; }
const rosii = out.split('\n').filter((l) => l.includes('[RAU ]'));
const lipsa = tinte.filter((x) => !rosii.some((l) => l.includes(x)));
if (r.status === 1 && !lipsa.length) { prinse++; console.log(` PRINS ${id}: ${rosii.length} verificari rosii, intre ele: ${tinte.map((t) => t.split(':')[0]).join(', ')}`); }
else console.log(` SCAPAT ${id}: cod ${r.status}, au ramas verzi: ${lipsa.join(' | ') || '-'}`);
} finally { fs.rmSync(copie, { force: true }); fs.rmSync(copie + '.lock', { force: true }); }
}
const planta = (t, a, b) => (t.split(a).length === 2 ? t.replace(a, b) : null);
const CALE = `${REV}:tools/aere-verify-layer/sidecar.mjs`;
if (spawnSync('git', ['cat-file', '-e', CALE], { cwd: AICI }).status !== 0) console.log(` SARIT V0: revizia ${REV} nu e in istoricul acestui depozit; NEMASURAT aici`);
else {
const g = spawnSync('git', ['-c', 'core.autocrlf=false', 'show', CALE], { cwd: AICI, encoding: 'utf8' });
if (g.status !== 0 || g.stdout.includes('verificaFataDeCap')) { stricate++; total++; console.log(' STRICAT V0: revizia veche nu se citeste sau are deja reparatia'); }
else caz('V0', g.stdout, [T.refacut, T.taiat, T.capMan, T.necitibil, T.digest]);
}
const nou = fs.readFileSync(SRC, 'utf8');
const NU = "process.env.AERE_PLANTA_NICIODATA === 'da'";
const P = [
['P1', " try { return fn(); } finally { try { fs.unlinkSync(lacat); } catch { /* deja ridicat */ } }\n}", ` try { return fn(); } finally { try { fs.unlinkSync(lacat); } catch { /* deja ridicat */ } }\n}\nconst cuLacatAdevarat = cuLacat;\ncuLacat = (p, fn) => (${NU} ? cuLacatAdevarat(p, fn) : fn());`, [T.concurent]],
['P2', 'if (h !== st.head.toLowerCase()) return', `if (h !== st.head.toLowerCase() && ${NU}) return`, [T.refacut]],
['P3', 'if (intrari.length < st.count) return', `if (intrari.length < st.count && ${NU}) return`, [T.taiat]],
['P4', "if (!eDigest(cap.statementHash) || sha256(Buffer.from(JSON.stringify(st), 'utf8')) !== cap.statementHash.toLowerCase()) return", `if (!eDigest(cap.statementHash) || (sha256(Buffer.from(JSON.stringify(st), 'utf8')) !== cap.statementHash.toLowerCase() && ${NU})) return`, [T.capMan]],
['P5', "try { return JSON.parse(l); } catch { return { necitibil: true }; }", `try { return JSON.parse(l); } catch (e) { if (${NU}) return { necitibil: true }; throw e; }`, [T.necitibil]],
['P6', 'if (!eDigest(attested)) {', `if (!eDigest(attested) && ${NU}) {`, [T.digest]],
];
for (const [id, a, b, tinte] of P) {
let t = planta(nou, a, b);
// P1 cere ca `cuLacat` sa poata fi reasignat: declaratia de functie devine o variabila
if (t && id === 'P1') t = planta(t, 'function cuLacat(p, fn) {', 'let cuLacat = function (p, fn) {');
if (!t) { stricate++; total++; console.log(` STRICAT ${id}: ancora nu apare exact o data`); continue; }
caz(id, t, tinte);
}
const ramase = fs.readdirSync(AICI).filter((f) => f.startsWith('.plantat-sidecar-'));
if (sha(SRC) !== inainte || ramase.length) { stricate++; console.log(' STRICAT originalul s-a schimbat sau au ramas copii: ' + ramase.join(',')); }
console.log(`\ncontrolul negativ al sidecar-ului: prinse ${prinse}/${total}, stricate ${stricate}`);
process.exitCode = stricate ? 2 : prinse === total ? 0 : 1;