aere-quantum/device-identity/control-negativ-device.mjs

79 lines
7.8 KiB
JavaScript

#!/usr/bin/env node
// control-negativ-device.mjs: the negative control of proba-device.mjs. Each guard is taken out of a COPY of this directory (with
// the files it imports), the probe runs on the copy, and the rows that depend on that guard, AND ONLY THEY, must go red.
//
// 1. the witness: the untouched copy is green;
// 2. one planting per RULE the verifier names (all its marked checks made true): the red rows must be exactly the rows whose
// `expects` lists that rule (read from the probe's own --json, not typed here);
// 3. the plantings of plantari-device.mjs, each with the rows it must turn red beside it. A check made of several conditions is planted out
// one CONDITION at a time (a whole rule taken out says nothing about each half of it). Two checks that guard the same thing
// are planted out TOGETHER, and the planting says so.
// Three states: VERDE (exactly those rows went red), ROSU (others did, or none), STRICAT (a pattern is not in the file exactly
// once, or the probe on the copy did not reach its summary: a red that measures nothing). STRICAT counts as a failure.
// node control-negativ-device.mjs [--doar <text>] exit 0 = the witness green and every planting red on exactly its rows
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 NUME = path.basename(AICI);
const DEV_VERIFY = path.resolve(AICI, '..', '..', 'verificator-falcon', 'verify-proof.mjs');
const VERIFY = process.env.AERE_VERIFY_PROOF || (fs.existsSync(DEV_VERIFY) ? DEV_VERIFY : '');
import { PLANTARI, M, C } from './plantari-device.mjs';
const PLANT = " || String(1) === '1' /*PLANTAT*/";
const a = process.argv.slice(2); const doar = a.includes('--doar') ? a[a.indexOf('--doar') + 1] : null;
let verzi = 0, rosii = 0, stricate = 0;
const rand = (stare, text) => { console.log(` [${stare.padEnd(7)}] ${text}`); if (stare === 'VERDE') verzi++; else if (stare === 'ROSU') rosii++; else stricate++; };
// the copy: this directory and the files it imports from its neighbours, under the same relative names
const T = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-device-control-'));
function copie(nume) {
const R = path.join(T, nume); const d = path.join(R, NUME); fs.mkdirSync(d, { recursive: true });
for (const e of fs.readdirSync(AICI, { withFileTypes: true })) { if (e.isDirectory()) { if (e.name === 'fixturi' || e.name === 'dovezi-ancora-28001') fs.cpSync(path.join(AICI, e.name), path.join(d, e.name), { recursive: true }); } else fs.copyFileSync(path.join(AICI, e.name), path.join(d, e.name)); }
const vecini = new Set(); for (const f of [M, C, 'proba-device.mjs']) for (const m of fs.readFileSync(path.join(AICI, f), 'utf8').matchAll(/from '\.\.\/([^']+)'/g)) vecini.add(m[1]);
for (const v of vecini) { const q = path.join(R, v); fs.mkdirSync(path.dirname(q), { recursive: true }); fs.copyFileSync(path.join(AICI, '..', v), q); }
return d;
}
function proba(d) {
return new Promise((gata) => {
const c = spawn(process.execPath, [path.join(d, 'proba-device.mjs'), '--json'], { env: { ...process.env, ...(VERIFY ? { AERE_VERIFY_PROOF: VERIFY } : {}) }, stdio: ['ignore', 'pipe', 'pipe'] }); let out = '', err = '';
c.stdout.on('data', (x) => { out += x; }); c.stderr.on('data', (x) => { err += x; });
c.on('close', () => { try { gata(JSON.parse(out.trim().split('\n').slice(-1)[0])); } catch { gata({ eroare: (err || out).trim().split('\n').slice(-2).join(' | ').slice(0, 300) }); } });
});
}
async function inParalel(sarcini, n = 6) { const rez = new Array(sarcini.length); let k = 0; await Promise.all(Array.from({ length: Math.min(n, sarcini.length) }, async () => { while (k < sarcini.length) { const i = k++; rez[i] = await sarcini[i](); } })); return rez; }
try {
console.log('1. the witness: the untouched copy');
const martor = await proba(copie('martor'));
if (martor.eroare || !Array.isArray(martor.rows)) { rand('STRICAT', 'the probe on the untouched copy did not reach its summary: ' + (martor.eroare || '')); throw new Error('stop'); }
const rosiiMartor = martor.rows.filter((r) => r.state === 'ROSU').map((r) => r.id);
rand(rosiiMartor.length ? 'ROSU' : 'VERDE', `the untouched copy: ${martor.verzi} green, ${martor.rosii} red, ${martor.nemasurat} not measured` + (rosiiMartor.length ? ' (' + rosiiMartor.join(', ') + ')' : ''));
if (rosiiMartor.length) throw new Error('stop');
const nemas = new Set(martor.rows.filter((r) => r.state !== 'VERDE').map((r) => r.id)); const iduri = new Set(martor.rows.map((r) => r.id));
const { REGULI } = await import('./device.mjs');
const cazuri = [];
for (const r of REGULI) cazuri.push({ nume: `rule ${r} taken out (every marked check of it made true)`, fisier: M, regula: r, asteptate: martor.rows.filter((x) => x.expects.includes(r)).map((x) => x.id) });
PLANTARI.forEach(([nume, fisier, de, cu, asteptate], i) => cazuri.push({ nume, fisier, de: [de].flat(), cu: [cu].flat(), asteptate: typeof asteptate === 'function' ? asteptate(martor.rows) : asteptate, i }));
const alese = cazuri.filter((c) => !doar || c.nume.includes(doar));
if (doar) console.log(` (FILTERED: --doar ${doar}: ${alese.length} of ${cazuri.length} plantings; the others are NOT controlled by this run)`);
console.log(`2-3. ${alese.length} plantings, each on its own copy (${REGULI.length} whole rules, ${PLANTARI.length} more)`);
const rez = await inParalel(alese.map((c, k) => async () => {
const lipsa = c.asteptate.filter((id) => !iduri.has(id)); if (lipsa.length) return { stricat: `the planting names rows the probe does not have: ${lipsa.join(', ')}` };
const d = copie('p' + k); const f = path.join(d, c.fisier); const src = fs.readFileSync(f, 'utf8'); let text = src;
if (c.regula) { const n = src.split(`/*PAZA:${c.regula}*/`).length - 1; if (n < 1) return { stricat: `no /*PAZA:${c.regula}*/ marker in ${c.fisier}` }; text = src.split(`/*PAZA:${c.regula}*/`).join(PLANT); }
else for (const [j, de] of c.de.entries()) { const n = src.split(de).length - 1; if (n !== 1) return { stricat: `pattern ${j + 1} appears ${n} times in ${c.fisier} (exactly once is required)` }; const m = text.split(de).length - 1; if (m !== 1) return { stricat: `pattern ${j + 1} is no longer there once after the pattern before it was replaced` }; text = text.replace(de, () => c.cu[j]); }
fs.writeFileSync(f, text); const r = await proba(d); fs.rmSync(path.dirname(d), { recursive: true, force: true }); return r;
}));
alese.forEach((c, k) => {
const r = rez[k];
if (r.stricat || r.eroare || !Array.isArray(r.rows)) return rand('STRICAT', `${c.nume}: ${r.stricat || 'the probe on the planted copy did not reach its summary: ' + (r.eroare || '')}`);
if (r.rows.length !== martor.rows.length) return rand('STRICAT', `${c.nume}: the planted probe ran ${r.rows.length} rows, the witness ${martor.rows.length}`);
const cazute = r.rows.filter((x) => x.state === 'ROSU').map((x) => x.id).sort(), ast = [...c.asteptate].sort();
const altele = r.rows.filter((x) => x.state !== 'VERDE' && x.state !== 'ROSU' && !nemas.has(x.id)).map((x) => x.id);
const ok = ast.length > 0 && cazute.join() === ast.join() && !altele.length;
rand(ok ? 'VERDE' : 'ROSU', `${c.nume}: ${cazute.length} row(s) red` + (ok ? `, exactly its ${ast.length} (${ast.join(', ')})` : `; required exactly [${ast.join(', ')}], red [${cazute.join(', ')}]` + (altele.length ? `, no longer measured [${altele.join(', ')}]` : '')));
});
if (doar) rand('STRICAT', `${cazuri.length - alese.length} plantings left out by --doar`);
} catch (e) { if (e.message !== 'stop') throw e; } finally { fs.rmSync(T, { recursive: true, force: true }); }
console.log(`\ndevice identity (control): ${verzi} VERDE, ${rosii} ROSU, ${stricate} STRICAT`);
process.exitCode = rosii || stricate ? 1 : 0;