verdifax 1.1.0 → 1.3.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.js CHANGED
@@ -27,6 +27,7 @@ __export(index_exports, {
27
27
  CRES_PREIMAGE_VERSION: () => CRES_PREIMAGE_VERSION,
28
28
  DCAE_PREIMAGE_VERSION: () => DCAE_PREIMAGE_VERSION,
29
29
  DSE_FORMULA_VERSION: () => DSE_FORMULA_VERSION,
30
+ EXECUTE_FIELDS: () => EXECUTE_FIELDS,
30
31
  MAX_BATCH_ROWS: () => MAX_BATCH_ROWS,
31
32
  NotFoundError: () => NotFoundError,
32
33
  RUN_STATUSES: () => RUN_STATUSES,
@@ -40,6 +41,8 @@ __export(index_exports, {
40
41
  formatProviderPayload: () => formatProviderPayload,
41
42
  fromEnv: () => fromEnv,
42
43
  generateRequestKeyPair: () => generateRequestKeyPair,
44
+ policyActivationApproval: () => policyActivationApproval,
45
+ policyActivationPreimage: () => policyActivationPreimage,
43
46
  requestKeyChangeApproval: () => requestKeyChangeApproval,
44
47
  requestKeyChangePreimage: () => requestKeyChangePreimage,
45
48
  requestKeyRegistrationBody: () => requestKeyRegistrationBody,
@@ -214,7 +217,144 @@ function normalizedJob(job) {
214
217
  if (job.final) out.final = true;
215
218
  return out;
216
219
  }
220
+ var POLICY_ACTOR_TYPES = ["agent", "user", "service", "system"];
221
+ var POLICY_OBJECT_TYPES = ["text", "transaction", "code", "data", "model", "file"];
222
+ var POLICY_CLASSIFICATIONS = [
223
+ "PUBLIC",
224
+ "INTERNAL",
225
+ "CONFIDENTIAL",
226
+ "RESTRICTED",
227
+ "TOP_SECRET",
228
+ "TS_SCI"
229
+ ];
230
+ var MAX_POLICY_OBJECT_ID_BYTES = 256;
231
+ var POLICY_CONTEXT_FIELDS = {
232
+ actor_type: true,
233
+ mfa_satisfied: true,
234
+ session_age_seconds: true,
235
+ object_type: true,
236
+ object_id: true,
237
+ classification: true
238
+ };
239
+ function refuseOutsideList(value, allowed, fieldName) {
240
+ if (typeof value !== "string" || !allowed.includes(value)) {
241
+ throw new VerdifaxError(
242
+ `verdifax: policy_context.${fieldName} must be one of ${allowed.join(", ")}`
243
+ );
244
+ }
245
+ }
246
+ function checkedPolicyContext(context) {
247
+ if (typeof context !== "object" || context === null || Array.isArray(context)) {
248
+ throw new VerdifaxError("verdifax: policy_context must be an object");
249
+ }
250
+ refuseUnknownFields(context, POLICY_CONTEXT_FIELDS, "policy_context");
251
+ const out = {};
252
+ const {
253
+ actor_type,
254
+ mfa_satisfied,
255
+ session_age_seconds,
256
+ object_type,
257
+ object_id,
258
+ classification
259
+ } = context;
260
+ if (actor_type !== void 0) {
261
+ refuseOutsideList(actor_type, POLICY_ACTOR_TYPES, "actor_type");
262
+ out.actor_type = actor_type;
263
+ }
264
+ if (mfa_satisfied !== void 0) {
265
+ if (typeof mfa_satisfied !== "boolean") {
266
+ throw new VerdifaxError(
267
+ "verdifax: policy_context.mfa_satisfied must be true or false"
268
+ );
269
+ }
270
+ out.mfa_satisfied = mfa_satisfied;
271
+ }
272
+ if (session_age_seconds !== void 0) {
273
+ if (!Number.isSafeInteger(session_age_seconds) || session_age_seconds < 0) {
274
+ throw new VerdifaxError(
275
+ "verdifax: policy_context.session_age_seconds must be a whole number of seconds, not negative"
276
+ );
277
+ }
278
+ out.session_age_seconds = session_age_seconds;
279
+ }
280
+ if (object_type !== void 0) {
281
+ refuseOutsideList(object_type, POLICY_OBJECT_TYPES, "object_type");
282
+ out.object_type = object_type;
283
+ }
284
+ if (object_id !== void 0) {
285
+ if (typeof object_id !== "string") {
286
+ throw new VerdifaxError("verdifax: policy_context.object_id must be a string");
287
+ }
288
+ if (Buffer.byteLength(object_id, "utf8") > MAX_POLICY_OBJECT_ID_BYTES) {
289
+ throw new VerdifaxError(
290
+ `verdifax: policy_context.object_id is longer than ${MAX_POLICY_OBJECT_ID_BYTES} bytes`
291
+ );
292
+ }
293
+ out.object_id = object_id;
294
+ }
295
+ if (classification !== void 0) {
296
+ refuseOutsideList(classification, POLICY_CLASSIFICATIONS, "classification");
297
+ out.classification = classification;
298
+ }
299
+ return out;
300
+ }
301
+ function refuseActorFactsWithoutActor(context, attested) {
302
+ const declaresActor = context.actor_type !== void 0 || context.mfa_satisfied === true || (context.session_age_seconds ?? 0) !== 0;
303
+ if (declaresActor && attested?.attested !== true) {
304
+ throw new VerdifaxError(
305
+ "verdifax: policy_context.actor_type, mfa_satisfied and session_age_seconds describe the attested actor; send attested_context with the actor, for example its actor_id"
306
+ );
307
+ }
308
+ }
309
+ var EXECUTE_FIELD_SET = {
310
+ payload: true,
311
+ payload_text: true,
312
+ program_id: true,
313
+ route_id: true,
314
+ registry_record_hash: true,
315
+ ai_output_text: true,
316
+ attested_context: true,
317
+ reproducibility_context: true,
318
+ request_substance: true,
319
+ authorization_chain: true,
320
+ regulatory_scaffolding: true,
321
+ causal_graph: true,
322
+ request_signature: true,
323
+ signed_request: true,
324
+ job: true,
325
+ policy_context: true,
326
+ expected_policy_hash: true
327
+ };
328
+ var EXECUTE_FIELDS = Object.freeze(
329
+ Object.keys(EXECUTE_FIELD_SET)
330
+ );
331
+ var ATTESTED_CONTEXT_FIELDS = {
332
+ attested: true,
333
+ actor_id: true,
334
+ actor_role: true,
335
+ authorization_policy: true,
336
+ actor_signature: true,
337
+ model_provider: true,
338
+ model_name: true,
339
+ model_version: true,
340
+ model_temperature: true,
341
+ prompt_hash: true,
342
+ decision_kind: true,
343
+ decision_result: true,
344
+ decision_note: true
345
+ };
346
+ function refuseUnknownRequestFields(req, label = "request") {
347
+ if (typeof req !== "object" || req === null || Array.isArray(req)) {
348
+ throw new VerdifaxError(`verdifax: ${label} must be an object`);
349
+ }
350
+ refuseUnknownFields(req, EXECUTE_FIELD_SET, label);
351
+ const context = req.attested_context;
352
+ if (typeof context === "object" && context !== null) {
353
+ refuseUnknownFields(context, ATTESTED_CONTEXT_FIELDS, "attested_context");
354
+ }
355
+ }
217
356
  function withExecuteFlags(req) {
357
+ refuseUnknownRequestFields(req);
218
358
  const body = { ...req };
219
359
  if (req.job === void 0 || req.job === null) {
220
360
  delete body.job;
@@ -227,6 +367,17 @@ function withExecuteFlags(req) {
227
367
  if (req.reproducibility_context) {
228
368
  body.reproducibility_context = withDeclaredFlag(req.reproducibility_context);
229
369
  }
370
+ if (req.policy_context === void 0 || req.policy_context === null) {
371
+ delete body.policy_context;
372
+ } else {
373
+ body.policy_context = checkedPolicyContext(req.policy_context);
374
+ refuseActorFactsWithoutActor(body.policy_context, body.attested_context);
375
+ }
376
+ if (req.expected_policy_hash === void 0 || req.expected_policy_hash === null) {
377
+ delete body.expected_policy_hash;
378
+ } else {
379
+ validateHex64(req.expected_policy_hash, "expected_policy_hash");
380
+ }
230
381
  return body;
231
382
  }
232
383
 
@@ -237,6 +388,7 @@ var REQUEST_SIGNATURE_VERSION_2 = "2";
237
388
  var JOB_FORM = "verdifax.job.v1";
238
389
  var REQUEST_KEY_PROOF_FORM = "verdifax.request-key.v1";
239
390
  var REQUEST_KEY_CHANGE_FORM = "verdifax.request-key-change.v1";
391
+ var POLICY_ACTIVATION_FORM = "verdifax.policy-activation.v1";
240
392
  var SIGNING_KEY_BYTES = 32;
241
393
  var PKCS8_ED25519_HEADER = Buffer.from(
242
394
  "302e020100300506032b657004220420",
@@ -391,6 +543,36 @@ function requestKeyChangeApproval(currentSigningKey, newPublicKey, options = {})
391
543
  nonce
392
544
  };
393
545
  }
546
+ function policyActivationPreimage(fields) {
547
+ return Buffer.from(
548
+ [
549
+ POLICY_ACTIVATION_FORM,
550
+ fields.chain_id,
551
+ fields.document_hash,
552
+ fields.previous_activation_hash,
553
+ fields.signed_at,
554
+ fields.nonce
555
+ ].join("\0"),
556
+ "utf8"
557
+ );
558
+ }
559
+ function policyActivationApproval(signingKey, fields, options = {}) {
560
+ const key = loadSigningKey(signingKey);
561
+ const signed_at = options.signedAt ?? signedAtNow();
562
+ const nonce = options.nonce ?? (0, import_node_crypto.randomBytes)(16).toString("hex");
563
+ const preimage = policyActivationPreimage({
564
+ chain_id: fields.chain_id,
565
+ document_hash: fields.document_hash,
566
+ previous_activation_hash: fields.previous_activation_hash,
567
+ signed_at,
568
+ nonce
569
+ });
570
+ return {
571
+ signature: (0, import_node_crypto.sign)(null, preimage, key).toString("base64"),
572
+ signed_at,
573
+ nonce
574
+ };
575
+ }
394
576
 
395
577
  // src/job.ts
396
578
  var STEP_OPTIONS = {
@@ -406,6 +588,8 @@ var STEP_OPTIONS = {
406
588
  authorization_chain: true,
407
589
  regulatory_scaffolding: true,
408
590
  causal_graph: true,
591
+ policy_context: true,
592
+ expected_policy_hash: true,
409
593
  after: true,
410
594
  external_previous: true,
411
595
  final: true
@@ -490,7 +674,7 @@ var VerdifaxJob = class {
490
674
  };
491
675
 
492
676
  // src/version.ts
493
- var SDK_VERSION = "1.1.0";
677
+ var SDK_VERSION = "1.3.0";
494
678
 
495
679
  // src/client.ts
496
680
  var DEFAULT_BASE_URL = "https://api.verdifax.com";
@@ -500,10 +684,46 @@ var DEFAULT_RETRY_BASE_DELAY_MS = 200;
500
684
  var SDK_USER_AGENT = `verdifax-sdk-node/${SDK_VERSION}`;
501
685
  var HEADER_API_KEY = "X-Verdifax-Key";
502
686
  var REQUEST_KEY_PATH = "/keys/request-key";
687
+ var POLICIES_PATH = "/policies";
688
+ var ACCOUNT_PATH = "/account";
689
+ var ACCOUNT_REQUEST_KEY_PATH = "/account/request-key";
690
+ var ACCOUNT_POLICIES_PATH = "/account/policies";
691
+ var ACCOUNT_CHAIN_ID = /^account-[1-9][0-9]*$/;
503
692
  var HEADER_EXPORT_TOTAL = "X-Verdifax-Total";
504
693
  var HEADER_EXPORT_TRUNCATED = "X-Verdifax-Truncated";
505
694
  var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
506
695
  var MAX_BATCH_ROWS = 1e3;
696
+ var ATTEST_FIELDS = {
697
+ payload: true,
698
+ payload_text: true,
699
+ program_id: true,
700
+ route_id: true,
701
+ registry_record_hash: true,
702
+ ai_output_text: true,
703
+ attested_context: true,
704
+ reproducibility_context: true,
705
+ request_substance: true,
706
+ authorization_chain: true,
707
+ regulatory_scaffolding: true,
708
+ causal_graph: true,
709
+ job: true,
710
+ policy_context: true,
711
+ expected_policy_hash: true
712
+ };
713
+ var PROVIDER_ARGS = Object.fromEntries(
714
+ [
715
+ "program_id",
716
+ "route_id",
717
+ "registry_record_hash",
718
+ "prompt",
719
+ "response",
720
+ "govern",
721
+ "job",
722
+ "policy_context",
723
+ "expected_policy_hash",
724
+ ...EXECUTE_FIELDS
725
+ ].map((name) => [name, true])
726
+ );
507
727
  var VerdifaxClient = class {
508
728
  baseURL;
509
729
  apiKey;
@@ -539,6 +759,7 @@ var VerdifaxClient = class {
539
759
  }
540
760
  // ── Execute / Attest (auth) ───────────────────────────────────
541
761
  async execute(req) {
762
+ refuseUnknownRequestFields(req);
542
763
  this.requireExecuteFields(req);
543
764
  const sent = this.prepared(req);
544
765
  const out = await this.request("POST", "/execute", sent);
@@ -549,6 +770,7 @@ var VerdifaxClient = class {
549
770
  * its nonce once. Both runs are recorded. A run that happens twice
550
771
  * cannot be one job step, so a `job` is refused here. */
551
772
  async verifyDeterminism(req) {
773
+ refuseUnknownRequestFields(req);
552
774
  this.requireExecuteFields(req);
553
775
  if (req.job !== void 0 && req.job !== null) {
554
776
  throw new VerdifaxError(
@@ -567,8 +789,9 @@ var VerdifaxClient = class {
567
789
  * signing key each row carries its own signature and nonce. A row
568
790
  * that fails comes back in `results` with `ok: false`, so the call
569
791
  * does not throw for it. The rows are not checked here, except a job
570
- * block, which is refused before anything is sent: the server reports
571
- * any other bad row in that row's result.
792
+ * block, a `policy_context` and an `expected_policy_hash`, which are
793
+ * refused before anything is sent: the server reports any other bad row
794
+ * in that row's result.
572
795
  *
573
796
  * This is one POST and it is never retried. After a timeout the
574
797
  * server may have run some or all of the rows. */
@@ -582,6 +805,7 @@ var VerdifaxClient = class {
582
805
  `verdifax: executeBatch accepts at most ${MAX_BATCH_ROWS} runs (got ${runs.length})`
583
806
  );
584
807
  }
808
+ runs.forEach((run, i) => refuseUnknownRequestFields(run, `runs[${i}]`));
585
809
  const sent = runs.map((run) => this.prepared(run));
586
810
  const out = await this.request(
587
811
  "POST",
@@ -602,6 +826,8 @@ var VerdifaxClient = class {
602
826
  * routes the call through to the canonical /execute. */
603
827
  async attest(req) {
604
828
  this.requireAPIKey();
829
+ refuseUnknownRequestFields(req, "attest request");
830
+ refuseUnknownFields(req, ATTEST_FIELDS, "attest request");
605
831
  if (req.payload !== void 0 && req.payload_text !== void 0) {
606
832
  throw new VerdifaxError(
607
833
  "verdifax: attest accepts payload OR payload_text, not both"
@@ -626,7 +852,9 @@ var VerdifaxClient = class {
626
852
  authorization_chain: req.authorization_chain,
627
853
  regulatory_scaffolding: req.regulatory_scaffolding,
628
854
  causal_graph: req.causal_graph,
629
- job: req.job
855
+ job: req.job,
856
+ policy_context: req.policy_context,
857
+ expected_policy_hash: req.expected_policy_hash
630
858
  };
631
859
  if (req.payload !== void 0) {
632
860
  body.payload = Buffer.isBuffer(req.payload) ? req.payload.toString("base64") : Buffer.from(req.payload).toString("base64");
@@ -646,15 +874,18 @@ var VerdifaxClient = class {
646
874
  * that the client holds the private key. From then on the server
647
875
  * refuses a run on this API key without a request signature. */
648
876
  async registerRequestKey() {
877
+ return this.registerRequestKeyAt(REQUEST_KEY_PATH, "registerRequestKey");
878
+ }
879
+ async registerRequestKeyAt(path, method) {
649
880
  this.requireAPIKey();
650
881
  if (!this.signingKey) {
651
882
  throw new VerdifaxError(
652
- "verdifax: registerRequestKey needs the signingKey client option"
883
+ `verdifax: ${method} needs the signingKey client option`
653
884
  );
654
885
  }
655
886
  return this.request(
656
887
  "POST",
657
- REQUEST_KEY_PATH,
888
+ path,
658
889
  requestKeyRegistrationBody(this.signingKey)
659
890
  );
660
891
  }
@@ -693,14 +924,277 @@ var VerdifaxClient = class {
693
924
  * unsigned requests, because the server refuses a signed run on an API
694
925
  * key with no registered key. If the call fails it keeps the key. */
695
926
  async clearRequestKey() {
927
+ await this.clearRequestKeyAt(REQUEST_KEY_PATH);
928
+ }
929
+ async clearRequestKeyAt(path) {
696
930
  this.requireAPIKey();
697
931
  await this.request(
698
932
  "DELETE",
699
- REQUEST_KEY_PATH,
933
+ path,
700
934
  this.signingKey ? { change: requestKeyChangeApproval(this.signingKey, "") } : void 0
701
935
  );
702
936
  this.signingKey = void 0;
703
937
  }
938
+ // ── Policies (auth) ──────────────────────────────────────────
939
+ /** Stores a policy document. Nothing changes until it is activated with
940
+ * {@link activatePolicy}. The server hashes the exact text, so this
941
+ * sends the string it is given unchanged and refuses anything that is
942
+ * not a string: serializing an object here would change the hash.
943
+ * Verdifax does not write, review or approve a customer's policy. */
944
+ async uploadPolicy(document) {
945
+ return this.uploadPolicyAt(POLICIES_PATH, document);
946
+ }
947
+ /** This key's stored documents, with the chain its changes belong to,
948
+ * the newest change in it (64 zeros when there is none yet) and the
949
+ * document in force (empty under the built-in policy). */
950
+ async listPolicies() {
951
+ this.requireAPIKey();
952
+ return this.request("GET", POLICIES_PATH);
953
+ }
954
+ /** One stored document, with its exact text as `document`. */
955
+ async getPolicy(documentHash) {
956
+ return this.getPolicyAt(POLICIES_PATH, documentHash);
957
+ }
958
+ /** Shows which rule would fire on sample inputs, for a document given as
959
+ * text or one already stored, named by its hash: exactly one of the two.
960
+ * It seals nothing. */
961
+ async testPolicy(req) {
962
+ return this.testPolicyAt(POLICIES_PATH, req, "testPolicy");
963
+ }
964
+ /** This key's policy changes, oldest first, as the list the server sends.
965
+ * Save it as JSON, for example with
966
+ * `writeFileSync("policy-history.json", JSON.stringify(history))`, and
967
+ * check it with `verdifax-verify --policy-history policy-history.json`. */
968
+ async getPolicyHistory() {
969
+ this.requireAPIKey();
970
+ return this.request("GET", `${POLICIES_PATH}/history`);
971
+ }
972
+ /** Makes a stored document the policy in force and returns the change
973
+ * record. It first reads GET /policies for the chain and its newest
974
+ * change, then names that change as the one this follows. With the
975
+ * `signingKey` option the request key signs the change, which the server
976
+ * requires while a request key is registered.
977
+ *
978
+ * Rolling back is activating an earlier document: it is one more change
979
+ * in the chain, so the change it undoes stays on the record.
980
+ *
981
+ * An HTTP 409 means the chain moved because another change came first,
982
+ * or the document is not yet effective. It throws the usual
983
+ * {@link APIError} and is not retried: read the policies again and
984
+ * decide. */
985
+ async activatePolicy(documentHash) {
986
+ this.requireAPIKey();
987
+ validateHex64(documentHash, "documentHash");
988
+ return this.activatePolicyAt(
989
+ POLICIES_PATH,
990
+ documentHash,
991
+ await this.listPolicies()
992
+ );
993
+ }
994
+ async uploadPolicyAt(base, document) {
995
+ this.requireAPIKey();
996
+ if (typeof document !== "string") {
997
+ throw new VerdifaxError(
998
+ "verdifax: document must be the policy's exact text, as a string"
999
+ );
1000
+ }
1001
+ return this.request("POST", base, { document });
1002
+ }
1003
+ async getPolicyAt(base, documentHash) {
1004
+ this.requireAPIKey();
1005
+ validateHex64(documentHash, "documentHash");
1006
+ return this.request("GET", `${base}/${documentHash}`);
1007
+ }
1008
+ async testPolicyAt(base, req, method) {
1009
+ this.requireAPIKey();
1010
+ const { inputs, document, documentHash } = req ?? {};
1011
+ if (document === void 0 === (documentHash === void 0)) {
1012
+ throw new VerdifaxError(
1013
+ `verdifax: ${method} needs exactly one of document or documentHash`
1014
+ );
1015
+ }
1016
+ if (typeof inputs !== "object" || inputs === null || Array.isArray(inputs)) {
1017
+ throw new VerdifaxError(`verdifax: ${method} needs inputs as an object`);
1018
+ }
1019
+ const body = { inputs };
1020
+ if (document !== void 0) {
1021
+ if (typeof document !== "string") {
1022
+ throw new VerdifaxError(
1023
+ "verdifax: document must be the policy's exact text, as a string"
1024
+ );
1025
+ }
1026
+ body.document = document;
1027
+ } else {
1028
+ validateHex64(documentHash, "documentHash");
1029
+ body.document_hash = documentHash;
1030
+ }
1031
+ return this.request("POST", `${base}/test`, body);
1032
+ }
1033
+ /** Posts the change that follows the newest one in `chain`, signed by
1034
+ * the request key when the client has one. */
1035
+ async activatePolicyAt(base, documentHash, chain) {
1036
+ const previous = chain?.newest_activation_hash;
1037
+ if (typeof previous !== "string") {
1038
+ throw new VerdifaxError(
1039
+ "verdifax: the server did not report newest_activation_hash, so the change cannot name the one it follows"
1040
+ );
1041
+ }
1042
+ const body = { previous_activation_hash: previous };
1043
+ if (this.signingKey) {
1044
+ if (typeof chain.chain_id !== "string" || chain.chain_id === "") {
1045
+ throw new VerdifaxError(
1046
+ "verdifax: the server did not report chain_id, so the change cannot be signed"
1047
+ );
1048
+ }
1049
+ body.signature = policyActivationApproval(this.signingKey, {
1050
+ chain_id: chain.chain_id,
1051
+ document_hash: documentHash,
1052
+ previous_activation_hash: previous
1053
+ });
1054
+ }
1055
+ const out = await this.request(
1056
+ "POST",
1057
+ `${base}/${documentHash}/activate`,
1058
+ body
1059
+ );
1060
+ return out.activation;
1061
+ }
1062
+ // ── Account, with its owner key (auth) ───────────────────────
1063
+ //
1064
+ // An account is a company on Verdifax. Every key belongs to one account
1065
+ // for life. These methods need the account's owner key, which manages the
1066
+ // account and cannot run anything: the server refuses it on every other
1067
+ // route. The account number is a label Verdifax assigned, not a verified
1068
+ // company identity.
1069
+ /** The owner's account: its number, name, tier, key counts and the
1070
+ * chain of its policy changes. */
1071
+ async getAccount() {
1072
+ this.requireAPIKey();
1073
+ return this.request("GET", ACCOUNT_PATH);
1074
+ }
1075
+ /** Creates a member key in the account. It takes the account's tier and
1076
+ * stays in the account for life. The secret is in the result this once.
1077
+ * On a trial account the key needs a request key before it can run. */
1078
+ async createAccountKey(name, options = {}) {
1079
+ this.requireAPIKey();
1080
+ if (typeof name !== "string" || name.trim() === "") {
1081
+ throw new VerdifaxError(
1082
+ "verdifax: name is required for createAccountKey"
1083
+ );
1084
+ }
1085
+ const body = { name };
1086
+ if (options.expiresAt !== void 0) body.expires_at = options.expiresAt;
1087
+ return this.request(
1088
+ "POST",
1089
+ `${ACCOUNT_PATH}/keys`,
1090
+ body
1091
+ );
1092
+ }
1093
+ /** Every key in the account, owner keys included, without secrets. */
1094
+ async listAccountKeys() {
1095
+ this.requireAPIKey();
1096
+ const out = await this.request(
1097
+ "GET",
1098
+ `${ACCOUNT_PATH}/keys`
1099
+ );
1100
+ return { ...out, keys: out?.keys ?? [] };
1101
+ }
1102
+ /** Revokes one of the account's member keys. A key in another account,
1103
+ * or an owner key, throws {@link NotFoundError}. */
1104
+ async revokeAccountKey(keyId) {
1105
+ this.requireAPIKey();
1106
+ requirePositiveId(keyId, "keyId");
1107
+ return this.request(
1108
+ "DELETE",
1109
+ `${ACCOUNT_PATH}/keys/${keyId}`
1110
+ );
1111
+ }
1112
+ /** Registers this client's public key as the owner key's request key,
1113
+ * which must sign every change to the account's policy. It works as
1114
+ * {@link registerRequestKey} does. */
1115
+ async registerAccountRequestKey() {
1116
+ return this.registerRequestKeyAt(
1117
+ ACCOUNT_REQUEST_KEY_PATH,
1118
+ "registerAccountRequestKey"
1119
+ );
1120
+ }
1121
+ /** Clears the owner key's request key, as {@link clearRequestKey} does
1122
+ * for a key's own. With none registered the account's policy cannot be
1123
+ * changed. A trial owner key's request key cannot be cleared. */
1124
+ async clearAccountRequestKey() {
1125
+ await this.clearRequestKeyAt(ACCOUNT_REQUEST_KEY_PATH);
1126
+ }
1127
+ /** The runs of every key in the account, paged as {@link listRuns}. */
1128
+ async listAccountRuns(params = {}) {
1129
+ return this.listRunsAt(`${ACCOUNT_PATH}/runs`, params);
1130
+ }
1131
+ /** The runs of every key in the account in a period, as
1132
+ * {@link exportRuns} lists a key's, with the account's number, its
1133
+ * policy documents and its chain of changes. The account's view covers
1134
+ * the runs its keys made through Verdifax, not runs made elsewhere. */
1135
+ async exportAccountRuns(params = {}) {
1136
+ this.requireAPIKey();
1137
+ return this.request(
1138
+ "GET",
1139
+ this.exportPath(`${ACCOUNT_PATH}/runs/export`, params, "json")
1140
+ );
1141
+ }
1142
+ /** The same rows as {@link exportAccountRuns} as raw CSV text, checked
1143
+ * as {@link exportRunsCSV} checks a key's. */
1144
+ async exportAccountRunsCSV(params = {}) {
1145
+ return this.exportCSVAt(`${ACCOUNT_PATH}/runs/export`, params);
1146
+ }
1147
+ /** Stores an account policy document, as {@link uploadPolicy} stores a
1148
+ * key's. Every key in the account must pass the account's policy on top
1149
+ * of its own. Verdifax does not write, review or approve it. */
1150
+ async uploadAccountPolicy(document) {
1151
+ return this.uploadPolicyAt(ACCOUNT_POLICIES_PATH, document);
1152
+ }
1153
+ /** The account's stored documents, its chain `account-N`, the newest
1154
+ * change in it and the document in force. */
1155
+ async listAccountPolicies() {
1156
+ this.requireAPIKey();
1157
+ return this.request("GET", ACCOUNT_POLICIES_PATH);
1158
+ }
1159
+ /** One of the account's documents, with its exact text. */
1160
+ async getAccountPolicy(documentHash) {
1161
+ return this.getPolicyAt(ACCOUNT_POLICIES_PATH, documentHash);
1162
+ }
1163
+ /** {@link testPolicy} for the account's documents. It seals nothing. */
1164
+ async testAccountPolicy(req) {
1165
+ return this.testPolicyAt(ACCOUNT_POLICIES_PATH, req, "testAccountPolicy");
1166
+ }
1167
+ /** The account's policy changes, oldest first, in the form
1168
+ * `verdifax-verify --policy-history FILE` reads. */
1169
+ async getAccountPolicyHistory() {
1170
+ this.requireAPIKey();
1171
+ return this.request(
1172
+ "GET",
1173
+ `${ACCOUNT_POLICIES_PATH}/history`
1174
+ );
1175
+ }
1176
+ /** Makes a stored document the account's policy in force, as
1177
+ * {@link activatePolicy} does for a key's. It reads
1178
+ * {@link listAccountPolicies} for the chain and its newest change, and
1179
+ * the owner key's request key signs the change over that chain,
1180
+ * `account-N`. The server refuses an unsigned account change, so this
1181
+ * needs the `signingKey` option and refuses to send without it. */
1182
+ async activateAccountPolicy(documentHash) {
1183
+ this.requireAPIKey();
1184
+ validateHex64(documentHash, "documentHash");
1185
+ if (!this.signingKey) {
1186
+ throw new VerdifaxError(
1187
+ "verdifax: activateAccountPolicy needs the owner key's request key in the signingKey client option, because the server refuses an unsigned account policy change"
1188
+ );
1189
+ }
1190
+ const chain = await this.listAccountPolicies();
1191
+ if (typeof chain?.chain_id !== "string" || !ACCOUNT_CHAIN_ID.test(chain.chain_id)) {
1192
+ throw new VerdifaxError(
1193
+ "verdifax: the server did not report an account chain_id (account-N), so the change cannot be signed"
1194
+ );
1195
+ }
1196
+ return this.activatePolicyAt(ACCOUNT_POLICIES_PATH, documentHash, chain);
1197
+ }
704
1198
  // ── Runs ──────────────────────────────────────────────────────
705
1199
  async getRun(runId) {
706
1200
  this.requireAPIKey();
@@ -713,6 +1207,9 @@ var VerdifaxClient = class {
713
1207
  return this.request("GET", `/runs/${runId}/verify`);
714
1208
  }
715
1209
  async listRuns(params = {}) {
1210
+ return this.listRunsAt("/runs", params);
1211
+ }
1212
+ async listRunsAt(base, params) {
716
1213
  this.requireAPIKey();
717
1214
  const q = new URLSearchParams();
718
1215
  if (params.limit && params.limit > 0) {
@@ -722,7 +1219,7 @@ var VerdifaxClient = class {
722
1219
  q.set("offset", String(params.offset));
723
1220
  }
724
1221
  const qs = q.toString();
725
- const path = qs ? `/runs?${qs}` : "/runs";
1222
+ const path = qs ? `${base}?${qs}` : base;
726
1223
  const out = await this.request("GET", path);
727
1224
  return { runs: out.runs ?? [], total: out.total ?? 0 };
728
1225
  }
@@ -760,7 +1257,10 @@ var VerdifaxClient = class {
760
1257
  * call that never reached Verdifax cannot appear in it. */
761
1258
  async exportRuns(params = {}) {
762
1259
  this.requireAPIKey();
763
- return this.request("GET", this.exportPath(params, "json"));
1260
+ return this.request(
1261
+ "GET",
1262
+ this.exportPath("/runs/export", params, "json")
1263
+ );
764
1264
  }
765
1265
  /** The same rows as {@link exportRuns}: the raw CSV text with `total`
766
1266
  * and `truncated` from the response headers. Check `truncated`: when
@@ -768,9 +1268,12 @@ var VerdifaxClient = class {
768
1268
  * does not send those headers, so that a cut-off export cannot pass
769
1269
  * for a complete one. */
770
1270
  async exportRunsCSV(params = {}) {
1271
+ return this.exportCSVAt("/runs/export", params);
1272
+ }
1273
+ async exportCSVAt(base, params) {
771
1274
  this.requireAPIKey();
772
1275
  const { text, headers } = await this.requestRaw(
773
- this.exportPath(params, "csv")
1276
+ this.exportPath(base, params, "csv")
774
1277
  );
775
1278
  const total = Number.parseInt(headers.get(HEADER_EXPORT_TOTAL) ?? "", 10);
776
1279
  const truncated = headers.get(HEADER_EXPORT_TRUNCATED);
@@ -781,13 +1284,13 @@ var VerdifaxClient = class {
781
1284
  }
782
1285
  return { csv: text, total, truncated: truncated === "true" };
783
1286
  }
784
- exportPath(params, format) {
1287
+ exportPath(base, params, format) {
785
1288
  const q = new URLSearchParams();
786
1289
  if (params.from) q.set("from", params.from);
787
1290
  if (params.to) q.set("to", params.to);
788
1291
  if (params.job_id) q.set("job_id", params.job_id);
789
1292
  q.set("format", format);
790
- return `/runs/export?${q.toString()}`;
1293
+ return `${base}?${q.toString()}`;
791
1294
  }
792
1295
  // ── Attestation receipts (auth) ──────────────────────────────
793
1296
  async listAttestations(params = {}) {
@@ -925,14 +1428,48 @@ var VerdifaxClient = class {
925
1428
  return this.request("POST", "/dcae/verify", bundle);
926
1429
  }
927
1430
  // ── Admin: API keys (auth) ───────────────────────────────────
928
- async adminCreateKey(name) {
1431
+ /** Creates a customer key. With `accountId` the key joins that existing
1432
+ * account; without it the key gets an account of its own. A key never
1433
+ * changes account. */
1434
+ async adminCreateKey(name, options = {}) {
929
1435
  this.requireAPIKey();
930
1436
  if (!name) {
931
1437
  throw new VerdifaxError("verdifax: name is required for adminCreateKey");
932
1438
  }
933
- return this.request("POST", "/admin/keys", {
934
- name
935
- });
1439
+ const body = { name };
1440
+ if (options.accountId !== void 0) {
1441
+ requirePositiveId(options.accountId, "accountId");
1442
+ body.account_id = options.accountId;
1443
+ }
1444
+ return this.request("POST", "/admin/keys", body);
1445
+ }
1446
+ /** Creates an account and its first owner key, whose secret is in the
1447
+ * result this once. The owner key creates the member keys. The account
1448
+ * number is a label Verdifax assigned, not a verified company identity. */
1449
+ async adminCreateAccount(name, options = {}) {
1450
+ this.requireAPIKey();
1451
+ if (typeof name !== "string" || name.trim() === "") {
1452
+ throw new VerdifaxError(
1453
+ "verdifax: name is required for adminCreateAccount"
1454
+ );
1455
+ }
1456
+ const body = { name };
1457
+ if (options.tier !== void 0) body.tier = options.tier;
1458
+ if (options.expiresAt !== void 0) body.expires_at = options.expiresAt;
1459
+ return this.request(
1460
+ "POST",
1461
+ "/admin/accounts",
1462
+ body
1463
+ );
1464
+ }
1465
+ /** Every account, with its key counts. */
1466
+ async adminListAccounts() {
1467
+ this.requireAPIKey();
1468
+ const out = await this.request(
1469
+ "GET",
1470
+ "/admin/accounts"
1471
+ );
1472
+ return out?.accounts ?? [];
936
1473
  }
937
1474
  async adminListKeys() {
938
1475
  this.requireAPIKey();
@@ -965,11 +1502,13 @@ var VerdifaxClient = class {
965
1502
  * `ai_output_text`; that is off by default, so the text leaves your
966
1503
  * machine for review only when you ask. */
967
1504
  async attestClaudeResponse(args) {
1505
+ refuseUnknownFields(args, PROVIDER_ARGS, "attestClaudeResponse arguments");
968
1506
  return this.attestProviderResponse({ ...args, provider: "anthropic" });
969
1507
  }
970
1508
  /** Convenience for attesting an OpenAI API response. `govern` works as
971
1509
  * in {@link attestClaudeResponse}. */
972
1510
  async attestOpenAIResponse(args) {
1511
+ refuseUnknownFields(args, PROVIDER_ARGS, "attestOpenAIResponse arguments");
973
1512
  return this.attestProviderResponse({ ...args, provider: "openai" });
974
1513
  }
975
1514
  async attestProviderResponse(args) {
@@ -992,7 +1531,9 @@ var VerdifaxClient = class {
992
1531
  actor_role: "ai_assistant",
993
1532
  model_provider: args.provider
994
1533
  },
995
- job: args.job
1534
+ job: args.job,
1535
+ policy_context: args.policy_context,
1536
+ expected_policy_hash: args.expected_policy_hash
996
1537
  });
997
1538
  }
998
1539
  // ── HTTP plumbing, retry-aware ──────────────────────────────
@@ -1107,6 +1648,11 @@ var VerdifaxClient = class {
1107
1648
  return this.signingKey ? signRequestV2(this.signingKey, req) : withExecuteFlags(req);
1108
1649
  }
1109
1650
  };
1651
+ function requirePositiveId(id, name) {
1652
+ if (!Number.isSafeInteger(id) || id <= 0) {
1653
+ throw new VerdifaxError(`verdifax: ${name} must be a whole number > 0`);
1654
+ }
1655
+ }
1110
1656
  function withSignedText(result, sent) {
1111
1657
  return "signed_request" in sent ? { ...result, signed_request: sent.signed_request } : result;
1112
1658
  }
@@ -1301,6 +1847,7 @@ function cresReceiptHash(fields) {
1301
1847
  CRES_PREIMAGE_VERSION,
1302
1848
  DCAE_PREIMAGE_VERSION,
1303
1849
  DSE_FORMULA_VERSION,
1850
+ EXECUTE_FIELDS,
1304
1851
  MAX_BATCH_ROWS,
1305
1852
  NotFoundError,
1306
1853
  RUN_STATUSES,
@@ -1314,6 +1861,8 @@ function cresReceiptHash(fields) {
1314
1861
  formatProviderPayload,
1315
1862
  fromEnv,
1316
1863
  generateRequestKeyPair,
1864
+ policyActivationApproval,
1865
+ policyActivationPreimage,
1317
1866
  requestKeyChangeApproval,
1318
1867
  requestKeyChangePreimage,
1319
1868
  requestKeyRegistrationBody,