#!/usr/bin/env node // vd.mjs: the publisher's command line of a verifiable dataset (roadmap point 20). Values on the command line are JSON ("x", 42). // node vd.mjs creeaza --nume [--index f1,f2] a new dataset folder // node vd.mjs adauga append records ({id, ...fields}); never rewrites // node vd.mjs angajeaza [--out c.json] a new version: commitment + its AIP-23 envelope (plicuri/.json) // node vd.mjs notarizeaza --rpc URL --key-file f --ethers-dir d [--notary 0x..] // the envelope's statementHash on AereNotary (refuses 2800 unless AERE_CONFIRM_MAINNET=yes) // node vd.mjs interogheaza --camp f (--eq J | [--de-la J] [--pana-la J]) [--campuri a,b|*] [--out answer.json] // node vd.mjs inregistrare [--campuri a,b|*] [--out rec.json] // node vd.mjs consistenta [--out proof.json] // node vd.mjs auditeaza rebuild every version from the records and compare the roots // node vd.mjs serveste [--port 8797] [--privat] the read-only HTTP face (serveste.mjs) import fs from 'node:fs'; import path from 'node:path'; import { createRequire } from 'node:module'; import { creeaza, deschide, adauga, angajeaza, versiune, dovadaInregistrare, interogheaza, dovadaConsistenta, auditeaza, plicAngajament, hashAngajament } from './baza.mjs'; import { notarizeEnvelope } from '../agents/agent-ancora.mjs'; import { porneste } from './serveste.mjs'; const a = process.argv.slice(2); const cmd = a[0]; const dir = a[1]; const opt = (k, d = null) => { const i = a.indexOf(k); return i >= 0 && i + 1 < a.length ? a[i + 1] : d; }; const are = (k) => a.includes(k); const J = (s) => (s === null ? undefined : JSON.parse(s)); const scrie = (o) => { const f = opt('--out'); const t = JSON.stringify(o, null, 1); if (f) { fs.writeFileSync(f, t); console.log('written ' + f); } else console.log(t); }; const plicuri = (d) => { const p = path.join(d, 'plicuri'); fs.mkdirSync(p, { recursive: true }); return p; }; try { if (cmd === 'creeaza') { const db = creeaza(dir, { name: opt('--nume'), indexes: (opt('--index', '') || '').split(',').filter(Boolean) }); console.log('created ' + db.set.name + ' at ' + dir + ' (indexes: ' + (db.set.indexes.join(', ') || 'none') + ')'); } else if (cmd === 'adauga') { const db = deschide(dir); const t = fs.readFileSync(a[2], 'utf8'); const recs = a[2].endsWith('.ndjson') ? t.split('\n').filter(Boolean).map((l) => JSON.parse(l)) : JSON.parse(t); console.log('appended ' + adauga(db, Array.isArray(recs) ? recs : [recs]) + ' records (' + db.inregistrari.length + ' in total)'); } else if (cmd === 'angajeaza') { const db = deschide(dir); const c = angajeaza(db); const env = plicAngajament(c); fs.writeFileSync(path.join(plicuri(dir), c.version + '.json'), JSON.stringify(env, null, 1)); console.log(`version ${c.version}: ${c.size} records, log root ${c.logRoot.slice(0, 18)}.., commitment ${hashAngajament(c).slice(0, 18)}.., envelope ${env.statementHash.slice(0, 18)}..`); scrie(c); } else if (cmd === 'notarizeaza') { const db = deschide(dir); const c = versiune(db, a[2]); const f = path.join(dir, 'plicuri', c.version + '.json'); if (!fs.existsSync(f)) throw new Error('no envelope for version ' + c.version + ' (run angajeaza first)'); const env = JSON.parse(fs.readFileSync(f, 'utf8')); if (env.notarization) { console.log('already notarized: firstSeen ' + env.notarization.firstSeen); process.exitCode = 0; } else { const ethersDir = opt('--ethers-dir'); if (!ethersDir || !opt('--rpc') || !opt('--key-file')) throw new Error('--rpc, --key-file and --ethers-dir are required'); const { ethers } = createRequire(path.join(path.resolve(ethersDir), 'package.json'))('ethers'); const r = await notarizeEnvelope({ envelope: env, kind: 'aere-proof-of-data', rpc: opt('--rpc'), keyFile: opt('--key-file'), ethers, notary: opt('--notary') }); if (!r.ok) { console.log(r.motiv); process.exitCode = r.cod || 1; } else { fs.writeFileSync(f, JSON.stringify(r.envelope, null, 1)); const n = r.envelope.notarization; console.log(`notarized on chain ${n.chainId}: firstSeen ${n.firstSeen}${n.txHash ? ', tx ' + n.txHash + ' block ' + n.block : ' (already)'}`); } } } else if (cmd === 'interogheaza') { const db = deschide(dir); const campuri = opt('--campuri', ''); scrie(interogheaza(db, a[2], { field: opt('--camp'), eq: J(opt('--eq')), from: J(opt('--de-la')), to: J(opt('--pana-la')), fields: campuri === '*' ? '*' : campuri.split(',').filter(Boolean), limit: Number(opt('--limita', 1000)) })); } else if (cmd === 'inregistrare') { const db = deschide(dir); const campuri = opt('--campuri', '*'); scrie(dovadaInregistrare(db, a[2], Number(a[3]), { fields: campuri === '*' ? '*' : campuri.split(',').filter(Boolean) })); } else if (cmd === 'consistenta') { const db = deschide(dir); scrie(dovadaConsistenta(db, a[2], a[3])); } else if (cmd === 'auditeaza') { const db = deschide(dir); const r = auditeaza(db); console.log(r.ok ? `AUDIT OK: ${db.versiuni.length} versions rebuilt from ${db.inregistrari.length} records, every root matches` : 'AUDIT FAILED:\n ' + r.probleme.join('\n ')); process.exitCode = r.ok ? 0 : 1; } else if (cmd === 'serveste') { const s = await porneste({ dir, port: Number(opt('--port', 8797)), privat: are('--privat') }); console.log('serving ' + s.url + (are('--privat') ? ' (records without field values)' : '')); } else { console.log('usage: vd.mjs creeaza|adauga|angajeaza|notarizeaza|interogheaza|inregistrare|consistenta|auditeaza|serveste ... (see the header)'); process.exitCode = 2; } } catch (e) { console.log('ERROR: ' + String(e.message).slice(0, 300)); process.exitCode = 1; }