verdifax 1.1.0 → 1.2.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 +68 -2
- package/dist/index.d.mts +186 -3
- package/dist/index.d.ts +186 -3
- package/dist/index.js +350 -5
- package/dist/index.mjs +347 -5
- 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.2.0";
|
|
494
678
|
|
|
495
679
|
// src/client.ts
|
|
496
680
|
var DEFAULT_BASE_URL = "https://api.verdifax.com";
|
|
@@ -500,10 +684,42 @@ 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";
|
|
503
688
|
var HEADER_EXPORT_TOTAL = "X-Verdifax-Total";
|
|
504
689
|
var HEADER_EXPORT_TRUNCATED = "X-Verdifax-Truncated";
|
|
505
690
|
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
|
|
506
691
|
var MAX_BATCH_ROWS = 1e3;
|
|
692
|
+
var ATTEST_FIELDS = {
|
|
693
|
+
payload: true,
|
|
694
|
+
payload_text: true,
|
|
695
|
+
program_id: true,
|
|
696
|
+
route_id: true,
|
|
697
|
+
registry_record_hash: true,
|
|
698
|
+
ai_output_text: true,
|
|
699
|
+
attested_context: true,
|
|
700
|
+
reproducibility_context: true,
|
|
701
|
+
request_substance: true,
|
|
702
|
+
authorization_chain: true,
|
|
703
|
+
regulatory_scaffolding: true,
|
|
704
|
+
causal_graph: true,
|
|
705
|
+
job: true,
|
|
706
|
+
policy_context: true,
|
|
707
|
+
expected_policy_hash: true
|
|
708
|
+
};
|
|
709
|
+
var PROVIDER_ARGS = Object.fromEntries(
|
|
710
|
+
[
|
|
711
|
+
"program_id",
|
|
712
|
+
"route_id",
|
|
713
|
+
"registry_record_hash",
|
|
714
|
+
"prompt",
|
|
715
|
+
"response",
|
|
716
|
+
"govern",
|
|
717
|
+
"job",
|
|
718
|
+
"policy_context",
|
|
719
|
+
"expected_policy_hash",
|
|
720
|
+
...EXECUTE_FIELDS
|
|
721
|
+
].map((name) => [name, true])
|
|
722
|
+
);
|
|
507
723
|
var VerdifaxClient = class {
|
|
508
724
|
baseURL;
|
|
509
725
|
apiKey;
|
|
@@ -539,6 +755,7 @@ var VerdifaxClient = class {
|
|
|
539
755
|
}
|
|
540
756
|
// ── Execute / Attest (auth) ───────────────────────────────────
|
|
541
757
|
async execute(req) {
|
|
758
|
+
refuseUnknownRequestFields(req);
|
|
542
759
|
this.requireExecuteFields(req);
|
|
543
760
|
const sent = this.prepared(req);
|
|
544
761
|
const out = await this.request("POST", "/execute", sent);
|
|
@@ -549,6 +766,7 @@ var VerdifaxClient = class {
|
|
|
549
766
|
* its nonce once. Both runs are recorded. A run that happens twice
|
|
550
767
|
* cannot be one job step, so a `job` is refused here. */
|
|
551
768
|
async verifyDeterminism(req) {
|
|
769
|
+
refuseUnknownRequestFields(req);
|
|
552
770
|
this.requireExecuteFields(req);
|
|
553
771
|
if (req.job !== void 0 && req.job !== null) {
|
|
554
772
|
throw new VerdifaxError(
|
|
@@ -567,8 +785,9 @@ var VerdifaxClient = class {
|
|
|
567
785
|
* signing key each row carries its own signature and nonce. A row
|
|
568
786
|
* that fails comes back in `results` with `ok: false`, so the call
|
|
569
787
|
* does not throw for it. The rows are not checked here, except a job
|
|
570
|
-
* block,
|
|
571
|
-
* any other bad row
|
|
788
|
+
* block, a `policy_context` and an `expected_policy_hash`, which are
|
|
789
|
+
* refused before anything is sent: the server reports any other bad row
|
|
790
|
+
* in that row's result.
|
|
572
791
|
*
|
|
573
792
|
* This is one POST and it is never retried. After a timeout the
|
|
574
793
|
* server may have run some or all of the rows. */
|
|
@@ -582,6 +801,7 @@ var VerdifaxClient = class {
|
|
|
582
801
|
`verdifax: executeBatch accepts at most ${MAX_BATCH_ROWS} runs (got ${runs.length})`
|
|
583
802
|
);
|
|
584
803
|
}
|
|
804
|
+
runs.forEach((run, i) => refuseUnknownRequestFields(run, `runs[${i}]`));
|
|
585
805
|
const sent = runs.map((run) => this.prepared(run));
|
|
586
806
|
const out = await this.request(
|
|
587
807
|
"POST",
|
|
@@ -602,6 +822,8 @@ var VerdifaxClient = class {
|
|
|
602
822
|
* routes the call through to the canonical /execute. */
|
|
603
823
|
async attest(req) {
|
|
604
824
|
this.requireAPIKey();
|
|
825
|
+
refuseUnknownRequestFields(req, "attest request");
|
|
826
|
+
refuseUnknownFields(req, ATTEST_FIELDS, "attest request");
|
|
605
827
|
if (req.payload !== void 0 && req.payload_text !== void 0) {
|
|
606
828
|
throw new VerdifaxError(
|
|
607
829
|
"verdifax: attest accepts payload OR payload_text, not both"
|
|
@@ -626,7 +848,9 @@ var VerdifaxClient = class {
|
|
|
626
848
|
authorization_chain: req.authorization_chain,
|
|
627
849
|
regulatory_scaffolding: req.regulatory_scaffolding,
|
|
628
850
|
causal_graph: req.causal_graph,
|
|
629
|
-
job: req.job
|
|
851
|
+
job: req.job,
|
|
852
|
+
policy_context: req.policy_context,
|
|
853
|
+
expected_policy_hash: req.expected_policy_hash
|
|
630
854
|
};
|
|
631
855
|
if (req.payload !== void 0) {
|
|
632
856
|
body.payload = Buffer.isBuffer(req.payload) ? req.payload.toString("base64") : Buffer.from(req.payload).toString("base64");
|
|
@@ -701,6 +925,120 @@ var VerdifaxClient = class {
|
|
|
701
925
|
);
|
|
702
926
|
this.signingKey = void 0;
|
|
703
927
|
}
|
|
928
|
+
// ── Policies (auth) ──────────────────────────────────────────
|
|
929
|
+
/** Stores a policy document. Nothing changes until it is activated with
|
|
930
|
+
* {@link activatePolicy}. The server hashes the exact text, so this
|
|
931
|
+
* sends the string it is given unchanged and refuses anything that is
|
|
932
|
+
* not a string: serializing an object here would change the hash.
|
|
933
|
+
* Verdifax does not write, review or approve a customer's policy. */
|
|
934
|
+
async uploadPolicy(document) {
|
|
935
|
+
this.requireAPIKey();
|
|
936
|
+
if (typeof document !== "string") {
|
|
937
|
+
throw new VerdifaxError(
|
|
938
|
+
"verdifax: document must be the policy's exact text, as a string"
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
return this.request("POST", POLICIES_PATH, { document });
|
|
942
|
+
}
|
|
943
|
+
/** This key's stored documents, with the chain its changes belong to,
|
|
944
|
+
* the newest change in it (64 zeros when there is none yet) and the
|
|
945
|
+
* document in force (empty under the built-in policy). */
|
|
946
|
+
async listPolicies() {
|
|
947
|
+
this.requireAPIKey();
|
|
948
|
+
return this.request("GET", POLICIES_PATH);
|
|
949
|
+
}
|
|
950
|
+
/** One stored document, with its exact text as `document`. */
|
|
951
|
+
async getPolicy(documentHash) {
|
|
952
|
+
this.requireAPIKey();
|
|
953
|
+
validateHex64(documentHash, "documentHash");
|
|
954
|
+
return this.request(
|
|
955
|
+
"GET",
|
|
956
|
+
`${POLICIES_PATH}/${documentHash}`
|
|
957
|
+
);
|
|
958
|
+
}
|
|
959
|
+
/** Shows which rule would fire on sample inputs, for a document given as
|
|
960
|
+
* text or one already stored, named by its hash: exactly one of the two.
|
|
961
|
+
* It seals nothing. */
|
|
962
|
+
async testPolicy(req) {
|
|
963
|
+
this.requireAPIKey();
|
|
964
|
+
const { inputs, document, documentHash } = req ?? {};
|
|
965
|
+
if (document === void 0 === (documentHash === void 0)) {
|
|
966
|
+
throw new VerdifaxError(
|
|
967
|
+
"verdifax: testPolicy needs exactly one of document or documentHash"
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
if (typeof inputs !== "object" || inputs === null || Array.isArray(inputs)) {
|
|
971
|
+
throw new VerdifaxError("verdifax: testPolicy needs inputs as an object");
|
|
972
|
+
}
|
|
973
|
+
const body = { inputs };
|
|
974
|
+
if (document !== void 0) {
|
|
975
|
+
if (typeof document !== "string") {
|
|
976
|
+
throw new VerdifaxError(
|
|
977
|
+
"verdifax: document must be the policy's exact text, as a string"
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
body.document = document;
|
|
981
|
+
} else {
|
|
982
|
+
validateHex64(documentHash, "documentHash");
|
|
983
|
+
body.document_hash = documentHash;
|
|
984
|
+
}
|
|
985
|
+
return this.request(
|
|
986
|
+
"POST",
|
|
987
|
+
`${POLICIES_PATH}/test`,
|
|
988
|
+
body
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
/** This key's policy changes, oldest first, as the list the server sends.
|
|
992
|
+
* Save it as JSON, for example with
|
|
993
|
+
* `writeFileSync("policy-history.json", JSON.stringify(history))`, and
|
|
994
|
+
* check it with `verdifax-verify --policy-history policy-history.json`. */
|
|
995
|
+
async getPolicyHistory() {
|
|
996
|
+
this.requireAPIKey();
|
|
997
|
+
return this.request("GET", `${POLICIES_PATH}/history`);
|
|
998
|
+
}
|
|
999
|
+
/** Makes a stored document the policy in force and returns the change
|
|
1000
|
+
* record. It first reads GET /policies for the chain and its newest
|
|
1001
|
+
* change, then names that change as the one this follows. With the
|
|
1002
|
+
* `signingKey` option the request key signs the change, which the server
|
|
1003
|
+
* requires while a request key is registered.
|
|
1004
|
+
*
|
|
1005
|
+
* Rolling back is activating an earlier document: it is one more change
|
|
1006
|
+
* in the chain, so the change it undoes stays on the record.
|
|
1007
|
+
*
|
|
1008
|
+
* An HTTP 409 means the chain moved because another change came first,
|
|
1009
|
+
* or the document is not yet effective. It throws the usual
|
|
1010
|
+
* {@link APIError} and is not retried: read the policies again and
|
|
1011
|
+
* decide. */
|
|
1012
|
+
async activatePolicy(documentHash) {
|
|
1013
|
+
this.requireAPIKey();
|
|
1014
|
+
validateHex64(documentHash, "documentHash");
|
|
1015
|
+
const chain = await this.listPolicies();
|
|
1016
|
+
const previous = chain?.newest_activation_hash;
|
|
1017
|
+
if (typeof previous !== "string") {
|
|
1018
|
+
throw new VerdifaxError(
|
|
1019
|
+
"verdifax: the server did not report newest_activation_hash, so the change cannot name the one it follows"
|
|
1020
|
+
);
|
|
1021
|
+
}
|
|
1022
|
+
const body = { previous_activation_hash: previous };
|
|
1023
|
+
if (this.signingKey) {
|
|
1024
|
+
if (typeof chain.chain_id !== "string" || chain.chain_id === "") {
|
|
1025
|
+
throw new VerdifaxError(
|
|
1026
|
+
"verdifax: the server did not report chain_id, so the change cannot be signed"
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
body.signature = policyActivationApproval(this.signingKey, {
|
|
1030
|
+
chain_id: chain.chain_id,
|
|
1031
|
+
document_hash: documentHash,
|
|
1032
|
+
previous_activation_hash: previous
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
const out = await this.request(
|
|
1036
|
+
"POST",
|
|
1037
|
+
`${POLICIES_PATH}/${documentHash}/activate`,
|
|
1038
|
+
body
|
|
1039
|
+
);
|
|
1040
|
+
return out.activation;
|
|
1041
|
+
}
|
|
704
1042
|
// ── Runs ──────────────────────────────────────────────────────
|
|
705
1043
|
async getRun(runId) {
|
|
706
1044
|
this.requireAPIKey();
|
|
@@ -965,11 +1303,13 @@ var VerdifaxClient = class {
|
|
|
965
1303
|
* `ai_output_text`; that is off by default, so the text leaves your
|
|
966
1304
|
* machine for review only when you ask. */
|
|
967
1305
|
async attestClaudeResponse(args) {
|
|
1306
|
+
refuseUnknownFields(args, PROVIDER_ARGS, "attestClaudeResponse arguments");
|
|
968
1307
|
return this.attestProviderResponse({ ...args, provider: "anthropic" });
|
|
969
1308
|
}
|
|
970
1309
|
/** Convenience for attesting an OpenAI API response. `govern` works as
|
|
971
1310
|
* in {@link attestClaudeResponse}. */
|
|
972
1311
|
async attestOpenAIResponse(args) {
|
|
1312
|
+
refuseUnknownFields(args, PROVIDER_ARGS, "attestOpenAIResponse arguments");
|
|
973
1313
|
return this.attestProviderResponse({ ...args, provider: "openai" });
|
|
974
1314
|
}
|
|
975
1315
|
async attestProviderResponse(args) {
|
|
@@ -992,7 +1332,9 @@ var VerdifaxClient = class {
|
|
|
992
1332
|
actor_role: "ai_assistant",
|
|
993
1333
|
model_provider: args.provider
|
|
994
1334
|
},
|
|
995
|
-
job: args.job
|
|
1335
|
+
job: args.job,
|
|
1336
|
+
policy_context: args.policy_context,
|
|
1337
|
+
expected_policy_hash: args.expected_policy_hash
|
|
996
1338
|
});
|
|
997
1339
|
}
|
|
998
1340
|
// ── HTTP plumbing, retry-aware ──────────────────────────────
|
|
@@ -1301,6 +1643,7 @@ function cresReceiptHash(fields) {
|
|
|
1301
1643
|
CRES_PREIMAGE_VERSION,
|
|
1302
1644
|
DCAE_PREIMAGE_VERSION,
|
|
1303
1645
|
DSE_FORMULA_VERSION,
|
|
1646
|
+
EXECUTE_FIELDS,
|
|
1304
1647
|
MAX_BATCH_ROWS,
|
|
1305
1648
|
NotFoundError,
|
|
1306
1649
|
RUN_STATUSES,
|
|
@@ -1314,6 +1657,8 @@ function cresReceiptHash(fields) {
|
|
|
1314
1657
|
formatProviderPayload,
|
|
1315
1658
|
fromEnv,
|
|
1316
1659
|
generateRequestKeyPair,
|
|
1660
|
+
policyActivationApproval,
|
|
1661
|
+
policyActivationPreimage,
|
|
1317
1662
|
requestKeyChangeApproval,
|
|
1318
1663
|
requestKeyChangePreimage,
|
|
1319
1664
|
requestKeyRegistrationBody,
|