// Dovada ca CONTROALELE NEGATIVE ale actiunii pot iesi rosii din motivul lor: pe o COPIE a actiunii (setul minim de fisiere + // dependintele fixate, intr-un dosar temporar; depozitul nu se atinge) se scoate cate un paznic cu o conditie falsa la RULARE // (compileaza, deci un rosu nu poate veni de la sintaxa), se ruleaza test/action.test.mjs pe copie si se cere ca EXACT probele // paznicului scos sa iasa ESEC, iar toate celelalte OK. // Trei grupe, fiindca paznicii interactioneaza: fara judecarea notarizarii (grupa B), ecoul cheii din raspunsul 403 nu mai // ajunge sa fie tiparit, si proba (d) ar ramane verde din alt motiv decat cel masurat; de aceea redactarea (grupa C) se scoate // singura, cu notarizarea intacta. // Verdict cu trei valori: VERDE (fiecare paznic scos si-a inrosit proba, restul verde), ROSU (un paznic scos si proba lui a // ramas verde, sau a picat alta), STRICAT (o ancora de plantare nu s-a gasit exact o data, copia nu compileaza, suita nu a // rulat pe copie). Un STRICAT e un esec al dovezii, nu o linie informativa. // node aerenew/tools/proof-of-software/test/action-dovada.mjs import fs from 'node:fs'; import os from 'node:os'; 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 RADACINA = path.resolve(AICI, '..', '..', '..'); const SUITA = path.join(AICI, 'action.test.mjs'); const FALS = "process.env.AERE_POS_NICIODATA === '1'"; const GRUPE = { A: { plantari: [ ['garda fisierelor necomise', ' if (st.out.trim()) {', ` if (st.out.trim() && ${FALS}) {`], ['refuzul verificarii', ' if (!r.valid) throw new Refuz(', ` if (!r.valid && ${FALS}) throw new Refuz(`], ['verificarea atestarii recitite', 'if (!r2.valid || inapoi.statementHash', `if ((!r2.valid && ${FALS}) || inapoi.statementHash`], ['refuzul reconstructiei', " if (picate.some((c) => /^(rebuild|source)/.test(c.name))) throw", ` if (picate.some((c) => /^(rebuild|source)/.test(c.name)) && ${FALS}) throw`], ], rosii: ['CONTROL NEGATIV (a):', 'CONTROL NEGATIV (a, varianta)', 'CONTROL NEGATIV (c):', 'CONTROL NEGATIV (c, varianta)'], }, // redactarea, singura: (d) o cere, si (b) o cere si ea (mesajul lui 403 trebuie sa arate cheia ecou ca ***) C: { plantari: [ ['redactarea secretelor', 'curata(text) { let t = String(text); for (const r of this.re)', `curata(text) { let t = String(text); for (const r of (${FALS} ? this.re : []))`], ], rosii: ['CONTROL NEGATIV (b):', 'CONTROL NEGATIV (d):'], }, B: { plantari: [ ['clientul care judeca raspunsul (inlocuit cu un fetch brut)', 'try { d = await new AereCloud({ apiKey: apiBrut, baseUrl: apiBase, fetch: f }).notarize(att.statementHash); } catch (e) {', "try { d = await (await f(apiBase + '/notarize', { method: 'POST', headers: { 'content-type': 'application/json', 'x-api-key': apiBrut }, body: JSON.stringify({ hash: att.statementHash }) })).json(); } catch (e) {"], ['judecarea codului HTTP', ' if (cod !== 200) throw new Refuz(', ` if (cod !== 200 && ${FALS}) throw new Refuz(`], ['judecarea chitantei', "|| d.block <= 0) throw new Refuz('notarization answered 200", `|| d.block <= 0) if (${FALS}) throw new Refuz('notarization answered 200`], ], rosii: ['CONTROL NEGATIV (b):', 'CONTROL NEGATIV (b, varianta): 200 fara txHash'], }, }; function copie() { const L = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-dovada-')); for (const f of ['tools/proof-of-software/pos.mjs', 'tools/proof-of-software/action/index.mjs', 'tools/proof-of-software/action/action.yml', 'sdk/index.mjs', 'sdk/package.json', 'sdk-pq-sign/index.mjs', 'sdk-pq-sign/package.json', 'sdk-pq-sign/package-lock.json']) { fs.mkdirSync(path.dirname(path.join(L, f)), { recursive: true }); fs.copyFileSync(path.join(RADACINA, f), path.join(L, f)); } fs.cpSync(path.join(RADACINA, 'sdk-pq-sign', 'node_modules'), path.join(L, 'sdk-pq-sign', 'node_modules'), { recursive: true }); return L; } let verdict = 'VERDE'; const slab = (v) => { if (v === 'STRICAT' || verdict === 'STRICAT') verdict = 'STRICAT'; else verdict = 'ROSU'; }; for (const [nume, g] of Object.entries(GRUPE)) { console.log(`=== grupa ${nume}: ${g.plantari.map((p) => p[0]).join('; ')}`); const L = copie(); const act = path.join(L, 'tools', 'proof-of-software', 'action', 'index.mjs'); let src = fs.readFileSync(act, 'utf8'); let bun = true; for (const [ce, ancora, inlocuire] of g.plantari) { const n = src.split(ancora).length - 1; if (n !== 1) { console.log(` STRICAT: ancora pentru "${ce}" apare de ${n} ori (se cere exact o data)`); bun = false; continue; } src = src.replace(ancora, inlocuire); } if (!bun) { slab('STRICAT'); continue; } fs.writeFileSync(act, src); const chk = spawnSync(process.execPath, ['--check', act], { encoding: 'utf8' }); if (chk.status !== 0) { console.log(' STRICAT: copia cu paznicii scosi nu compileaza: ' + (chk.stderr || '').split('\n').slice(0, 3).join(' | ')); slab('STRICAT'); continue; } const env = { ...process.env, AERE_POS_ACTIUNE_PROBA: act }; delete env.AERE_POS_NICIODATA; const r = spawnSync(process.execPath, [SUITA], { encoding: 'utf8', env, maxBuffer: 1 << 26, timeout: 900000 }); const out = r.stdout || ''; if (!out.includes('actiunea: ' + act)) { console.log(' STRICAT: suita nu a rulat pe copie (nu numeste actiunea copiata)'); slab('STRICAT'); continue; } const ok = [...out.matchAll(/^ {2}OK {3}(.+)$/gm)].map((m) => m[1]); // randul intreg (numele probelor contin si ele ': ', deci numele nu se poate taia la primul) const esec = [...out.matchAll(/^ {2}ESEC (.+)$/gm)].map((m) => m[1]); if (!/^\d+ treceri, \d+ esecuri$/m.test(out) || ok.length + esec.length < 20) { console.log(` STRICAT: suita nu a terminat (${ok.length} OK, ${esec.length} ESEC citite)`); slab('STRICAT'); continue; } const eRosie = (t) => g.rosii.some((p) => t.startsWith(p)); const rosiiLipsa = g.rosii.filter((p) => !esec.some((t) => t.startsWith(p))); const rosiiStraine = esec.filter((t) => !eRosie(t)); for (const p of g.rosii) console.log(` ${esec.some((t) => t.startsWith(p)) ? 'ROSIE, cum trebuia' : 'VERDE, deci paznicul scos NU e vazut de proba'}: ${p}`); for (const m of [...out.matchAll(/^ {2}ESEC (.+)$/gm)].map((x) => x[1])) if (eRosie(m)) console.log(` ${m.slice(0, 40)} ... ${m.slice(-180)}`); if (rosiiStraine.length) console.log(' au picat si probe care nu tin de paznicii scosi: ' + rosiiStraine.map((x) => x.slice(0, 160)).join(' | ')); if (rosiiLipsa.length || rosiiStraine.length) slab('ROSU'); console.log(` grupa ${nume}: ${ok.length} OK, ${esec.length} ESEC (asteptate ${g.rosii.length})`); try { fs.rmSync(L, { recursive: true, force: true, maxRetries: 3 }); } catch { console.log(' (copia temporara nu s-a putut sterge: ' + L + ')'); } } console.log(`\nDOVADA CONTROALELOR NEGATIVE: ${verdict}`); process.exitCode = verdict === 'VERDE' ? 0 : verdict === 'ROSU' ? 1 : 2;