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.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.
|
|
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,
|
|
524
|
-
* any other bad row
|
|
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
|
-
|
|
833
|
+
`verdifax: ${method} needs the signingKey client option`
|
|
606
834
|
);
|
|
607
835
|
}
|
|
608
836
|
return this.request(
|
|
609
837
|
"POST",
|
|
610
|
-
|
|
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
|
-
|
|
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 ?
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
887
|
-
|
|
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,
|