verdifax 0.2.11 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +158 -5
- package/dist/index.d.mts +443 -9
- package/dist/index.d.ts +443 -9
- package/dist/index.js +542 -23
- package/dist/index.mjs +535 -23
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -23,10 +23,25 @@ __export(index_exports, {
|
|
|
23
23
|
APIError: () => APIError,
|
|
24
24
|
APIKeyRequiredError: () => APIKeyRequiredError,
|
|
25
25
|
AuthError: () => AuthError,
|
|
26
|
+
CRES_ENGINE_VERSION: () => CRES_ENGINE_VERSION,
|
|
27
|
+
CRES_PREIMAGE_VERSION: () => CRES_PREIMAGE_VERSION,
|
|
28
|
+
DCAE_PREIMAGE_VERSION: () => DCAE_PREIMAGE_VERSION,
|
|
29
|
+
DSE_FORMULA_VERSION: () => DSE_FORMULA_VERSION,
|
|
30
|
+
MAX_BATCH_ROWS: () => MAX_BATCH_ROWS,
|
|
26
31
|
NotFoundError: () => NotFoundError,
|
|
32
|
+
RUN_STATUSES: () => RUN_STATUSES,
|
|
27
33
|
VerdifaxClient: () => VerdifaxClient,
|
|
28
34
|
VerdifaxError: () => VerdifaxError,
|
|
35
|
+
captureEnvironment: () => captureEnvironment,
|
|
36
|
+
cresReceiptHash: () => cresReceiptHash,
|
|
37
|
+
dcaeClosureHash: () => dcaeClosureHash,
|
|
38
|
+
dseDispatchHash: () => dseDispatchHash,
|
|
39
|
+
formatProviderPayload: () => formatProviderPayload,
|
|
29
40
|
fromEnv: () => fromEnv,
|
|
41
|
+
generateRequestKeyPair: () => generateRequestKeyPair,
|
|
42
|
+
requestKeyRegistrationBody: () => requestKeyRegistrationBody,
|
|
43
|
+
requestSignaturePreimage: () => requestSignaturePreimage,
|
|
44
|
+
signRequest: () => signRequest,
|
|
30
45
|
validateHex64: () => validateHex64,
|
|
31
46
|
validateRouteId: () => validateRouteId
|
|
32
47
|
});
|
|
@@ -75,6 +90,127 @@ var APIKeyRequiredError = class extends VerdifaxError {
|
|
|
75
90
|
}
|
|
76
91
|
};
|
|
77
92
|
|
|
93
|
+
// src/signing.ts
|
|
94
|
+
var import_node_crypto = require("crypto");
|
|
95
|
+
var REQUEST_SIGNATURE_FORM = "verdifax.request.v1";
|
|
96
|
+
var REQUEST_KEY_PROOF_FORM = "verdifax.request-key.v1";
|
|
97
|
+
var SIGNING_KEY_BYTES = 32;
|
|
98
|
+
var PKCS8_ED25519_HEADER = Buffer.from(
|
|
99
|
+
"302e020100300506032b657004220420",
|
|
100
|
+
"hex"
|
|
101
|
+
);
|
|
102
|
+
function rawPublicKey(key) {
|
|
103
|
+
return Buffer.from(key.export({ format: "der", type: "spki" }).subarray(-32));
|
|
104
|
+
}
|
|
105
|
+
function generateRequestKeyPair() {
|
|
106
|
+
const { publicKey, privateKey } = (0, import_node_crypto.generateKeyPairSync)("ed25519");
|
|
107
|
+
return {
|
|
108
|
+
publicKey: rawPublicKey(publicKey),
|
|
109
|
+
privateKey: Buffer.from(
|
|
110
|
+
privateKey.export({ format: "der", type: "pkcs8" }).subarray(-32)
|
|
111
|
+
)
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function loadSigningKey(key) {
|
|
115
|
+
if (key instanceof import_node_crypto.KeyObject) return key;
|
|
116
|
+
if (!(key instanceof Uint8Array) || key.length !== SIGNING_KEY_BYTES) {
|
|
117
|
+
throw new VerdifaxError(
|
|
118
|
+
"verdifax: signingKey must be the 32 raw bytes of an Ed25519 private key"
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
return (0, import_node_crypto.createPrivateKey)({
|
|
122
|
+
key: Buffer.concat([PKCS8_ED25519_HEADER, key]),
|
|
123
|
+
format: "der",
|
|
124
|
+
type: "pkcs8"
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function requestSignaturePreimage(fields) {
|
|
128
|
+
return Buffer.from(
|
|
129
|
+
[
|
|
130
|
+
REQUEST_SIGNATURE_FORM,
|
|
131
|
+
fields.payload_hash,
|
|
132
|
+
fields.program_id,
|
|
133
|
+
fields.route_id,
|
|
134
|
+
fields.registry_record_hash,
|
|
135
|
+
fields.signed_at,
|
|
136
|
+
fields.nonce
|
|
137
|
+
].join("\0"),
|
|
138
|
+
"utf8"
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
function signRequest(signingKey, req, options = {}) {
|
|
142
|
+
const key = loadSigningKey(signingKey);
|
|
143
|
+
const signed_at = options.signedAt ?? (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "Z");
|
|
144
|
+
const nonce = options.nonce ?? (0, import_node_crypto.randomBytes)(16).toString("hex");
|
|
145
|
+
const payload_hash = (0, import_node_crypto.createHash)("sha256").update(Buffer.from(req.payload_text || req.payload || "", "utf8")).digest("hex");
|
|
146
|
+
const preimage = requestSignaturePreimage({
|
|
147
|
+
payload_hash,
|
|
148
|
+
program_id: req.program_id,
|
|
149
|
+
route_id: req.route_id,
|
|
150
|
+
registry_record_hash: req.registry_record_hash,
|
|
151
|
+
signed_at,
|
|
152
|
+
nonce
|
|
153
|
+
});
|
|
154
|
+
return {
|
|
155
|
+
signature: (0, import_node_crypto.sign)(null, preimage, key).toString("base64"),
|
|
156
|
+
signed_at,
|
|
157
|
+
nonce
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function requestKeyRegistrationBody(signingKey) {
|
|
161
|
+
const key = loadSigningKey(signingKey);
|
|
162
|
+
const public_key = rawPublicKey((0, import_node_crypto.createPublicKey)(key)).toString("base64");
|
|
163
|
+
const proof = (0, import_node_crypto.sign)(
|
|
164
|
+
null,
|
|
165
|
+
Buffer.from(`${REQUEST_KEY_PROOF_FORM}\0${public_key}`, "utf8"),
|
|
166
|
+
key
|
|
167
|
+
).toString("base64");
|
|
168
|
+
return { public_key, proof };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/helpers.ts
|
|
172
|
+
function formatProviderPayload(provider, prompt, response) {
|
|
173
|
+
if ([provider, prompt, response].some((text) => /[\uD800-\uDFFF]/u.test(text))) {
|
|
174
|
+
throw new VerdifaxError(
|
|
175
|
+
"verdifax: provider payload text cannot be encoded as UTF-8"
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
return JSON.stringify({ prompt, provider, response });
|
|
179
|
+
}
|
|
180
|
+
function isProvided(value) {
|
|
181
|
+
return value !== null && value !== void 0 && value !== "" && value !== false;
|
|
182
|
+
}
|
|
183
|
+
function withAttestedFlag(context) {
|
|
184
|
+
const provided = Object.entries(context).some(
|
|
185
|
+
([name, value]) => name !== "attested" && isProvided(value)
|
|
186
|
+
);
|
|
187
|
+
return { ...context, attested: provided };
|
|
188
|
+
}
|
|
189
|
+
function withDeclaredFlag(context) {
|
|
190
|
+
const declared = Object.entries(context).some(
|
|
191
|
+
([name, value]) => name !== "declared" && isProvided(value) && !(Array.isArray(value) && value.length === 0)
|
|
192
|
+
);
|
|
193
|
+
return { ...context, declared };
|
|
194
|
+
}
|
|
195
|
+
function refuseCallerOriginHashes(substance) {
|
|
196
|
+
const origin = substance?.origin;
|
|
197
|
+
if (origin?.source_ip_hash || origin?.user_agent_hash) {
|
|
198
|
+
throw new VerdifaxError(
|
|
199
|
+
"verdifax: request_substance.origin.source_ip_hash and user_agent_hash are computed by the server and cannot be supplied"
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function withExecuteFlags(req) {
|
|
204
|
+
const body = { ...req };
|
|
205
|
+
if (req.attested_context) {
|
|
206
|
+
body.attested_context = withAttestedFlag(req.attested_context);
|
|
207
|
+
}
|
|
208
|
+
if (req.reproducibility_context) {
|
|
209
|
+
body.reproducibility_context = withDeclaredFlag(req.reproducibility_context);
|
|
210
|
+
}
|
|
211
|
+
return body;
|
|
212
|
+
}
|
|
213
|
+
|
|
78
214
|
// src/validation.ts
|
|
79
215
|
function validateHex64(value, fieldName) {
|
|
80
216
|
if (typeof value !== "string" || value.length !== 64) {
|
|
@@ -105,7 +241,7 @@ function validateRouteId(value) {
|
|
|
105
241
|
}
|
|
106
242
|
|
|
107
243
|
// src/version.ts
|
|
108
|
-
var SDK_VERSION = "0.
|
|
244
|
+
var SDK_VERSION = "1.0.0";
|
|
109
245
|
|
|
110
246
|
// src/client.ts
|
|
111
247
|
var DEFAULT_BASE_URL = "https://api.verdifax.com";
|
|
@@ -114,7 +250,11 @@ var DEFAULT_MAX_RETRIES = 3;
|
|
|
114
250
|
var DEFAULT_RETRY_BASE_DELAY_MS = 200;
|
|
115
251
|
var SDK_USER_AGENT = `verdifax-sdk-node/${SDK_VERSION}`;
|
|
116
252
|
var HEADER_API_KEY = "X-Verdifax-Key";
|
|
253
|
+
var REQUEST_KEY_PATH = "/keys/request-key";
|
|
254
|
+
var HEADER_EXPORT_TOTAL = "X-Verdifax-Total";
|
|
255
|
+
var HEADER_EXPORT_TRUNCATED = "X-Verdifax-Truncated";
|
|
117
256
|
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
|
|
257
|
+
var MAX_BATCH_ROWS = 1e3;
|
|
118
258
|
var VerdifaxClient = class {
|
|
119
259
|
baseURL;
|
|
120
260
|
apiKey;
|
|
@@ -122,6 +262,7 @@ var VerdifaxClient = class {
|
|
|
122
262
|
userAgent;
|
|
123
263
|
maxRetries;
|
|
124
264
|
retryBaseDelayMs;
|
|
265
|
+
signingKey;
|
|
125
266
|
constructor(config = {}) {
|
|
126
267
|
this.baseURL = (config.baseURL ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
127
268
|
this.apiKey = config.apiKey ?? "";
|
|
@@ -129,6 +270,7 @@ var VerdifaxClient = class {
|
|
|
129
270
|
this.userAgent = config.userAgent ?? SDK_USER_AGENT;
|
|
130
271
|
this.maxRetries = config.maxRetries === void 0 ? DEFAULT_MAX_RETRIES : Math.max(0, config.maxRetries);
|
|
131
272
|
this.retryBaseDelayMs = config.retryBaseDelayMs ?? DEFAULT_RETRY_BASE_DELAY_MS;
|
|
273
|
+
this.signingKey = config.signingKey === void 0 ? void 0 : loadSigningKey(config.signingKey);
|
|
132
274
|
}
|
|
133
275
|
// ── Health + Version (anonymous) ──────────────────────────────
|
|
134
276
|
async health() {
|
|
@@ -137,20 +279,52 @@ var VerdifaxClient = class {
|
|
|
137
279
|
async version() {
|
|
138
280
|
return this.request("GET", "/version");
|
|
139
281
|
}
|
|
282
|
+
/** The server's own API description. */
|
|
283
|
+
async getOpenAPI() {
|
|
284
|
+
return this.request("GET", "/openapi.json");
|
|
285
|
+
}
|
|
286
|
+
/** Run counts for this API key. */
|
|
287
|
+
async getStats() {
|
|
288
|
+
this.requireAPIKey();
|
|
289
|
+
return this.request("GET", "/stats");
|
|
290
|
+
}
|
|
140
291
|
// ── Execute / Attest (auth) ───────────────────────────────────
|
|
141
292
|
async execute(req) {
|
|
293
|
+
this.requireExecuteFields(req);
|
|
294
|
+
return this.request("POST", "/execute", this.prepared(req));
|
|
295
|
+
}
|
|
296
|
+
/** Runs one payload twice on the server and compares the manifest
|
|
297
|
+
* hashes. One signature covers the request, and the server claims
|
|
298
|
+
* its nonce once. Both runs are recorded. */
|
|
299
|
+
async verifyDeterminism(req) {
|
|
300
|
+
this.requireExecuteFields(req);
|
|
301
|
+
return this.request(
|
|
302
|
+
"POST",
|
|
303
|
+
"/execute/verify-determinism",
|
|
304
|
+
this.prepared(req)
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
/** Sends up to {@link MAX_BATCH_ROWS} runs in one request. With a
|
|
308
|
+
* signing key each row carries its own signature and nonce. A row
|
|
309
|
+
* that fails comes back in `results` with `ok: false`, so the call
|
|
310
|
+
* does not throw for it. The rows are not checked here: the server
|
|
311
|
+
* reports a bad row in that row's result.
|
|
312
|
+
*
|
|
313
|
+
* This is one POST and it is never retried. After a timeout the
|
|
314
|
+
* server may have run some or all of the rows. */
|
|
315
|
+
async executeBatch(runs) {
|
|
142
316
|
this.requireAPIKey();
|
|
143
|
-
if (!
|
|
144
|
-
throw new VerdifaxError(
|
|
145
|
-
"verdifax: ExecuteRequest needs payload or payload_text"
|
|
146
|
-
);
|
|
317
|
+
if (!Array.isArray(runs) || runs.length === 0) {
|
|
318
|
+
throw new VerdifaxError("verdifax: executeBatch needs at least one run");
|
|
147
319
|
}
|
|
148
|
-
if (
|
|
320
|
+
if (runs.length > MAX_BATCH_ROWS) {
|
|
149
321
|
throw new VerdifaxError(
|
|
150
|
-
|
|
322
|
+
`verdifax: executeBatch accepts at most ${MAX_BATCH_ROWS} runs (got ${runs.length})`
|
|
151
323
|
);
|
|
152
324
|
}
|
|
153
|
-
return this.request("POST", "/execute",
|
|
325
|
+
return this.request("POST", "/execute/batch", {
|
|
326
|
+
runs: runs.map((run) => this.prepared(run))
|
|
327
|
+
});
|
|
154
328
|
}
|
|
155
329
|
/** High-level convenience wrapper around `execute()`. Accepts
|
|
156
330
|
* either a Buffer (for binary payloads, auto-base64) or a
|
|
@@ -176,7 +350,12 @@ var VerdifaxClient = class {
|
|
|
176
350
|
route_id: req.route_id,
|
|
177
351
|
registry_record_hash: req.registry_record_hash,
|
|
178
352
|
ai_output_text: req.ai_output_text,
|
|
179
|
-
attested_context: req.attested_context
|
|
353
|
+
attested_context: req.attested_context,
|
|
354
|
+
reproducibility_context: req.reproducibility_context,
|
|
355
|
+
request_substance: req.request_substance,
|
|
356
|
+
authorization_chain: req.authorization_chain,
|
|
357
|
+
regulatory_scaffolding: req.regulatory_scaffolding,
|
|
358
|
+
causal_graph: req.causal_graph
|
|
180
359
|
};
|
|
181
360
|
if (req.payload !== void 0) {
|
|
182
361
|
body.payload = Buffer.isBuffer(req.payload) ? req.payload.toString("base64") : Buffer.from(req.payload).toString("base64");
|
|
@@ -185,6 +364,29 @@ var VerdifaxClient = class {
|
|
|
185
364
|
}
|
|
186
365
|
return this.execute(body);
|
|
187
366
|
}
|
|
367
|
+
// ── Request signing (auth) ───────────────────────────────────
|
|
368
|
+
/** Registers this client's public key on its API key, with the proof
|
|
369
|
+
* that the client holds the private key. From then on the server
|
|
370
|
+
* refuses a run on this API key without a request signature. */
|
|
371
|
+
async registerRequestKey() {
|
|
372
|
+
this.requireAPIKey();
|
|
373
|
+
if (!this.signingKey) {
|
|
374
|
+
throw new VerdifaxError(
|
|
375
|
+
"verdifax: registerRequestKey needs the signingKey client option"
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
return this.request(
|
|
379
|
+
"POST",
|
|
380
|
+
REQUEST_KEY_PATH,
|
|
381
|
+
requestKeyRegistrationBody(this.signingKey)
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
/** Clears the registered public key, so the server stops requiring
|
|
385
|
+
* request signatures on this API key. */
|
|
386
|
+
async clearRequestKey() {
|
|
387
|
+
this.requireAPIKey();
|
|
388
|
+
await this.request("DELETE", REQUEST_KEY_PATH);
|
|
389
|
+
}
|
|
188
390
|
// ── Runs ──────────────────────────────────────────────────────
|
|
189
391
|
async getRun(runId) {
|
|
190
392
|
this.requireAPIKey();
|
|
@@ -210,6 +412,94 @@ var VerdifaxClient = class {
|
|
|
210
412
|
const out = await this.request("GET", path);
|
|
211
413
|
return { runs: out.runs ?? [], total: out.total ?? 0 };
|
|
212
414
|
}
|
|
415
|
+
/** Searches this key's runs. `manifest_hash` matches a prefix. The
|
|
416
|
+
* server ignores a `from` or `to` it cannot parse here, unlike
|
|
417
|
+
* {@link exportRuns}, which refuses one. */
|
|
418
|
+
async searchRuns(params = {}) {
|
|
419
|
+
this.requireAPIKey();
|
|
420
|
+
const q = new URLSearchParams();
|
|
421
|
+
for (const name of [
|
|
422
|
+
"manifest_hash",
|
|
423
|
+
"program_id",
|
|
424
|
+
"route_id",
|
|
425
|
+
"status",
|
|
426
|
+
"from",
|
|
427
|
+
"to"
|
|
428
|
+
]) {
|
|
429
|
+
const value = params[name];
|
|
430
|
+
if (value) q.set(name, value);
|
|
431
|
+
}
|
|
432
|
+
if (params.limit && params.limit > 0) {
|
|
433
|
+
q.set("limit", String(params.limit));
|
|
434
|
+
}
|
|
435
|
+
if (params.offset && params.offset > 0) {
|
|
436
|
+
q.set("offset", String(params.offset));
|
|
437
|
+
}
|
|
438
|
+
const qs = q.toString();
|
|
439
|
+
const path = qs ? `/runs/search?${qs}` : "/runs/search";
|
|
440
|
+
const out = await this.request("GET", path);
|
|
441
|
+
return { runs: out.runs ?? [], total: out.total ?? 0 };
|
|
442
|
+
}
|
|
443
|
+
/** Lists what the server holds for this key in a period, failures
|
|
444
|
+
* included, up to 10,000 rows. `truncated` is true when the period
|
|
445
|
+
* holds more. The list states what was stored, not a verdict, and a
|
|
446
|
+
* call that never reached Verdifax cannot appear in it. */
|
|
447
|
+
async exportRuns(params = {}) {
|
|
448
|
+
this.requireAPIKey();
|
|
449
|
+
return this.request("GET", this.exportPath(params, "json"));
|
|
450
|
+
}
|
|
451
|
+
/** The same rows as {@link exportRuns}: the raw CSV text with `total`
|
|
452
|
+
* and `truncated` from the response headers. Check `truncated`: when
|
|
453
|
+
* true, the CSV is missing rows for the period. Throws when the server
|
|
454
|
+
* does not send those headers, so that a cut-off export cannot pass
|
|
455
|
+
* for a complete one. */
|
|
456
|
+
async exportRunsCSV(params = {}) {
|
|
457
|
+
this.requireAPIKey();
|
|
458
|
+
const { text, headers } = await this.requestRaw(
|
|
459
|
+
this.exportPath(params, "csv")
|
|
460
|
+
);
|
|
461
|
+
const total = Number.parseInt(headers.get(HEADER_EXPORT_TOTAL) ?? "", 10);
|
|
462
|
+
const truncated = headers.get(HEADER_EXPORT_TRUNCATED);
|
|
463
|
+
if (!Number.isSafeInteger(total) || truncated !== "true" && truncated !== "false") {
|
|
464
|
+
throw new VerdifaxError(
|
|
465
|
+
`verdifax: the server did not report ${HEADER_EXPORT_TOTAL} and ${HEADER_EXPORT_TRUNCATED}, so this CSV may be cut off; use exportRuns instead`
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
return { csv: text, total, truncated: truncated === "true" };
|
|
469
|
+
}
|
|
470
|
+
exportPath(params, format) {
|
|
471
|
+
const q = new URLSearchParams();
|
|
472
|
+
if (params.from) q.set("from", params.from);
|
|
473
|
+
if (params.to) q.set("to", params.to);
|
|
474
|
+
q.set("format", format);
|
|
475
|
+
return `/runs/export?${q.toString()}`;
|
|
476
|
+
}
|
|
477
|
+
// ── Attestation receipts (auth) ──────────────────────────────
|
|
478
|
+
async listAttestations(params = {}) {
|
|
479
|
+
this.requireAPIKey();
|
|
480
|
+
const q = new URLSearchParams();
|
|
481
|
+
if (params.limit && params.limit > 0) {
|
|
482
|
+
q.set("limit", String(params.limit));
|
|
483
|
+
}
|
|
484
|
+
if (params.offset && params.offset > 0) {
|
|
485
|
+
q.set("offset", String(params.offset));
|
|
486
|
+
}
|
|
487
|
+
const qs = q.toString();
|
|
488
|
+
return this.request(
|
|
489
|
+
"GET",
|
|
490
|
+
qs ? `/attestations?${qs}` : "/attestations"
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
async getAttestation(attestationId) {
|
|
494
|
+
this.requireAPIKey();
|
|
495
|
+
if (attestationId <= 0) {
|
|
496
|
+
throw new VerdifaxError("verdifax: attestationId must be > 0");
|
|
497
|
+
}
|
|
498
|
+
return this.request(
|
|
499
|
+
"GET",
|
|
500
|
+
`/attestations/${attestationId}`
|
|
501
|
+
);
|
|
502
|
+
}
|
|
213
503
|
// ── Sealed-artifact retrieval (auth) ─────────────────────────
|
|
214
504
|
async getAllowToken(runId) {
|
|
215
505
|
return this.getRunArtifact(runId, "allow-token");
|
|
@@ -226,6 +516,39 @@ var VerdifaxClient = class {
|
|
|
226
516
|
async getAivpT4HaltReceipt(runId) {
|
|
227
517
|
return this.getRunArtifact(runId, "aivp-t4-halt-receipt");
|
|
228
518
|
}
|
|
519
|
+
async getAttestationGapReceipt(runId) {
|
|
520
|
+
return this.getRunArtifact(runId, "attestation-gap-receipt");
|
|
521
|
+
}
|
|
522
|
+
async getCESAuditRecord(runId) {
|
|
523
|
+
return this.getRunArtifact(runId, "ces-audit-record");
|
|
524
|
+
}
|
|
525
|
+
/** The audit report for a run, as JSON. */
|
|
526
|
+
async getRunReport(runId) {
|
|
527
|
+
const out = await this.getRunArtifact(runId, "report");
|
|
528
|
+
return out.report;
|
|
529
|
+
}
|
|
530
|
+
/** The run's sealed audit bundle as the exact JSON text the server
|
|
531
|
+
* sent, to save and check with `verdifax-verify`. It is not parsed:
|
|
532
|
+
* JSON.parse would round a number above 2^53 and the recomputed hash
|
|
533
|
+
* would stop matching. */
|
|
534
|
+
async getRunArtifacts(runId) {
|
|
535
|
+
this.requireAPIKey();
|
|
536
|
+
if (runId <= 0) throw new VerdifaxError("verdifax: runId must be > 0");
|
|
537
|
+
return (await this.requestRaw(`/runs/${runId}/artifacts`)).text;
|
|
538
|
+
}
|
|
539
|
+
/** The public audit bundle of a record published by run id, as the
|
|
540
|
+
* exact JSON text the server sent. Needs no API key. */
|
|
541
|
+
async getPublicBundle(runId) {
|
|
542
|
+
if (runId <= 0) throw new VerdifaxError("verdifax: runId must be > 0");
|
|
543
|
+
return (await this.requestRaw(`/runs/${runId}/bundle.json`)).text;
|
|
544
|
+
}
|
|
545
|
+
/** The public audit bundle a share token points to, as the exact JSON
|
|
546
|
+
* text the server sent. Needs no API key. */
|
|
547
|
+
async getPublicBundleByToken(token) {
|
|
548
|
+
if (!token) throw new VerdifaxError("verdifax: token is required");
|
|
549
|
+
const path = `/public/runs/${encodeURIComponent(token)}/bundle.json`;
|
|
550
|
+
return (await this.requestRaw(path)).text;
|
|
551
|
+
}
|
|
229
552
|
async getRunArtifact(runId, suffix) {
|
|
230
553
|
this.requireAPIKey();
|
|
231
554
|
if (runId <= 0) throw new VerdifaxError("verdifax: runId must be > 0");
|
|
@@ -312,13 +635,15 @@ var VerdifaxClient = class {
|
|
|
312
635
|
await this.request("DELETE", `/admin/keys/${keyId}`);
|
|
313
636
|
}
|
|
314
637
|
// ── Provider helpers (Claude / OpenAI) ───────────────────────
|
|
315
|
-
/** Convenience for attesting an Anthropic Claude API response.
|
|
316
|
-
*
|
|
317
|
-
* by
|
|
638
|
+
/** Convenience for attesting an Anthropic Claude API response. With
|
|
639
|
+
* `govern: true` the response text also goes to the AIVP review as
|
|
640
|
+
* `ai_output_text`; that is off by default, so the text leaves your
|
|
641
|
+
* machine for review only when you ask. */
|
|
318
642
|
async attestClaudeResponse(args) {
|
|
319
643
|
return this.attestProviderResponse({ ...args, provider: "anthropic" });
|
|
320
644
|
}
|
|
321
|
-
/** Convenience for attesting an OpenAI API response.
|
|
645
|
+
/** Convenience for attesting an OpenAI API response. `govern` works as
|
|
646
|
+
* in {@link attestClaudeResponse}. */
|
|
322
647
|
async attestOpenAIResponse(args) {
|
|
323
648
|
return this.attestProviderResponse({ ...args, provider: "openai" });
|
|
324
649
|
}
|
|
@@ -328,17 +653,16 @@ var VerdifaxClient = class {
|
|
|
328
653
|
"verdifax: response is required for attestation"
|
|
329
654
|
);
|
|
330
655
|
}
|
|
331
|
-
const payloadText = JSON.stringify({
|
|
332
|
-
provider: args.provider,
|
|
333
|
-
prompt: args.prompt,
|
|
334
|
-
response: args.response
|
|
335
|
-
});
|
|
336
656
|
return this.attest({
|
|
337
|
-
payload_text:
|
|
657
|
+
payload_text: formatProviderPayload(
|
|
658
|
+
args.provider,
|
|
659
|
+
args.prompt,
|
|
660
|
+
args.response
|
|
661
|
+
),
|
|
338
662
|
program_id: args.program_id,
|
|
339
663
|
route_id: args.route_id,
|
|
340
664
|
registry_record_hash: args.registry_record_hash,
|
|
341
|
-
ai_output_text: args.response,
|
|
665
|
+
ai_output_text: args.govern ? args.response : void 0,
|
|
342
666
|
attested_context: {
|
|
343
667
|
actor_role: "ai_assistant",
|
|
344
668
|
model_provider: args.provider
|
|
@@ -346,7 +670,7 @@ var VerdifaxClient = class {
|
|
|
346
670
|
});
|
|
347
671
|
}
|
|
348
672
|
// ── HTTP plumbing, retry-aware ──────────────────────────────
|
|
349
|
-
async request(method, path, body) {
|
|
673
|
+
async request(method, path, body, rawText = false) {
|
|
350
674
|
const url = this.baseURL + path;
|
|
351
675
|
const headers = {
|
|
352
676
|
"User-Agent": this.userAgent
|
|
@@ -403,6 +727,7 @@ var VerdifaxClient = class {
|
|
|
403
727
|
throw new APIError(resp.status, path, errMessage);
|
|
404
728
|
}
|
|
405
729
|
}
|
|
730
|
+
if (rawText) return { text, headers: resp.headers };
|
|
406
731
|
if (text.length === 0) return void 0;
|
|
407
732
|
try {
|
|
408
733
|
return JSON.parse(text);
|
|
@@ -416,17 +741,44 @@ var VerdifaxClient = class {
|
|
|
416
741
|
`verdifax: retry budget exhausted${lastErr ? `: ${String(lastErr)}` : ""}`
|
|
417
742
|
);
|
|
418
743
|
}
|
|
744
|
+
/** A GET whose body is kept as sent, retried like any other GET. */
|
|
745
|
+
requestRaw(path) {
|
|
746
|
+
return this.request("GET", path, void 0, true);
|
|
747
|
+
}
|
|
419
748
|
requireAPIKey() {
|
|
420
749
|
if (!this.apiKey) throw new APIKeyRequiredError();
|
|
421
750
|
}
|
|
751
|
+
requireExecuteFields(req) {
|
|
752
|
+
this.requireAPIKey();
|
|
753
|
+
if (!req.payload && !req.payload_text) {
|
|
754
|
+
throw new VerdifaxError(
|
|
755
|
+
"verdifax: ExecuteRequest needs payload or payload_text"
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
if (!req.program_id || !req.route_id || !req.registry_record_hash) {
|
|
759
|
+
throw new VerdifaxError(
|
|
760
|
+
"verdifax: program_id, route_id, and registry_record_hash are required"
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
validateHex64(req.program_id, "program_id");
|
|
764
|
+
validateHex64(req.registry_record_hash, "registry_record_hash");
|
|
765
|
+
validateRouteId(req.route_id);
|
|
766
|
+
refuseCallerOriginHashes(req.request_substance);
|
|
767
|
+
}
|
|
768
|
+
/** The request with its derived flags and, with a signing key, a fresh
|
|
769
|
+
* signature and nonce, on a copy. */
|
|
770
|
+
prepared(req) {
|
|
771
|
+
const body = withExecuteFlags(req);
|
|
772
|
+
return this.signingKey ? { ...body, request_signature: signRequest(this.signingKey, body) } : body;
|
|
773
|
+
}
|
|
422
774
|
};
|
|
423
775
|
|
|
424
776
|
// src/config.ts
|
|
425
777
|
function fromEnv() {
|
|
426
778
|
const cfg = {
|
|
427
|
-
baseURL: process.env.VERDIFAX_BASE_URL
|
|
779
|
+
baseURL: process.env.VERDIFAX_BASE_URL || process.env.VERDIFAX_API_URL || void 0,
|
|
428
780
|
apiKey: process.env.VERDIFAX_API_KEY ?? void 0,
|
|
429
|
-
userAgent: process.env.VERDIFAX_USER_AGENT
|
|
781
|
+
userAgent: process.env.VERDIFAX_USER_AGENT || void 0
|
|
430
782
|
};
|
|
431
783
|
const timeoutRaw = process.env.VERDIFAX_TIMEOUT_MS;
|
|
432
784
|
if (timeoutRaw !== void 0 && timeoutRaw !== "") {
|
|
@@ -450,15 +802,182 @@ function fromEnv() {
|
|
|
450
802
|
}
|
|
451
803
|
return new VerdifaxClient(cfg);
|
|
452
804
|
}
|
|
805
|
+
|
|
806
|
+
// src/environment.ts
|
|
807
|
+
var import_node_child_process = require("child_process");
|
|
808
|
+
var import_node_fs = require("fs");
|
|
809
|
+
var import_node_path = require("path");
|
|
810
|
+
var HASH_64 = /^[0-9a-f]{64}$/;
|
|
811
|
+
var GIT_SHA = /^([0-9a-f]{40}|[0-9a-f]{64})$/;
|
|
812
|
+
var PLATFORM_NAMES = { win32: "windows" };
|
|
813
|
+
var ARCH_NAMES = { x64: "amd64", ia32: "386" };
|
|
814
|
+
function packageVersion(dir) {
|
|
815
|
+
try {
|
|
816
|
+
const pkg = JSON.parse((0, import_node_fs.readFileSync)((0, import_node_path.join)(dir, "package.json"), "utf8"));
|
|
817
|
+
return pkg.name && pkg.version ? `${pkg.name}==${pkg.version}` : void 0;
|
|
818
|
+
} catch {
|
|
819
|
+
return void 0;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
function packageDirs(root) {
|
|
823
|
+
const dirs = [];
|
|
824
|
+
for (const entry of (0, import_node_fs.readdirSync)(root)) {
|
|
825
|
+
if (entry.startsWith(".")) continue;
|
|
826
|
+
if (!entry.startsWith("@")) {
|
|
827
|
+
dirs.push((0, import_node_path.join)(root, entry));
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
for (const scoped of (0, import_node_fs.readdirSync)((0, import_node_path.join)(root, entry))) {
|
|
831
|
+
dirs.push((0, import_node_path.join)(root, entry, scoped));
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
return dirs;
|
|
835
|
+
}
|
|
836
|
+
function detectDependencies() {
|
|
837
|
+
try {
|
|
838
|
+
const pins = packageDirs((0, import_node_path.join)(process.cwd(), "node_modules")).map(packageVersion).filter((pin) => pin !== void 0);
|
|
839
|
+
pins.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
840
|
+
return pins.length > 0 ? pins : void 0;
|
|
841
|
+
} catch {
|
|
842
|
+
return void 0;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
function detectGitSHA() {
|
|
846
|
+
try {
|
|
847
|
+
const sha = (0, import_node_child_process.execFileSync)("git", ["rev-parse", "HEAD"], {
|
|
848
|
+
encoding: "utf8",
|
|
849
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
850
|
+
timeout: 2e3
|
|
851
|
+
}).trim().toLowerCase();
|
|
852
|
+
return GIT_SHA.test(sha) ? sha : void 0;
|
|
853
|
+
} catch {
|
|
854
|
+
return void 0;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
function detectContainerHash() {
|
|
858
|
+
try {
|
|
859
|
+
const text = (0, import_node_fs.readFileSync)("/proc/self/cgroup", "utf8");
|
|
860
|
+
return text.split("\n").flatMap((line) => line.split("/")).map((segment) => segment.trim()).find((segment) => HASH_64.test(segment));
|
|
861
|
+
} catch {
|
|
862
|
+
return void 0;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
function captureEnvironment(options = {}) {
|
|
866
|
+
const seeds = Object.entries(options.declaredSeeds ?? {}).map(([name, seed]) => `${name}=${seed}`).sort();
|
|
867
|
+
const pinned = options.includeDependencies === false ? void 0 : detectDependencies();
|
|
868
|
+
const git = options.includeGit === false ? void 0 : detectGitSHA();
|
|
869
|
+
const container = options.includeContainer === false ? void 0 : detectContainerHash();
|
|
870
|
+
const os = PLATFORM_NAMES[process.platform] ?? process.platform;
|
|
871
|
+
const arch = ARCH_NAMES[process.arch] ?? process.arch;
|
|
872
|
+
return {
|
|
873
|
+
declared: true,
|
|
874
|
+
runtime_name: "node",
|
|
875
|
+
runtime_version: process.versions.node,
|
|
876
|
+
platform: `${os}/${arch}`,
|
|
877
|
+
...pinned ? { pinned_dependencies: pinned } : {},
|
|
878
|
+
...git ? { git_commit_sha: git } : {},
|
|
879
|
+
...container ? { container_image_hash: container } : {},
|
|
880
|
+
...seeds.length > 0 ? { random_seeds: seeds } : {}
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// src/types.ts
|
|
885
|
+
var RUN_STATUSES = [
|
|
886
|
+
"ok",
|
|
887
|
+
"probe",
|
|
888
|
+
"pepg_deny",
|
|
889
|
+
"ccv_halt",
|
|
890
|
+
"macc_halt",
|
|
891
|
+
"aivp_t4_halt",
|
|
892
|
+
"attestation_gap",
|
|
893
|
+
"stage_error"
|
|
894
|
+
];
|
|
895
|
+
|
|
896
|
+
// src/section0.ts
|
|
897
|
+
var import_node_crypto2 = require("crypto");
|
|
898
|
+
var DSE_FORMULA_VERSION = "dse.tcu.dispatch.v1";
|
|
899
|
+
var DCAE_PREIMAGE_VERSION = "verdifax.dcae.v1";
|
|
900
|
+
var CRES_PREIMAGE_VERSION = "verdifax.cres.v1";
|
|
901
|
+
var CRES_ENGINE_VERSION = "verdifax-cres/1.0.0";
|
|
902
|
+
function sha256Hex(preimage) {
|
|
903
|
+
return (0, import_node_crypto2.createHash)("sha256").update(Buffer.from(preimage, "utf8")).digest("hex");
|
|
904
|
+
}
|
|
905
|
+
function requireHex64(value, name) {
|
|
906
|
+
try {
|
|
907
|
+
validateHex64(value, name);
|
|
908
|
+
} catch (err) {
|
|
909
|
+
throw new VerdifaxError(
|
|
910
|
+
`verdifax: section0: ${err instanceof Error ? err.message : String(err)}`
|
|
911
|
+
);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
function dseDispatchHash(fields) {
|
|
915
|
+
requireHex64(fields.program_id, "program_id");
|
|
916
|
+
requireHex64(fields.registry_record_hash, "registry_record_hash");
|
|
917
|
+
return sha256Hex(
|
|
918
|
+
[
|
|
919
|
+
DSE_FORMULA_VERSION,
|
|
920
|
+
fields.envelope_id,
|
|
921
|
+
fields.sequence_id,
|
|
922
|
+
fields.program_id,
|
|
923
|
+
fields.registry_record_hash
|
|
924
|
+
].join(".")
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
function dcaeClosureHash(fields) {
|
|
928
|
+
requireHex64(fields.manifest_hash, "manifest_hash");
|
|
929
|
+
requireHex64(fields.aer_hash, "aer_hash");
|
|
930
|
+
requireHex64(fields.zksp_binding_hash, "zksp_binding_hash");
|
|
931
|
+
return sha256Hex(
|
|
932
|
+
[
|
|
933
|
+
DCAE_PREIMAGE_VERSION,
|
|
934
|
+
fields.manifest_hash,
|
|
935
|
+
fields.aer_hash,
|
|
936
|
+
fields.envelope_id,
|
|
937
|
+
fields.zksp_binding_hash,
|
|
938
|
+
fields.formal_verifier_status
|
|
939
|
+
].join("|")
|
|
940
|
+
);
|
|
941
|
+
}
|
|
942
|
+
function cresReceiptHash(fields) {
|
|
943
|
+
requireHex64(fields.ciphertext_hash_at_shred, "ciphertext_hash_at_shred");
|
|
944
|
+
return sha256Hex(
|
|
945
|
+
[
|
|
946
|
+
CRES_PREIMAGE_VERSION,
|
|
947
|
+
fields.envelope_id,
|
|
948
|
+
fields.field_path,
|
|
949
|
+
fields.deletion_clock,
|
|
950
|
+
fields.actor_id,
|
|
951
|
+
fields.ciphertext_hash_at_shred,
|
|
952
|
+
fields.dsar_reference,
|
|
953
|
+
fields.engine_version ?? CRES_ENGINE_VERSION
|
|
954
|
+
].join("|")
|
|
955
|
+
);
|
|
956
|
+
}
|
|
453
957
|
// Annotate the CommonJS export names for ESM import in node:
|
|
454
958
|
0 && (module.exports = {
|
|
455
959
|
APIError,
|
|
456
960
|
APIKeyRequiredError,
|
|
457
961
|
AuthError,
|
|
962
|
+
CRES_ENGINE_VERSION,
|
|
963
|
+
CRES_PREIMAGE_VERSION,
|
|
964
|
+
DCAE_PREIMAGE_VERSION,
|
|
965
|
+
DSE_FORMULA_VERSION,
|
|
966
|
+
MAX_BATCH_ROWS,
|
|
458
967
|
NotFoundError,
|
|
968
|
+
RUN_STATUSES,
|
|
459
969
|
VerdifaxClient,
|
|
460
970
|
VerdifaxError,
|
|
971
|
+
captureEnvironment,
|
|
972
|
+
cresReceiptHash,
|
|
973
|
+
dcaeClosureHash,
|
|
974
|
+
dseDispatchHash,
|
|
975
|
+
formatProviderPayload,
|
|
461
976
|
fromEnv,
|
|
977
|
+
generateRequestKeyPair,
|
|
978
|
+
requestKeyRegistrationBody,
|
|
979
|
+
requestSignaturePreimage,
|
|
980
|
+
signRequest,
|
|
462
981
|
validateHex64,
|
|
463
982
|
validateRouteId
|
|
464
983
|
});
|