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.mjs CHANGED
@@ -167,7 +167,144 @@ function normalizedJob(job) {
167
167
  if (job.final) out.final = true;
168
168
  return out;
169
169
  }
170
+ var POLICY_ACTOR_TYPES = ["agent", "user", "service", "system"];
171
+ var POLICY_OBJECT_TYPES = ["text", "transaction", "code", "data", "model", "file"];
172
+ var POLICY_CLASSIFICATIONS = [
173
+ "PUBLIC",
174
+ "INTERNAL",
175
+ "CONFIDENTIAL",
176
+ "RESTRICTED",
177
+ "TOP_SECRET",
178
+ "TS_SCI"
179
+ ];
180
+ var MAX_POLICY_OBJECT_ID_BYTES = 256;
181
+ var POLICY_CONTEXT_FIELDS = {
182
+ actor_type: true,
183
+ mfa_satisfied: true,
184
+ session_age_seconds: true,
185
+ object_type: true,
186
+ object_id: true,
187
+ classification: true
188
+ };
189
+ function refuseOutsideList(value, allowed, fieldName) {
190
+ if (typeof value !== "string" || !allowed.includes(value)) {
191
+ throw new VerdifaxError(
192
+ `verdifax: policy_context.${fieldName} must be one of ${allowed.join(", ")}`
193
+ );
194
+ }
195
+ }
196
+ function checkedPolicyContext(context) {
197
+ if (typeof context !== "object" || context === null || Array.isArray(context)) {
198
+ throw new VerdifaxError("verdifax: policy_context must be an object");
199
+ }
200
+ refuseUnknownFields(context, POLICY_CONTEXT_FIELDS, "policy_context");
201
+ const out = {};
202
+ const {
203
+ actor_type,
204
+ mfa_satisfied,
205
+ session_age_seconds,
206
+ object_type,
207
+ object_id,
208
+ classification
209
+ } = context;
210
+ if (actor_type !== void 0) {
211
+ refuseOutsideList(actor_type, POLICY_ACTOR_TYPES, "actor_type");
212
+ out.actor_type = actor_type;
213
+ }
214
+ if (mfa_satisfied !== void 0) {
215
+ if (typeof mfa_satisfied !== "boolean") {
216
+ throw new VerdifaxError(
217
+ "verdifax: policy_context.mfa_satisfied must be true or false"
218
+ );
219
+ }
220
+ out.mfa_satisfied = mfa_satisfied;
221
+ }
222
+ if (session_age_seconds !== void 0) {
223
+ if (!Number.isSafeInteger(session_age_seconds) || session_age_seconds < 0) {
224
+ throw new VerdifaxError(
225
+ "verdifax: policy_context.session_age_seconds must be a whole number of seconds, not negative"
226
+ );
227
+ }
228
+ out.session_age_seconds = session_age_seconds;
229
+ }
230
+ if (object_type !== void 0) {
231
+ refuseOutsideList(object_type, POLICY_OBJECT_TYPES, "object_type");
232
+ out.object_type = object_type;
233
+ }
234
+ if (object_id !== void 0) {
235
+ if (typeof object_id !== "string") {
236
+ throw new VerdifaxError("verdifax: policy_context.object_id must be a string");
237
+ }
238
+ if (Buffer.byteLength(object_id, "utf8") > MAX_POLICY_OBJECT_ID_BYTES) {
239
+ throw new VerdifaxError(
240
+ `verdifax: policy_context.object_id is longer than ${MAX_POLICY_OBJECT_ID_BYTES} bytes`
241
+ );
242
+ }
243
+ out.object_id = object_id;
244
+ }
245
+ if (classification !== void 0) {
246
+ refuseOutsideList(classification, POLICY_CLASSIFICATIONS, "classification");
247
+ out.classification = classification;
248
+ }
249
+ return out;
250
+ }
251
+ function refuseActorFactsWithoutActor(context, attested) {
252
+ const declaresActor = context.actor_type !== void 0 || context.mfa_satisfied === true || (context.session_age_seconds ?? 0) !== 0;
253
+ if (declaresActor && attested?.attested !== true) {
254
+ throw new VerdifaxError(
255
+ "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"
256
+ );
257
+ }
258
+ }
259
+ var EXECUTE_FIELD_SET = {
260
+ payload: true,
261
+ payload_text: true,
262
+ program_id: true,
263
+ route_id: true,
264
+ registry_record_hash: true,
265
+ ai_output_text: true,
266
+ attested_context: true,
267
+ reproducibility_context: true,
268
+ request_substance: true,
269
+ authorization_chain: true,
270
+ regulatory_scaffolding: true,
271
+ causal_graph: true,
272
+ request_signature: true,
273
+ signed_request: true,
274
+ job: true,
275
+ policy_context: true,
276
+ expected_policy_hash: true
277
+ };
278
+ var EXECUTE_FIELDS = Object.freeze(
279
+ Object.keys(EXECUTE_FIELD_SET)
280
+ );
281
+ var ATTESTED_CONTEXT_FIELDS = {
282
+ attested: true,
283
+ actor_id: true,
284
+ actor_role: true,
285
+ authorization_policy: true,
286
+ actor_signature: true,
287
+ model_provider: true,
288
+ model_name: true,
289
+ model_version: true,
290
+ model_temperature: true,
291
+ prompt_hash: true,
292
+ decision_kind: true,
293
+ decision_result: true,
294
+ decision_note: true
295
+ };
296
+ function refuseUnknownRequestFields(req, label = "request") {
297
+ if (typeof req !== "object" || req === null || Array.isArray(req)) {
298
+ throw new VerdifaxError(`verdifax: ${label} must be an object`);
299
+ }
300
+ refuseUnknownFields(req, EXECUTE_FIELD_SET, label);
301
+ const context = req.attested_context;
302
+ if (typeof context === "object" && context !== null) {
303
+ refuseUnknownFields(context, ATTESTED_CONTEXT_FIELDS, "attested_context");
304
+ }
305
+ }
170
306
  function withExecuteFlags(req) {
307
+ refuseUnknownRequestFields(req);
171
308
  const body = { ...req };
172
309
  if (req.job === void 0 || req.job === null) {
173
310
  delete body.job;
@@ -180,6 +317,17 @@ function withExecuteFlags(req) {
180
317
  if (req.reproducibility_context) {
181
318
  body.reproducibility_context = withDeclaredFlag(req.reproducibility_context);
182
319
  }
320
+ if (req.policy_context === void 0 || req.policy_context === null) {
321
+ delete body.policy_context;
322
+ } else {
323
+ body.policy_context = checkedPolicyContext(req.policy_context);
324
+ refuseActorFactsWithoutActor(body.policy_context, body.attested_context);
325
+ }
326
+ if (req.expected_policy_hash === void 0 || req.expected_policy_hash === null) {
327
+ delete body.expected_policy_hash;
328
+ } else {
329
+ validateHex64(req.expected_policy_hash, "expected_policy_hash");
330
+ }
183
331
  return body;
184
332
  }
185
333
 
@@ -190,6 +338,7 @@ var REQUEST_SIGNATURE_VERSION_2 = "2";
190
338
  var JOB_FORM = "verdifax.job.v1";
191
339
  var REQUEST_KEY_PROOF_FORM = "verdifax.request-key.v1";
192
340
  var REQUEST_KEY_CHANGE_FORM = "verdifax.request-key-change.v1";
341
+ var POLICY_ACTIVATION_FORM = "verdifax.policy-activation.v1";
193
342
  var SIGNING_KEY_BYTES = 32;
194
343
  var PKCS8_ED25519_HEADER = Buffer.from(
195
344
  "302e020100300506032b657004220420",
@@ -344,6 +493,36 @@ function requestKeyChangeApproval(currentSigningKey, newPublicKey, options = {})
344
493
  nonce
345
494
  };
346
495
  }
496
+ function policyActivationPreimage(fields) {
497
+ return Buffer.from(
498
+ [
499
+ POLICY_ACTIVATION_FORM,
500
+ fields.chain_id,
501
+ fields.document_hash,
502
+ fields.previous_activation_hash,
503
+ fields.signed_at,
504
+ fields.nonce
505
+ ].join("\0"),
506
+ "utf8"
507
+ );
508
+ }
509
+ function policyActivationApproval(signingKey, fields, options = {}) {
510
+ const key = loadSigningKey(signingKey);
511
+ const signed_at = options.signedAt ?? signedAtNow();
512
+ const nonce = options.nonce ?? randomBytes(16).toString("hex");
513
+ const preimage = policyActivationPreimage({
514
+ chain_id: fields.chain_id,
515
+ document_hash: fields.document_hash,
516
+ previous_activation_hash: fields.previous_activation_hash,
517
+ signed_at,
518
+ nonce
519
+ });
520
+ return {
521
+ signature: sign(null, preimage, key).toString("base64"),
522
+ signed_at,
523
+ nonce
524
+ };
525
+ }
347
526
 
348
527
  // src/job.ts
349
528
  var STEP_OPTIONS = {
@@ -359,6 +538,8 @@ var STEP_OPTIONS = {
359
538
  authorization_chain: true,
360
539
  regulatory_scaffolding: true,
361
540
  causal_graph: true,
541
+ policy_context: true,
542
+ expected_policy_hash: true,
362
543
  after: true,
363
544
  external_previous: true,
364
545
  final: true
@@ -443,7 +624,7 @@ var VerdifaxJob = class {
443
624
  };
444
625
 
445
626
  // src/version.ts
446
- var SDK_VERSION = "1.1.0";
627
+ var SDK_VERSION = "1.3.0";
447
628
 
448
629
  // src/client.ts
449
630
  var DEFAULT_BASE_URL = "https://api.verdifax.com";
@@ -453,10 +634,46 @@ var DEFAULT_RETRY_BASE_DELAY_MS = 200;
453
634
  var SDK_USER_AGENT = `verdifax-sdk-node/${SDK_VERSION}`;
454
635
  var HEADER_API_KEY = "X-Verdifax-Key";
455
636
  var REQUEST_KEY_PATH = "/keys/request-key";
637
+ var POLICIES_PATH = "/policies";
638
+ var ACCOUNT_PATH = "/account";
639
+ var ACCOUNT_REQUEST_KEY_PATH = "/account/request-key";
640
+ var ACCOUNT_POLICIES_PATH = "/account/policies";
641
+ var ACCOUNT_CHAIN_ID = /^account-[1-9][0-9]*$/;
456
642
  var HEADER_EXPORT_TOTAL = "X-Verdifax-Total";
457
643
  var HEADER_EXPORT_TRUNCATED = "X-Verdifax-Truncated";
458
644
  var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
459
645
  var MAX_BATCH_ROWS = 1e3;
646
+ var ATTEST_FIELDS = {
647
+ payload: true,
648
+ payload_text: true,
649
+ program_id: true,
650
+ route_id: true,
651
+ registry_record_hash: true,
652
+ ai_output_text: true,
653
+ attested_context: true,
654
+ reproducibility_context: true,
655
+ request_substance: true,
656
+ authorization_chain: true,
657
+ regulatory_scaffolding: true,
658
+ causal_graph: true,
659
+ job: true,
660
+ policy_context: true,
661
+ expected_policy_hash: true
662
+ };
663
+ var PROVIDER_ARGS = Object.fromEntries(
664
+ [
665
+ "program_id",
666
+ "route_id",
667
+ "registry_record_hash",
668
+ "prompt",
669
+ "response",
670
+ "govern",
671
+ "job",
672
+ "policy_context",
673
+ "expected_policy_hash",
674
+ ...EXECUTE_FIELDS
675
+ ].map((name) => [name, true])
676
+ );
460
677
  var VerdifaxClient = class {
461
678
  baseURL;
462
679
  apiKey;
@@ -492,6 +709,7 @@ var VerdifaxClient = class {
492
709
  }
493
710
  // ── Execute / Attest (auth) ───────────────────────────────────
494
711
  async execute(req) {
712
+ refuseUnknownRequestFields(req);
495
713
  this.requireExecuteFields(req);
496
714
  const sent = this.prepared(req);
497
715
  const out = await this.request("POST", "/execute", sent);
@@ -502,6 +720,7 @@ var VerdifaxClient = class {
502
720
  * its nonce once. Both runs are recorded. A run that happens twice
503
721
  * cannot be one job step, so a `job` is refused here. */
504
722
  async verifyDeterminism(req) {
723
+ refuseUnknownRequestFields(req);
505
724
  this.requireExecuteFields(req);
506
725
  if (req.job !== void 0 && req.job !== null) {
507
726
  throw new VerdifaxError(
@@ -520,8 +739,9 @@ var VerdifaxClient = class {
520
739
  * signing key each row carries its own signature and nonce. A row
521
740
  * that fails comes back in `results` with `ok: false`, so the call
522
741
  * does not throw for it. The rows are not checked here, except a job
523
- * block, which is refused before anything is sent: the server reports
524
- * any other bad row in that row's result.
742
+ * block, a `policy_context` and an `expected_policy_hash`, which are
743
+ * refused before anything is sent: the server reports any other bad row
744
+ * in that row's result.
525
745
  *
526
746
  * This is one POST and it is never retried. After a timeout the
527
747
  * server may have run some or all of the rows. */
@@ -535,6 +755,7 @@ var VerdifaxClient = class {
535
755
  `verdifax: executeBatch accepts at most ${MAX_BATCH_ROWS} runs (got ${runs.length})`
536
756
  );
537
757
  }
758
+ runs.forEach((run, i) => refuseUnknownRequestFields(run, `runs[${i}]`));
538
759
  const sent = runs.map((run) => this.prepared(run));
539
760
  const out = await this.request(
540
761
  "POST",
@@ -555,6 +776,8 @@ var VerdifaxClient = class {
555
776
  * routes the call through to the canonical /execute. */
556
777
  async attest(req) {
557
778
  this.requireAPIKey();
779
+ refuseUnknownRequestFields(req, "attest request");
780
+ refuseUnknownFields(req, ATTEST_FIELDS, "attest request");
558
781
  if (req.payload !== void 0 && req.payload_text !== void 0) {
559
782
  throw new VerdifaxError(
560
783
  "verdifax: attest accepts payload OR payload_text, not both"
@@ -579,7 +802,9 @@ var VerdifaxClient = class {
579
802
  authorization_chain: req.authorization_chain,
580
803
  regulatory_scaffolding: req.regulatory_scaffolding,
581
804
  causal_graph: req.causal_graph,
582
- job: req.job
805
+ job: req.job,
806
+ policy_context: req.policy_context,
807
+ expected_policy_hash: req.expected_policy_hash
583
808
  };
584
809
  if (req.payload !== void 0) {
585
810
  body.payload = Buffer.isBuffer(req.payload) ? req.payload.toString("base64") : Buffer.from(req.payload).toString("base64");
@@ -599,15 +824,18 @@ var VerdifaxClient = class {
599
824
  * that the client holds the private key. From then on the server
600
825
  * refuses a run on this API key without a request signature. */
601
826
  async registerRequestKey() {
827
+ return this.registerRequestKeyAt(REQUEST_KEY_PATH, "registerRequestKey");
828
+ }
829
+ async registerRequestKeyAt(path, method) {
602
830
  this.requireAPIKey();
603
831
  if (!this.signingKey) {
604
832
  throw new VerdifaxError(
605
- "verdifax: registerRequestKey needs the signingKey client option"
833
+ `verdifax: ${method} needs the signingKey client option`
606
834
  );
607
835
  }
608
836
  return this.request(
609
837
  "POST",
610
- REQUEST_KEY_PATH,
838
+ path,
611
839
  requestKeyRegistrationBody(this.signingKey)
612
840
  );
613
841
  }
@@ -646,14 +874,277 @@ var VerdifaxClient = class {
646
874
  * unsigned requests, because the server refuses a signed run on an API
647
875
  * key with no registered key. If the call fails it keeps the key. */
648
876
  async clearRequestKey() {
877
+ await this.clearRequestKeyAt(REQUEST_KEY_PATH);
878
+ }
879
+ async clearRequestKeyAt(path) {
649
880
  this.requireAPIKey();
650
881
  await this.request(
651
882
  "DELETE",
652
- REQUEST_KEY_PATH,
883
+ path,
653
884
  this.signingKey ? { change: requestKeyChangeApproval(this.signingKey, "") } : void 0
654
885
  );
655
886
  this.signingKey = void 0;
656
887
  }
888
+ // ── Policies (auth) ──────────────────────────────────────────
889
+ /** Stores a policy document. Nothing changes until it is activated with
890
+ * {@link activatePolicy}. The server hashes the exact text, so this
891
+ * sends the string it is given unchanged and refuses anything that is
892
+ * not a string: serializing an object here would change the hash.
893
+ * Verdifax does not write, review or approve a customer's policy. */
894
+ async uploadPolicy(document) {
895
+ return this.uploadPolicyAt(POLICIES_PATH, document);
896
+ }
897
+ /** This key's stored documents, with the chain its changes belong to,
898
+ * the newest change in it (64 zeros when there is none yet) and the
899
+ * document in force (empty under the built-in policy). */
900
+ async listPolicies() {
901
+ this.requireAPIKey();
902
+ return this.request("GET", POLICIES_PATH);
903
+ }
904
+ /** One stored document, with its exact text as `document`. */
905
+ async getPolicy(documentHash) {
906
+ return this.getPolicyAt(POLICIES_PATH, documentHash);
907
+ }
908
+ /** Shows which rule would fire on sample inputs, for a document given as
909
+ * text or one already stored, named by its hash: exactly one of the two.
910
+ * It seals nothing. */
911
+ async testPolicy(req) {
912
+ return this.testPolicyAt(POLICIES_PATH, req, "testPolicy");
913
+ }
914
+ /** This key's policy changes, oldest first, as the list the server sends.
915
+ * Save it as JSON, for example with
916
+ * `writeFileSync("policy-history.json", JSON.stringify(history))`, and
917
+ * check it with `verdifax-verify --policy-history policy-history.json`. */
918
+ async getPolicyHistory() {
919
+ this.requireAPIKey();
920
+ return this.request("GET", `${POLICIES_PATH}/history`);
921
+ }
922
+ /** Makes a stored document the policy in force and returns the change
923
+ * record. It first reads GET /policies for the chain and its newest
924
+ * change, then names that change as the one this follows. With the
925
+ * `signingKey` option the request key signs the change, which the server
926
+ * requires while a request key is registered.
927
+ *
928
+ * Rolling back is activating an earlier document: it is one more change
929
+ * in the chain, so the change it undoes stays on the record.
930
+ *
931
+ * An HTTP 409 means the chain moved because another change came first,
932
+ * or the document is not yet effective. It throws the usual
933
+ * {@link APIError} and is not retried: read the policies again and
934
+ * decide. */
935
+ async activatePolicy(documentHash) {
936
+ this.requireAPIKey();
937
+ validateHex64(documentHash, "documentHash");
938
+ return this.activatePolicyAt(
939
+ POLICIES_PATH,
940
+ documentHash,
941
+ await this.listPolicies()
942
+ );
943
+ }
944
+ async uploadPolicyAt(base, document) {
945
+ this.requireAPIKey();
946
+ if (typeof document !== "string") {
947
+ throw new VerdifaxError(
948
+ "verdifax: document must be the policy's exact text, as a string"
949
+ );
950
+ }
951
+ return this.request("POST", base, { document });
952
+ }
953
+ async getPolicyAt(base, documentHash) {
954
+ this.requireAPIKey();
955
+ validateHex64(documentHash, "documentHash");
956
+ return this.request("GET", `${base}/${documentHash}`);
957
+ }
958
+ async testPolicyAt(base, req, method) {
959
+ this.requireAPIKey();
960
+ const { inputs, document, documentHash } = req ?? {};
961
+ if (document === void 0 === (documentHash === void 0)) {
962
+ throw new VerdifaxError(
963
+ `verdifax: ${method} needs exactly one of document or documentHash`
964
+ );
965
+ }
966
+ if (typeof inputs !== "object" || inputs === null || Array.isArray(inputs)) {
967
+ throw new VerdifaxError(`verdifax: ${method} needs inputs as an object`);
968
+ }
969
+ const body = { inputs };
970
+ if (document !== void 0) {
971
+ if (typeof document !== "string") {
972
+ throw new VerdifaxError(
973
+ "verdifax: document must be the policy's exact text, as a string"
974
+ );
975
+ }
976
+ body.document = document;
977
+ } else {
978
+ validateHex64(documentHash, "documentHash");
979
+ body.document_hash = documentHash;
980
+ }
981
+ return this.request("POST", `${base}/test`, body);
982
+ }
983
+ /** Posts the change that follows the newest one in `chain`, signed by
984
+ * the request key when the client has one. */
985
+ async activatePolicyAt(base, documentHash, chain) {
986
+ const previous = chain?.newest_activation_hash;
987
+ if (typeof previous !== "string") {
988
+ throw new VerdifaxError(
989
+ "verdifax: the server did not report newest_activation_hash, so the change cannot name the one it follows"
990
+ );
991
+ }
992
+ const body = { previous_activation_hash: previous };
993
+ if (this.signingKey) {
994
+ if (typeof chain.chain_id !== "string" || chain.chain_id === "") {
995
+ throw new VerdifaxError(
996
+ "verdifax: the server did not report chain_id, so the change cannot be signed"
997
+ );
998
+ }
999
+ body.signature = policyActivationApproval(this.signingKey, {
1000
+ chain_id: chain.chain_id,
1001
+ document_hash: documentHash,
1002
+ previous_activation_hash: previous
1003
+ });
1004
+ }
1005
+ const out = await this.request(
1006
+ "POST",
1007
+ `${base}/${documentHash}/activate`,
1008
+ body
1009
+ );
1010
+ return out.activation;
1011
+ }
1012
+ // ── Account, with its owner key (auth) ───────────────────────
1013
+ //
1014
+ // An account is a company on Verdifax. Every key belongs to one account
1015
+ // for life. These methods need the account's owner key, which manages the
1016
+ // account and cannot run anything: the server refuses it on every other
1017
+ // route. The account number is a label Verdifax assigned, not a verified
1018
+ // company identity.
1019
+ /** The owner's account: its number, name, tier, key counts and the
1020
+ * chain of its policy changes. */
1021
+ async getAccount() {
1022
+ this.requireAPIKey();
1023
+ return this.request("GET", ACCOUNT_PATH);
1024
+ }
1025
+ /** Creates a member key in the account. It takes the account's tier and
1026
+ * stays in the account for life. The secret is in the result this once.
1027
+ * On a trial account the key needs a request key before it can run. */
1028
+ async createAccountKey(name, options = {}) {
1029
+ this.requireAPIKey();
1030
+ if (typeof name !== "string" || name.trim() === "") {
1031
+ throw new VerdifaxError(
1032
+ "verdifax: name is required for createAccountKey"
1033
+ );
1034
+ }
1035
+ const body = { name };
1036
+ if (options.expiresAt !== void 0) body.expires_at = options.expiresAt;
1037
+ return this.request(
1038
+ "POST",
1039
+ `${ACCOUNT_PATH}/keys`,
1040
+ body
1041
+ );
1042
+ }
1043
+ /** Every key in the account, owner keys included, without secrets. */
1044
+ async listAccountKeys() {
1045
+ this.requireAPIKey();
1046
+ const out = await this.request(
1047
+ "GET",
1048
+ `${ACCOUNT_PATH}/keys`
1049
+ );
1050
+ return { ...out, keys: out?.keys ?? [] };
1051
+ }
1052
+ /** Revokes one of the account's member keys. A key in another account,
1053
+ * or an owner key, throws {@link NotFoundError}. */
1054
+ async revokeAccountKey(keyId) {
1055
+ this.requireAPIKey();
1056
+ requirePositiveId(keyId, "keyId");
1057
+ return this.request(
1058
+ "DELETE",
1059
+ `${ACCOUNT_PATH}/keys/${keyId}`
1060
+ );
1061
+ }
1062
+ /** Registers this client's public key as the owner key's request key,
1063
+ * which must sign every change to the account's policy. It works as
1064
+ * {@link registerRequestKey} does. */
1065
+ async registerAccountRequestKey() {
1066
+ return this.registerRequestKeyAt(
1067
+ ACCOUNT_REQUEST_KEY_PATH,
1068
+ "registerAccountRequestKey"
1069
+ );
1070
+ }
1071
+ /** Clears the owner key's request key, as {@link clearRequestKey} does
1072
+ * for a key's own. With none registered the account's policy cannot be
1073
+ * changed. A trial owner key's request key cannot be cleared. */
1074
+ async clearAccountRequestKey() {
1075
+ await this.clearRequestKeyAt(ACCOUNT_REQUEST_KEY_PATH);
1076
+ }
1077
+ /** The runs of every key in the account, paged as {@link listRuns}. */
1078
+ async listAccountRuns(params = {}) {
1079
+ return this.listRunsAt(`${ACCOUNT_PATH}/runs`, params);
1080
+ }
1081
+ /** The runs of every key in the account in a period, as
1082
+ * {@link exportRuns} lists a key's, with the account's number, its
1083
+ * policy documents and its chain of changes. The account's view covers
1084
+ * the runs its keys made through Verdifax, not runs made elsewhere. */
1085
+ async exportAccountRuns(params = {}) {
1086
+ this.requireAPIKey();
1087
+ return this.request(
1088
+ "GET",
1089
+ this.exportPath(`${ACCOUNT_PATH}/runs/export`, params, "json")
1090
+ );
1091
+ }
1092
+ /** The same rows as {@link exportAccountRuns} as raw CSV text, checked
1093
+ * as {@link exportRunsCSV} checks a key's. */
1094
+ async exportAccountRunsCSV(params = {}) {
1095
+ return this.exportCSVAt(`${ACCOUNT_PATH}/runs/export`, params);
1096
+ }
1097
+ /** Stores an account policy document, as {@link uploadPolicy} stores a
1098
+ * key's. Every key in the account must pass the account's policy on top
1099
+ * of its own. Verdifax does not write, review or approve it. */
1100
+ async uploadAccountPolicy(document) {
1101
+ return this.uploadPolicyAt(ACCOUNT_POLICIES_PATH, document);
1102
+ }
1103
+ /** The account's stored documents, its chain `account-N`, the newest
1104
+ * change in it and the document in force. */
1105
+ async listAccountPolicies() {
1106
+ this.requireAPIKey();
1107
+ return this.request("GET", ACCOUNT_POLICIES_PATH);
1108
+ }
1109
+ /** One of the account's documents, with its exact text. */
1110
+ async getAccountPolicy(documentHash) {
1111
+ return this.getPolicyAt(ACCOUNT_POLICIES_PATH, documentHash);
1112
+ }
1113
+ /** {@link testPolicy} for the account's documents. It seals nothing. */
1114
+ async testAccountPolicy(req) {
1115
+ return this.testPolicyAt(ACCOUNT_POLICIES_PATH, req, "testAccountPolicy");
1116
+ }
1117
+ /** The account's policy changes, oldest first, in the form
1118
+ * `verdifax-verify --policy-history FILE` reads. */
1119
+ async getAccountPolicyHistory() {
1120
+ this.requireAPIKey();
1121
+ return this.request(
1122
+ "GET",
1123
+ `${ACCOUNT_POLICIES_PATH}/history`
1124
+ );
1125
+ }
1126
+ /** Makes a stored document the account's policy in force, as
1127
+ * {@link activatePolicy} does for a key's. It reads
1128
+ * {@link listAccountPolicies} for the chain and its newest change, and
1129
+ * the owner key's request key signs the change over that chain,
1130
+ * `account-N`. The server refuses an unsigned account change, so this
1131
+ * needs the `signingKey` option and refuses to send without it. */
1132
+ async activateAccountPolicy(documentHash) {
1133
+ this.requireAPIKey();
1134
+ validateHex64(documentHash, "documentHash");
1135
+ if (!this.signingKey) {
1136
+ throw new VerdifaxError(
1137
+ "verdifax: activateAccountPolicy needs the owner key's request key in the signingKey client option, because the server refuses an unsigned account policy change"
1138
+ );
1139
+ }
1140
+ const chain = await this.listAccountPolicies();
1141
+ if (typeof chain?.chain_id !== "string" || !ACCOUNT_CHAIN_ID.test(chain.chain_id)) {
1142
+ throw new VerdifaxError(
1143
+ "verdifax: the server did not report an account chain_id (account-N), so the change cannot be signed"
1144
+ );
1145
+ }
1146
+ return this.activatePolicyAt(ACCOUNT_POLICIES_PATH, documentHash, chain);
1147
+ }
657
1148
  // ── Runs ──────────────────────────────────────────────────────
658
1149
  async getRun(runId) {
659
1150
  this.requireAPIKey();
@@ -666,6 +1157,9 @@ var VerdifaxClient = class {
666
1157
  return this.request("GET", `/runs/${runId}/verify`);
667
1158
  }
668
1159
  async listRuns(params = {}) {
1160
+ return this.listRunsAt("/runs", params);
1161
+ }
1162
+ async listRunsAt(base, params) {
669
1163
  this.requireAPIKey();
670
1164
  const q = new URLSearchParams();
671
1165
  if (params.limit && params.limit > 0) {
@@ -675,7 +1169,7 @@ var VerdifaxClient = class {
675
1169
  q.set("offset", String(params.offset));
676
1170
  }
677
1171
  const qs = q.toString();
678
- const path = qs ? `/runs?${qs}` : "/runs";
1172
+ const path = qs ? `${base}?${qs}` : base;
679
1173
  const out = await this.request("GET", path);
680
1174
  return { runs: out.runs ?? [], total: out.total ?? 0 };
681
1175
  }
@@ -713,7 +1207,10 @@ var VerdifaxClient = class {
713
1207
  * call that never reached Verdifax cannot appear in it. */
714
1208
  async exportRuns(params = {}) {
715
1209
  this.requireAPIKey();
716
- return this.request("GET", this.exportPath(params, "json"));
1210
+ return this.request(
1211
+ "GET",
1212
+ this.exportPath("/runs/export", params, "json")
1213
+ );
717
1214
  }
718
1215
  /** The same rows as {@link exportRuns}: the raw CSV text with `total`
719
1216
  * and `truncated` from the response headers. Check `truncated`: when
@@ -721,9 +1218,12 @@ var VerdifaxClient = class {
721
1218
  * does not send those headers, so that a cut-off export cannot pass
722
1219
  * for a complete one. */
723
1220
  async exportRunsCSV(params = {}) {
1221
+ return this.exportCSVAt("/runs/export", params);
1222
+ }
1223
+ async exportCSVAt(base, params) {
724
1224
  this.requireAPIKey();
725
1225
  const { text, headers } = await this.requestRaw(
726
- this.exportPath(params, "csv")
1226
+ this.exportPath(base, params, "csv")
727
1227
  );
728
1228
  const total = Number.parseInt(headers.get(HEADER_EXPORT_TOTAL) ?? "", 10);
729
1229
  const truncated = headers.get(HEADER_EXPORT_TRUNCATED);
@@ -734,13 +1234,13 @@ var VerdifaxClient = class {
734
1234
  }
735
1235
  return { csv: text, total, truncated: truncated === "true" };
736
1236
  }
737
- exportPath(params, format) {
1237
+ exportPath(base, params, format) {
738
1238
  const q = new URLSearchParams();
739
1239
  if (params.from) q.set("from", params.from);
740
1240
  if (params.to) q.set("to", params.to);
741
1241
  if (params.job_id) q.set("job_id", params.job_id);
742
1242
  q.set("format", format);
743
- return `/runs/export?${q.toString()}`;
1243
+ return `${base}?${q.toString()}`;
744
1244
  }
745
1245
  // ── Attestation receipts (auth) ──────────────────────────────
746
1246
  async listAttestations(params = {}) {
@@ -878,14 +1378,48 @@ var VerdifaxClient = class {
878
1378
  return this.request("POST", "/dcae/verify", bundle);
879
1379
  }
880
1380
  // ── Admin: API keys (auth) ───────────────────────────────────
881
- async adminCreateKey(name) {
1381
+ /** Creates a customer key. With `accountId` the key joins that existing
1382
+ * account; without it the key gets an account of its own. A key never
1383
+ * changes account. */
1384
+ async adminCreateKey(name, options = {}) {
882
1385
  this.requireAPIKey();
883
1386
  if (!name) {
884
1387
  throw new VerdifaxError("verdifax: name is required for adminCreateKey");
885
1388
  }
886
- return this.request("POST", "/admin/keys", {
887
- name
888
- });
1389
+ const body = { name };
1390
+ if (options.accountId !== void 0) {
1391
+ requirePositiveId(options.accountId, "accountId");
1392
+ body.account_id = options.accountId;
1393
+ }
1394
+ return this.request("POST", "/admin/keys", body);
1395
+ }
1396
+ /** Creates an account and its first owner key, whose secret is in the
1397
+ * result this once. The owner key creates the member keys. The account
1398
+ * number is a label Verdifax assigned, not a verified company identity. */
1399
+ async adminCreateAccount(name, options = {}) {
1400
+ this.requireAPIKey();
1401
+ if (typeof name !== "string" || name.trim() === "") {
1402
+ throw new VerdifaxError(
1403
+ "verdifax: name is required for adminCreateAccount"
1404
+ );
1405
+ }
1406
+ const body = { name };
1407
+ if (options.tier !== void 0) body.tier = options.tier;
1408
+ if (options.expiresAt !== void 0) body.expires_at = options.expiresAt;
1409
+ return this.request(
1410
+ "POST",
1411
+ "/admin/accounts",
1412
+ body
1413
+ );
1414
+ }
1415
+ /** Every account, with its key counts. */
1416
+ async adminListAccounts() {
1417
+ this.requireAPIKey();
1418
+ const out = await this.request(
1419
+ "GET",
1420
+ "/admin/accounts"
1421
+ );
1422
+ return out?.accounts ?? [];
889
1423
  }
890
1424
  async adminListKeys() {
891
1425
  this.requireAPIKey();
@@ -918,11 +1452,13 @@ var VerdifaxClient = class {
918
1452
  * `ai_output_text`; that is off by default, so the text leaves your
919
1453
  * machine for review only when you ask. */
920
1454
  async attestClaudeResponse(args) {
1455
+ refuseUnknownFields(args, PROVIDER_ARGS, "attestClaudeResponse arguments");
921
1456
  return this.attestProviderResponse({ ...args, provider: "anthropic" });
922
1457
  }
923
1458
  /** Convenience for attesting an OpenAI API response. `govern` works as
924
1459
  * in {@link attestClaudeResponse}. */
925
1460
  async attestOpenAIResponse(args) {
1461
+ refuseUnknownFields(args, PROVIDER_ARGS, "attestOpenAIResponse arguments");
926
1462
  return this.attestProviderResponse({ ...args, provider: "openai" });
927
1463
  }
928
1464
  async attestProviderResponse(args) {
@@ -945,7 +1481,9 @@ var VerdifaxClient = class {
945
1481
  actor_role: "ai_assistant",
946
1482
  model_provider: args.provider
947
1483
  },
948
- job: args.job
1484
+ job: args.job,
1485
+ policy_context: args.policy_context,
1486
+ expected_policy_hash: args.expected_policy_hash
949
1487
  });
950
1488
  }
951
1489
  // ── HTTP plumbing, retry-aware ──────────────────────────────
@@ -1060,6 +1598,11 @@ var VerdifaxClient = class {
1060
1598
  return this.signingKey ? signRequestV2(this.signingKey, req) : withExecuteFlags(req);
1061
1599
  }
1062
1600
  };
1601
+ function requirePositiveId(id, name) {
1602
+ if (!Number.isSafeInteger(id) || id <= 0) {
1603
+ throw new VerdifaxError(`verdifax: ${name} must be a whole number > 0`);
1604
+ }
1605
+ }
1063
1606
  function withSignedText(result, sent) {
1064
1607
  return "signed_request" in sent ? { ...result, signed_request: sent.signed_request } : result;
1065
1608
  }
@@ -1253,6 +1796,7 @@ export {
1253
1796
  CRES_PREIMAGE_VERSION,
1254
1797
  DCAE_PREIMAGE_VERSION,
1255
1798
  DSE_FORMULA_VERSION,
1799
+ EXECUTE_FIELDS,
1256
1800
  MAX_BATCH_ROWS,
1257
1801
  NotFoundError,
1258
1802
  RUN_STATUSES,
@@ -1266,6 +1810,8 @@ export {
1266
1810
  formatProviderPayload,
1267
1811
  fromEnv,
1268
1812
  generateRequestKeyPair,
1813
+ policyActivationApproval,
1814
+ policyActivationPreimage,
1269
1815
  requestKeyChangeApproval,
1270
1816
  requestKeyChangePreimage,
1271
1817
  requestKeyRegistrationBody,