@oxdeai/conformance 1.3.0 → 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 +101 -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 +37 -12
  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
package/src/validate.ts DELETED
@@ -1,828 +0,0 @@
1
- import { readFileSync } from "node:fs";
2
- import { resolve } from "node:path";
3
- import { createHash } from "node:crypto";
4
- import {
5
- encodeCanonicalState,
6
- encodeEnvelope,
7
- PolicyEngine,
8
- sha256HexFromJson,
9
- signAuthorizationEd25519,
10
- signEnvelopeEd25519,
11
- verifyAuthorization,
12
- verifyAuditEvents,
13
- verifyEnvelope,
14
- verifySnapshot
15
- } from "@oxdeai/core";
16
- import type { AuthorizationV1, Intent, KeySet, State, VerificationResult } from "@oxdeai/core";
17
- import {
18
- TEST_ONLY_ED25519_PRIVATE_KEY_PEM_DO_NOT_USE_IN_PRODUCTION,
19
- TEST_ONLY_ED25519_PUBLIC_KEY_PEM_DO_NOT_USE_IN_PRODUCTION,
20
- } from "./fixtures/ed25519.test-only.fixture.js";
21
-
22
- type JsonRecord = Record<string, unknown>;
23
-
24
- type AuthorizationLike = {
25
- auth_id: string;
26
- issuer: string;
27
- audience: string;
28
- intent_hash: string;
29
- state_hash: string;
30
- policy_id: string;
31
- decision: "ALLOW" | "DENY";
32
- issued_at: number;
33
- expiry: number;
34
- alg: "Ed25519" | "HMAC-SHA256";
35
- kid: string;
36
- signature: string;
37
- state_snapshot_hash: string;
38
- expires_at: number;
39
- engine_signature: string;
40
- };
41
-
42
- type EnvelopeEvent = Parameters<typeof encodeEnvelope>[0]["events"][number];
43
-
44
- type ConformanceAdapter = {
45
- name: string;
46
- canonicalJson(value: unknown): string;
47
- intentHash(intent: Intent): string;
48
- evaluateAuthorization(intent: Intent): { authorization: AuthorizationLike; policyId: string };
49
- encodeSnapshot(state: State): { bytes: Uint8Array; policyId: string };
50
- verifySnapshot(bytes: Uint8Array, expectedPolicyId?: string): VerificationResult;
51
- verifyAuditEvents(
52
- events: unknown[],
53
- opts?: { expectedPolicyId?: string; mode?: "strict" | "best-effort"; requireStateAnchors?: boolean }
54
- ): VerificationResult;
55
- verifyEnvelope(bytes: Uint8Array, opts?: {
56
- expectedPolicyId?: string;
57
- mode?: "strict" | "best-effort";
58
- expectedIssuer?: string;
59
- trustedKeySets?: KeySet | readonly KeySet[];
60
- requireSignatureVerification?: boolean;
61
- now?: number;
62
- }): VerificationResult;
63
- verifyAuthorization(auth: AuthorizationV1, opts?: {
64
- now?: number;
65
- expectedIssuer?: string;
66
- expectedAudience?: string;
67
- expectedPolicyId?: string;
68
- consumedAuthIds?: readonly string[];
69
- trustedKeySets?: KeySet | readonly KeySet[];
70
- requireSignatureVerification?: boolean;
71
- legacyHmacSecret?: string;
72
- }): VerificationResult;
73
- };
74
-
75
- const TEST_KEYSET: KeySet = {
76
- issuer: "oxdeai.policy-engine",
77
- version: "1",
78
- keys: [
79
- {
80
- kid: "2026-01",
81
- alg: "Ed25519",
82
- public_key: TEST_ONLY_ED25519_PUBLIC_KEY_PEM_DO_NOT_USE_IN_PRODUCTION
83
- }
84
- ]
85
- };
86
-
87
- const CORE_ENGINE_SECRET = "test-secret";
88
- const CORE_POLICY_ID = "a".repeat(64);
89
- const INTENT_BINDING_FIELDS = [
90
- "intent_id",
91
- "agent_id",
92
- "action_type",
93
- "depth",
94
- "amount",
95
- "asset",
96
- "target",
97
- "timestamp",
98
- "metadata_hash",
99
- "nonce",
100
- "type",
101
- "authorization_id",
102
- "tool",
103
- "tool_call"
104
- ] as const;
105
-
106
- function makeEngine(): PolicyEngine {
107
- return new PolicyEngine({
108
- policy_version: "v1.0.0",
109
- engine_secret: CORE_ENGINE_SECRET,
110
- authorization_ttl_seconds: 60,
111
- policyId: CORE_POLICY_ID
112
- });
113
- }
114
-
115
- function makeBaseState(): State {
116
- return {
117
- policy_version: "v1.0.0",
118
- period_id: "period-1",
119
- kill_switch: { global: false, agents: {} },
120
- allowlists: {},
121
- budget: {
122
- budget_limit: { "agent-1": 5_000_000n },
123
- spent_in_period: { "agent-1": 0n }
124
- },
125
- max_amount_per_action: { "agent-1": 2_000_000n },
126
- velocity: {
127
- config: { window_seconds: 60, max_actions: 10 },
128
- counters: {}
129
- },
130
- replay: {
131
- window_seconds: 300,
132
- max_nonces_per_agent: 256,
133
- nonces: {}
134
- },
135
- concurrency: {
136
- max_concurrent: { "agent-1": 3 },
137
- active: {},
138
- active_auths: {}
139
- },
140
- recursion: {
141
- max_depth: { "agent-1": 5 }
142
- },
143
- tool_limits: {
144
- window_seconds: 60,
145
- max_calls: { "agent-1": 50 },
146
- calls: {}
147
- }
148
- };
149
- }
150
-
151
- function asRecord(value: unknown): JsonRecord {
152
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
153
- throw new Error("expected object");
154
- }
155
- return value as JsonRecord;
156
- }
157
-
158
- function parseIntent(input: unknown): Intent {
159
- const r = asRecord(input);
160
- const maybeType = r.type;
161
- const type = maybeType === "RELEASE" ? "RELEASE" : "EXECUTE";
162
-
163
- const base = {
164
- intent_id: String(r.intent_id),
165
- agent_id: String(r.agent_id),
166
- action_type: String(r.action_type) as Intent["action_type"],
167
- amount: BigInt(String(r.amount)),
168
- asset: r.asset === undefined ? undefined : String(r.asset),
169
- target: String(r.target),
170
- timestamp: Number(r.timestamp),
171
- metadata_hash: String(r.metadata_hash),
172
- nonce: BigInt(String(r.nonce)),
173
- signature: String(r.signature),
174
- depth: r.depth === undefined ? undefined : Number(r.depth),
175
- tool: r.tool === undefined ? undefined : String(r.tool),
176
- tool_call: r.tool_call === undefined ? undefined : Boolean(r.tool_call)
177
- };
178
-
179
- if (type === "RELEASE") {
180
- return {
181
- ...base,
182
- type: "RELEASE",
183
- authorization_id: String(r.authorization_id)
184
- };
185
- }
186
-
187
- return {
188
- ...base,
189
- type: "EXECUTE",
190
- authorization_id: r.authorization_id === undefined ? undefined : String(r.authorization_id)
191
- };
192
- }
193
-
194
- function parseState(input: unknown): State {
195
- const r = asRecord(input);
196
- const budget = asRecord(r.budget);
197
- const budget_limit = asRecord(budget.budget_limit);
198
- const spent_in_period = asRecord(budget.spent_in_period);
199
-
200
- const max_amount_per_action = asRecord(r.max_amount_per_action);
201
-
202
- const toBigintRecord = (rec: JsonRecord): Record<string, bigint> => {
203
- const out: Record<string, bigint> = {};
204
- for (const [k, v] of Object.entries(rec)) out[k] = BigInt(String(v));
205
- return out;
206
- };
207
-
208
- const state = makeBaseState();
209
- state.policy_version = String(r.policy_version);
210
- state.period_id = String(r.period_id);
211
-
212
- state.kill_switch = {
213
- global: Boolean(asRecord(r.kill_switch).global),
214
- agents: asRecord(asRecord(r.kill_switch).agents) as Record<string, boolean>
215
- };
216
- state.allowlists = asRecord(r.allowlists) as State["allowlists"];
217
- state.budget = {
218
- budget_limit: toBigintRecord(budget_limit),
219
- spent_in_period: toBigintRecord(spent_in_period)
220
- };
221
- state.max_amount_per_action = toBigintRecord(max_amount_per_action);
222
- state.velocity = asRecord(r.velocity) as State["velocity"];
223
- state.replay = asRecord(r.replay) as State["replay"];
224
- state.concurrency = asRecord(r.concurrency) as State["concurrency"];
225
- state.recursion = asRecord(r.recursion) as State["recursion"];
226
- state.tool_limits = asRecord(r.tool_limits) as State["tool_limits"];
227
-
228
- return state;
229
- }
230
-
231
- function b64ToBytes(value: string): Uint8Array {
232
- return Uint8Array.from(Buffer.from(value, "base64"));
233
- }
234
-
235
- function hexSha256Utf8(value: string): string {
236
- return createHash("sha256").update(value, "utf8").digest("hex");
237
- }
238
-
239
- function isPlainObject(v: unknown): v is Record<string, unknown> {
240
- return typeof v === "object" && v !== null && !Array.isArray(v);
241
- }
242
-
243
- function canonicalize(value: unknown): unknown {
244
- if (value === undefined) return null;
245
- if (typeof value === "bigint") return value.toString();
246
- if (Array.isArray(value)) return value.map(canonicalize);
247
- if (isPlainObject(value)) {
248
- const out: Record<string, unknown> = {};
249
- for (const k of Object.keys(value).sort()) out[k] = canonicalize(value[k]);
250
- return out;
251
- }
252
- return value;
253
- }
254
-
255
- function canonicalJson(value: unknown): string {
256
- return JSON.stringify(canonicalize(value));
257
- }
258
-
259
- const coreAdapter: ConformanceAdapter = {
260
- name: "@oxdeai/core",
261
- canonicalJson,
262
- intentHash(intent: Intent): string {
263
- const src = intent as unknown as Record<string, unknown>;
264
- const binding: Record<string, unknown> = {};
265
- for (const key of INTENT_BINDING_FIELDS) {
266
- const value = src[key];
267
- if (value !== undefined) binding[key] = value;
268
- }
269
- return sha256HexFromJson(binding);
270
- },
271
- evaluateAuthorization(intent: Intent) {
272
- const engine = makeEngine();
273
- const out = engine.evaluatePure(intent, makeBaseState());
274
- if (out.decision !== "ALLOW") {
275
- throw new Error(`expected ALLOW, got DENY: ${out.reasons.join(",")}`);
276
- }
277
- return { authorization: out.authorization, policyId: engine.computePolicyId() };
278
- },
279
- encodeSnapshot(state: State) {
280
- const engine = makeEngine();
281
- const snapshot = engine.exportState(state);
282
- return { bytes: encodeCanonicalState(snapshot), policyId: engine.computePolicyId() };
283
- },
284
- verifySnapshot(bytes: Uint8Array, expectedPolicyId?: string): VerificationResult {
285
- return verifySnapshot(bytes, expectedPolicyId ? { expectedPolicyId } : undefined);
286
- },
287
- verifyAuditEvents(events, opts) {
288
- return verifyAuditEvents(events as any, opts);
289
- },
290
- verifyEnvelope,
291
- verifyAuthorization
292
- };
293
-
294
- function loadJson<T>(name: string): T {
295
- const p = resolve(process.cwd(), "vectors", name);
296
- return JSON.parse(readFileSync(p, "utf8")) as T;
297
- }
298
-
299
- type VectorFile = { version: string; vectors: Array<JsonRecord> };
300
-
301
- type CheckCtx = {
302
- failures: string[];
303
- passed: number;
304
- };
305
-
306
- function pass(ctx: CheckCtx, msg: string): void {
307
- ctx.passed += 1;
308
- console.log(`PASS ${msg}`);
309
- }
310
-
311
- function fail(ctx: CheckCtx, msg: string): void {
312
- ctx.failures.push(msg);
313
- console.error(`FAIL ${msg}`);
314
- }
315
-
316
- function eq(ctx: CheckCtx, label: string, actual: unknown, expected: unknown): void {
317
- if (JSON.stringify(actual) !== JSON.stringify(expected)) {
318
- fail(ctx, `${label}: expected=${JSON.stringify(expected)} actual=${JSON.stringify(actual)}`);
319
- } else {
320
- pass(ctx, label);
321
- }
322
- }
323
-
324
- function validateIntentHashVectors(ctx: CheckCtx, adapter: ConformanceAdapter): void {
325
- const file = loadJson<VectorFile>("intent-hash.json");
326
- const vectors = file.vectors;
327
- const hashes: Record<string, string> = {};
328
-
329
- for (const v of vectors) {
330
- const id = String(v.id);
331
- const intent = parseIntent(v.input);
332
- const got = adapter.intentHash(intent);
333
- const want = String(asRecord(v.expected).hash);
334
- hashes[id] = got;
335
- eq(ctx, `${id} hash`, got, want);
336
- }
337
-
338
- eq(ctx, "intent-hash-002 invariant", hashes["intent-hash-002"], hashes["intent-hash-001"]);
339
- eq(ctx, "intent-hash-003 invariant", hashes["intent-hash-003"], hashes["intent-hash-001"]);
340
- }
341
-
342
- function validateAuthorizationVectors(ctx: CheckCtx, adapter: ConformanceAdapter): void {
343
- const file = loadJson<VectorFile>("authorization-payload.json");
344
-
345
- for (const v of file.vectors) {
346
- const id = String(v.id);
347
- const intent = parseIntent(v.input);
348
- const { authorization, policyId } = adapter.evaluateAuthorization(intent);
349
- const expected = asRecord(v.expected);
350
-
351
- eq(ctx, `${id} intent_hash`, authorization.intent_hash, String(expected.intent_hash));
352
- if (expected.state_hash !== undefined) {
353
- eq(ctx, `${id} state_hash`, authorization.state_snapshot_hash, String(expected.state_hash));
354
- }
355
- eq(ctx, `${id} expires_at`, authorization.expires_at, Number(expected.expires_at));
356
-
357
- const payload = adapter.canonicalJson({
358
- expires_at: authorization.expires_at,
359
- intent_hash: authorization.intent_hash,
360
- policy_id: policyId,
361
- state_hash: authorization.state_snapshot_hash
362
- });
363
- eq(ctx, `${id} canonical_signing_payload`, payload, String(expected.canonical_signing_payload));
364
- eq(ctx, `${id} signature`, authorization.engine_signature, String(expected.signature));
365
- }
366
- }
367
-
368
- function validateAuthorizationVerificationVectors(ctx: CheckCtx, adapter: ConformanceAdapter): void {
369
- const file = loadJson<VectorFile>("authorization-verification.json");
370
-
371
- for (const v of file.vectors) {
372
- const id = String(v.id);
373
- const input = asRecord(v.input);
374
- const auth = {
375
- alg: "HMAC-SHA256",
376
- kid: "legacy",
377
- signature: "legacy-placeholder",
378
- ...(asRecord(input.auth) as Record<string, unknown>)
379
- } as AuthorizationV1;
380
- const opts = (input.opts ? asRecord(input.opts) : {}) as {
381
- now?: number;
382
- expectedIssuer?: string;
383
- expectedAudience?: string;
384
- expectedPolicyId?: string;
385
- consumedAuthIds?: readonly string[];
386
- };
387
- const expected = asRecord(v.expected);
388
- const got = adapter.verifyAuthorization(auth, opts);
389
-
390
- eq(ctx, `${id} status`, got.status, String(expected.status));
391
- eq(ctx, `${id} violations`, got.violations, expected.violations ?? []);
392
- }
393
- }
394
-
395
- function makeSignedAuthorizationBase(now = 1730000000): AuthorizationV1 {
396
- return signAuthorizationEd25519(
397
- {
398
- auth_id: "f".repeat(64),
399
- issuer: "oxdeai.policy-engine",
400
- audience: "merchant-gateway",
401
- intent_hash: "a".repeat(64),
402
- state_hash: "b".repeat(64),
403
- policy_id: "c".repeat(64),
404
- decision: "ALLOW",
405
- issued_at: now,
406
- expiry: now + 60,
407
- kid: "2026-01"
408
- },
409
- TEST_ONLY_ED25519_PRIVATE_KEY_PEM_DO_NOT_USE_IN_PRODUCTION
410
- );
411
- }
412
-
413
- function validateAuthorizationSignatureVectors(ctx: CheckCtx, adapter: ConformanceAdapter): void {
414
- const file = loadJson<VectorFile>("authorization-signature-verification.json");
415
-
416
- for (const v of file.vectors) {
417
- const id = String(v.id);
418
- const mode = String(v.mode);
419
- const expected = asRecord(v.expected);
420
- let auth = makeSignedAuthorizationBase();
421
- const opts: Parameters<ConformanceAdapter["verifyAuthorization"]>[1] = {
422
- now: 1730000010,
423
- expectedIssuer: "oxdeai.policy-engine",
424
- expectedAudience: "merchant-gateway",
425
- expectedPolicyId: "c".repeat(64),
426
- trustedKeySets: TEST_KEYSET,
427
- requireSignatureVerification: true,
428
- consumedAuthIds: []
429
- };
430
-
431
- if (mode === "invalid-signature") {
432
- auth = { ...auth, signature: `${auth.signature.slice(0, -2)}aa` };
433
- } else if (mode === "wrong-kid") {
434
- auth = { ...auth, kid: "unknown-kid" };
435
- } else if (mode === "wrong-issuer") {
436
- auth = { ...auth, issuer: "other-issuer" };
437
- } else if (mode === "wrong-audience") {
438
- const { signature: _sig, alg: _alg, ...unsigned } = auth;
439
- auth = signAuthorizationEd25519(
440
- {
441
- ...unsigned,
442
- audience: "other-audience"
443
- },
444
- TEST_ONLY_ED25519_PRIVATE_KEY_PEM_DO_NOT_USE_IN_PRODUCTION
445
- );
446
- opts.expectedAudience = "merchant-gateway";
447
- } else if (mode === "tampered-field") {
448
- auth = { ...auth, state_hash: "d".repeat(64) };
449
- } else if (mode === "expired") {
450
- const { signature: _sig, alg: _alg, ...unsigned } = auth;
451
- auth = signAuthorizationEd25519(
452
- {
453
- ...unsigned,
454
- issued_at: 100,
455
- expiry: 110
456
- },
457
- TEST_ONLY_ED25519_PRIVATE_KEY_PEM_DO_NOT_USE_IN_PRODUCTION
458
- );
459
- opts.now = 120;
460
- } else if (mode === "replay") {
461
- opts.consumedAuthIds = [auth.auth_id];
462
- } else if (mode === "unknown-alg") {
463
- auth = { ...auth, alg: "Unknown" as any };
464
- }
465
-
466
- const got = adapter.verifyAuthorization(auth, opts);
467
- eq(ctx, `${id} status`, got.status, String(expected.status));
468
- eq(ctx, `${id} violations`, got.violations, expected.violations ?? []);
469
- }
470
- }
471
-
472
- function validateSnapshotVectors(ctx: CheckCtx, adapter: ConformanceAdapter): void {
473
- const file = loadJson<VectorFile>("snapshot-hash.json");
474
-
475
- for (const v of file.vectors) {
476
- const id = String(v.id);
477
- const expected = asRecord(v.expected);
478
-
479
- if (v.input_state !== undefined) {
480
- const state = parseState(v.input_state);
481
- const { bytes, policyId } = adapter.encodeSnapshot(state);
482
- const out = adapter.verifySnapshot(bytes, policyId);
483
- if (out.status !== "ok" || !out.stateHash) {
484
- fail(ctx, `${id} verifySnapshot expected ok`);
485
- continue;
486
- }
487
- eq(ctx, `${id} snapshot_base64`, Buffer.from(bytes).toString("base64"), String(expected.snapshot_base64));
488
- eq(ctx, `${id} state_hash`, out.stateHash, String(expected.state_hash));
489
- } else {
490
- const bytes = b64ToBytes(String(v.input_snapshot_base64));
491
- const out = adapter.verifySnapshot(bytes, CORE_POLICY_ID);
492
- if (out.status !== "ok" || !out.stateHash) {
493
- fail(ctx, `${id} verifySnapshot expected ok`);
494
- continue;
495
- }
496
- eq(ctx, `${id} state_hash`, out.stateHash, String(expected.state_hash));
497
- }
498
- }
499
- }
500
-
501
- function validateAuditChainVectors(ctx: CheckCtx, adapter: ConformanceAdapter): void {
502
- const file = loadJson<VectorFile>("audit-chain.json");
503
- const [v1, v2, v3, v4] = file.vectors;
504
-
505
- const genesis = hexSha256Utf8(String(v1.input));
506
- eq(ctx, "audit-chain-001 genesis", genesis, String(asRecord(v1.expected).genesis_hex));
507
-
508
- const i2 = asRecord(v2.input);
509
- const e0 = i2.event_0;
510
- const head1 = hexSha256Utf8(`${String(i2.head_0)}\n${adapter.canonicalJson(e0)}`);
511
- eq(ctx, "audit-chain-002 head_1", head1, String(asRecord(v2.expected).head_1));
512
-
513
- const i3 = asRecord(v3.input);
514
- const events = (i3.events as unknown[]) ?? [];
515
- let head = String(i3.genesis);
516
- const heads: string[] = [];
517
- for (const ev of events) {
518
- head = hexSha256Utf8(`${head}\n${adapter.canonicalJson(ev)}`);
519
- heads.push(head);
520
- }
521
- const e3 = asRecord(v3.expected);
522
- eq(ctx, "audit-chain-003 head_1", heads[0], String(e3.head_1));
523
- eq(ctx, "audit-chain-003 head_2", heads[1], String(e3.head_2));
524
- eq(ctx, "audit-chain-003 head_3", heads[2], String(e3.head_3));
525
-
526
- const i4 = asRecord(v4.input);
527
- const mut = i4.mutated_event_0;
528
- const mutHead1 = hexSha256Utf8(`${String(i4.head_0)}\n${adapter.canonicalJson(mut)}`);
529
- const e4 = asRecord(v4.expected);
530
- eq(ctx, "audit-chain-004 original_head_1", String(e4.original_head_1), String(e4.original_head_1));
531
- eq(ctx, "audit-chain-004 mutated_head_1", mutHead1, String(e4.mutated_head_1));
532
- eq(ctx, "audit-chain-004 must differ", mutHead1 !== String(e4.original_head_1), true);
533
- }
534
-
535
- function buildEnvelopeCases(adapter: ConformanceAdapter): Array<{ status: string; violations: unknown[]; policyId?: string; stateHash?: string; auditHeadHash?: string }> {
536
- const engine = makeEngine();
537
- const state = makeBaseState();
538
- const intent = parseIntent({
539
- intent_id: "intent-300",
540
- agent_id: "agent-1",
541
- action_type: "PAYMENT",
542
- amount: "1000000",
543
- target: "merchant-1",
544
- timestamp: 1730000000,
545
- metadata_hash: "0".repeat(64),
546
- nonce: "300",
547
- signature: "sig-placeholder",
548
- depth: 0,
549
- tool: "openai.responses",
550
- tool_call: true,
551
- type: "EXECUTE"
552
- });
553
-
554
- const out = engine.evaluatePure(intent, state);
555
- if (out.decision !== "ALLOW") throw new Error("expected ALLOW for envelope cases");
556
-
557
- const events = engine.audit.snapshot();
558
- const snapshotBytes = encodeCanonicalState(engine.exportState(state));
559
- const snap = adapter.verifySnapshot(snapshotBytes, engine.computePolicyId());
560
- if (snap.status !== "ok" || !snap.stateHash) throw new Error("failed to build envelope cases");
561
-
562
- const valid = encodeEnvelope({ formatVersion: 1, snapshot: snapshotBytes, events });
563
- const withCheckpointEvents: EnvelopeEvent[] = [
564
- ...events,
565
- {
566
- type: "STATE_CHECKPOINT" as const,
567
- stateHash: snap.stateHash,
568
- timestamp: intent.timestamp,
569
- policyId: engine.computePolicyId()
570
- }
571
- ];
572
- const withCheckpoint = encodeEnvelope({
573
- formatVersion: 1,
574
- snapshot: snapshotBytes,
575
- events: withCheckpointEvents
576
- });
577
- const mismatchedEvents: EnvelopeEvent[] = events.map((e) => ({
578
- ...e,
579
- policyId: "c".repeat(64)
580
- }));
581
- const mismatched = encodeEnvelope({
582
- formatVersion: 1,
583
- snapshot: snapshotBytes,
584
- events: mismatchedEvents
585
- });
586
- const corrupt = new Uint8Array([1, 2, 3, 4, 5]);
587
-
588
- return [
589
- adapter.verifyEnvelope(withCheckpoint, { expectedPolicyId: engine.computePolicyId(), mode: "strict" }),
590
- adapter.verifyEnvelope(valid, { expectedPolicyId: engine.computePolicyId(), mode: "best-effort" }),
591
- adapter.verifyEnvelope(mismatched, { expectedPolicyId: engine.computePolicyId(), mode: "best-effort" }),
592
- adapter.verifyEnvelope(corrupt, { expectedPolicyId: engine.computePolicyId(), mode: "best-effort" }),
593
- adapter.verifyEnvelope(valid, { expectedPolicyId: engine.computePolicyId(), mode: "strict" })
594
- ];
595
- }
596
-
597
- function validateEnvelopeVectors(ctx: CheckCtx, adapter: ConformanceAdapter): void {
598
- const file = loadJson<VectorFile>("envelope-verification.json");
599
- const actual = buildEnvelopeCases(adapter);
600
-
601
- for (let i = 0; i < file.vectors.length; i++) {
602
- const id = String(file.vectors[i].id);
603
- const expected = asRecord(file.vectors[i].expected);
604
- const got = actual[i];
605
-
606
- eq(ctx, `${id} status`, got.status, String(expected.status));
607
- if (expected.policyId !== undefined) eq(ctx, `${id} policyId`, got.policyId, String(expected.policyId));
608
- if (expected.stateHash !== undefined) eq(ctx, `${id} stateHash`, got.stateHash, String(expected.stateHash));
609
- if (expected.auditHeadHash !== undefined) eq(ctx, `${id} auditHeadHash`, got.auditHeadHash, String(expected.auditHeadHash));
610
- eq(ctx, `${id} violations`, got.violations, expected.violations ?? []);
611
- }
612
- }
613
-
614
- function validateEnvelopeSignatureVectors(ctx: CheckCtx, adapter: ConformanceAdapter): void {
615
- const file = loadJson<VectorFile>("envelope-signature-verification.json");
616
- const engine = makeEngine();
617
- const state = makeBaseState();
618
- const bytes = encodeCanonicalState(engine.exportState(state));
619
- const policyId = engine.computePolicyId();
620
- const events = [
621
- {
622
- type: "INTENT_RECEIVED" as const,
623
- intent_hash: "11".repeat(32),
624
- agent_id: "agent-1",
625
- timestamp: 100,
626
- policyId
627
- },
628
- {
629
- type: "STATE_CHECKPOINT" as const,
630
- stateHash: verifySnapshot(bytes, { expectedPolicyId: policyId }).stateHash!,
631
- timestamp: 101,
632
- policyId
633
- }
634
- ];
635
- const signed = signEnvelopeEd25519(
636
- {
637
- formatVersion: 1,
638
- snapshot: bytes,
639
- events
640
- },
641
- { issuer: "oxdeai.policy-engine", kid: "2026-01", privateKeyPem: TEST_ONLY_ED25519_PRIVATE_KEY_PEM_DO_NOT_USE_IN_PRODUCTION }
642
- );
643
-
644
- for (const v of file.vectors) {
645
- const id = String(v.id);
646
- const mode = String(v.mode);
647
- const expected = asRecord(v.expected);
648
- let env = structuredClone(signed);
649
- const opts: Parameters<ConformanceAdapter["verifyEnvelope"]>[1] & {
650
- trustedKeySets?: KeySet;
651
- expectedIssuer?: string;
652
- requireSignatureVerification?: boolean;
653
- now?: number;
654
- } = {
655
- mode: "strict",
656
- expectedPolicyId: policyId,
657
- expectedIssuer: "oxdeai.policy-engine",
658
- trustedKeySets: TEST_KEYSET,
659
- requireSignatureVerification: true,
660
- now: 1730000010
661
- };
662
-
663
- if (mode === "tampered-envelope") {
664
- env = { ...env, events: [...env.events, { type: "DECISION", intent_hash: "11".repeat(32), decision: "ALLOW", reasons: [], policy_version: "v1", timestamp: 102, policyId }] as any };
665
- } else if (mode === "unknown-alg") {
666
- env = { ...env, alg: "Unknown" as any };
667
- } else if (mode === "unknown-kid") {
668
- env = { ...env, kid: "missing-kid" };
669
- } else if (mode === "malformed-signature") {
670
- env = { ...env, signature: "!!!not-base64!!!" };
671
- }
672
-
673
- const wire = encodeEnvelope(env as any);
674
- const got = adapter.verifyEnvelope(wire, opts as any);
675
- eq(ctx, `${id} status`, got.status, String(expected.status));
676
- eq(ctx, `${id} violations`, got.violations, expected.violations ?? []);
677
- }
678
- }
679
-
680
- function buildAuditVerificationCases(
681
- adapter: ConformanceAdapter
682
- ): Array<{ status: string; violations: unknown[] }> {
683
- const expectedPolicyId = "a".repeat(64);
684
- const mismatchPolicyId = "b".repeat(64);
685
- const altPolicyId = "c".repeat(64);
686
-
687
- const policyMismatch = adapter.verifyAuditEvents(
688
- [
689
- {
690
- type: "INTENT_RECEIVED",
691
- intent_hash: "11".repeat(32),
692
- agent_id: "agent-1",
693
- timestamp: 100,
694
- policyId: mismatchPolicyId
695
- }
696
- ],
697
- { expectedPolicyId, mode: "best-effort" }
698
- );
699
-
700
- const nonMonotonic = adapter.verifyAuditEvents(
701
- [
702
- {
703
- type: "INTENT_RECEIVED",
704
- intent_hash: "22".repeat(32),
705
- agent_id: "agent-1",
706
- timestamp: 200,
707
- policyId: expectedPolicyId
708
- },
709
- {
710
- type: "DECISION",
711
- intent_hash: "22".repeat(32),
712
- decision: "ALLOW",
713
- reasons: [],
714
- policy_version: "v1",
715
- timestamp: 100,
716
- policyId: expectedPolicyId
717
- }
718
- ],
719
- { mode: "best-effort" }
720
- );
721
-
722
- const mixedPolicy = adapter.verifyAuditEvents(
723
- [
724
- {
725
- type: "INTENT_RECEIVED",
726
- intent_hash: "33".repeat(32),
727
- agent_id: "agent-1",
728
- timestamp: 100,
729
- policyId: expectedPolicyId
730
- },
731
- {
732
- type: "DECISION",
733
- intent_hash: "33".repeat(32),
734
- decision: "ALLOW",
735
- reasons: [],
736
- policy_version: "v1",
737
- timestamp: 100,
738
- policyId: mismatchPolicyId
739
- }
740
- ],
741
- { mode: "best-effort" }
742
- );
743
-
744
- const strictMissingAnchor = adapter.verifyAuditEvents(
745
- [
746
- {
747
- type: "INTENT_RECEIVED",
748
- intent_hash: "44".repeat(32),
749
- agent_id: "agent-1",
750
- timestamp: 100,
751
- policyId: expectedPolicyId
752
- },
753
- {
754
- type: "DECISION",
755
- intent_hash: "44".repeat(32),
756
- decision: "ALLOW",
757
- reasons: [],
758
- policy_version: "v1",
759
- timestamp: 100,
760
- policyId: expectedPolicyId
761
- }
762
- ],
763
- { mode: "strict" }
764
- );
765
-
766
- const orderingCase = adapter.verifyAuditEvents(
767
- [
768
- {
769
- type: "INTENT_RECEIVED",
770
- intent_hash: "55".repeat(32),
771
- agent_id: "agent-1",
772
- timestamp: 300,
773
- policyId: altPolicyId
774
- },
775
- {
776
- type: "DECISION",
777
- intent_hash: "55".repeat(32),
778
- decision: "ALLOW",
779
- reasons: [],
780
- policy_version: "v1",
781
- timestamp: 100,
782
- policyId: mismatchPolicyId
783
- }
784
- ],
785
- { expectedPolicyId, mode: "strict" }
786
- );
787
-
788
- return [policyMismatch, nonMonotonic, mixedPolicy, strictMissingAnchor, orderingCase];
789
- }
790
-
791
- function validateAuditVerificationVectors(ctx: CheckCtx, adapter: ConformanceAdapter): void {
792
- const file = loadJson<VectorFile>("audit-verification.json");
793
- const actual = buildAuditVerificationCases(adapter);
794
-
795
- for (let i = 0; i < file.vectors.length; i++) {
796
- const id = String(file.vectors[i].id);
797
- const expected = asRecord(file.vectors[i].expected);
798
- const got = actual[i];
799
- eq(ctx, `${id} status`, got.status, String(expected.status));
800
- eq(ctx, `${id} violations`, got.violations, expected.violations ?? []);
801
- }
802
- }
803
-
804
- function main(): void {
805
- const ctx: CheckCtx = { failures: [], passed: 0 };
806
- const adapter = coreAdapter;
807
-
808
- console.log(`Running conformance validation with ${adapter.name}`);
809
-
810
- validateIntentHashVectors(ctx, adapter);
811
- validateAuthorizationVectors(ctx, adapter);
812
- validateAuthorizationVerificationVectors(ctx, adapter);
813
- validateAuthorizationSignatureVectors(ctx, adapter);
814
- validateSnapshotVectors(ctx, adapter);
815
- validateAuditChainVectors(ctx, adapter);
816
- validateAuditVerificationVectors(ctx, adapter);
817
- validateEnvelopeVectors(ctx, adapter);
818
- validateEnvelopeSignatureVectors(ctx, adapter);
819
-
820
- if (ctx.failures.length > 0) {
821
- console.error(`\nConformance failed: ${ctx.failures.length} assertion(s)`);
822
- process.exit(1);
823
- }
824
-
825
- console.log(`\nConformance passed: ${ctx.passed} assertions`);
826
- }
827
-
828
- main();