ai-trust/ links six existing proofs (agent identity, model artifact attestation, host runtime record, policy and signed ledger,
Proof of AI envelope, on-chain witness) into one bundle judged by one verifier that checks only the links; 13/13 in the lab,
16/16 live on testnet 28001 (ledger head notarized). proof-of-ai/ is the AIP-23 envelope builder it uses. verifiable-data/
publishes append-only versions with an RFC 9162 root and a sorted index per field, range and equality queries proven correct
and complete, selective disclosure per field, availability by sampling, and the AIP-23 data envelope notarized (version 2 on
testnet 28001). agents/agent-ancora.mjs gains notarizeEnvelope({kind}), the general form of notarizeHead. Each README says what
is not built: zero-knowledge queries need a prover and are not here. No third party has reviewed any of this.
53 lines
4.9 KiB
JavaScript
53 lines
4.9 KiB
JavaScript
// serveste.mjs: the read-only HTTP face of a verifiable dataset (roadmap point 20, "availability"): commitments, records with their
|
|
// inclusion proofs, query answers with their completeness proofs, consistency proofs between versions. Every answer is checkable
|
|
// against a commitment with verifica.mjs, without trusting this server. What this server proves is only that the bytes were served
|
|
// when asked: availability sampling (verifica.mjs disponibilitate) draws random record positions and verifies each against the
|
|
// commitment; a publisher that withholds a record fails the sample that lands on it. This is retrievability under sampling, not an
|
|
// erasure-coded data-availability scheme.
|
|
// GET /v1/vd/<name>/commitments every version's commitment (+ its AIP-23 envelope, notarized or not)
|
|
// GET /v1/vd/<name>/commitment/<n>
|
|
// GET /v1/vd/<name>/record/<n>/<i> one record at version n with its log proof (all fields, or none with --privat)
|
|
// GET /v1/vd/<name>/query/<n>?field=f&eq=J | &from=J&to=J [&fields=a,b|*] [&limit=N] J = a JSON value ("x", 42, true, null)
|
|
// GET /v1/vd/<name>/consistency/<a>/<b>
|
|
// Only GET; JSON; the dataset is re-read when versiuni.json changes; bodies are bounded by the query limit (default 1000 rows).
|
|
import http from 'node:http';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { deschide, versiune, dovadaInregistrare, interogheaza, dovadaConsistenta } from './baza.mjs';
|
|
|
|
const json = (res, cod, o) => { const b = Buffer.from(JSON.stringify(o)); res.writeHead(cod, { 'content-type': 'application/json', 'content-length': b.length, 'cache-control': 'no-store' }); res.end(b); };
|
|
const dinJson = (s) => { if (s === null || s === undefined) return undefined; try { return JSON.parse(s); } catch { throw new Error('a query value is JSON: "x", 42, true or null (got ' + String(s).slice(0, 40) + ')'); } };
|
|
|
|
/** Starts the server; resolves with { server, url, inchide }. `ascunde`: record positions the server withholds (a test hook, for the
|
|
* availability control: a publisher that does not serve a record). */
|
|
export function porneste({ dir, port = 0, host = '127.0.0.1', privat = false, limit = 1000, ascunde = [] } = {}) {
|
|
let db = deschide(dir); let mtime = fs.statSync(path.join(dir, 'versiuni.json')).mtimeMs;
|
|
const plic = (n) => { const f = path.join(dir, 'plicuri', n + '.json'); return fs.existsSync(f) ? JSON.parse(fs.readFileSync(f, 'utf8')) : null; };
|
|
const recarca = () => { const m = fs.statSync(path.join(dir, 'versiuni.json')).mtimeMs; if (m !== mtime) { db = deschide(dir); mtime = m; } };
|
|
const ascunse = new Set(ascunde.map(Number));
|
|
const server = http.createServer((req, res) => {
|
|
try {
|
|
if (req.method !== 'GET') return json(res, 405, { error: 'method_not_allowed' });
|
|
const url = new URL(req.url, 'http://x'); const p = url.pathname.split('/').filter(Boolean);
|
|
if (p[0] !== 'v1' || p[1] !== 'vd' || p.length < 4) return json(res, 404, { error: 'not_found' });
|
|
recarca();
|
|
if (p[2] !== db.set.name) return json(res, 404, { error: 'no_such_dataset' });
|
|
const n = (s) => { const x = Number(s); if (!Number.isInteger(x) || x < 0) throw new Error('not a non-negative integer: ' + s); return x; };
|
|
if (p[3] === 'health') return json(res, 200, { ok: true, name: db.set.name, versions: db.versiuni.length, records: db.inregistrari.length });
|
|
if (p[3] === 'commitments' && p.length === 4) return json(res, 200, { name: db.set.name, commitments: db.versiuni.map((c) => ({ commitment: c, envelope: plic(c.version) })) });
|
|
if (p[3] === 'commitment' && p.length === 5) { const c = versiune(db, n(p[4])); return json(res, 200, { commitment: c, envelope: plic(c.version) }); }
|
|
if (p[3] === 'record' && p.length === 6) { const i = n(p[5]); if (ascunse.has(i)) return json(res, 404, { error: 'record_not_served' }); return json(res, 200, dovadaInregistrare(db, n(p[4]), i, { fields: privat ? [] : '*' })); }
|
|
if (p[3] === 'query' && p.length === 5) {
|
|
const q = url.searchParams; const fields = q.get('fields'); const lim = Math.min(Number(q.get('limit') || limit) || limit, limit);
|
|
return json(res, 200, interogheaza(db, n(p[4]), { field: q.get('field'), eq: dinJson(q.get('eq')), from: dinJson(q.get('from')), to: dinJson(q.get('to')), fields: fields === '*' ? '*' : (fields ? fields.split(',').filter(Boolean) : []), limit: lim }));
|
|
}
|
|
if (p[3] === 'consistency' && p.length === 6) return json(res, 200, dovadaConsistenta(db, n(p[4]), n(p[5])));
|
|
return json(res, 404, { error: 'not_found' });
|
|
} catch (e) { return json(res, 400, { error: 'bad_request', detail: String(e.message).slice(0, 200) }); }
|
|
});
|
|
return new Promise((ok, nu) => {
|
|
server.on('error', nu);
|
|
server.listen(port, host, () => { const a = server.address(); ok({ server, url: `http://${host}:${a.port}/v1/vd/${db.set.name}`, inchide: () => new Promise((r) => server.close(r)) }); });
|
|
});
|
|
}
|