verdifax 1.0.0 → 1.1.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 CHANGED
@@ -80,13 +80,17 @@ if (v.verified) {
80
80
  | `client.getPublicBundleByToken(token)` | `GET /public/runs/{token}/bundle.json` | none |
81
81
  | `client.dcaeVerify(bundle)` | `POST /dcae/verify` | none |
82
82
  | `client.registerRequestKey()` | `POST /keys/request-key` | required |
83
+ | `client.rotateRequestKey(newSigningKey)` | `POST /keys/request-key` | required |
83
84
  | `client.clearRequestKey()` | `DELETE /keys/request-key` | required |
84
85
  | `client.adminCreateKey(name)` | `POST /admin/keys` | admin |
85
86
  | `client.adminListKeys()` | `GET /admin/keys` | admin |
86
87
  | `client.adminRevokeKey(id)` | `DELETE /admin/keys/{id}` | admin |
88
+ | `client.adminClearRequestKey(id)` | `DELETE /admin/keys/{id}/request-key` | admin |
87
89
  | `client.adminErase(req)` | `POST /admin/erase` | admin |
88
90
 
89
- These functions make no request: `captureEnvironment`, `formatProviderPayload`, `signRequest`, `requestKeyRegistrationBody`, `requestSignaturePreimage`, `generateRequestKeyPair`, `validateHex64`, `validateRouteId`, `dseDispatchHash`, `dcaeClosureHash` and `cresReceiptHash`. `RUN_STATUSES` lists every run status the server uses.
91
+ `client.job(jobId)` returns a [job helper](#jobs) and makes no request until a step runs.
92
+
93
+ These functions make no request: `captureEnvironment`, `formatProviderPayload`, `signRequest`, `signRequestV2`, `requestKeyRegistrationBody`, `requestKeyChangeApproval`, `requestKeyChangePreimage`, `requestSignaturePreimage`, `requestSignaturePreimageV2`, `generateRequestKeyPair`, `validateHex64`, `validateJobName`, `validateRouteId`, `dseDispatchHash`, `dcaeClosureHash` and `cresReceiptHash`. `RUN_STATUSES` lists every run status the server uses.
90
94
 
91
95
  `getRunArtifacts`, `getPublicBundle` and `getPublicBundleByToken` return the exact JSON text the server sent, so you can save it and check it with `verdifax-verify`.
92
96
 
@@ -126,6 +130,44 @@ for (const row of batch.results) {
126
130
 
127
131
  The client's timeout covers the whole batch, so raise `timeoutMs` for a large one. A batch is one POST and it is never retried. After a timeout the server may have run some or all of the rows, so list your runs before you send them again.
128
132
 
133
+ ## Jobs
134
+
135
+ A job links several runs into one map: each step names the records it directly follows, so work can split into branches and join again. `client.job(jobId)` returns a helper that keeps the map for you.
136
+
137
+ ```typescript
138
+ const job = client.job("loan-1042");
139
+ const run = { program_id, route_id, registry_record_hash };
140
+
141
+ await job.step("intake", { ...run, payload_text: "application received" });
142
+
143
+ // Two branches after one step.
144
+ await job.step("credit", { ...run, payload_text: "credit check", after: ["intake"] });
145
+ await job.step("income", { ...run, payload_text: "income check", after: ["intake"] });
146
+
147
+ // A final step joining them.
148
+ await job.step("decide", {
149
+ ...run,
150
+ payload_text: "decision",
151
+ after: ["credit", "income"],
152
+ final: true,
153
+ });
154
+
155
+ console.log(job.recordHashes); // step id to record hash
156
+ console.log(job.signedRequests); // step id to signed text, when the client signs
157
+ ```
158
+
159
+ `job.step(stepId, options)` takes everything `attest` takes, plus `after`, `external_previous` and `final`, and returns what `attest` returns. `after` lists step ids in this job. Without it, a step follows the one run just before it through this helper, and the first step follows nothing. An `after` that names an unknown step throws a `VerdifaxError` before anything is sent.
160
+
161
+ A refused or halted step stays in the map, so a later step can follow it. `job.step` records its hash and signed text, then throws the same `APIError` that `attest` throws, with the hash in `err.recordHash`. A step that sealed nothing, such as a timeout or a 409, is not in the map and does not move the default `after`, so it can run again under the same id. To run branches at the same time, give each one its `after`. An empty `after` starts another root step.
162
+
163
+ A successful step records `record_hash`, or `manifest_hash` if the response has no `record_hash`. If it has neither, `job.step` throws a `VerdifaxError`, so the map never silently gets a gap. A mistyped option such as `afer` is refused before sending, so a typo cannot silently drop a link.
164
+
165
+ `execute`, `attest` and each row of `executeBatch` also take a `job` block directly: `{ job_id, step_id, previous_record_hashes, external_previous, final }`. Results carry `record_hash`. `job_id` and `step_id` are 1 to 128 letters, digits, `.`, `_`, `:` or `-`, and any other name is refused before sending. The SDK sorts both lists and removes repeats, and refuses unknown fields, a `final` that is not true or false, and hash lists that are not lists. A determinism check runs twice, so `verifyDeterminism` refuses a job.
166
+
167
+ The server refuses a step in a closed job, a repeated step id, and a final step that leaves a branch with no step after it. To check a whole job, save each record in one folder and run `verdifax-verify --job FOLDER`.
168
+
169
+ A job map covers what was sent. It cannot show a step that was never sent.
170
+
129
171
  ## Evidence export
130
172
 
131
173
  `exportRuns` lists the runs the server holds for your key in a period, failures included, with up to 10,000 rows per export. `exportRunsCSV` returns the same rows as raw CSV text.
@@ -142,6 +184,8 @@ if (truncated) {
142
184
  }
143
185
  ```
144
186
 
187
+ Pass `job_id` in the period to export one job. JSON rows also carry `anchor_source` (`bundle`, `receipt` or `none`), and `job_id` and `step_id` when the run is a job step.
188
+
145
189
  The export states what the server stored, not a verdict. A call that never reached Verdifax cannot appear in it. Both forms say when a period holds more rows than the export returned: check `truncated`. `exportRunsCSV` reads `total` and `truncated` from the `X-Verdifax-Total` and `X-Verdifax-Truncated` response headers, and throws if the server does not send them. In the CSV, a `route_id` that a spreadsheet could read as a formula gets a leading `'`.
146
190
 
147
191
  ## Provider helpers
@@ -159,6 +203,8 @@ await client.attestClaudeResponse({
159
203
  });
160
204
  ```
161
205
 
206
+ Both helpers take the same optional `job` block as `attest` and return `record_hash` and `signed_request` the same way.
207
+
162
208
  `govern` is false by default, which changes earlier behavior. Set `govern: true` to also send the response text as `ai_output_text` for the AIVP review, which runs on Anthropic in production. Without it, the AI output text is not sent for review.
163
209
 
164
210
  ## Optional execute fields
@@ -219,6 +265,7 @@ Request signing is optional. A client with no signing key sends the same request
219
265
  You can register an Ed25519 public key on your API key, so that each run must also be signed with a private key that only you hold. The key pair is created on your machine, and Verdifax receives only the public key.
220
266
 
221
267
  ```typescript
268
+ import { writeFileSync } from "node:fs";
222
269
  import { VerdifaxClient, generateRequestKeyPair } from "verdifax";
223
270
 
224
271
  // Run once, then store privateKey as a secret.
@@ -233,21 +280,29 @@ await client.registerRequestKey(); // sends the public key with its proof
233
280
 
234
281
  // execute, attest, attestClaudeResponse, attestOpenAIResponse, executeBatch
235
282
  // and verifyDeterminism now sign.
236
- await client.execute({
283
+ const run = await client.execute({
237
284
  program_id: "1111111111111111111111111111111111111111111111111111111111111111",
238
285
  route_id: "demo-attestation",
239
286
  registry_record_hash: "2222222222222222222222222222222222222222222222222222222222222222",
240
287
  payload_text: "first signed attestation",
241
288
  });
242
289
 
243
- await client.clearRequestKey(); // stops requiring signatures on this API key
290
+ // The exact text that was signed. Keep it for verdifax-verify --request FILE.
291
+ writeFileSync("signed-request.json", run.signed_request!);
292
+
293
+ // The current key approves the change, and this client then signs with the new key.
294
+ await client.rotateRequestKey(generateRequestKeyPair().privateKey);
295
+
296
+ await client.clearRequestKey(); // stops requiring signatures, and this client stops signing
244
297
  ```
245
298
 
246
- Registering a key changes that API key for every client that uses it: the server then refuses a run without a valid signature, so every caller on the key needs the private key. Each request carries the time and a fresh random nonce. The server refuses a repeated nonce and a time more than five minutes from its own clock.
299
+ A signed call serializes the request once and signs that exact text, using signature version 2, so the signature covers the whole request. It sends `signed_request` and `request_signature` and nothing else, and a batch row has the same shape. The result carries the text as `signed_request`, on each row for a batch. A refused or halted run throws the usual `APIError`, with the text in `err.signedRequest`. It holds your payload, so it stays out of the error message and logging the error does not print it: read it by name.
300
+
301
+ Registering a key changes that API key for every client that uses it: the server then refuses a run without a valid signature, so every caller on the key needs the private key. An API key alone cannot replace or clear a registered key: `rotateRequestKey` and `clearRequestKey` send the current key's approval. After a successful `clearRequestKey` the client drops its signing key and sends unsigned requests, because the server refuses a signed run on an API key with no registered key. To sign again, create a client with a `signingKey` and register it. Each request carries the time and a fresh random nonce. The server refuses a repeated nonce and a time more than five minutes from its own clock.
247
302
 
248
303
  In a batch each row carries its own signature and nonce, and the server checks them as it reaches that row. A row refused for its signature comes back in `results`, so a repeated batch returns HTTP 200 with failed rows, and a row reached more than five minutes after it was signed fails the same way. A determinism check uses one signature for the request, and repeating it returns HTTP 403.
249
304
 
250
- The signature shows the holder of the key requested the run. It does not show the key holder accepted the outcome.
305
+ A signature shows who requested the run, not that they accepted the outcome.
251
306
 
252
307
  ## Trust model
253
308
 
package/dist/index.d.mts CHANGED
@@ -68,9 +68,27 @@ interface ExecuteRequest {
68
68
  /** Caller-supplied record of what led to this run. Recorded as
69
69
  * supplied. */
70
70
  causal_graph?: Record<string, unknown>;
71
- /** Filled in by the client when the `signingKey` option is set. */
71
+ /** Places this run in a job map. Not accepted by a determinism check,
72
+ * which runs twice. */
73
+ job?: JobLink;
74
+ /** A version 1 signature for an unsigned client to send. A client with
75
+ * the `signingKey` option sends version 2 and drops this field. */
72
76
  request_signature?: RequestSignature;
73
77
  }
78
+ /** Places one run in a job. `job_id` and `step_id` are 1 to 128 letters,
79
+ * digits, dots, underscores, colons or dashes. The client sorts both
80
+ * lists, removes repeats and leaves out an empty list and a false
81
+ * `final`. */
82
+ interface JobLink {
83
+ job_id: string;
84
+ step_id: string;
85
+ /** Record hashes of the steps this one directly follows, in this job. */
86
+ previous_record_hashes?: Hex64[];
87
+ /** Record hashes from another account that this step follows. */
88
+ external_previous?: Hex64[];
89
+ /** True on the step that closes the job. */
90
+ final?: boolean;
91
+ }
74
92
  /** The runtime a caller declares for a run. Every field is optional.
75
93
  * The server records these values as given and does not check them. */
76
94
  interface ReproducibilityContext {
@@ -98,6 +116,27 @@ interface RequestSignature {
98
116
  /** 16 random bytes as 32 lowercase hex characters, fresh per request. */
99
117
  nonce: string;
100
118
  }
119
+ /** A version 2 request signature, which covers the exact `signed_request`
120
+ * text. */
121
+ interface RequestSignatureV2 extends RequestSignature {
122
+ version: "2";
123
+ }
124
+ /** The whole body of a signed call, and of each row of a signed batch.
125
+ * The server reads the request from `signed_request` alone. */
126
+ interface SignedRequestBody {
127
+ /** The request as JSON text, exactly as it was signed. */
128
+ signed_request: string;
129
+ request_signature: RequestSignatureV2;
130
+ }
131
+ /** The current key's approval of replacing or clearing itself. */
132
+ interface RequestKeyChange {
133
+ /** Base64 Ed25519 signature by the current key. */
134
+ signature: string;
135
+ /** RFC 3339 UTC with whole seconds. */
136
+ signed_at: string;
137
+ /** 16 random bytes as 32 lowercase hex characters, fresh per change. */
138
+ nonce: string;
139
+ }
101
140
  /** A key pair from {@link generateRequestKeyPair}, as raw bytes. */
102
141
  interface RequestKeyPair {
103
142
  /** The 32-byte Ed25519 public key. */
@@ -112,7 +151,8 @@ interface RequestKeyRegistrationBody {
112
151
  /** Base64 signature proving the caller holds the private key. */
113
152
  proof: string;
114
153
  }
115
- /** Returned by {@link VerdifaxClient.registerRequestKey}. */
154
+ /** Returned by {@link VerdifaxClient.registerRequestKey} and
155
+ * {@link VerdifaxClient.rotateRequestKey}. */
116
156
  interface RequestKeyRegistration {
117
157
  ok: boolean;
118
158
  public_key: string;
@@ -120,8 +160,16 @@ interface RequestKeyRegistration {
120
160
  interface ExecuteResponse {
121
161
  ok: boolean;
122
162
  run_id: number;
163
+ /** The record's hash: the manifest hash of a completed run. A refused or
164
+ * halted run throws an {@link APIError} carrying its `recordHash` and,
165
+ * when signed, its `signedRequest`. */
166
+ record_hash?: Hex64;
167
+ manifest_hash?: Hex64;
123
168
  manifest: ExecutionManifest;
124
169
  duration_ms: number;
170
+ /** The exact text that was signed, when the call was signed. Keep it to
171
+ * pass to `verdifax-verify --request FILE`. */
172
+ signed_request?: string;
125
173
  }
126
174
  /** One row of {@link ExecuteBatchResponse}. A successful row has
127
175
  * `manifest`. A failed row has `error`, and `error_stage` and a typed
@@ -131,11 +179,16 @@ interface ExecuteBatchResult {
131
179
  index: number;
132
180
  ok: boolean;
133
181
  run_id?: number;
182
+ /** The manifest hash of a completed row, or the receipt hash of a refused
183
+ * or halted one. */
184
+ record_hash?: Hex64;
134
185
  manifest_hash?: Hex64;
135
186
  manifest?: ExecutionManifest;
136
187
  error?: string;
137
188
  error_stage?: string;
138
189
  duration_ms: number;
190
+ /** The exact text that was signed for this row, when it was signed. */
191
+ signed_request?: string;
139
192
  partial_manifest?: ExecutionManifest;
140
193
  deny_receipt?: Record<string, unknown>;
141
194
  ccv_halt_receipt?: Record<string, unknown>;
@@ -176,6 +229,8 @@ interface VerifyDeterminismResponse {
176
229
  first: DeterminismRun;
177
230
  second: DeterminismRun;
178
231
  diff: DeterminismDiff;
232
+ /** The exact text that was signed, when the call was signed. */
233
+ signed_request?: string;
179
234
  }
180
235
  /**
181
236
  * Sealed manifest. Field names match the orchestrator's Go-default
@@ -284,6 +339,21 @@ interface AttestRequest {
284
339
  authorization_chain?: Record<string, unknown>;
285
340
  regulatory_scaffolding?: Record<string, unknown>;
286
341
  causal_graph?: Record<string, unknown>;
342
+ /** Places this run in a job map. {@link VerdifaxJob.step} fills it in. */
343
+ job?: JobLink;
344
+ }
345
+ /** The second argument of {@link VerdifaxJob.step}: everything
346
+ * {@link VerdifaxClient.attest} takes except `job`, which the helper
347
+ * builds, plus the step's place in the job. */
348
+ interface JobStepOptions extends Omit<AttestRequest, "job"> {
349
+ /** Step ids in this job that this step directly follows. When omitted,
350
+ * the step follows the one run just before it through this helper. An
351
+ * empty list means it follows none. */
352
+ after?: string[];
353
+ /** Record hashes from another account that this step follows. */
354
+ external_previous?: Hex64[];
355
+ /** True on the step that closes the job. */
356
+ final?: boolean;
287
357
  }
288
358
  /** Optional filters for {@link VerdifaxClient.listRuns}. */
289
359
  interface ListRunsParams {
@@ -325,6 +395,8 @@ interface SearchRunsParams {
325
395
  interface ExportRunsParams {
326
396
  from?: string;
327
397
  to?: string;
398
+ /** Limits the export to the runs of one job. */
399
+ job_id?: string;
328
400
  }
329
401
  /** One run in an evidence export: what the server stored, not a verdict. */
330
402
  interface ExportRow {
@@ -338,6 +410,11 @@ interface ExportRow {
338
410
  leaf_hash: string;
339
411
  complete_record_entry_id: string;
340
412
  complete_record_leaf_hash: string;
413
+ /** Where the row's public-log entry was read from: `bundle`, `receipt`
414
+ * or `none`. */
415
+ anchor_source?: string;
416
+ job_id?: string;
417
+ step_id?: string;
341
418
  }
342
419
  /** Body returned by GET /runs/export in JSON form. */
343
420
  interface RunsExport {
@@ -453,6 +530,47 @@ interface AdminAPIKey {
453
530
  revoked: boolean;
454
531
  }
455
532
 
533
+ /**
534
+ * The job helper: runs the steps of one job and keeps the map of step ids
535
+ * to record hashes, so a later step can name the steps it follows.
536
+ */
537
+
538
+ /** Returned by {@link VerdifaxClient.job}.
539
+ *
540
+ * Every step goes through `attest`, so it signs when the client signs. A
541
+ * refused or halted step stays in the map and a later step can name it.
542
+ * A step that sealed nothing, for example a timeout or a 409, is not in the
543
+ * map and does not move the default `after`, so it can run again under the
544
+ * same id. An empty `after` starts another root step.
545
+ *
546
+ * `signedRequests` holds the payloads of signed steps, so it is a private
547
+ * field: logging a job prints the hashes and not that text.
548
+ *
549
+ * To run branches at the same time, give each its `after`: the default
550
+ * reads the last step that finished. */
551
+ declare class VerdifaxJob {
552
+ #private;
553
+ readonly jobId: string;
554
+ private readonly client;
555
+ private readonly hashes;
556
+ private lastStepId;
557
+ constructor(client: {
558
+ attest(req: AttestRequest): Promise<ExecuteResponse>;
559
+ }, jobId: string);
560
+ /** Step id to record hash for every step that left a record, on a copy. */
561
+ get recordHashes(): Record<string, Hex64>;
562
+ /** Step id to the exact text that was signed, for every signed step that
563
+ * left a record, refused and halted steps included, on a copy. Each text
564
+ * can be passed to `verdifax-verify --request FILE`. */
565
+ get signedRequests(): Record<string, string>;
566
+ /** Runs one step and records its hash and signed text. A refused or halted
567
+ * step records both from the error, then throws the same error `attest`
568
+ * throws. */
569
+ step(stepId: string, options: JobStepOptions): Promise<ExecuteResponse>;
570
+ private previousHashes;
571
+ private record;
572
+ }
573
+
456
574
  /** The most rows POST /execute/batch accepts in one request. */
457
575
  declare const MAX_BATCH_ROWS = 1000;
458
576
  /**
@@ -496,14 +614,19 @@ declare const MAX_BATCH_ROWS = 1000;
496
614
  * | `adminCreateKey(name)` | POST /admin/keys | required |
497
615
  * | `adminListKeys()` | GET /admin/keys | required |
498
616
  * | `adminRevokeKey(id)` | DELETE /admin/keys/{id} | required |
617
+ * | `adminClearRequestKey(id)` | DELETE /admin/keys/{id}/request-key | required |
499
618
  * | `attestClaudeResponse(...)` | POST /execute (helper) | required |
500
619
  * | `attestOpenAIResponse(...)` | POST /execute (helper) | required |
501
620
  * | `registerRequestKey()` | POST /keys/request-key | required |
621
+ * | `rotateRequestKey(newSigningKey)` | POST /keys/request-key | required |
502
622
  * | `clearRequestKey()` | DELETE /keys/request-key | required |
623
+ * | `job(jobId)` | none, returns a {@link VerdifaxJob} | none |
503
624
  *
504
625
  * With the `signingKey` option set, every path to POST /execute carries
505
626
  * a fresh request signature, each row of a batch carries its own, and a
506
- * determinism check carries one. Without it, requests are unsigned.
627
+ * determinism check carries one. Each sends `signed_request` and
628
+ * `request_signature` and nothing else, and the result carries the signed
629
+ * text as `signed_request`. Without it, requests are unsigned.
507
630
  *
508
631
  * Idempotent GETs that hit a transient failure (502/503/504/429)
509
632
  * are retried with exponential backoff + full jitter. Non-
@@ -517,7 +640,7 @@ declare class VerdifaxClient {
517
640
  private readonly userAgent;
518
641
  private readonly maxRetries;
519
642
  private readonly retryBaseDelayMs;
520
- private readonly signingKey;
643
+ private signingKey;
521
644
  constructor(config?: ClientConfig);
522
645
  health(): Promise<Health>;
523
646
  version(): Promise<VerdifaxVersion>;
@@ -528,13 +651,15 @@ declare class VerdifaxClient {
528
651
  execute(req: ExecuteRequest): Promise<ExecuteResponse>;
529
652
  /** Runs one payload twice on the server and compares the manifest
530
653
  * hashes. One signature covers the request, and the server claims
531
- * its nonce once. Both runs are recorded. */
654
+ * its nonce once. Both runs are recorded. A run that happens twice
655
+ * cannot be one job step, so a `job` is refused here. */
532
656
  verifyDeterminism(req: ExecuteRequest): Promise<VerifyDeterminismResponse>;
533
657
  /** Sends up to {@link MAX_BATCH_ROWS} runs in one request. With a
534
658
  * signing key each row carries its own signature and nonce. A row
535
659
  * that fails comes back in `results` with `ok: false`, so the call
536
- * does not throw for it. The rows are not checked here: the server
537
- * reports a bad row in that row's result.
660
+ * does not throw for it. The rows are not checked here, except a job
661
+ * block, which is refused before anything is sent: the server reports
662
+ * any other bad row in that row's result.
538
663
  *
539
664
  * This is one POST and it is never retried. After a timeout the
540
665
  * server may have run some or all of the rows. */
@@ -544,12 +669,26 @@ declare class VerdifaxClient {
544
669
  * string (for plain text). Validates the hex fields and
545
670
  * routes the call through to the canonical /execute. */
546
671
  attest(req: AttestRequest): Promise<ExecuteResponse>;
672
+ /** A helper that runs the steps of one job through {@link attest} and
673
+ * keeps each step's record hash, so a later step can name it. It makes
674
+ * no request until a step runs. */
675
+ job(jobId: string): VerdifaxJob;
547
676
  /** Registers this client's public key on its API key, with the proof
548
677
  * that the client holds the private key. From then on the server
549
678
  * refuses a run on this API key without a request signature. */
550
679
  registerRequestKey(): Promise<RequestKeyRegistration>;
680
+ /** Replaces the registered key with `newSigningKey`. The new key proves
681
+ * itself and this client's current key approves the change. After
682
+ * success this client signs with the new key. If the call fails it
683
+ * keeps the current key, including after a timeout, when the server may
684
+ * have made the change. */
685
+ rotateRequestKey(newSigningKey: Uint8Array | KeyObject): Promise<RequestKeyRegistration>;
551
686
  /** Clears the registered public key, so the server stops requiring
552
- * request signatures on this API key. */
687
+ * request signatures on this API key. With a signing key the client
688
+ * sends that key's approval, which the server requires while a key is
689
+ * registered. After success the client drops its signing key and sends
690
+ * unsigned requests, because the server refuses a signed run on an API
691
+ * key with no registered key. If the call fails it keeps the key. */
553
692
  clearRequestKey(): Promise<void>;
554
693
  getRun(runId: number): Promise<Run>;
555
694
  verifyRun(runId: number): Promise<VerifyResponse>;
@@ -608,6 +747,10 @@ declare class VerdifaxClient {
608
747
  adminCreateKey(name: string): Promise<AdminCreateKeyResponse>;
609
748
  adminListKeys(): Promise<AdminAPIKey[]>;
610
749
  adminRevokeKey(keyId: number): Promise<void>;
750
+ /** Clears the request key on an API key whose holder lost it, after a
751
+ * manual check. It is the one way to clear a key without that key. The
752
+ * id can be any API key, so this client keeps its own signing key. */
753
+ adminClearRequestKey(keyId: number): Promise<void>;
611
754
  /** Convenience for attesting an Anthropic Claude API response. With
612
755
  * `govern: true` the response text also goes to the AIVP review as
613
756
  * `ai_output_text`; that is off by default, so the text leaves your
@@ -619,6 +762,7 @@ declare class VerdifaxClient {
619
762
  prompt: string;
620
763
  response: string;
621
764
  govern?: boolean;
765
+ job?: JobLink;
622
766
  }): Promise<ExecuteResponse>;
623
767
  /** Convenience for attesting an OpenAI API response. `govern` works as
624
768
  * in {@link attestClaudeResponse}. */
@@ -629,6 +773,7 @@ declare class VerdifaxClient {
629
773
  prompt: string;
630
774
  response: string;
631
775
  govern?: boolean;
776
+ job?: JobLink;
632
777
  }): Promise<ExecuteResponse>;
633
778
  private attestProviderResponse;
634
779
  private request;
@@ -636,8 +781,8 @@ declare class VerdifaxClient {
636
781
  private requestRaw;
637
782
  private requireAPIKey;
638
783
  private requireExecuteFields;
639
- /** The request with its derived flags and, with a signing key, a fresh
640
- * signature and nonce, on a copy. */
784
+ /** The body to send, on a copy: the request with its derived flags or,
785
+ * with a signing key, the signed text with a fresh signature and nonce. */
641
786
  private prepared;
642
787
  }
643
788
 
@@ -749,8 +894,9 @@ declare function cresReceiptHash(fields: {
749
894
  * 32-byte public key and a 32-byte private key. Nothing is sent to
750
895
  * the server. Store the private key as a secret. */
751
896
  declare function generateRequestKeyPair(): RequestKeyPair;
752
- /** The bytes a request signature signs: the fields joined with single
753
- * zero bytes, matching the orchestrator's RequestSignaturePreimage. */
897
+ /** The bytes a version 1 request signature signs: the fields joined with
898
+ * single zero bytes, matching the orchestrator's RequestSignaturePreimage.
899
+ * The client now sends version 2. See {@link requestSignaturePreimageV2}. */
754
900
  declare function requestSignaturePreimage(fields: {
755
901
  payload_hash: string;
756
902
  program_id: string;
@@ -759,9 +905,37 @@ declare function requestSignaturePreimage(fields: {
759
905
  signed_at: string;
760
906
  nonce: string;
761
907
  }): Buffer;
762
- /** Signs one execute request. The server hashes payload_text when it is
763
- * non-empty, otherwise the payload string exactly as sent (it does not
764
- * base64-decode it), so this does the same.
908
+ /** The bytes a version 2 request signature signs: the fields joined with
909
+ * single zero bytes, matching the orchestrator's
910
+ * RequestSignaturePreimageV2. With no job, the job fields are empty and
911
+ * `final` is false. */
912
+ declare function requestSignaturePreimageV2(fields: {
913
+ body_hash: string;
914
+ payload_hash: string;
915
+ program_id: string;
916
+ route_id: string;
917
+ registry_record_hash: string;
918
+ ai_output_hash: string;
919
+ job?: JobLink;
920
+ signed_at: string;
921
+ nonce: string;
922
+ }): Buffer;
923
+ /** Signs one execute request with signature version 2 and returns the
924
+ * whole body to send. The request is serialized once, and that exact text
925
+ * is what is hashed, signed and sent.
926
+ *
927
+ * The payload hash follows the same rule as {@link signRequest}.
928
+ * `signedAt` and `nonce` default to the current time and fresh random
929
+ * bytes. A nonce the server has already seen is refused. */
930
+ declare function signRequestV2(signingKey: Uint8Array | KeyObject, req: ExecuteRequest, options?: {
931
+ signedAt?: string;
932
+ nonce?: string;
933
+ }): SignedRequestBody;
934
+ /** Signs one execute request with signature version 1, which the server
935
+ * still accepts. The client now sends version 2: see {@link signRequestV2}.
936
+ * The server hashes payload_text when it is non-empty, otherwise the
937
+ * payload string exactly as sent (it does not base64-decode it), so this
938
+ * does the same.
765
939
  *
766
940
  * `signedAt` and `nonce` default to the current time and fresh random
767
941
  * bytes. A nonce the server has already seen is refused. */
@@ -772,6 +946,21 @@ declare function signRequest(signingKey: Uint8Array | KeyObject, req: ExecuteReq
772
946
  /** The body of POST /keys/request-key: the base64 public key and a
773
947
  * signature proving the caller holds its private key. */
774
948
  declare function requestKeyRegistrationBody(signingKey: Uint8Array | KeyObject): RequestKeyRegistrationBody;
949
+ /** The bytes the current key signs to replace or clear itself. `new_public_key`
950
+ * is the new base64 public key, or empty when clearing. */
951
+ declare function requestKeyChangePreimage(fields: {
952
+ new_public_key: string;
953
+ signed_at: string;
954
+ nonce: string;
955
+ }): Buffer;
956
+ /** The current key's approval of a key change, the `change` value of POST
957
+ * and DELETE /keys/request-key. Pass an empty `newPublicKey` when clearing.
958
+ * `signedAt` and `nonce` default to the current time and fresh random
959
+ * bytes. */
960
+ declare function requestKeyChangeApproval(currentSigningKey: Uint8Array | KeyObject, newPublicKey: string, options?: {
961
+ signedAt?: string;
962
+ nonce?: string;
963
+ }): RequestKeyChange;
775
964
 
776
965
  /**
777
966
  * Input-validation helpers, mirrors the Python SDK's
@@ -791,6 +980,9 @@ declare function validateHex64(value: string, fieldName: string): void;
791
980
  * a wider character set, but the SDK enforces the conservative
792
981
  * subset by default to catch typos early. */
793
982
  declare function validateRouteId(value: string): void;
983
+ /** Throws unless `value` is a job or step name: 1 to 128 letters, digits,
984
+ * dots, underscores, colons or dashes, the server's own rule. */
985
+ declare function validateJobName(value: string, fieldName: string): void;
794
986
 
795
987
  /**
796
988
  * Typed errors returned by the Verdifax SDK.
@@ -830,13 +1022,22 @@ declare class NotFoundError extends VerdifaxError {
830
1022
  constructor(path: string, message: string);
831
1023
  }
832
1024
  /** Every other non-2xx response. Carries the HTTP status and
833
- * the path so callers can switch on either dimension. */
1025
+ * the path so callers can switch on either dimension. `recordHash` is
1026
+ * the receipt hash of a refused or halted run, when the server sealed
1027
+ * one; it is undefined when no record exists.
1028
+ *
1029
+ * `signedRequest` is the exact text that was signed, when the call was a
1030
+ * signed run. Keep it to pass to `verdifax-verify --request FILE`. It holds
1031
+ * the payload, so it stays out of the message and is not enumerable: read
1032
+ * it by name, and logging the error does not print it. */
834
1033
  declare class APIError extends VerdifaxError {
835
1034
  readonly status: number;
836
1035
  readonly path: string;
837
1036
  readonly message: string;
1037
+ readonly recordHash?: string | undefined;
838
1038
  readonly name: string;
839
- constructor(status: number, path: string, message: string);
1039
+ readonly signedRequest?: string;
1040
+ constructor(status: number, path: string, message: string, recordHash?: string | undefined, signedRequest?: string);
840
1041
  }
841
1042
  /** Returned when an authenticated method is called on a client
842
1043
  * constructed without an apiKey. */
@@ -845,4 +1046,4 @@ declare class APIKeyRequiredError extends VerdifaxError {
845
1046
  constructor();
846
1047
  }
847
1048
 
848
- export { APIError, APIKeyRequiredError, type AdminAPIKey, type AdminCreateKeyResponse, type AdminEraseRequest, type AttestRequest, type AttestationList, AuthError, CRES_ENGINE_VERSION, CRES_PREIMAGE_VERSION, type ClientConfig, type DCAEProofBundle, type DCAEVerdict, DCAE_PREIMAGE_VERSION, DSE_FORMULA_VERSION, type DeletionReceipt, type DeterminismDiff, type DeterminismRun, type ExecuteBatchResponse, type ExecuteBatchResult, type ExecuteRequest, type ExecuteResponse, type ExecutionManifest, type ExportRow, type ExportRunsParams, type FormulaEra, type Health, type Hex64, type ListAttestationsParams, type ListRunsParams, MAX_BATCH_ROWS, NotFoundError, type PDFFormat, RUN_STATUSES, type ReproducibilityContext, type RequestKeyPair, type RequestKeyRegistration, type RequestKeyRegistrationBody, type RequestSignature, type Run, type RunStats, type RunStatus, type RunSummary, type RunsExport, type RunsExportCSV, type SearchRunsParams, VerdifaxClient, VerdifaxError, type VerdifaxVersion, type VerifyDeterminismResponse, type VerifyResponse, captureEnvironment, cresReceiptHash, dcaeClosureHash, dseDispatchHash, formatProviderPayload, fromEnv, generateRequestKeyPair, requestKeyRegistrationBody, requestSignaturePreimage, signRequest, validateHex64, validateRouteId };
1049
+ export { APIError, APIKeyRequiredError, type AdminAPIKey, type AdminCreateKeyResponse, type AdminEraseRequest, type AttestRequest, type AttestationList, AuthError, CRES_ENGINE_VERSION, CRES_PREIMAGE_VERSION, type ClientConfig, type DCAEProofBundle, type DCAEVerdict, DCAE_PREIMAGE_VERSION, DSE_FORMULA_VERSION, type DeletionReceipt, type DeterminismDiff, type DeterminismRun, type ExecuteBatchResponse, type ExecuteBatchResult, type ExecuteRequest, type ExecuteResponse, type ExecutionManifest, type ExportRow, type ExportRunsParams, type FormulaEra, type Health, type Hex64, type JobLink, type JobStepOptions, type ListAttestationsParams, type ListRunsParams, MAX_BATCH_ROWS, NotFoundError, type PDFFormat, RUN_STATUSES, type ReproducibilityContext, type RequestKeyChange, type RequestKeyPair, type RequestKeyRegistration, type RequestKeyRegistrationBody, type RequestSignature, type RequestSignatureV2, type Run, type RunStats, type RunStatus, type RunSummary, type RunsExport, type RunsExportCSV, type SearchRunsParams, type SignedRequestBody, VerdifaxClient, VerdifaxError, VerdifaxJob, type VerdifaxVersion, type VerifyDeterminismResponse, type VerifyResponse, captureEnvironment, cresReceiptHash, dcaeClosureHash, dseDispatchHash, formatProviderPayload, fromEnv, generateRequestKeyPair, requestKeyChangeApproval, requestKeyChangePreimage, requestKeyRegistrationBody, requestSignaturePreimage, requestSignaturePreimageV2, signRequest, signRequestV2, validateHex64, validateJobName, validateRouteId };