@oxdeai/sdk 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,27 @@ This project follows Semantic Versioning.
7
7
 
8
8
  ---
9
9
 
10
+ ## [1.3.0] - 2026-03-08
11
+
12
+ ### Added
13
+
14
+ - Stable public guard API for callback-boundary enforcement (`createGuard`).
15
+ - Guard-focused test coverage for allow/deny execution behavior and authorization enforcement.
16
+
17
+ ### Changed
18
+
19
+ - SDK integration documentation updated for the v1.3 adoption layer.
20
+ - Protocol-stack version alignment with:
21
+ - `@oxdeai/core@1.3.0`
22
+ - `@oxdeai/conformance@1.3.0`
23
+
24
+ ### Notes
25
+
26
+ - `@oxdeai/cli` remains on a separate tooling version line.
27
+ - No intentional protocol semantic break from `1.2.x`.
28
+
29
+ ---
30
+
10
31
  ## [1.2.0] - 2026-03-08
11
32
 
12
33
  ### Added
package/README.md CHANGED
@@ -2,18 +2,47 @@
2
2
 
3
3
  Developer-facing integration layer on top of `@oxdeai/core`.
4
4
 
5
+ ## Status
6
+
7
+ Current protocol stack line: **v1.3.x** (`@oxdeai/core`, `@oxdeai/sdk`, `@oxdeai/conformance`).
8
+
9
+ The SDK is an integration surface and does not redefine protocol semantics.
10
+
5
11
  ## What It Adds
6
12
 
7
13
  - Intent/state builder helpers
8
14
  - Typed client wrapper for common flow: evaluate + persist + verify
15
+ - Public guard API for callback-boundary enforcement (`createGuard`)
9
16
  - Runtime adapters (in-memory and file-based)
10
17
 
18
+ ## Guard API (v1.3 adoption layer)
19
+
20
+ The SDK exposes a framework-agnostic guard boundary:
21
+
22
+ ```ts
23
+ const guard = createGuard({ engine, stateAdapter, auditAdapter, clock });
24
+ const result = await guard(intent, async ({ authorization }) => {
25
+ return executeTool(authorization);
26
+ });
27
+ ```
28
+
29
+ The callback runs only when OxDeAI returns `ALLOW` and authorization enforcement passes.
30
+ On `DENY` (or failed auth verification), the callback is not executed.
31
+
32
+ This keeps PDP/PEP separation explicit:
33
+
34
+ - PDP: `PolicyEngine.evaluatePure(...)` decides
35
+ - PEP: guard callback boundary enforces execute-or-refuse
36
+
37
+ OxDeAI sits below agent frameworks (OpenAI tools, LangGraph, others) as the deterministic authorization boundary.
38
+
11
39
  ## Quick Example
12
40
 
13
41
  ```ts
14
42
  import { PolicyEngine } from "@oxdeai/core";
15
43
  import {
16
44
  OxDeAIClient,
45
+ createGuard,
17
46
  buildState,
18
47
  buildIntent,
19
48
  InMemoryStateAdapter,
@@ -54,6 +83,18 @@ const intent = buildIntent({
54
83
  });
55
84
 
56
85
  const result = await client.evaluateAndCommit(intent);
86
+
87
+ const guard = createGuard({
88
+ engine,
89
+ stateAdapter,
90
+ auditAdapter,
91
+ clock: { now: () => 1770000000 }
92
+ });
93
+
94
+ await guard(intent, async () => {
95
+ // execute side effect only when ALLOW + auth checks pass
96
+ return { ok: true };
97
+ });
57
98
  ```
58
99
 
59
100
  ## Main Exports
@@ -0,0 +1,50 @@
1
+ import type { Intent, PolicyEngine, State, Authorization } from "@oxdeai/core";
2
+ import type { AuditAdapter, ClockAdapter, MaybePromise, StateAdapter, IntentBuilderInput } from "./types.js";
3
+ export type GuardDecision = {
4
+ decision: "ALLOW";
5
+ reasons: [];
6
+ authorization: Authorization;
7
+ } | {
8
+ decision: "DENY";
9
+ reasons: string[];
10
+ };
11
+ export type GuardAllowResult<T> = {
12
+ output: GuardDecision & {
13
+ decision: "ALLOW";
14
+ reasons: [];
15
+ authorization: Authorization;
16
+ };
17
+ intent: Intent;
18
+ state: State;
19
+ auditEvents: unknown[];
20
+ executed: true;
21
+ executionResult: T;
22
+ };
23
+ export type GuardDenyResult = {
24
+ output: GuardDecision & {
25
+ decision: "DENY";
26
+ reasons: string[];
27
+ };
28
+ intent: Intent;
29
+ state: State;
30
+ auditEvents: unknown[];
31
+ executed: false;
32
+ };
33
+ export type GuardResult<T> = GuardAllowResult<T> | GuardDenyResult;
34
+ export type GuardInputIntent = Intent | IntentBuilderInput;
35
+ export type GuardOptions = {
36
+ engine: PolicyEngine;
37
+ stateAdapter: StateAdapter;
38
+ auditAdapter?: AuditAdapter;
39
+ clock?: ClockAdapter;
40
+ mode?: "fail-fast" | "collect-all";
41
+ verifyAuthorization?: boolean;
42
+ };
43
+ type GuardExecuteContext = {
44
+ intent: Intent;
45
+ authorization: Authorization;
46
+ state: State;
47
+ };
48
+ export type GuardFn = <T>(input: GuardInputIntent, execute: (ctx: GuardExecuteContext) => MaybePromise<T>) => Promise<GuardResult<T>>;
49
+ export declare function createGuard(opts: GuardOptions): GuardFn;
50
+ export {};
package/dist/guard.js ADDED
@@ -0,0 +1,77 @@
1
+ import { buildIntent } from "./builders.js";
2
+ function collectNewEvents(engine, cursor) {
3
+ const all = engine.audit.snapshot();
4
+ return { nextCursor: all.length, events: all.slice(cursor) };
5
+ }
6
+ function isIntent(value) {
7
+ return (typeof value.intent_id === "string" &&
8
+ typeof value.agent_id === "string" &&
9
+ typeof value.action_type === "string" &&
10
+ typeof value.signature === "string");
11
+ }
12
+ export function createGuard(opts) {
13
+ const clock = opts.clock ?? { now: () => Math.floor(Date.now() / 1000) };
14
+ let auditCursor = 0;
15
+ return async function guard(input, execute) {
16
+ const state = await opts.stateAdapter.load();
17
+ const intent = isIntent(input)
18
+ ? {
19
+ ...input,
20
+ timestamp: input.timestamp === 0 ? clock.now() : input.timestamp,
21
+ }
22
+ : buildIntent({
23
+ ...input,
24
+ timestamp: input.timestamp ?? clock.now(),
25
+ });
26
+ const out = opts.engine.evaluatePure(intent, state, { mode: opts.mode ?? "fail-fast" });
27
+ const emitted = collectNewEvents(opts.engine, auditCursor);
28
+ auditCursor = emitted.nextCursor;
29
+ if (opts.auditAdapter)
30
+ await opts.auditAdapter.append(emitted.events);
31
+ if (out.decision === "DENY") {
32
+ return {
33
+ output: { decision: "DENY", reasons: out.reasons },
34
+ intent,
35
+ state,
36
+ auditEvents: emitted.events,
37
+ executed: false,
38
+ };
39
+ }
40
+ if (!out.authorization || !out.nextState) {
41
+ return {
42
+ output: { decision: "DENY", reasons: ["PEP_INVARIANT_VIOLATION"] },
43
+ intent,
44
+ state,
45
+ auditEvents: emitted.events,
46
+ executed: false,
47
+ };
48
+ }
49
+ if (opts.verifyAuthorization !== false) {
50
+ const authCheck = opts.engine.verifyAuthorization(intent, out.authorization, out.nextState, intent.timestamp);
51
+ if (!authCheck.valid) {
52
+ return {
53
+ output: { decision: "DENY", reasons: [`AUTH_INVALID:${authCheck.reason ?? "unknown"}`] },
54
+ intent,
55
+ state,
56
+ auditEvents: emitted.events,
57
+ executed: false,
58
+ };
59
+ }
60
+ }
61
+ const executionResult = await execute({
62
+ intent,
63
+ authorization: out.authorization,
64
+ state: out.nextState,
65
+ });
66
+ await opts.stateAdapter.save(out.nextState);
67
+ return {
68
+ output: { decision: "ALLOW", reasons: [], authorization: out.authorization },
69
+ intent,
70
+ state: out.nextState,
71
+ auditEvents: emitted.events,
72
+ executed: true,
73
+ executionResult,
74
+ };
75
+ };
76
+ }
77
+ //# sourceMappingURL=guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.js","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,139 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { PolicyEngine } from "@oxdeai/core";
4
+ import { InMemoryAuditAdapter, InMemoryStateAdapter } from "./adapters.js";
5
+ import { buildIntent, buildState } from "./builders.js";
6
+ import { createGuard } from "./guard.js";
7
+ function makeEngine() {
8
+ return new PolicyEngine({
9
+ policy_version: "v1",
10
+ engine_secret: "dev-secret",
11
+ authorization_ttl_seconds: 60,
12
+ authorization_issuer: "issuer-A",
13
+ authorization_audience: "rp-A",
14
+ policyId: "a".repeat(64),
15
+ });
16
+ }
17
+ function makeAllowState() {
18
+ return buildState({
19
+ policy_version: "v1",
20
+ agent_id: "agent-1",
21
+ allow_action_types: ["PROVISION"],
22
+ allow_targets: ["us-east-1"],
23
+ budget_limit: 1000n,
24
+ spent_in_period: 0n,
25
+ max_amount_per_action: 500n,
26
+ });
27
+ }
28
+ test("guard executes callback on ALLOW", async () => {
29
+ const engine = makeEngine();
30
+ const stateAdapter = new InMemoryStateAdapter(makeAllowState());
31
+ const auditAdapter = new InMemoryAuditAdapter();
32
+ const guard = createGuard({
33
+ engine,
34
+ stateAdapter,
35
+ auditAdapter,
36
+ clock: { now: () => 1_770_000_000 },
37
+ });
38
+ let called = 0;
39
+ const result = await guard(buildIntent({
40
+ intent_id: "intent-allow-1",
41
+ agent_id: "agent-1",
42
+ action_type: "PROVISION",
43
+ amount: 500n,
44
+ target: "us-east-1",
45
+ nonce: 1n,
46
+ }), async () => {
47
+ called++;
48
+ return "ok";
49
+ });
50
+ assert.equal(called, 1);
51
+ assert.equal(result.executed, true);
52
+ assert.equal(result.output.decision, "ALLOW");
53
+ if (result.executed)
54
+ assert.equal(result.executionResult, "ok");
55
+ assert.ok(auditAdapter.snapshot().length > 0);
56
+ });
57
+ test("guard does not execute callback on DENY", async () => {
58
+ const engine = makeEngine();
59
+ const stateAdapter = new InMemoryStateAdapter(makeAllowState());
60
+ const guard = createGuard({
61
+ engine,
62
+ stateAdapter,
63
+ clock: { now: () => 1_770_000_000 },
64
+ });
65
+ let called = 0;
66
+ const result = await guard(buildIntent({
67
+ intent_id: "intent-deny-1",
68
+ agent_id: "agent-1",
69
+ action_type: "PROVISION",
70
+ amount: 900n,
71
+ target: "us-east-1",
72
+ nonce: 2n,
73
+ }), async () => {
74
+ called++;
75
+ return "should-not-run";
76
+ });
77
+ assert.equal(called, 0);
78
+ assert.equal(result.executed, false);
79
+ assert.equal(result.output.decision, "DENY");
80
+ });
81
+ test("guard enforces authorization verification at callback boundary", async () => {
82
+ const engine = makeEngine();
83
+ const original = engine.verifyAuthorization.bind(engine);
84
+ engine.verifyAuthorization = () => ({ valid: false, reason: "forced-test-failure" });
85
+ const stateAdapter = new InMemoryStateAdapter(makeAllowState());
86
+ const guard = createGuard({
87
+ engine,
88
+ stateAdapter,
89
+ clock: { now: () => 1_770_000_000 },
90
+ });
91
+ let called = 0;
92
+ const result = await guard(buildIntent({
93
+ intent_id: "intent-allow-auth-check",
94
+ agent_id: "agent-1",
95
+ action_type: "PROVISION",
96
+ amount: 500n,
97
+ target: "us-east-1",
98
+ nonce: 3n,
99
+ }), async () => {
100
+ called++;
101
+ return "should-not-run";
102
+ });
103
+ assert.equal(called, 0);
104
+ assert.equal(result.executed, false);
105
+ assert.equal(result.output.decision, "DENY");
106
+ assert.ok(result.output.reasons.some((r) => r.startsWith("AUTH_INVALID:")));
107
+ engine.verifyAuthorization = original;
108
+ });
109
+ test("guard output is deterministic for equivalent inputs", async () => {
110
+ const intent = buildIntent({
111
+ intent_id: "intent-deterministic-1",
112
+ agent_id: "agent-1",
113
+ action_type: "PROVISION",
114
+ amount: 500n,
115
+ target: "us-east-1",
116
+ nonce: 10n,
117
+ timestamp: 1_770_000_000,
118
+ });
119
+ const g1 = createGuard({
120
+ engine: makeEngine(),
121
+ stateAdapter: new InMemoryStateAdapter(makeAllowState()),
122
+ clock: { now: () => 1_770_000_000 },
123
+ });
124
+ const g2 = createGuard({
125
+ engine: makeEngine(),
126
+ stateAdapter: new InMemoryStateAdapter(makeAllowState()),
127
+ clock: { now: () => 1_770_000_000 },
128
+ });
129
+ const r1 = await g1(intent, async () => "ok");
130
+ const r2 = await g2(intent, async () => "ok");
131
+ assert.equal(r1.output.decision, "ALLOW");
132
+ assert.equal(r2.output.decision, "ALLOW");
133
+ if (r1.output.decision === "ALLOW" && r2.output.decision === "ALLOW") {
134
+ assert.equal(r1.output.authorization.auth_id, r2.output.authorization.auth_id);
135
+ assert.equal(r1.output.authorization.state_hash, r2.output.authorization.state_hash);
136
+ assert.equal(r1.output.authorization.intent_hash, r2.output.authorization.intent_hash);
137
+ }
138
+ });
139
+ //# sourceMappingURL=guard.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.test.js","sourceRoot":"","sources":["../src/guard.test.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -4,3 +4,4 @@ export * from "./types.js";
4
4
  export * from "./builders.js";
5
5
  export * from "./adapters.js";
6
6
  export * from "./client.js";
7
+ export * from "./guard.js";
package/dist/index.js CHANGED
@@ -4,4 +4,5 @@ export * from "./types.js";
4
4
  export * from "./builders.js";
5
5
  export * from "./adapters.js";
6
6
  export * from "./client.js";
7
+ export * from "./guard.js";
7
8
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxdeai/sdk",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -14,7 +14,7 @@
14
14
  },
15
15
  "publishConfig": {
16
16
  "dependencies": {
17
- "@oxdeai/core": "^1.2.0"
17
+ "@oxdeai/core": "^1.3.0"
18
18
  }
19
19
  }
20
20
  }
@@ -0,0 +1,165 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+
4
+ import { PolicyEngine } from "@oxdeai/core";
5
+
6
+ import { InMemoryAuditAdapter, InMemoryStateAdapter } from "./adapters.js";
7
+ import { buildIntent, buildState } from "./builders.js";
8
+ import { createGuard } from "./guard.js";
9
+
10
+ function makeEngine() {
11
+ return new PolicyEngine({
12
+ policy_version: "v1",
13
+ engine_secret: "dev-secret",
14
+ authorization_ttl_seconds: 60,
15
+ authorization_issuer: "issuer-A",
16
+ authorization_audience: "rp-A",
17
+ policyId: "a".repeat(64),
18
+ });
19
+ }
20
+
21
+ function makeAllowState() {
22
+ return buildState({
23
+ policy_version: "v1",
24
+ agent_id: "agent-1",
25
+ allow_action_types: ["PROVISION"],
26
+ allow_targets: ["us-east-1"],
27
+ budget_limit: 1_000n,
28
+ spent_in_period: 0n,
29
+ max_amount_per_action: 500n,
30
+ });
31
+ }
32
+
33
+ test("guard executes callback on ALLOW", async () => {
34
+ const engine = makeEngine();
35
+ const stateAdapter = new InMemoryStateAdapter(makeAllowState());
36
+ const auditAdapter = new InMemoryAuditAdapter();
37
+ const guard = createGuard({
38
+ engine,
39
+ stateAdapter,
40
+ auditAdapter,
41
+ clock: { now: () => 1_770_000_000 },
42
+ });
43
+
44
+ let called = 0;
45
+ const result = await guard(
46
+ buildIntent({
47
+ intent_id: "intent-allow-1",
48
+ agent_id: "agent-1",
49
+ action_type: "PROVISION",
50
+ amount: 500n,
51
+ target: "us-east-1",
52
+ nonce: 1n,
53
+ }),
54
+ async () => {
55
+ called++;
56
+ return "ok";
57
+ }
58
+ );
59
+
60
+ assert.equal(called, 1);
61
+ assert.equal(result.executed, true);
62
+ assert.equal(result.output.decision, "ALLOW");
63
+ if (result.executed) assert.equal(result.executionResult, "ok");
64
+ assert.ok(auditAdapter.snapshot().length > 0);
65
+ });
66
+
67
+ test("guard does not execute callback on DENY", async () => {
68
+ const engine = makeEngine();
69
+ const stateAdapter = new InMemoryStateAdapter(makeAllowState());
70
+ const guard = createGuard({
71
+ engine,
72
+ stateAdapter,
73
+ clock: { now: () => 1_770_000_000 },
74
+ });
75
+
76
+ let called = 0;
77
+ const result = await guard(
78
+ buildIntent({
79
+ intent_id: "intent-deny-1",
80
+ agent_id: "agent-1",
81
+ action_type: "PROVISION",
82
+ amount: 900n,
83
+ target: "us-east-1",
84
+ nonce: 2n,
85
+ }),
86
+ async () => {
87
+ called++;
88
+ return "should-not-run";
89
+ }
90
+ );
91
+
92
+ assert.equal(called, 0);
93
+ assert.equal(result.executed, false);
94
+ assert.equal(result.output.decision, "DENY");
95
+ });
96
+
97
+ test("guard enforces authorization verification at callback boundary", async () => {
98
+ const engine = makeEngine();
99
+ const original = engine.verifyAuthorization.bind(engine);
100
+ (engine as any).verifyAuthorization = () => ({ valid: false, reason: "forced-test-failure" });
101
+
102
+ const stateAdapter = new InMemoryStateAdapter(makeAllowState());
103
+ const guard = createGuard({
104
+ engine,
105
+ stateAdapter,
106
+ clock: { now: () => 1_770_000_000 },
107
+ });
108
+
109
+ let called = 0;
110
+ const result = await guard(
111
+ buildIntent({
112
+ intent_id: "intent-allow-auth-check",
113
+ agent_id: "agent-1",
114
+ action_type: "PROVISION",
115
+ amount: 500n,
116
+ target: "us-east-1",
117
+ nonce: 3n,
118
+ }),
119
+ async () => {
120
+ called++;
121
+ return "should-not-run";
122
+ }
123
+ );
124
+
125
+ assert.equal(called, 0);
126
+ assert.equal(result.executed, false);
127
+ assert.equal(result.output.decision, "DENY");
128
+ assert.ok(result.output.reasons.some((r) => r.startsWith("AUTH_INVALID:")));
129
+
130
+ (engine as any).verifyAuthorization = original;
131
+ });
132
+
133
+ test("guard output is deterministic for equivalent inputs", async () => {
134
+ const intent = buildIntent({
135
+ intent_id: "intent-deterministic-1",
136
+ agent_id: "agent-1",
137
+ action_type: "PROVISION",
138
+ amount: 500n,
139
+ target: "us-east-1",
140
+ nonce: 10n,
141
+ timestamp: 1_770_000_000,
142
+ });
143
+
144
+ const g1 = createGuard({
145
+ engine: makeEngine(),
146
+ stateAdapter: new InMemoryStateAdapter(makeAllowState()),
147
+ clock: { now: () => 1_770_000_000 },
148
+ });
149
+ const g2 = createGuard({
150
+ engine: makeEngine(),
151
+ stateAdapter: new InMemoryStateAdapter(makeAllowState()),
152
+ clock: { now: () => 1_770_000_000 },
153
+ });
154
+
155
+ const r1 = await g1(intent, async () => "ok");
156
+ const r2 = await g2(intent, async () => "ok");
157
+
158
+ assert.equal(r1.output.decision, "ALLOW");
159
+ assert.equal(r2.output.decision, "ALLOW");
160
+ if (r1.output.decision === "ALLOW" && r2.output.decision === "ALLOW") {
161
+ assert.equal(r1.output.authorization.auth_id, r2.output.authorization.auth_id);
162
+ assert.equal(r1.output.authorization.state_hash, r2.output.authorization.state_hash);
163
+ assert.equal(r1.output.authorization.intent_hash, r2.output.authorization.intent_hash);
164
+ }
165
+ });
package/src/guard.ts ADDED
@@ -0,0 +1,138 @@
1
+ import type { Intent, PolicyEngine, State, Authorization } from "@oxdeai/core";
2
+ import type { AuditAdapter, ClockAdapter, MaybePromise, StateAdapter, IntentBuilderInput } from "./types.js";
3
+ import { buildIntent } from "./builders.js";
4
+
5
+ export type GuardDecision =
6
+ | { decision: "ALLOW"; reasons: []; authorization: Authorization }
7
+ | { decision: "DENY"; reasons: string[] };
8
+
9
+ export type GuardAllowResult<T> = {
10
+ output: GuardDecision & { decision: "ALLOW"; reasons: []; authorization: Authorization };
11
+ intent: Intent;
12
+ state: State;
13
+ auditEvents: unknown[];
14
+ executed: true;
15
+ executionResult: T;
16
+ };
17
+
18
+ export type GuardDenyResult = {
19
+ output: GuardDecision & { decision: "DENY"; reasons: string[] };
20
+ intent: Intent;
21
+ state: State;
22
+ auditEvents: unknown[];
23
+ executed: false;
24
+ };
25
+
26
+ export type GuardResult<T> = GuardAllowResult<T> | GuardDenyResult;
27
+
28
+ export type GuardInputIntent = Intent | IntentBuilderInput;
29
+
30
+ export type GuardOptions = {
31
+ engine: PolicyEngine;
32
+ stateAdapter: StateAdapter;
33
+ auditAdapter?: AuditAdapter;
34
+ clock?: ClockAdapter;
35
+ mode?: "fail-fast" | "collect-all";
36
+ verifyAuthorization?: boolean;
37
+ };
38
+
39
+ type GuardExecuteContext = {
40
+ intent: Intent;
41
+ authorization: Authorization;
42
+ state: State;
43
+ };
44
+
45
+ function collectNewEvents(engine: PolicyEngine, cursor: number): { nextCursor: number; events: unknown[] } {
46
+ const all = engine.audit.snapshot() as unknown[];
47
+ return { nextCursor: all.length, events: all.slice(cursor) };
48
+ }
49
+
50
+ function isIntent(value: GuardInputIntent): value is Intent {
51
+ return (
52
+ typeof (value as Intent).intent_id === "string" &&
53
+ typeof (value as Intent).agent_id === "string" &&
54
+ typeof (value as Intent).action_type === "string" &&
55
+ typeof (value as Intent).signature === "string"
56
+ );
57
+ }
58
+
59
+ export type GuardFn = <T>(
60
+ input: GuardInputIntent,
61
+ execute: (ctx: GuardExecuteContext) => MaybePromise<T>
62
+ ) => Promise<GuardResult<T>>;
63
+
64
+ export function createGuard(opts: GuardOptions): GuardFn {
65
+ const clock = opts.clock ?? { now: () => Math.floor(Date.now() / 1000) };
66
+ let auditCursor = 0;
67
+
68
+ return async function guard<T>(
69
+ input: GuardInputIntent,
70
+ execute: (ctx: GuardExecuteContext) => MaybePromise<T>
71
+ ): Promise<GuardResult<T>> {
72
+ const state = await opts.stateAdapter.load();
73
+ const intent: Intent = isIntent(input)
74
+ ? ({
75
+ ...input,
76
+ timestamp: input.timestamp === 0 ? clock.now() : input.timestamp,
77
+ } as Intent)
78
+ : buildIntent({
79
+ ...input,
80
+ timestamp: input.timestamp ?? clock.now(),
81
+ });
82
+
83
+ const out = opts.engine.evaluatePure(intent, state, { mode: opts.mode ?? "fail-fast" });
84
+ const emitted = collectNewEvents(opts.engine, auditCursor);
85
+ auditCursor = emitted.nextCursor;
86
+
87
+ if (opts.auditAdapter) await opts.auditAdapter.append(emitted.events);
88
+
89
+ if (out.decision === "DENY") {
90
+ return {
91
+ output: { decision: "DENY", reasons: out.reasons },
92
+ intent,
93
+ state,
94
+ auditEvents: emitted.events,
95
+ executed: false,
96
+ };
97
+ }
98
+
99
+ if (!out.authorization || !out.nextState) {
100
+ return {
101
+ output: { decision: "DENY", reasons: ["PEP_INVARIANT_VIOLATION"] },
102
+ intent,
103
+ state,
104
+ auditEvents: emitted.events,
105
+ executed: false,
106
+ };
107
+ }
108
+
109
+ if (opts.verifyAuthorization !== false) {
110
+ const authCheck = opts.engine.verifyAuthorization(intent, out.authorization, out.nextState, intent.timestamp);
111
+ if (!authCheck.valid) {
112
+ return {
113
+ output: { decision: "DENY", reasons: [`AUTH_INVALID:${authCheck.reason ?? "unknown"}`] },
114
+ intent,
115
+ state,
116
+ auditEvents: emitted.events,
117
+ executed: false,
118
+ };
119
+ }
120
+ }
121
+
122
+ const executionResult = await execute({
123
+ intent,
124
+ authorization: out.authorization,
125
+ state: out.nextState,
126
+ });
127
+ await opts.stateAdapter.save(out.nextState);
128
+
129
+ return {
130
+ output: { decision: "ALLOW", reasons: [], authorization: out.authorization },
131
+ intent,
132
+ state: out.nextState,
133
+ auditEvents: emitted.events,
134
+ executed: true,
135
+ executionResult,
136
+ };
137
+ };
138
+ }
package/src/index.ts CHANGED
@@ -4,3 +4,4 @@ export * from "./types.js";
4
4
  export * from "./builders.js";
5
5
  export * from "./adapters.js";
6
6
  export * from "./client.js";
7
+ export * from "./guard.js";