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/README.md +131 -4
- package/dist/index.d.mts +394 -6
- package/dist/index.d.ts +394 -6
- package/dist/index.js +566 -17
- package/dist/index.mjs +563 -17
- package/package.json +1 -1
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.
|
|
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,
|
|
571
|
-
* any other bad row
|
|
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
|
-
|
|
883
|
+
`verdifax: ${method} needs the signingKey client option`
|
|
653
884
|
);
|
|
654
885
|
}
|
|
655
886
|
return this.request(
|
|
656
887
|
"POST",
|
|
657
|
-
|
|
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
|
-
|
|
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 ?
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
934
|
-
|
|
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,
|