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/dist/index.mjs CHANGED
@@ -41,6 +41,135 @@ var APIKeyRequiredError = class extends VerdifaxError {
41
41
  }
42
42
  };
43
43
 
44
+ // src/signing.ts
45
+ import {
46
+ createHash,
47
+ createPrivateKey,
48
+ createPublicKey,
49
+ generateKeyPairSync,
50
+ KeyObject,
51
+ randomBytes,
52
+ sign
53
+ } from "crypto";
54
+ var REQUEST_SIGNATURE_FORM = "verdifax.request.v1";
55
+ var REQUEST_KEY_PROOF_FORM = "verdifax.request-key.v1";
56
+ var SIGNING_KEY_BYTES = 32;
57
+ var PKCS8_ED25519_HEADER = Buffer.from(
58
+ "302e020100300506032b657004220420",
59
+ "hex"
60
+ );
61
+ function rawPublicKey(key) {
62
+ return Buffer.from(key.export({ format: "der", type: "spki" }).subarray(-32));
63
+ }
64
+ function generateRequestKeyPair() {
65
+ const { publicKey, privateKey } = generateKeyPairSync("ed25519");
66
+ return {
67
+ publicKey: rawPublicKey(publicKey),
68
+ privateKey: Buffer.from(
69
+ privateKey.export({ format: "der", type: "pkcs8" }).subarray(-32)
70
+ )
71
+ };
72
+ }
73
+ function loadSigningKey(key) {
74
+ if (key instanceof KeyObject) return key;
75
+ if (!(key instanceof Uint8Array) || key.length !== SIGNING_KEY_BYTES) {
76
+ throw new VerdifaxError(
77
+ "verdifax: signingKey must be the 32 raw bytes of an Ed25519 private key"
78
+ );
79
+ }
80
+ return createPrivateKey({
81
+ key: Buffer.concat([PKCS8_ED25519_HEADER, key]),
82
+ format: "der",
83
+ type: "pkcs8"
84
+ });
85
+ }
86
+ function requestSignaturePreimage(fields) {
87
+ return Buffer.from(
88
+ [
89
+ REQUEST_SIGNATURE_FORM,
90
+ fields.payload_hash,
91
+ fields.program_id,
92
+ fields.route_id,
93
+ fields.registry_record_hash,
94
+ fields.signed_at,
95
+ fields.nonce
96
+ ].join("\0"),
97
+ "utf8"
98
+ );
99
+ }
100
+ function signRequest(signingKey, req, options = {}) {
101
+ const key = loadSigningKey(signingKey);
102
+ const signed_at = options.signedAt ?? (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "Z");
103
+ const nonce = options.nonce ?? randomBytes(16).toString("hex");
104
+ const payload_hash = createHash("sha256").update(Buffer.from(req.payload_text || req.payload || "", "utf8")).digest("hex");
105
+ const preimage = requestSignaturePreimage({
106
+ payload_hash,
107
+ program_id: req.program_id,
108
+ route_id: req.route_id,
109
+ registry_record_hash: req.registry_record_hash,
110
+ signed_at,
111
+ nonce
112
+ });
113
+ return {
114
+ signature: sign(null, preimage, key).toString("base64"),
115
+ signed_at,
116
+ nonce
117
+ };
118
+ }
119
+ function requestKeyRegistrationBody(signingKey) {
120
+ const key = loadSigningKey(signingKey);
121
+ const public_key = rawPublicKey(createPublicKey(key)).toString("base64");
122
+ const proof = sign(
123
+ null,
124
+ Buffer.from(`${REQUEST_KEY_PROOF_FORM}\0${public_key}`, "utf8"),
125
+ key
126
+ ).toString("base64");
127
+ return { public_key, proof };
128
+ }
129
+
130
+ // src/helpers.ts
131
+ function formatProviderPayload(provider, prompt, response) {
132
+ if ([provider, prompt, response].some((text) => /[\uD800-\uDFFF]/u.test(text))) {
133
+ throw new VerdifaxError(
134
+ "verdifax: provider payload text cannot be encoded as UTF-8"
135
+ );
136
+ }
137
+ return JSON.stringify({ prompt, provider, response });
138
+ }
139
+ function isProvided(value) {
140
+ return value !== null && value !== void 0 && value !== "" && value !== false;
141
+ }
142
+ function withAttestedFlag(context) {
143
+ const provided = Object.entries(context).some(
144
+ ([name, value]) => name !== "attested" && isProvided(value)
145
+ );
146
+ return { ...context, attested: provided };
147
+ }
148
+ function withDeclaredFlag(context) {
149
+ const declared = Object.entries(context).some(
150
+ ([name, value]) => name !== "declared" && isProvided(value) && !(Array.isArray(value) && value.length === 0)
151
+ );
152
+ return { ...context, declared };
153
+ }
154
+ function refuseCallerOriginHashes(substance) {
155
+ const origin = substance?.origin;
156
+ if (origin?.source_ip_hash || origin?.user_agent_hash) {
157
+ throw new VerdifaxError(
158
+ "verdifax: request_substance.origin.source_ip_hash and user_agent_hash are computed by the server and cannot be supplied"
159
+ );
160
+ }
161
+ }
162
+ function withExecuteFlags(req) {
163
+ const body = { ...req };
164
+ if (req.attested_context) {
165
+ body.attested_context = withAttestedFlag(req.attested_context);
166
+ }
167
+ if (req.reproducibility_context) {
168
+ body.reproducibility_context = withDeclaredFlag(req.reproducibility_context);
169
+ }
170
+ return body;
171
+ }
172
+
44
173
  // src/validation.ts
45
174
  function validateHex64(value, fieldName) {
46
175
  if (typeof value !== "string" || value.length !== 64) {
@@ -71,7 +200,7 @@ function validateRouteId(value) {
71
200
  }
72
201
 
73
202
  // src/version.ts
74
- var SDK_VERSION = "0.2.11";
203
+ var SDK_VERSION = "1.0.0";
75
204
 
76
205
  // src/client.ts
77
206
  var DEFAULT_BASE_URL = "https://api.verdifax.com";
@@ -80,7 +209,11 @@ var DEFAULT_MAX_RETRIES = 3;
80
209
  var DEFAULT_RETRY_BASE_DELAY_MS = 200;
81
210
  var SDK_USER_AGENT = `verdifax-sdk-node/${SDK_VERSION}`;
82
211
  var HEADER_API_KEY = "X-Verdifax-Key";
212
+ var REQUEST_KEY_PATH = "/keys/request-key";
213
+ var HEADER_EXPORT_TOTAL = "X-Verdifax-Total";
214
+ var HEADER_EXPORT_TRUNCATED = "X-Verdifax-Truncated";
83
215
  var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
216
+ var MAX_BATCH_ROWS = 1e3;
84
217
  var VerdifaxClient = class {
85
218
  baseURL;
86
219
  apiKey;
@@ -88,6 +221,7 @@ var VerdifaxClient = class {
88
221
  userAgent;
89
222
  maxRetries;
90
223
  retryBaseDelayMs;
224
+ signingKey;
91
225
  constructor(config = {}) {
92
226
  this.baseURL = (config.baseURL ?? DEFAULT_BASE_URL).replace(/\/$/, "");
93
227
  this.apiKey = config.apiKey ?? "";
@@ -95,6 +229,7 @@ var VerdifaxClient = class {
95
229
  this.userAgent = config.userAgent ?? SDK_USER_AGENT;
96
230
  this.maxRetries = config.maxRetries === void 0 ? DEFAULT_MAX_RETRIES : Math.max(0, config.maxRetries);
97
231
  this.retryBaseDelayMs = config.retryBaseDelayMs ?? DEFAULT_RETRY_BASE_DELAY_MS;
232
+ this.signingKey = config.signingKey === void 0 ? void 0 : loadSigningKey(config.signingKey);
98
233
  }
99
234
  // ── Health + Version (anonymous) ──────────────────────────────
100
235
  async health() {
@@ -103,20 +238,52 @@ var VerdifaxClient = class {
103
238
  async version() {
104
239
  return this.request("GET", "/version");
105
240
  }
241
+ /** The server's own API description. */
242
+ async getOpenAPI() {
243
+ return this.request("GET", "/openapi.json");
244
+ }
245
+ /** Run counts for this API key. */
246
+ async getStats() {
247
+ this.requireAPIKey();
248
+ return this.request("GET", "/stats");
249
+ }
106
250
  // ── Execute / Attest (auth) ───────────────────────────────────
107
251
  async execute(req) {
252
+ this.requireExecuteFields(req);
253
+ return this.request("POST", "/execute", this.prepared(req));
254
+ }
255
+ /** Runs one payload twice on the server and compares the manifest
256
+ * hashes. One signature covers the request, and the server claims
257
+ * its nonce once. Both runs are recorded. */
258
+ async verifyDeterminism(req) {
259
+ this.requireExecuteFields(req);
260
+ return this.request(
261
+ "POST",
262
+ "/execute/verify-determinism",
263
+ this.prepared(req)
264
+ );
265
+ }
266
+ /** Sends up to {@link MAX_BATCH_ROWS} runs in one request. With a
267
+ * signing key each row carries its own signature and nonce. A row
268
+ * that fails comes back in `results` with `ok: false`, so the call
269
+ * does not throw for it. The rows are not checked here: the server
270
+ * reports a bad row in that row's result.
271
+ *
272
+ * This is one POST and it is never retried. After a timeout the
273
+ * server may have run some or all of the rows. */
274
+ async executeBatch(runs) {
108
275
  this.requireAPIKey();
109
- if (!req.payload && !req.payload_text) {
110
- throw new VerdifaxError(
111
- "verdifax: ExecuteRequest needs payload or payload_text"
112
- );
276
+ if (!Array.isArray(runs) || runs.length === 0) {
277
+ throw new VerdifaxError("verdifax: executeBatch needs at least one run");
113
278
  }
114
- if (!req.program_id || !req.route_id || !req.registry_record_hash) {
279
+ if (runs.length > MAX_BATCH_ROWS) {
115
280
  throw new VerdifaxError(
116
- "verdifax: program_id, route_id, and registry_record_hash are required"
281
+ `verdifax: executeBatch accepts at most ${MAX_BATCH_ROWS} runs (got ${runs.length})`
117
282
  );
118
283
  }
119
- return this.request("POST", "/execute", req);
284
+ return this.request("POST", "/execute/batch", {
285
+ runs: runs.map((run) => this.prepared(run))
286
+ });
120
287
  }
121
288
  /** High-level convenience wrapper around `execute()`. Accepts
122
289
  * either a Buffer (for binary payloads, auto-base64) or a
@@ -142,7 +309,12 @@ var VerdifaxClient = class {
142
309
  route_id: req.route_id,
143
310
  registry_record_hash: req.registry_record_hash,
144
311
  ai_output_text: req.ai_output_text,
145
- attested_context: req.attested_context
312
+ attested_context: req.attested_context,
313
+ reproducibility_context: req.reproducibility_context,
314
+ request_substance: req.request_substance,
315
+ authorization_chain: req.authorization_chain,
316
+ regulatory_scaffolding: req.regulatory_scaffolding,
317
+ causal_graph: req.causal_graph
146
318
  };
147
319
  if (req.payload !== void 0) {
148
320
  body.payload = Buffer.isBuffer(req.payload) ? req.payload.toString("base64") : Buffer.from(req.payload).toString("base64");
@@ -151,6 +323,29 @@ var VerdifaxClient = class {
151
323
  }
152
324
  return this.execute(body);
153
325
  }
326
+ // ── Request signing (auth) ───────────────────────────────────
327
+ /** Registers this client's public key on its API key, with the proof
328
+ * that the client holds the private key. From then on the server
329
+ * refuses a run on this API key without a request signature. */
330
+ async registerRequestKey() {
331
+ this.requireAPIKey();
332
+ if (!this.signingKey) {
333
+ throw new VerdifaxError(
334
+ "verdifax: registerRequestKey needs the signingKey client option"
335
+ );
336
+ }
337
+ return this.request(
338
+ "POST",
339
+ REQUEST_KEY_PATH,
340
+ requestKeyRegistrationBody(this.signingKey)
341
+ );
342
+ }
343
+ /** Clears the registered public key, so the server stops requiring
344
+ * request signatures on this API key. */
345
+ async clearRequestKey() {
346
+ this.requireAPIKey();
347
+ await this.request("DELETE", REQUEST_KEY_PATH);
348
+ }
154
349
  // ── Runs ──────────────────────────────────────────────────────
155
350
  async getRun(runId) {
156
351
  this.requireAPIKey();
@@ -176,6 +371,94 @@ var VerdifaxClient = class {
176
371
  const out = await this.request("GET", path);
177
372
  return { runs: out.runs ?? [], total: out.total ?? 0 };
178
373
  }
374
+ /** Searches this key's runs. `manifest_hash` matches a prefix. The
375
+ * server ignores a `from` or `to` it cannot parse here, unlike
376
+ * {@link exportRuns}, which refuses one. */
377
+ async searchRuns(params = {}) {
378
+ this.requireAPIKey();
379
+ const q = new URLSearchParams();
380
+ for (const name of [
381
+ "manifest_hash",
382
+ "program_id",
383
+ "route_id",
384
+ "status",
385
+ "from",
386
+ "to"
387
+ ]) {
388
+ const value = params[name];
389
+ if (value) q.set(name, value);
390
+ }
391
+ if (params.limit && params.limit > 0) {
392
+ q.set("limit", String(params.limit));
393
+ }
394
+ if (params.offset && params.offset > 0) {
395
+ q.set("offset", String(params.offset));
396
+ }
397
+ const qs = q.toString();
398
+ const path = qs ? `/runs/search?${qs}` : "/runs/search";
399
+ const out = await this.request("GET", path);
400
+ return { runs: out.runs ?? [], total: out.total ?? 0 };
401
+ }
402
+ /** Lists what the server holds for this key in a period, failures
403
+ * included, up to 10,000 rows. `truncated` is true when the period
404
+ * holds more. The list states what was stored, not a verdict, and a
405
+ * call that never reached Verdifax cannot appear in it. */
406
+ async exportRuns(params = {}) {
407
+ this.requireAPIKey();
408
+ return this.request("GET", this.exportPath(params, "json"));
409
+ }
410
+ /** The same rows as {@link exportRuns}: the raw CSV text with `total`
411
+ * and `truncated` from the response headers. Check `truncated`: when
412
+ * true, the CSV is missing rows for the period. Throws when the server
413
+ * does not send those headers, so that a cut-off export cannot pass
414
+ * for a complete one. */
415
+ async exportRunsCSV(params = {}) {
416
+ this.requireAPIKey();
417
+ const { text, headers } = await this.requestRaw(
418
+ this.exportPath(params, "csv")
419
+ );
420
+ const total = Number.parseInt(headers.get(HEADER_EXPORT_TOTAL) ?? "", 10);
421
+ const truncated = headers.get(HEADER_EXPORT_TRUNCATED);
422
+ if (!Number.isSafeInteger(total) || truncated !== "true" && truncated !== "false") {
423
+ throw new VerdifaxError(
424
+ `verdifax: the server did not report ${HEADER_EXPORT_TOTAL} and ${HEADER_EXPORT_TRUNCATED}, so this CSV may be cut off; use exportRuns instead`
425
+ );
426
+ }
427
+ return { csv: text, total, truncated: truncated === "true" };
428
+ }
429
+ exportPath(params, format) {
430
+ const q = new URLSearchParams();
431
+ if (params.from) q.set("from", params.from);
432
+ if (params.to) q.set("to", params.to);
433
+ q.set("format", format);
434
+ return `/runs/export?${q.toString()}`;
435
+ }
436
+ // ── Attestation receipts (auth) ──────────────────────────────
437
+ async listAttestations(params = {}) {
438
+ this.requireAPIKey();
439
+ const q = new URLSearchParams();
440
+ if (params.limit && params.limit > 0) {
441
+ q.set("limit", String(params.limit));
442
+ }
443
+ if (params.offset && params.offset > 0) {
444
+ q.set("offset", String(params.offset));
445
+ }
446
+ const qs = q.toString();
447
+ return this.request(
448
+ "GET",
449
+ qs ? `/attestations?${qs}` : "/attestations"
450
+ );
451
+ }
452
+ async getAttestation(attestationId) {
453
+ this.requireAPIKey();
454
+ if (attestationId <= 0) {
455
+ throw new VerdifaxError("verdifax: attestationId must be > 0");
456
+ }
457
+ return this.request(
458
+ "GET",
459
+ `/attestations/${attestationId}`
460
+ );
461
+ }
179
462
  // ── Sealed-artifact retrieval (auth) ─────────────────────────
180
463
  async getAllowToken(runId) {
181
464
  return this.getRunArtifact(runId, "allow-token");
@@ -192,6 +475,39 @@ var VerdifaxClient = class {
192
475
  async getAivpT4HaltReceipt(runId) {
193
476
  return this.getRunArtifact(runId, "aivp-t4-halt-receipt");
194
477
  }
478
+ async getAttestationGapReceipt(runId) {
479
+ return this.getRunArtifact(runId, "attestation-gap-receipt");
480
+ }
481
+ async getCESAuditRecord(runId) {
482
+ return this.getRunArtifact(runId, "ces-audit-record");
483
+ }
484
+ /** The audit report for a run, as JSON. */
485
+ async getRunReport(runId) {
486
+ const out = await this.getRunArtifact(runId, "report");
487
+ return out.report;
488
+ }
489
+ /** The run's sealed audit bundle as the exact JSON text the server
490
+ * sent, to save and check with `verdifax-verify`. It is not parsed:
491
+ * JSON.parse would round a number above 2^53 and the recomputed hash
492
+ * would stop matching. */
493
+ async getRunArtifacts(runId) {
494
+ this.requireAPIKey();
495
+ if (runId <= 0) throw new VerdifaxError("verdifax: runId must be > 0");
496
+ return (await this.requestRaw(`/runs/${runId}/artifacts`)).text;
497
+ }
498
+ /** The public audit bundle of a record published by run id, as the
499
+ * exact JSON text the server sent. Needs no API key. */
500
+ async getPublicBundle(runId) {
501
+ if (runId <= 0) throw new VerdifaxError("verdifax: runId must be > 0");
502
+ return (await this.requestRaw(`/runs/${runId}/bundle.json`)).text;
503
+ }
504
+ /** The public audit bundle a share token points to, as the exact JSON
505
+ * text the server sent. Needs no API key. */
506
+ async getPublicBundleByToken(token) {
507
+ if (!token) throw new VerdifaxError("verdifax: token is required");
508
+ const path = `/public/runs/${encodeURIComponent(token)}/bundle.json`;
509
+ return (await this.requestRaw(path)).text;
510
+ }
195
511
  async getRunArtifact(runId, suffix) {
196
512
  this.requireAPIKey();
197
513
  if (runId <= 0) throw new VerdifaxError("verdifax: runId must be > 0");
@@ -278,13 +594,15 @@ var VerdifaxClient = class {
278
594
  await this.request("DELETE", `/admin/keys/${keyId}`);
279
595
  }
280
596
  // ── Provider helpers (Claude / OpenAI) ───────────────────────
281
- /** Convenience for attesting an Anthropic Claude API response.
282
- * Wires the response text into the AIVP governance pipeline
283
- * by setting `ai_output_text`. */
597
+ /** Convenience for attesting an Anthropic Claude API response. With
598
+ * `govern: true` the response text also goes to the AIVP review as
599
+ * `ai_output_text`; that is off by default, so the text leaves your
600
+ * machine for review only when you ask. */
284
601
  async attestClaudeResponse(args) {
285
602
  return this.attestProviderResponse({ ...args, provider: "anthropic" });
286
603
  }
287
- /** Convenience for attesting an OpenAI API response. */
604
+ /** Convenience for attesting an OpenAI API response. `govern` works as
605
+ * in {@link attestClaudeResponse}. */
288
606
  async attestOpenAIResponse(args) {
289
607
  return this.attestProviderResponse({ ...args, provider: "openai" });
290
608
  }
@@ -294,17 +612,16 @@ var VerdifaxClient = class {
294
612
  "verdifax: response is required for attestation"
295
613
  );
296
614
  }
297
- const payloadText = JSON.stringify({
298
- provider: args.provider,
299
- prompt: args.prompt,
300
- response: args.response
301
- });
302
615
  return this.attest({
303
- payload_text: payloadText,
616
+ payload_text: formatProviderPayload(
617
+ args.provider,
618
+ args.prompt,
619
+ args.response
620
+ ),
304
621
  program_id: args.program_id,
305
622
  route_id: args.route_id,
306
623
  registry_record_hash: args.registry_record_hash,
307
- ai_output_text: args.response,
624
+ ai_output_text: args.govern ? args.response : void 0,
308
625
  attested_context: {
309
626
  actor_role: "ai_assistant",
310
627
  model_provider: args.provider
@@ -312,7 +629,7 @@ var VerdifaxClient = class {
312
629
  });
313
630
  }
314
631
  // ── HTTP plumbing, retry-aware ──────────────────────────────
315
- async request(method, path, body) {
632
+ async request(method, path, body, rawText = false) {
316
633
  const url = this.baseURL + path;
317
634
  const headers = {
318
635
  "User-Agent": this.userAgent
@@ -369,6 +686,7 @@ var VerdifaxClient = class {
369
686
  throw new APIError(resp.status, path, errMessage);
370
687
  }
371
688
  }
689
+ if (rawText) return { text, headers: resp.headers };
372
690
  if (text.length === 0) return void 0;
373
691
  try {
374
692
  return JSON.parse(text);
@@ -382,17 +700,44 @@ var VerdifaxClient = class {
382
700
  `verdifax: retry budget exhausted${lastErr ? `: ${String(lastErr)}` : ""}`
383
701
  );
384
702
  }
703
+ /** A GET whose body is kept as sent, retried like any other GET. */
704
+ requestRaw(path) {
705
+ return this.request("GET", path, void 0, true);
706
+ }
385
707
  requireAPIKey() {
386
708
  if (!this.apiKey) throw new APIKeyRequiredError();
387
709
  }
710
+ requireExecuteFields(req) {
711
+ this.requireAPIKey();
712
+ if (!req.payload && !req.payload_text) {
713
+ throw new VerdifaxError(
714
+ "verdifax: ExecuteRequest needs payload or payload_text"
715
+ );
716
+ }
717
+ if (!req.program_id || !req.route_id || !req.registry_record_hash) {
718
+ throw new VerdifaxError(
719
+ "verdifax: program_id, route_id, and registry_record_hash are required"
720
+ );
721
+ }
722
+ validateHex64(req.program_id, "program_id");
723
+ validateHex64(req.registry_record_hash, "registry_record_hash");
724
+ validateRouteId(req.route_id);
725
+ refuseCallerOriginHashes(req.request_substance);
726
+ }
727
+ /** The request with its derived flags and, with a signing key, a fresh
728
+ * signature and nonce, on a copy. */
729
+ prepared(req) {
730
+ const body = withExecuteFlags(req);
731
+ return this.signingKey ? { ...body, request_signature: signRequest(this.signingKey, body) } : body;
732
+ }
388
733
  };
389
734
 
390
735
  // src/config.ts
391
736
  function fromEnv() {
392
737
  const cfg = {
393
- baseURL: process.env.VERDIFAX_BASE_URL ?? void 0,
738
+ baseURL: process.env.VERDIFAX_BASE_URL || process.env.VERDIFAX_API_URL || void 0,
394
739
  apiKey: process.env.VERDIFAX_API_KEY ?? void 0,
395
- userAgent: process.env.VERDIFAX_USER_AGENT ?? void 0
740
+ userAgent: process.env.VERDIFAX_USER_AGENT || void 0
396
741
  };
397
742
  const timeoutRaw = process.env.VERDIFAX_TIMEOUT_MS;
398
743
  if (timeoutRaw !== void 0 && timeoutRaw !== "") {
@@ -416,14 +761,181 @@ function fromEnv() {
416
761
  }
417
762
  return new VerdifaxClient(cfg);
418
763
  }
764
+
765
+ // src/environment.ts
766
+ import { execFileSync } from "child_process";
767
+ import { readdirSync, readFileSync } from "fs";
768
+ import { join } from "path";
769
+ var HASH_64 = /^[0-9a-f]{64}$/;
770
+ var GIT_SHA = /^([0-9a-f]{40}|[0-9a-f]{64})$/;
771
+ var PLATFORM_NAMES = { win32: "windows" };
772
+ var ARCH_NAMES = { x64: "amd64", ia32: "386" };
773
+ function packageVersion(dir) {
774
+ try {
775
+ const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
776
+ return pkg.name && pkg.version ? `${pkg.name}==${pkg.version}` : void 0;
777
+ } catch {
778
+ return void 0;
779
+ }
780
+ }
781
+ function packageDirs(root) {
782
+ const dirs = [];
783
+ for (const entry of readdirSync(root)) {
784
+ if (entry.startsWith(".")) continue;
785
+ if (!entry.startsWith("@")) {
786
+ dirs.push(join(root, entry));
787
+ continue;
788
+ }
789
+ for (const scoped of readdirSync(join(root, entry))) {
790
+ dirs.push(join(root, entry, scoped));
791
+ }
792
+ }
793
+ return dirs;
794
+ }
795
+ function detectDependencies() {
796
+ try {
797
+ const pins = packageDirs(join(process.cwd(), "node_modules")).map(packageVersion).filter((pin) => pin !== void 0);
798
+ pins.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
799
+ return pins.length > 0 ? pins : void 0;
800
+ } catch {
801
+ return void 0;
802
+ }
803
+ }
804
+ function detectGitSHA() {
805
+ try {
806
+ const sha = execFileSync("git", ["rev-parse", "HEAD"], {
807
+ encoding: "utf8",
808
+ stdio: ["ignore", "pipe", "ignore"],
809
+ timeout: 2e3
810
+ }).trim().toLowerCase();
811
+ return GIT_SHA.test(sha) ? sha : void 0;
812
+ } catch {
813
+ return void 0;
814
+ }
815
+ }
816
+ function detectContainerHash() {
817
+ try {
818
+ const text = readFileSync("/proc/self/cgroup", "utf8");
819
+ return text.split("\n").flatMap((line) => line.split("/")).map((segment) => segment.trim()).find((segment) => HASH_64.test(segment));
820
+ } catch {
821
+ return void 0;
822
+ }
823
+ }
824
+ function captureEnvironment(options = {}) {
825
+ const seeds = Object.entries(options.declaredSeeds ?? {}).map(([name, seed]) => `${name}=${seed}`).sort();
826
+ const pinned = options.includeDependencies === false ? void 0 : detectDependencies();
827
+ const git = options.includeGit === false ? void 0 : detectGitSHA();
828
+ const container = options.includeContainer === false ? void 0 : detectContainerHash();
829
+ const os = PLATFORM_NAMES[process.platform] ?? process.platform;
830
+ const arch = ARCH_NAMES[process.arch] ?? process.arch;
831
+ return {
832
+ declared: true,
833
+ runtime_name: "node",
834
+ runtime_version: process.versions.node,
835
+ platform: `${os}/${arch}`,
836
+ ...pinned ? { pinned_dependencies: pinned } : {},
837
+ ...git ? { git_commit_sha: git } : {},
838
+ ...container ? { container_image_hash: container } : {},
839
+ ...seeds.length > 0 ? { random_seeds: seeds } : {}
840
+ };
841
+ }
842
+
843
+ // src/types.ts
844
+ var RUN_STATUSES = [
845
+ "ok",
846
+ "probe",
847
+ "pepg_deny",
848
+ "ccv_halt",
849
+ "macc_halt",
850
+ "aivp_t4_halt",
851
+ "attestation_gap",
852
+ "stage_error"
853
+ ];
854
+
855
+ // src/section0.ts
856
+ import { createHash as createHash2 } from "crypto";
857
+ var DSE_FORMULA_VERSION = "dse.tcu.dispatch.v1";
858
+ var DCAE_PREIMAGE_VERSION = "verdifax.dcae.v1";
859
+ var CRES_PREIMAGE_VERSION = "verdifax.cres.v1";
860
+ var CRES_ENGINE_VERSION = "verdifax-cres/1.0.0";
861
+ function sha256Hex(preimage) {
862
+ return createHash2("sha256").update(Buffer.from(preimage, "utf8")).digest("hex");
863
+ }
864
+ function requireHex64(value, name) {
865
+ try {
866
+ validateHex64(value, name);
867
+ } catch (err) {
868
+ throw new VerdifaxError(
869
+ `verdifax: section0: ${err instanceof Error ? err.message : String(err)}`
870
+ );
871
+ }
872
+ }
873
+ function dseDispatchHash(fields) {
874
+ requireHex64(fields.program_id, "program_id");
875
+ requireHex64(fields.registry_record_hash, "registry_record_hash");
876
+ return sha256Hex(
877
+ [
878
+ DSE_FORMULA_VERSION,
879
+ fields.envelope_id,
880
+ fields.sequence_id,
881
+ fields.program_id,
882
+ fields.registry_record_hash
883
+ ].join(".")
884
+ );
885
+ }
886
+ function dcaeClosureHash(fields) {
887
+ requireHex64(fields.manifest_hash, "manifest_hash");
888
+ requireHex64(fields.aer_hash, "aer_hash");
889
+ requireHex64(fields.zksp_binding_hash, "zksp_binding_hash");
890
+ return sha256Hex(
891
+ [
892
+ DCAE_PREIMAGE_VERSION,
893
+ fields.manifest_hash,
894
+ fields.aer_hash,
895
+ fields.envelope_id,
896
+ fields.zksp_binding_hash,
897
+ fields.formal_verifier_status
898
+ ].join("|")
899
+ );
900
+ }
901
+ function cresReceiptHash(fields) {
902
+ requireHex64(fields.ciphertext_hash_at_shred, "ciphertext_hash_at_shred");
903
+ return sha256Hex(
904
+ [
905
+ CRES_PREIMAGE_VERSION,
906
+ fields.envelope_id,
907
+ fields.field_path,
908
+ fields.deletion_clock,
909
+ fields.actor_id,
910
+ fields.ciphertext_hash_at_shred,
911
+ fields.dsar_reference,
912
+ fields.engine_version ?? CRES_ENGINE_VERSION
913
+ ].join("|")
914
+ );
915
+ }
419
916
  export {
420
917
  APIError,
421
918
  APIKeyRequiredError,
422
919
  AuthError,
920
+ CRES_ENGINE_VERSION,
921
+ CRES_PREIMAGE_VERSION,
922
+ DCAE_PREIMAGE_VERSION,
923
+ DSE_FORMULA_VERSION,
924
+ MAX_BATCH_ROWS,
423
925
  NotFoundError,
926
+ RUN_STATUSES,
424
927
  VerdifaxClient,
425
928
  VerdifaxError,
929
+ captureEnvironment,
930
+ cresReceiptHash,
931
+ dcaeClosureHash,
932
+ dseDispatchHash,
933
+ formatProviderPayload,
426
934
  fromEnv,
935
+ generateRequestKeyPair,
936
+ requestKeyRegistrationBody,
937
+ requestSignaturePreimage,
938
+ signRequest,
427
939
  validateHex64,
428
940
  validateRouteId
429
941
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "verdifax",
3
- "version": "0.2.11",
3
+ "version": "1.0.0",
4
4
  "description": "Official Node.js / TypeScript SDK for the Verdifax orchestrator REST API.",
5
5
  "license": "MIT",
6
6
  "author": "Verdifax <team@verdifax.com>",