@oxdeai/conformance 1.3.1 → 2.0.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/LICENSE +213 -0
  3. package/README.md +217 -5
  4. package/dist/evidence-metadata.json +3489 -0
  5. package/dist/scripts/build-evidence-metadata.d.mts +1 -0
  6. package/dist/scripts/build-evidence-metadata.mjs +7 -0
  7. package/dist/scripts/build-evidence-metadata.mjs.map +1 -0
  8. package/dist/scripts/extract-vectors.js +228 -22
  9. package/dist/scripts/extract-vectors.js.map +1 -1
  10. package/dist/src/evidenceScope.d.mts +68 -0
  11. package/dist/src/evidenceScope.mjs +127 -0
  12. package/dist/src/evidenceScope.mjs.map +1 -0
  13. package/dist/src/fixtures/conformance-engine-secret.fixture.d.ts +5 -0
  14. package/dist/src/fixtures/conformance-engine-secret.fixture.js +7 -0
  15. package/dist/src/fixtures/conformance-engine-secret.fixture.js.map +1 -0
  16. package/dist/src/fixtures/ed25519.test-only.fixture.d.ts +1 -1
  17. package/dist/src/fixtures/ed25519.test-only.fixture.js +2 -1
  18. package/dist/src/fixtures/ed25519.test-only.fixture.js.map +1 -1
  19. package/dist/src/fixtures/krl-ed25519.test-only.fixture.d.ts +13 -0
  20. package/dist/src/fixtures/krl-ed25519.test-only.fixture.js +19 -0
  21. package/dist/src/fixtures/krl-ed25519.test-only.fixture.js.map +1 -0
  22. package/dist/src/trustedTimeConformance.d.ts +32 -0
  23. package/dist/src/trustedTimeConformance.js +484 -0
  24. package/dist/src/trustedTimeConformance.js.map +1 -0
  25. package/dist/src/validate-trusted-time.d.ts +1 -0
  26. package/dist/src/validate-trusted-time.js +14 -0
  27. package/dist/src/validate-trusted-time.js.map +1 -0
  28. package/dist/src/validate.d.ts +1 -0
  29. package/dist/src/validate.js +723 -31
  30. package/dist/src/validate.js.map +1 -1
  31. package/package.json +38 -8
  32. package/vectors/audit-chain.json +2 -2
  33. package/vectors/authorization-payload.json +46 -34
  34. package/vectors/authorization-signature-verification.json +39 -1
  35. package/vectors/authorization-verification.json +54 -1
  36. package/vectors/clock-semantics-verification.json +136 -0
  37. package/vectors/delegation-chain-verification.json +366 -0
  38. package/vectors/delegation-parent-hash.json +54 -0
  39. package/vectors/delegation-signature-verification.json +317 -0
  40. package/vectors/delegation-verification.json +252 -0
  41. package/vectors/envelope-verification.json +3 -3
  42. package/vectors/key-lifecycle-verification.json +131 -0
  43. package/vectors/profile-c-state-verification.json +217 -0
  44. package/vectors/signed-krl-verification.json +103 -0
  45. package/vectors/trusted-time.json +50 -0
  46. package/dist/extract-vectors.js +0 -416
  47. package/scripts/extract-vectors.ts +0 -466
  48. package/src/fixtures/README.md +0 -11
  49. package/src/fixtures/ed25519.test-only.fixture.ts +0 -14
  50. package/src/validate.ts +0 -828
  51. package/tsconfig.json +0 -14
@@ -1,8 +1,15 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { evidenceScope } from "./evidenceScope.mjs";
1
4
  import { readFileSync } from "node:fs";
2
5
  import { resolve } from "node:path";
3
- import { createHash } from "node:crypto";
4
- import { encodeCanonicalState, encodeEnvelope, PolicyEngine, sha256HexFromJson, signAuthorizationEd25519, signEnvelopeEd25519, verifyAuthorization, verifyAuditEvents, verifyEnvelope, verifySnapshot } from "@oxdeai/core";
6
+ import { fileURLToPath } from "node:url";
7
+ import { createHash, sign as nodeSign } from "node:crypto";
8
+ import { encodeCanonicalState, encodeEnvelope, PolicyEngine, RECOMMENDED_TRUSTED_TIME_PROFILE, sha256HexFromJson, signAuthorizationEd25519, signEnvelopeEd25519, verifyAuthorization, verifyAuditEvents, verifyEnvelope, verifySnapshot, createDelegation, verifyDelegation, verifyDelegationChain, signEd25519, SIGNING_DOMAINS, signedKrlSigningPayload, verifySignedKrl, } from "@oxdeai/core";
5
9
  import { TEST_ONLY_ED25519_PRIVATE_KEY_PEM_DO_NOT_USE_IN_PRODUCTION, TEST_ONLY_ED25519_PUBLIC_KEY_PEM_DO_NOT_USE_IN_PRODUCTION, } from "./fixtures/ed25519.test-only.fixture.js";
10
+ import { KRL_TEST_ONLY_ED25519_PRIVATE_KEY_PEM_DO_NOT_USE_IN_PRODUCTION, KRL_TEST_ONLY_ED25519_PUBLIC_KEY_PEM_DO_NOT_USE_IN_PRODUCTION, } from "./fixtures/krl-ed25519.test-only.fixture.js";
11
+ import { CONFORMANCE_ENGINE_SECRET } from "./fixtures/conformance-engine-secret.fixture.js";
12
+ import { runTrustedTimeConformance } from "./trustedTimeConformance.js";
6
13
  const TEST_KEYSET = {
7
14
  issuer: "oxdeai.policy-engine",
8
15
  version: "1",
@@ -14,7 +21,7 @@ const TEST_KEYSET = {
14
21
  }
15
22
  ]
16
23
  };
17
- const CORE_ENGINE_SECRET = "test-secret";
24
+ const CORE_ENGINE_SECRET = CONFORMANCE_ENGINE_SECRET;
18
25
  const CORE_POLICY_ID = "a".repeat(64);
19
26
  const INTENT_BINDING_FIELDS = [
20
27
  "intent_id",
@@ -32,12 +39,13 @@ const INTENT_BINDING_FIELDS = [
32
39
  "tool",
33
40
  "tool_call"
34
41
  ];
35
- function makeEngine() {
42
+ function makeEngine(authorizationTtlSeconds = 60) {
36
43
  return new PolicyEngine({
37
44
  policy_version: "v1.0.0",
38
- engine_secret: CORE_ENGINE_SECRET,
39
- authorization_ttl_seconds: 60,
40
- policyId: CORE_POLICY_ID
45
+ engine_secret: CORE_ENGINE_SECRET, // now typed as string
46
+ authorization_ttl_seconds: authorizationTtlSeconds,
47
+ policyId: CORE_POLICY_ID,
48
+ ...RECOMMENDED_TRUSTED_TIME_PROFILE
41
49
  });
42
50
  }
43
51
  function makeBaseState() {
@@ -151,6 +159,23 @@ function b64ToBytes(value) {
151
159
  function hexSha256Utf8(value) {
152
160
  return createHash("sha256").update(value, "utf8").digest("hex");
153
161
  }
162
+ // Returns the raw signature bytes string regardless of which shape is present.
163
+ // Used wherever the conformance layer needs to inspect or compare signature material.
164
+ function getSignatureBytes(signature) {
165
+ return typeof signature === "string" ? signature : signature.sig;
166
+ }
167
+ // Corrupts the signature bytes by replacing the last two characters with "aa".
168
+ // Works for both the legacy bare-string shape and the normative nested-object shape.
169
+ // All mutation-test sites must go through this helper; no direct .slice() on signature.
170
+ function mutateSignature(auth) {
171
+ if (typeof auth.signature === "string") {
172
+ return { ...auth, signature: auth.signature.slice(0, -2) + "aa" };
173
+ }
174
+ return {
175
+ ...auth,
176
+ signature: { ...auth.signature, sig: auth.signature.sig.slice(0, -2) + "aa" },
177
+ };
178
+ }
154
179
  function isPlainObject(v) {
155
180
  return typeof v === "object" && v !== null && !Array.isArray(v);
156
181
  }
@@ -185,12 +210,15 @@ const coreAdapter = {
185
210
  }
186
211
  return sha256HexFromJson(binding);
187
212
  },
188
- evaluateAuthorization(intent) {
213
+ evaluateAuthorization(intent, evaluationTime) {
189
214
  const engine = makeEngine();
190
- const out = engine.evaluatePure(intent, makeBaseState());
215
+ const out = engine.evaluatePure(intent, makeBaseState(), evaluationTime);
191
216
  if (out.decision !== "ALLOW") {
192
217
  throw new Error(`expected ALLOW, got DENY: ${out.reasons.join(",")}`);
193
218
  }
219
+ // Cast to AuthorizationLike: runtime object retains internal engine fields
220
+ // (state_snapshot_hash, expires_at, engine_signature) even though the
221
+ // TypeScript type was narrowed to AuthorizationV1 at the evaluatePure boundary.
194
222
  return { authorization: out.authorization, policyId: engine.computePolicyId() };
195
223
  },
196
224
  encodeSnapshot(state) {
@@ -207,16 +235,29 @@ const coreAdapter = {
207
235
  verifyEnvelope,
208
236
  verifyAuthorization
209
237
  };
238
+ const here = fileURLToPath(new URL(".", import.meta.url));
239
+ const exercised = new Map();
240
+ const caseResults = new Map();
241
+ const jsonOutput = process.argv.includes("--json");
242
+ function recordCase(label, passed) {
243
+ const id = label.split(/[ :]/)[0];
244
+ caseResults.set(id, (caseResults.get(id) ?? true) && passed);
245
+ }
210
246
  function loadJson(name) {
211
- const p = resolve(process.cwd(), "vectors", name);
212
- return JSON.parse(readFileSync(p, "utf8"));
247
+ const p = resolve(here, "../../vectors", name);
248
+ const data = JSON.parse(readFileSync(p, "utf8"));
249
+ exercised.set(`packages/conformance/vectors/${name}`, data);
250
+ return data;
213
251
  }
214
252
  function pass(ctx, msg) {
215
253
  ctx.passed += 1;
216
- console.log(`PASS ${msg}`);
254
+ recordCase(msg, true);
255
+ if (!jsonOutput)
256
+ console.log(`PASS ${msg}`);
217
257
  }
218
258
  function fail(ctx, msg) {
219
259
  ctx.failures.push(msg);
260
+ recordCase(msg, false);
220
261
  console.error(`FAIL ${msg}`);
221
262
  }
222
263
  function eq(ctx, label, actual, expected) {
@@ -246,14 +287,20 @@ function validateAuthorizationVectors(ctx, adapter) {
246
287
  const file = loadJson("authorization-payload.json");
247
288
  for (const v of file.vectors) {
248
289
  const id = String(v.id);
249
- const intent = parseIntent(v.input);
250
- const { authorization, policyId } = adapter.evaluateAuthorization(intent);
290
+ const input = asRecord(v.input);
291
+ const intent = parseIntent(input.intent);
292
+ const evaluationTime = Number(input.evaluation_time);
293
+ const effectiveTtl = Number(input.effective_ttl);
294
+ eq(ctx, `${id} intent_timestamp`, intent.timestamp, Number(input.intent_timestamp));
295
+ const { authorization, policyId } = adapter.evaluateAuthorization(intent, evaluationTime);
251
296
  const expected = asRecord(v.expected);
252
297
  eq(ctx, `${id} intent_hash`, authorization.intent_hash, String(expected.intent_hash));
253
298
  if (expected.state_hash !== undefined) {
254
299
  eq(ctx, `${id} state_hash`, authorization.state_snapshot_hash, String(expected.state_hash));
255
300
  }
256
- eq(ctx, `${id} expires_at`, authorization.expires_at, Number(expected.expires_at));
301
+ eq(ctx, `${id} issued_at`, authorization.issued_at, Number(expected.expected_issued_at));
302
+ eq(ctx, `${id} expiry`, authorization.expires_at, Number(expected.expected_expiry));
303
+ eq(ctx, `${id} effective_ttl`, authorization.expires_at - authorization.issued_at, effectiveTtl);
257
304
  const payload = adapter.canonicalJson({
258
305
  expires_at: authorization.expires_at,
259
306
  intent_hash: authorization.intent_hash,
@@ -282,6 +329,263 @@ function validateAuthorizationVerificationVectors(ctx, adapter) {
282
329
  eq(ctx, `${id} violations`, got.violations, expected.violations ?? []);
283
330
  }
284
331
  }
332
+ function validateClockSemanticsVectors(ctx, adapter) {
333
+ const file = loadJson("clock-semantics-verification.json");
334
+ for (const v of file.vectors) {
335
+ const id = String(v.id);
336
+ const input = asRecord(v.input);
337
+ const auth = {
338
+ alg: "HMAC-SHA256",
339
+ kid: "legacy",
340
+ signature: "legacy-placeholder",
341
+ ...asRecord(input.auth)
342
+ };
343
+ const opts = (input.opts ? asRecord(input.opts) : {});
344
+ const expected = asRecord(v.expected);
345
+ const got = adapter.verifyAuthorization(auth, opts);
346
+ eq(ctx, `${id} status`, got.status, String(expected.status));
347
+ eq(ctx, `${id} violations`, got.violations, expected.violations ?? []);
348
+ }
349
+ }
350
+ // ── Delegation conformance helpers ────────────────────────────────────────────
351
+ const DELEGATION_T_ISSUED = 1_000_000;
352
+ const DELEGATION_T_NOW = 1_001_000;
353
+ const DELEGATION_T_DEL_EXP = 1_002_000;
354
+ const DELEGATION_T_PAR_EXP = 1_003_000;
355
+ // Delegation signatures are verified against the delegating principal's KeySet.
356
+ // issuer = parent.audience = "parent-agent" (not the PDP issuer).
357
+ const DELEGATION_TEST_KEYSET = {
358
+ issuer: "parent-agent",
359
+ version: "1",
360
+ keys: [{
361
+ kid: "2026-01",
362
+ alg: "Ed25519",
363
+ public_key: TEST_ONLY_ED25519_PUBLIC_KEY_PEM_DO_NOT_USE_IN_PRODUCTION,
364
+ }],
365
+ };
366
+ function makeParentAuth() {
367
+ return signAuthorizationEd25519({
368
+ auth_id: "f".repeat(64),
369
+ issuer: "oxdeai.policy-engine",
370
+ audience: "parent-agent",
371
+ intent_hash: "a".repeat(64),
372
+ state_hash: "b".repeat(64),
373
+ policy_id: "c".repeat(64),
374
+ decision: "ALLOW",
375
+ issued_at: DELEGATION_T_ISSUED,
376
+ expiry: DELEGATION_T_PAR_EXP,
377
+ kid: "2026-01",
378
+ }, TEST_ONLY_ED25519_PRIVATE_KEY_PEM_DO_NOT_USE_IN_PRODUCTION);
379
+ }
380
+ function makeSignedDelegationBase(parent) {
381
+ return createDelegation(parent, {
382
+ delegatee: "child-agent",
383
+ scope: { tools: ["provision_gpu"] },
384
+ expiry: DELEGATION_T_DEL_EXP,
385
+ kid: "2026-01",
386
+ delegationId: "d1d1d1d1-0000-0000-0000-c0nf0rm4nce",
387
+ issuedAt: DELEGATION_T_ISSUED,
388
+ }, TEST_ONLY_ED25519_PRIVATE_KEY_PEM_DO_NOT_USE_IN_PRODUCTION);
389
+ }
390
+ function parseDelegationScope(input) {
391
+ if (input === undefined || input === null)
392
+ return {};
393
+ const r = asRecord(input);
394
+ const scope = {};
395
+ if (r.tools !== undefined)
396
+ scope.tools = r.tools;
397
+ if (r.max_amount !== undefined)
398
+ scope.max_amount = BigInt(String(r.max_amount));
399
+ if (r.max_actions !== undefined)
400
+ scope.max_actions = Number(r.max_actions);
401
+ if (r.max_depth !== undefined)
402
+ scope.max_depth = Number(r.max_depth);
403
+ return scope;
404
+ }
405
+ function parseDelegationInput(input) {
406
+ const r = asRecord(input);
407
+ return {
408
+ delegation_id: String(r.delegation_id),
409
+ issuer: String(r.issuer),
410
+ audience: String(r.audience),
411
+ parent_auth_hash: String(r.parent_auth_hash),
412
+ delegator: String(r.delegator),
413
+ delegatee: String(r.delegatee),
414
+ scope: parseDelegationScope(r.scope),
415
+ policy_id: String(r.policy_id),
416
+ issued_at: Number(r.issued_at),
417
+ expiry: Number(r.expiry),
418
+ alg: "Ed25519",
419
+ kid: String(r.kid),
420
+ signature: String(r.signature),
421
+ };
422
+ }
423
+ function validateDelegationParentHashVectors(ctx) {
424
+ const file = loadJson("delegation-parent-hash.json");
425
+ const seen = {};
426
+ for (const v of file.vectors) {
427
+ const id = String(v.id);
428
+ const input = asRecord(v.input);
429
+ const parent = asRecord(input.parent);
430
+ const hash = hexSha256Utf8(canonicalJson(parent));
431
+ const expected = String(asRecord(v.expected).parent_auth_hash);
432
+ eq(ctx, `${id} parent_auth_hash`, hash, expected);
433
+ seen[id] = hash;
434
+ if (v.invariant) {
435
+ const inv = String(v.invariant);
436
+ if (inv.startsWith("equals ")) {
437
+ const refId = inv.slice("equals ".length);
438
+ if (refId in seen) {
439
+ eq(ctx, `${id} invariant`, hash, seen[refId]);
440
+ }
441
+ }
442
+ }
443
+ }
444
+ }
445
+ function validateDelegationVerificationVectors(ctx) {
446
+ const file = loadJson("delegation-verification.json");
447
+ for (const v of file.vectors) {
448
+ const id = String(v.id);
449
+ const input = asRecord(v.input);
450
+ const delegation = parseDelegationInput(input.delegation);
451
+ const optsRaw = input.opts ? asRecord(input.opts) : {};
452
+ const opts = {};
453
+ if (optsRaw.now !== undefined)
454
+ opts.now = Number(optsRaw.now);
455
+ if (optsRaw.expectedDelegatee !== undefined)
456
+ opts.expectedDelegatee = String(optsRaw.expectedDelegatee);
457
+ if (optsRaw.expectedPolicyId !== undefined)
458
+ opts.expectedPolicyId = String(optsRaw.expectedPolicyId);
459
+ if (optsRaw.requireSignatureVerification !== undefined)
460
+ opts.requireSignatureVerification = Boolean(optsRaw.requireSignatureVerification);
461
+ if (optsRaw.consumedDelegationIds !== undefined)
462
+ opts.consumedDelegationIds = optsRaw.consumedDelegationIds;
463
+ if (optsRaw.parentScope !== undefined)
464
+ opts.parentScope = parseDelegationScope(optsRaw.parentScope);
465
+ const got = verifyDelegation(delegation, opts);
466
+ const expected = asRecord(v.expected);
467
+ eq(ctx, `${id} status`, got.status, String(expected.status));
468
+ eq(ctx, `${id} violations`, got.violations, expected.violations ?? []);
469
+ }
470
+ }
471
+ function buildDelegationChainCases() {
472
+ const parent = makeParentAuth();
473
+ const delegation = makeSignedDelegationBase(parent);
474
+ const opts = { now: DELEGATION_T_NOW };
475
+ // valid
476
+ const valid = verifyDelegationChain(delegation, parent, opts);
477
+ // parent-hash-mismatch: different parent with same audience/policy so only hash differs
478
+ const otherParent = signAuthorizationEd25519({
479
+ auth_id: "e".repeat(64), // different → different hash
480
+ issuer: "oxdeai.policy-engine",
481
+ audience: "parent-agent", // same → delegator check passes
482
+ intent_hash: "a".repeat(64),
483
+ state_hash: "b".repeat(64),
484
+ policy_id: "c".repeat(64), // same → policy check passes
485
+ decision: "ALLOW",
486
+ issued_at: DELEGATION_T_ISSUED,
487
+ expiry: DELEGATION_T_PAR_EXP,
488
+ kid: "2026-01",
489
+ }, TEST_ONLY_ED25519_PRIVATE_KEY_PEM_DO_NOT_USE_IN_PRODUCTION);
490
+ const hashMismatch = verifyDelegationChain(delegation, otherParent, opts);
491
+ // delegator-mismatch: tamper delegator field after signing (chain check catches before sig)
492
+ const delegatorMismatch = verifyDelegationChain({ ...delegation, delegator: "wrong-agent" }, parent, opts);
493
+ // parent-expired: now === parent.expiry → DELEGATION_PARENT_EXPIRED
494
+ const parentExpired = verifyDelegationChain(delegation, parent, { now: DELEGATION_T_PAR_EXP });
495
+ // expiry-exceeds-parent: delegation.expiry > parent.expiry
496
+ const exceedsDelegation = createDelegation(parent, {
497
+ delegatee: "child-agent",
498
+ scope: {},
499
+ expiry: DELEGATION_T_PAR_EXP + 1,
500
+ kid: "2026-01",
501
+ issuedAt: DELEGATION_T_ISSUED,
502
+ }, TEST_ONLY_ED25519_PRIVATE_KEY_PEM_DO_NOT_USE_IN_PRODUCTION);
503
+ const expiryExceeds = verifyDelegationChain(exceedsDelegation, parent, opts);
504
+ // multi-hop: delegation passed as its own parent (has delegation_id field)
505
+ const multiHop = verifyDelegationChain(delegation, delegation, opts);
506
+ // policy-id-mismatch: tamper policy_id after signing (chain check catches before sig)
507
+ const policyMismatch = verifyDelegationChain({ ...delegation, policy_id: "d".repeat(64) }, parent, opts);
508
+ return [valid, hashMismatch, delegatorMismatch, parentExpired, expiryExceeds, multiHop, policyMismatch];
509
+ }
510
+ function validateDelegationChainVectors(ctx) {
511
+ const file = loadJson("delegation-chain-verification.json");
512
+ const actual = buildDelegationChainCases();
513
+ for (let i = 0; i < file.vectors.length; i++) {
514
+ const id = String(file.vectors[i].id);
515
+ const expected = asRecord(file.vectors[i].expected);
516
+ const got = actual[i];
517
+ eq(ctx, `${id} status`, got.status, String(expected.status));
518
+ eq(ctx, `${id} violations`, got.violations, expected.violations ?? []);
519
+ }
520
+ }
521
+ function buildDelegationSignatureCases() {
522
+ const parent = makeParentAuth();
523
+ const delegation = makeSignedDelegationBase(parent);
524
+ const chainOpts = {
525
+ now: DELEGATION_T_NOW,
526
+ trustedKeySets: DELEGATION_TEST_KEYSET,
527
+ requireSignatureVerification: true,
528
+ };
529
+ // valid
530
+ const valid = verifyDelegationChain(delegation, parent, chainOpts);
531
+ // tampered-signature: corrupt last bytes
532
+ const tamperedSig = verifyDelegationChain({ ...delegation, signature: delegation.signature.slice(0, -4) + "AAAA" }, parent, chainOpts);
533
+ // wrong-kid: key not found in trustedKeySets
534
+ const wrongKid = verifyDelegationChain({ ...delegation, kid: "unknown-kid" }, parent, chainOpts);
535
+ // tampered-field: mutate delegatee after signing → sig covers original payload
536
+ const tamperedField = verifyDelegationChain({ ...delegation, delegatee: "evil-agent" }, parent, chainOpts);
537
+ // expired: correctly signed but expiry already past
538
+ const expiredDelegation = createDelegation(parent, {
539
+ delegatee: "child-agent",
540
+ scope: {},
541
+ expiry: DELEGATION_T_NOW - 1,
542
+ kid: "2026-01",
543
+ issuedAt: DELEGATION_T_ISSUED,
544
+ }, TEST_ONLY_ED25519_PRIVATE_KEY_PEM_DO_NOT_USE_IN_PRODUCTION);
545
+ const expired = verifyDelegationChain(expiredDelegation, parent, chainOpts);
546
+ return [valid, tamperedSig, wrongKid, tamperedField, expired];
547
+ }
548
+ function validateDelegationSignatureVectors(ctx) {
549
+ const file = loadJson("delegation-signature-verification.json");
550
+ const actual = buildDelegationSignatureCases();
551
+ for (let i = 0; i < file.vectors.length; i++) {
552
+ const id = String(file.vectors[i].id);
553
+ const expected = asRecord(file.vectors[i].expected);
554
+ const got = actual[i];
555
+ eq(ctx, `${id} status`, got.status, String(expected.status));
556
+ eq(ctx, `${id} violations`, got.violations, expected.violations ?? []);
557
+ }
558
+ }
559
+ /**
560
+ * Builds a signed AuthorizationV1 in Sift-compatible wire format:
561
+ * - alg = "ed25519" (lowercase, nested signature object)
562
+ * - expires_at (not expiry)
563
+ * - base64url-encoded signature bytes
564
+ * - signing preimage: canonicalJson(unsigned) with no domain prefix
565
+ *
566
+ * The verifier accepts this via the ed25519 branch + verifyEd25519Raw fallback
567
+ * + base64url decoding in decodeSignatureBytes.
568
+ */
569
+ function makeSiftWireFormatAuth(now = 1730000000) {
570
+ const unsigned = {
571
+ auth_id: "f".repeat(64),
572
+ issuer: "oxdeai.policy-engine",
573
+ audience: "merchant-gateway",
574
+ intent_hash: "a".repeat(64),
575
+ state_hash: "b".repeat(64),
576
+ policy_id: "c".repeat(64),
577
+ decision: "ALLOW",
578
+ issued_at: now,
579
+ expires_at: now + 60,
580
+ signature: { alg: "ed25519", kid: "2026-01" },
581
+ };
582
+ const preimage = Buffer.from(canonicalJson(unsigned), "utf8");
583
+ const sigBytes = nodeSign(null, preimage, TEST_ONLY_ED25519_PRIVATE_KEY_PEM_DO_NOT_USE_IN_PRODUCTION);
584
+ return {
585
+ ...unsigned,
586
+ signature: { alg: "ed25519", kid: "2026-01", sig: sigBytes.toString("base64url") },
587
+ };
588
+ }
285
589
  function makeSignedAuthorizationBase(now = 1730000000) {
286
590
  return signAuthorizationEd25519({
287
591
  auth_id: "f".repeat(64),
@@ -313,7 +617,7 @@ function validateAuthorizationSignatureVectors(ctx, adapter) {
313
617
  consumedAuthIds: []
314
618
  };
315
619
  if (mode === "invalid-signature") {
316
- auth = { ...auth, signature: `${auth.signature.slice(0, -2)}aa` };
620
+ auth = mutateSignature(auth);
317
621
  }
318
622
  else if (mode === "wrong-kid") {
319
623
  auth = { ...auth, kid: "unknown-kid" };
@@ -347,6 +651,15 @@ function validateAuthorizationSignatureVectors(ctx, adapter) {
347
651
  else if (mode === "unknown-alg") {
348
652
  auth = { ...auth, alg: "Unknown" };
349
653
  }
654
+ else if (mode === "sift-wire-format") {
655
+ auth = makeSiftWireFormatAuth();
656
+ }
657
+ else if (mode === "unsupported-alg-EdDSA") {
658
+ auth = { ...auth, alg: "EdDSA" };
659
+ }
660
+ else if (mode === "unsupported-alg-ED25519") {
661
+ auth = { ...auth, alg: "ED25519" };
662
+ }
350
663
  const got = adapter.verifyAuthorization(auth, opts);
351
664
  eq(ctx, `${id} status`, got.status, String(expected.status));
352
665
  eq(ctx, `${id} violations`, got.violations, expected.violations ?? []);
@@ -426,7 +739,7 @@ function buildEnvelopeCases(adapter) {
426
739
  tool_call: true,
427
740
  type: "EXECUTE"
428
741
  });
429
- const out = engine.evaluatePure(intent, state);
742
+ const out = engine.evaluatePure(intent, state, intent.timestamp);
430
743
  if (out.decision !== "ALLOW")
431
744
  throw new Error("expected ALLOW for envelope cases");
432
745
  const events = engine.audit.snapshot();
@@ -460,11 +773,11 @@ function buildEnvelopeCases(adapter) {
460
773
  });
461
774
  const corrupt = new Uint8Array([1, 2, 3, 4, 5]);
462
775
  return [
463
- adapter.verifyEnvelope(withCheckpoint, { expectedPolicyId: engine.computePolicyId(), mode: "strict" }),
776
+ adapter.verifyEnvelope(withCheckpoint, { expectedPolicyId: engine.computePolicyId(), mode: "strict", trustedKeySets: TEST_KEYSET }),
464
777
  adapter.verifyEnvelope(valid, { expectedPolicyId: engine.computePolicyId(), mode: "best-effort" }),
465
778
  adapter.verifyEnvelope(mismatched, { expectedPolicyId: engine.computePolicyId(), mode: "best-effort" }),
466
779
  adapter.verifyEnvelope(corrupt, { expectedPolicyId: engine.computePolicyId(), mode: "best-effort" }),
467
- adapter.verifyEnvelope(valid, { expectedPolicyId: engine.computePolicyId(), mode: "strict" })
780
+ adapter.verifyEnvelope(valid, { expectedPolicyId: engine.computePolicyId(), mode: "strict", trustedKeySets: TEST_KEYSET })
468
781
  ];
469
782
  }
470
783
  function validateEnvelopeVectors(ctx, adapter) {
@@ -639,24 +952,403 @@ function validateAuditVerificationVectors(ctx, adapter) {
639
952
  eq(ctx, `${id} violations`, got.violations, expected.violations ?? []);
640
953
  }
641
954
  }
955
+ // ── Profile C: Semantic State Verification ────────────────────────────────────
956
+ //
957
+ // Tests the guard's step 10 behavior: computeStateHash(liveState) must equal
958
+ // authorization.state_hash. Two hash strategies are modelled:
959
+ // "core" — sha256HexFromJson (standard OxDeAI; matches stateSnapshotHash for flat objects)
960
+ // "provider" — SHA-256 of "PROVIDER:" + canonicalJson (simulates an external provider
961
+ // algorithm such as siftCanonicalJsonHash; distinct from "core")
962
+ // "throws" — always throws (models a broken or unavailable hash implementation)
963
+ //
964
+ // Encoding B vectors additionally exercise selected Profile-C semantics: Sift-compatible wire format
965
+ // (alg="ed25519", expires_at, base64url sig) + live-state semantic verification.
966
+ function profileCCoreHash(state) {
967
+ return sha256HexFromJson(state);
968
+ }
969
+ function profileCProviderHash(state) {
970
+ // Deterministic but distinct from coreHash — simulates an external provider algorithm.
971
+ return createHash("sha256")
972
+ .update("PROVIDER:" + canonicalJson(state), "utf8")
973
+ .digest("hex");
974
+ }
975
+ function profileCGetHashFn(strategy) {
976
+ if (strategy === "core")
977
+ return profileCCoreHash;
978
+ if (strategy === "provider")
979
+ return profileCProviderHash;
980
+ if (strategy === "throws")
981
+ return (_s) => { throw new Error("hash backend unavailable"); };
982
+ throw new Error(`unknown hash_strategy: ${strategy}`);
983
+ }
984
+ // ── Key lifecycle constants ───────────────────────────────────────────────────
985
+ const KL_SIGNING_NOW = 1730000000;
986
+ const KL_VERIFY_NOW = 1730000010;
987
+ const KL_PAST = KL_SIGNING_NOW - 7200; // 2 h before signing
988
+ const KL_FUTURE = KL_SIGNING_NOW + 7200; // 2 h after signing
989
+ function makeKLAuth() {
990
+ return signAuthorizationEd25519({
991
+ auth_id: "e".repeat(64),
992
+ issuer: "kl.issuer",
993
+ audience: "kl.audience",
994
+ intent_hash: "a".repeat(64),
995
+ state_hash: "b".repeat(64),
996
+ policy_id: "c".repeat(64),
997
+ decision: "ALLOW",
998
+ issued_at: KL_SIGNING_NOW,
999
+ expiry: KL_SIGNING_NOW + 300,
1000
+ kid: "kl-key-001",
1001
+ }, TEST_ONLY_ED25519_PRIVATE_KEY_PEM_DO_NOT_USE_IN_PRODUCTION);
1002
+ }
1003
+ function makeKLKeyEntry(overrides = {}) {
1004
+ return {
1005
+ kid: "kl-key-001",
1006
+ alg: "Ed25519",
1007
+ public_key: TEST_ONLY_ED25519_PUBLIC_KEY_PEM_DO_NOT_USE_IN_PRODUCTION,
1008
+ ...overrides,
1009
+ };
1010
+ }
1011
+ function makeKLKeyset(entry) {
1012
+ return { issuer: "kl.issuer", version: "1", keys: [entry] };
1013
+ }
1014
+ function klVerifyOpts(keyset) {
1015
+ return {
1016
+ now: KL_VERIFY_NOW,
1017
+ expectedIssuer: "kl.issuer",
1018
+ expectedAudience: "kl.audience",
1019
+ expectedPolicyId: "c".repeat(64),
1020
+ trustedKeySets: keyset,
1021
+ requireSignatureVerification: true,
1022
+ consumedAuthIds: [],
1023
+ };
1024
+ }
1025
+ function validateKeyLifecycleVectors(ctx, adapter) {
1026
+ const file = loadJson("key-lifecycle-verification.json");
1027
+ const auth = makeKLAuth();
1028
+ for (const v of file.vectors) {
1029
+ const id = String(v.id);
1030
+ const mode = String(v.mode);
1031
+ const expected = asRecord(v.expected);
1032
+ let keyset;
1033
+ if (mode === "key-active") {
1034
+ keyset = makeKLKeyset(makeKLKeyEntry({ status: "active" }));
1035
+ }
1036
+ else if (mode === "key-revoked") {
1037
+ keyset = makeKLKeyset(makeKLKeyEntry({ status: "revoked" }));
1038
+ }
1039
+ else if (mode === "key-not-before-future") {
1040
+ keyset = makeKLKeyset(makeKLKeyEntry({ status: "active", not_before: KL_FUTURE }));
1041
+ }
1042
+ else if (mode === "key-not-after-past") {
1043
+ keyset = makeKLKeyset(makeKLKeyEntry({ status: "active", not_after: KL_PAST }));
1044
+ }
1045
+ else if (mode === "key-valid-window") {
1046
+ keyset = makeKLKeyset(makeKLKeyEntry({ status: "active", not_before: KL_PAST, not_after: KL_FUTURE }));
1047
+ }
1048
+ else if (mode === "key-expired-window") {
1049
+ keyset = makeKLKeyset(makeKLKeyEntry({ status: "active", not_before: KL_PAST - 3600, not_after: KL_PAST }));
1050
+ }
1051
+ else if (mode === "key-retired-within-window") {
1052
+ keyset = makeKLKeyset(makeKLKeyEntry({ status: "retired", not_before: KL_PAST, not_after: KL_FUTURE }));
1053
+ }
1054
+ else if (mode === "key-retired-past-window") {
1055
+ keyset = makeKLKeyset(makeKLKeyEntry({ status: "retired", not_after: KL_PAST }));
1056
+ }
1057
+ else if (mode === "key-revoked-valid-window") {
1058
+ keyset = makeKLKeyset(makeKLKeyEntry({ status: "revoked", not_before: KL_PAST, not_after: KL_FUTURE }));
1059
+ }
1060
+ else if (mode === "wrong-kid-known-issuer") {
1061
+ keyset = makeKLKeyset(makeKLKeyEntry({ kid: "different-key-id", status: "active" }));
1062
+ }
1063
+ else {
1064
+ fail(ctx, `${id}: unknown mode "${mode}"`);
1065
+ continue;
1066
+ }
1067
+ const got = adapter.verifyAuthorization(auth, klVerifyOpts(keyset));
1068
+ eq(ctx, `${id} status`, got.status, String(expected.status));
1069
+ eq(ctx, `${id} violations`, got.violations, expected.violations ?? []);
1070
+ }
1071
+ }
1072
+ function validateProfileCStateVerificationVectors(ctx, adapter) {
1073
+ const file = loadJson("profile-c-state-verification.json");
1074
+ for (const v of file.vectors) {
1075
+ const id = String(v.id);
1076
+ const mode = String(v.mode);
1077
+ const expected = asRecord(v.expected);
1078
+ const expectedStatus = String(expected.status);
1079
+ // ── Pure state-hash comparison modes ─────────────────────────────────────
1080
+ // These modes test the semantic comparison (step 10) in isolation.
1081
+ // They do not involve a real AuthorizationV1 artifact — they model the
1082
+ // guard's computeStateHash(liveState) == authorization.state_hash logic.
1083
+ if (mode === "live-state-match" ||
1084
+ mode === "live-state-mismatch" ||
1085
+ mode === "hash-strategy-mismatch" ||
1086
+ mode === "toctou-stale-state") {
1087
+ const stateInput = v.state_input;
1088
+ const liveStateInput = (v.live_state_input ?? v.state_input);
1089
+ const signingStrategy = String((v.signing_strategy ?? v.hash_strategy) ?? "core");
1090
+ const verifyStrategy = String((v.verify_strategy ?? v.hash_strategy) ?? "core");
1091
+ const signingHashFn = profileCGetHashFn(signingStrategy);
1092
+ const committedHash = signingHashFn(stateInput);
1093
+ let liveHash;
1094
+ let threw = false;
1095
+ try {
1096
+ liveHash = profileCGetHashFn(verifyStrategy)(liveStateInput);
1097
+ }
1098
+ catch {
1099
+ threw = true;
1100
+ }
1101
+ if (threw) {
1102
+ eq(ctx, `${id} outcome`, "compute-error", expectedStatus);
1103
+ }
1104
+ else if (liveHash === committedHash) {
1105
+ eq(ctx, `${id} outcome`, "ok", expectedStatus);
1106
+ }
1107
+ else {
1108
+ eq(ctx, `${id} outcome`, "state-hash-mismatch", expectedStatus);
1109
+ }
1110
+ continue;
1111
+ }
1112
+ // ── compute-throws mode ───────────────────────────────────────────────────
1113
+ if (mode === "compute-throws") {
1114
+ let threw = false;
1115
+ try {
1116
+ profileCGetHashFn("throws")(null);
1117
+ }
1118
+ catch {
1119
+ threw = true;
1120
+ }
1121
+ eq(ctx, `${id} throws`, threw, true);
1122
+ eq(ctx, `${id} outcome`, threw ? "compute-error" : "ok", expectedStatus);
1123
+ continue;
1124
+ }
1125
+ // ── Encoding B modes ──────────────────────────────────────────────────────
1126
+ // These consume a committed Sift-compatible (Encoding B) AuthorizationV1 artifact
1127
+ // with state_hash = signingHashFn(state_input), verify the signature
1128
+ // (exercising the ed25519/expires_at/base64url path), then simulate
1129
+ // Profile C step 10: computeStateHash(live_state_input) == authorization.state_hash.
1130
+ if (mode === "encoding-b-live-state-match" ||
1131
+ mode === "encoding-b-live-state-mismatch" ||
1132
+ mode === "encoding-b-hash-strategy-mismatch") {
1133
+ const stateInput = v.state_input;
1134
+ const liveStateInput = (v.live_state_input ?? v.state_input);
1135
+ const signingStrategy = String((v.signing_strategy ?? v.hash_strategy) ?? "provider");
1136
+ const verifyStrategy = String((v.verify_strategy ?? v.hash_strategy) ?? "provider");
1137
+ const committedHash = profileCGetHashFn(signingStrategy)(stateInput);
1138
+ // Consume the committed authority fixture preserved by the one-way projection.
1139
+ // Do not synthesize/re-sign an artifact: Go and Python verify these same bytes.
1140
+ const auth = v.auth;
1141
+ const opts = asRecord(v.opts);
1142
+ const trustedKey = asRecord(opts.trustedKey);
1143
+ eq(ctx, `${id} committed state hash`, auth.state_hash, committedHash);
1144
+ const sigResult = adapter.verifyAuthorization(auth, {
1145
+ now: Number(opts.now),
1146
+ expectedIssuer: "oxdeai.policy-engine",
1147
+ expectedAudience: "merchant-gateway",
1148
+ trustedKeySets: {
1149
+ issuer: "oxdeai.policy-engine",
1150
+ version: "1",
1151
+ keys: [{ kid: String(trustedKey.kid), alg: trustedKey.alg, public_key: String(trustedKey.public_key) }],
1152
+ },
1153
+ requireSignatureVerification: true,
1154
+ consumedAuthIds: [],
1155
+ });
1156
+ if (sigResult.status !== "ok") {
1157
+ fail(ctx, `${id} signature: expected ok, got ${sigResult.status} — ${JSON.stringify(sigResult.violations)}`);
1158
+ continue;
1159
+ }
1160
+ pass(ctx, `${id} signature`);
1161
+ // Step 2: semantic state verification (Profile C step 10).
1162
+ let liveHash;
1163
+ let threw = false;
1164
+ try {
1165
+ liveHash = profileCGetHashFn(verifyStrategy)(liveStateInput);
1166
+ }
1167
+ catch {
1168
+ threw = true;
1169
+ }
1170
+ let outcome;
1171
+ if (threw) {
1172
+ outcome = "compute-error";
1173
+ }
1174
+ else if (liveHash === committedHash) {
1175
+ outcome = "ok";
1176
+ }
1177
+ else {
1178
+ outcome = "state-hash-mismatch";
1179
+ }
1180
+ eq(ctx, `${id} outcome`, outcome, expectedStatus);
1181
+ continue;
1182
+ }
1183
+ fail(ctx, `${id}: unknown mode "${mode}"`);
1184
+ }
1185
+ }
1186
+ // ── SignedKRLV1 verification ───────────────────────────────────────────────────
1187
+ const KRL_ISSUER = "krl.issuer";
1188
+ const KRL_KID = "krl-2026-01";
1189
+ const KRL_T_ISSUED = 1_730_000_000;
1190
+ const KRL_T_NOW = 1_730_000_010;
1191
+ const KRL_T_NOT_AFTER = 1_730_003_600;
1192
+ const KRL_TRUSTED_KEYSET = {
1193
+ issuer: KRL_ISSUER,
1194
+ version: "1",
1195
+ keys: [{
1196
+ kid: KRL_KID,
1197
+ alg: "Ed25519",
1198
+ public_key: KRL_TEST_ONLY_ED25519_PUBLIC_KEY_PEM_DO_NOT_USE_IN_PRODUCTION,
1199
+ }],
1200
+ };
1201
+ function buildValidKrlBase() {
1202
+ return {
1203
+ version: "SignedKRLV1",
1204
+ issuer: KRL_ISSUER,
1205
+ krl_version: 1,
1206
+ issued_at: KRL_T_ISSUED,
1207
+ not_after: KRL_T_NOT_AFTER,
1208
+ revoked_kids: ["kid-provider-1", "kid-provider-2"],
1209
+ };
1210
+ }
1211
+ function signValidKrl(override) {
1212
+ const base = { ...buildValidKrlBase(), ...override };
1213
+ const envelope = {
1214
+ ...base,
1215
+ signature: { alg: "Ed25519", kid: KRL_KID, sig: "" },
1216
+ };
1217
+ const payload = signedKrlSigningPayload(envelope);
1218
+ const sig = signEd25519(SIGNING_DOMAINS.KRL_V1, payload, KRL_TEST_ONLY_ED25519_PRIVATE_KEY_PEM_DO_NOT_USE_IN_PRODUCTION);
1219
+ return { ...envelope, signature: { alg: "Ed25519", kid: KRL_KID, sig } };
1220
+ }
1221
+ function validateSignedKrlVectors(ctx) {
1222
+ const file = loadJson("signed-krl-verification.json");
1223
+ for (const v of file.vectors) {
1224
+ const id = String(v.id);
1225
+ const mode = String(v.mode);
1226
+ const expected = asRecord(v.expected);
1227
+ let result;
1228
+ if (mode === "valid") {
1229
+ // Valid KRL — signature verifies, not expired
1230
+ const krl = signValidKrl();
1231
+ result = verifySignedKrl(krl, { now: KRL_T_NOW, trustedKeySets: KRL_TRUSTED_KEYSET });
1232
+ }
1233
+ else if (mode === "invalid-signature") {
1234
+ // Tamper last 4 base64 characters of the signature
1235
+ const krl = signValidKrl();
1236
+ const tampered = {
1237
+ ...krl,
1238
+ signature: { ...krl.signature, sig: krl.signature.sig.slice(0, -4) + "AAAA" },
1239
+ };
1240
+ result = verifySignedKrl(tampered, { now: KRL_T_NOW, trustedKeySets: KRL_TRUSTED_KEYSET });
1241
+ }
1242
+ else if (mode === "expired") {
1243
+ // Verification time is one second past not_after
1244
+ const krl = signValidKrl();
1245
+ result = verifySignedKrl(krl, { now: KRL_T_NOT_AFTER + 1, trustedKeySets: KRL_TRUSTED_KEYSET });
1246
+ }
1247
+ else if (mode === "malformed-revoked-kids") {
1248
+ // revoked_kids is a string, not an array — structural malformation
1249
+ const raw = {
1250
+ ...buildValidKrlBase(),
1251
+ revoked_kids: "not-an-array",
1252
+ signature: { alg: "Ed25519", kid: KRL_KID, sig: "placeholder" },
1253
+ };
1254
+ result = verifySignedKrl(raw, { now: KRL_T_NOW, trustedKeySets: KRL_TRUSTED_KEYSET });
1255
+ }
1256
+ else if (mode === "duplicate-revoked-kids") {
1257
+ // Valid signature but revoked_kids has duplicate entries
1258
+ const krl = signValidKrl({ revoked_kids: ["kid-1", "kid-1"] });
1259
+ result = verifySignedKrl(krl, { now: KRL_T_NOW, trustedKeySets: KRL_TRUSTED_KEYSET });
1260
+ }
1261
+ else if (mode === "unknown-signing-kid") {
1262
+ // kid tampered after signing — not found in trusted key sets
1263
+ const krl = signValidKrl();
1264
+ const wrongKid = {
1265
+ ...krl,
1266
+ signature: { ...krl.signature, kid: "unknown-kid" },
1267
+ };
1268
+ result = verifySignedKrl(wrongKid, { now: KRL_T_NOW, trustedKeySets: KRL_TRUSTED_KEYSET });
1269
+ }
1270
+ else if (mode === "signing-key-inactive") {
1271
+ // The correct kid exists in the trusted set but is marked revoked
1272
+ const krl = signValidKrl();
1273
+ const revokedKeyset = {
1274
+ ...KRL_TRUSTED_KEYSET,
1275
+ keys: [{ ...KRL_TRUSTED_KEYSET.keys[0], status: "revoked" }],
1276
+ };
1277
+ result = verifySignedKrl(krl, { now: KRL_T_NOW, trustedKeySets: revokedKeyset });
1278
+ }
1279
+ else if (mode === "unsupported-alg") {
1280
+ // signature.alg is not "Ed25519" — structural KRL_UNSUPPORTED_ALG before sig check
1281
+ const raw = {
1282
+ ...buildValidKrlBase(),
1283
+ signature: { alg: "HMAC-SHA256", kid: KRL_KID, sig: "placeholder" },
1284
+ };
1285
+ result = verifySignedKrl(raw, { now: KRL_T_NOW, trustedKeySets: KRL_TRUSTED_KEYSET });
1286
+ }
1287
+ else if (mode === "version-regression") {
1288
+ // krl_version=1 but caller knows previous version was 5
1289
+ const krl = signValidKrl(); // krl_version = 1
1290
+ result = verifySignedKrl(krl, {
1291
+ now: KRL_T_NOW,
1292
+ trustedKeySets: KRL_TRUSTED_KEYSET,
1293
+ previousKrlVersionByIssuer: { [KRL_ISSUER]: 5 },
1294
+ });
1295
+ }
1296
+ else {
1297
+ fail(ctx, `${id}: unknown mode "${mode}"`);
1298
+ continue;
1299
+ }
1300
+ eq(ctx, `${id} status`, result.status, String(expected.status));
1301
+ eq(ctx, `${id} violations`, result.violations, expected.violations ?? []);
1302
+ }
1303
+ }
642
1304
  function main() {
643
1305
  const ctx = { failures: [], passed: 0 };
644
1306
  const adapter = coreAdapter;
645
- console.log(`Running conformance validation with ${adapter.name}`);
646
- validateIntentHashVectors(ctx, adapter);
647
- validateAuthorizationVectors(ctx, adapter);
648
- validateAuthorizationVerificationVectors(ctx, adapter);
649
- validateAuthorizationSignatureVectors(ctx, adapter);
650
- validateSnapshotVectors(ctx, adapter);
651
- validateAuditChainVectors(ctx, adapter);
652
- validateAuditVerificationVectors(ctx, adapter);
653
- validateEnvelopeVectors(ctx, adapter);
654
- validateEnvelopeSignatureVectors(ctx, adapter);
1307
+ if (!jsonOutput)
1308
+ console.log(`Running conformance validation with ${adapter.name}`);
1309
+ try {
1310
+ validateIntentHashVectors(ctx, adapter);
1311
+ validateAuthorizationVectors(ctx, adapter);
1312
+ const trustedTime = runTrustedTimeConformance(loadJson("trusted-time.json"), line => {
1313
+ const match = /^(PASS|FAIL) ([^ :]+)/.exec(line);
1314
+ if (match)
1315
+ recordCase(match[2], match[1] === "PASS");
1316
+ if (!jsonOutput)
1317
+ console.log(line);
1318
+ });
1319
+ ctx.passed += trustedTime.passed;
1320
+ ctx.failures.push(...trustedTime.failures);
1321
+ validateAuthorizationVerificationVectors(ctx, adapter);
1322
+ validateAuthorizationSignatureVectors(ctx, adapter);
1323
+ validateSnapshotVectors(ctx, adapter);
1324
+ validateAuditChainVectors(ctx, adapter);
1325
+ validateAuditVerificationVectors(ctx, adapter);
1326
+ validateEnvelopeVectors(ctx, adapter);
1327
+ validateEnvelopeSignatureVectors(ctx, adapter);
1328
+ validateDelegationParentHashVectors(ctx);
1329
+ validateDelegationVerificationVectors(ctx);
1330
+ validateDelegationChainVectors(ctx);
1331
+ validateDelegationSignatureVectors(ctx);
1332
+ validateKeyLifecycleVectors(ctx, adapter);
1333
+ validateClockSemanticsVectors(ctx, adapter);
1334
+ validateProfileCStateVerificationVectors(ctx, adapter);
1335
+ validateSignedKrlVectors(ctx);
1336
+ }
1337
+ catch (error) {
1338
+ ctx.failures.push(error instanceof Error ? error.message : String(error));
1339
+ }
1340
+ const scope = evidenceScope({ consumer: "packages/conformance/src/validate.ts", runtime: "TypeScript", selections: [...exercised].map(([representation, data]) => {
1341
+ const ids = data.vectors.map(v => String(v.id)).filter(id => caseResults.has(id));
1342
+ return { representation, data, consumer: "packages/conformance/src/validate.ts", runtime: "TypeScript", caseIds: ids, passedCaseIds: ids.filter(id => caseResults.get(id)) };
1343
+ }) });
1344
+ console.log(JSON.stringify({ result: ctx.failures.length ? "FAIL" : "PASS", passed: ctx.passed, failures: ctx.failures, evidenceScope: scope }));
655
1345
  if (ctx.failures.length > 0) {
656
1346
  console.error(`\nConformance failed: ${ctx.failures.length} assertion(s)`);
657
- process.exit(1);
1347
+ process.exitCode = 1;
1348
+ return;
658
1349
  }
659
- console.log(`\nConformance passed: ${ctx.passed} assertions`);
1350
+ if (!jsonOutput)
1351
+ console.log(`\nConformance passed: ${ctx.passed} assertions (declared evidenceScope only)`);
660
1352
  }
661
1353
  main();
662
1354
  //# sourceMappingURL=validate.js.map