@oxdeai/sdk 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.
- package/CHANGELOG.md +101 -0
- package/LICENSE +213 -0
- package/README.md +68 -8
- package/dist/adapters.js +1 -0
- package/dist/adapters.js.map +1 -1
- package/dist/builders.js.map +1 -1
- package/dist/client.d.ts +29 -2
- package/dist/client.js +44 -4
- package/dist/client.js.map +1 -1
- package/dist/guard.d.ts +18 -4
- package/dist/guard.js +26 -4
- package/dist/guard.js.map +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/package.json +22 -4
- package/dist/guard.test.d.ts +0 -1
- package/dist/guard.test.js +0 -139
- package/dist/guard.test.js.map +0 -1
- package/dist/sdk.test.d.ts +0 -1
- package/dist/sdk.test.js +0 -71
- package/dist/sdk.test.js.map +0 -1
- package/src/adapters.ts +0 -109
- package/src/builders.ts +0 -80
- package/src/client.ts +0 -106
- package/src/guard.test.ts +0 -165
- package/src/guard.ts +0 -138
- package/src/index.ts +0 -7
- package/src/sdk.test.ts +0 -83
- package/src/types.ts +0 -70
- package/tsconfig.json +0 -9
package/src/client.ts
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
PolicyEngine,
|
|
3
|
-
encodeCanonicalState,
|
|
4
|
-
encodeEnvelope,
|
|
5
|
-
verifyAuditEvents,
|
|
6
|
-
verifyEnvelope,
|
|
7
|
-
verifySnapshot
|
|
8
|
-
} from "@oxdeai/core";
|
|
9
|
-
import type { Authorization, Intent } from "@oxdeai/core";
|
|
10
|
-
|
|
11
|
-
import type {
|
|
12
|
-
AuditAdapter,
|
|
13
|
-
ClockAdapter,
|
|
14
|
-
EvaluateAndCommitResult,
|
|
15
|
-
EvaluateDecision,
|
|
16
|
-
StateAdapter,
|
|
17
|
-
VerifyBundleResult
|
|
18
|
-
} from "./types.js";
|
|
19
|
-
|
|
20
|
-
type ClientOptions = {
|
|
21
|
-
engine: PolicyEngine;
|
|
22
|
-
stateAdapter: StateAdapter;
|
|
23
|
-
auditAdapter?: AuditAdapter;
|
|
24
|
-
clock?: ClockAdapter;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
type EvaluateInput = Omit<Intent, "timestamp"> & { timestamp?: number };
|
|
28
|
-
|
|
29
|
-
function collectNewEvents(engine: PolicyEngine, cursor: number): { nextCursor: number; events: unknown[] } {
|
|
30
|
-
const all = engine.audit.snapshot() as unknown[];
|
|
31
|
-
const events = all.slice(cursor);
|
|
32
|
-
return { nextCursor: all.length, events };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export class OxDeAIClient {
|
|
36
|
-
private readonly engine: PolicyEngine;
|
|
37
|
-
private readonly stateAdapter: StateAdapter;
|
|
38
|
-
private readonly auditAdapter?: AuditAdapter;
|
|
39
|
-
private readonly clock: ClockAdapter;
|
|
40
|
-
private auditCursor = 0;
|
|
41
|
-
|
|
42
|
-
constructor(opts: ClientOptions) {
|
|
43
|
-
this.engine = opts.engine;
|
|
44
|
-
this.stateAdapter = opts.stateAdapter;
|
|
45
|
-
this.auditAdapter = opts.auditAdapter;
|
|
46
|
-
this.clock = opts.clock ?? { now: () => Math.floor(Date.now() / 1000) };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async evaluateAndCommit(input: EvaluateInput): Promise<EvaluateAndCommitResult> {
|
|
50
|
-
const state = await this.stateAdapter.load();
|
|
51
|
-
const resolvedTimestamp = input.timestamp === undefined || input.timestamp === 0
|
|
52
|
-
? this.clock.now()
|
|
53
|
-
: input.timestamp;
|
|
54
|
-
const intent: Intent = {
|
|
55
|
-
...input,
|
|
56
|
-
timestamp: resolvedTimestamp
|
|
57
|
-
} as Intent;
|
|
58
|
-
|
|
59
|
-
const out = this.engine.evaluatePure(intent, state, { mode: "fail-fast" });
|
|
60
|
-
const emitted = collectNewEvents(this.engine, this.auditCursor);
|
|
61
|
-
this.auditCursor = emitted.nextCursor;
|
|
62
|
-
|
|
63
|
-
if (this.auditAdapter) await this.auditAdapter.append(emitted.events);
|
|
64
|
-
|
|
65
|
-
if (out.decision === "ALLOW") {
|
|
66
|
-
await this.stateAdapter.save(out.nextState);
|
|
67
|
-
const output: EvaluateDecision = {
|
|
68
|
-
decision: "ALLOW",
|
|
69
|
-
reasons: [],
|
|
70
|
-
authorization: out.authorization
|
|
71
|
-
};
|
|
72
|
-
return { output, state: out.nextState, auditEvents: emitted.events };
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const output: EvaluateDecision = {
|
|
76
|
-
decision: "DENY",
|
|
77
|
-
reasons: out.reasons
|
|
78
|
-
};
|
|
79
|
-
return { output, state, auditEvents: emitted.events };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async verifyAuthorization(intent: Intent, authorization: Authorization): Promise<{ valid: boolean; reason?: string }> {
|
|
83
|
-
const state = await this.stateAdapter.load();
|
|
84
|
-
const result = this.engine.verifyAuthorization(intent, authorization, state, intent.timestamp);
|
|
85
|
-
return result.valid ? { valid: true } : { valid: false, reason: result.reason };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async verifyCurrentArtifacts(opts?: { mode?: "strict" | "best-effort" }): Promise<VerifyBundleResult> {
|
|
89
|
-
const state = await this.stateAdapter.load();
|
|
90
|
-
const snapshotBytes = encodeCanonicalState(this.engine.exportState(state));
|
|
91
|
-
const snapshot = verifySnapshot(snapshotBytes);
|
|
92
|
-
|
|
93
|
-
const allEvents = this.engine.audit.snapshot();
|
|
94
|
-
const audit = verifyAuditEvents(allEvents as Parameters<typeof verifyAuditEvents>[0], {
|
|
95
|
-
mode: opts?.mode ?? "best-effort"
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
const envelopeBytes = encodeEnvelope({
|
|
99
|
-
formatVersion: 1,
|
|
100
|
-
snapshot: snapshotBytes,
|
|
101
|
-
events: allEvents as any
|
|
102
|
-
});
|
|
103
|
-
const envelope = verifyEnvelope(envelopeBytes, { mode: opts?.mode ?? "best-effort" });
|
|
104
|
-
return { snapshot, audit, envelope };
|
|
105
|
-
}
|
|
106
|
-
}
|
package/src/guard.test.ts
DELETED
|
@@ -1,165 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
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
DELETED
package/src/sdk.test.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
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 { OxDeAIClient } from "./client.js";
|
|
9
|
-
|
|
10
|
-
test("builder helpers create engine-compatible intent/state", () => {
|
|
11
|
-
const state = buildState({
|
|
12
|
-
policy_version: "v1",
|
|
13
|
-
agent_id: "agent-1",
|
|
14
|
-
allow_action_types: ["PROVISION"],
|
|
15
|
-
allow_targets: ["us-east-1"]
|
|
16
|
-
});
|
|
17
|
-
const intent = buildIntent({
|
|
18
|
-
intent_id: "intent-1",
|
|
19
|
-
agent_id: "agent-1",
|
|
20
|
-
action_type: "PROVISION",
|
|
21
|
-
amount: 100n,
|
|
22
|
-
target: "us-east-1",
|
|
23
|
-
nonce: 1n,
|
|
24
|
-
timestamp: 1_770_000_000
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
const engine = new PolicyEngine({
|
|
28
|
-
policy_version: "v1",
|
|
29
|
-
engine_secret: "dev-secret",
|
|
30
|
-
authorization_ttl_seconds: 60
|
|
31
|
-
});
|
|
32
|
-
const out = engine.evaluatePure(intent, state, { mode: "fail-fast" });
|
|
33
|
-
assert.equal(out.decision, "ALLOW");
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test("OxDeAIClient evaluate+persist+verify flow", async () => {
|
|
37
|
-
const engine = new PolicyEngine({
|
|
38
|
-
policy_version: "v1",
|
|
39
|
-
engine_secret: "dev-secret",
|
|
40
|
-
authorization_ttl_seconds: 60
|
|
41
|
-
});
|
|
42
|
-
const stateAdapter = new InMemoryStateAdapter(
|
|
43
|
-
buildState({
|
|
44
|
-
policy_version: "v1",
|
|
45
|
-
agent_id: "agent-1",
|
|
46
|
-
allow_action_types: ["PROVISION"],
|
|
47
|
-
allow_targets: ["us-east-1"]
|
|
48
|
-
})
|
|
49
|
-
);
|
|
50
|
-
const auditAdapter = new InMemoryAuditAdapter();
|
|
51
|
-
const client = new OxDeAIClient({
|
|
52
|
-
engine,
|
|
53
|
-
stateAdapter,
|
|
54
|
-
auditAdapter,
|
|
55
|
-
clock: { now: () => 1_770_000_000 }
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
const intent = buildIntent({
|
|
59
|
-
intent_id: "intent-2",
|
|
60
|
-
agent_id: "agent-1",
|
|
61
|
-
action_type: "PROVISION",
|
|
62
|
-
amount: 320n,
|
|
63
|
-
target: "us-east-1",
|
|
64
|
-
nonce: 2n
|
|
65
|
-
});
|
|
66
|
-
const res = await client.evaluateAndCommit(intent);
|
|
67
|
-
|
|
68
|
-
assert.equal(res.output.decision, "ALLOW");
|
|
69
|
-
if (res.output.decision !== "ALLOW") return;
|
|
70
|
-
assert.ok(res.auditEvents.length >= 3);
|
|
71
|
-
assert.equal(auditAdapter.snapshot().length, res.auditEvents.length);
|
|
72
|
-
|
|
73
|
-
const auth = await client.verifyAuthorization(
|
|
74
|
-
{ ...intent, timestamp: 1_770_000_000 },
|
|
75
|
-
res.output.authorization
|
|
76
|
-
);
|
|
77
|
-
assert.equal(auth.valid, true);
|
|
78
|
-
|
|
79
|
-
const verify = await client.verifyCurrentArtifacts({ mode: "best-effort" });
|
|
80
|
-
assert.equal(verify.snapshot.status, "ok");
|
|
81
|
-
assert.equal(verify.audit.status, "ok");
|
|
82
|
-
assert.equal(verify.envelope.status, "ok");
|
|
83
|
-
});
|
package/src/types.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import type { Authorization, Intent, State, VerificationResult } from "@oxdeai/core";
|
|
2
|
-
|
|
3
|
-
export type MaybePromise<T> = T | Promise<T>;
|
|
4
|
-
|
|
5
|
-
export type EvaluateDecision =
|
|
6
|
-
| { decision: "ALLOW"; reasons: []; authorization: Authorization }
|
|
7
|
-
| { decision: "DENY"; reasons: string[] };
|
|
8
|
-
|
|
9
|
-
export type EvaluateAndCommitResult = {
|
|
10
|
-
output: EvaluateDecision;
|
|
11
|
-
state: State;
|
|
12
|
-
auditEvents: unknown[];
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export type VerifyBundleResult = {
|
|
16
|
-
snapshot: VerificationResult;
|
|
17
|
-
audit: VerificationResult;
|
|
18
|
-
envelope: VerificationResult;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export interface StateAdapter {
|
|
22
|
-
load(): MaybePromise<State>;
|
|
23
|
-
save(state: State): MaybePromise<void>;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface AuditAdapter {
|
|
27
|
-
append(events: readonly unknown[]): MaybePromise<void>;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface ClockAdapter {
|
|
31
|
-
now(): number;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export type IntentBuilderInput = {
|
|
35
|
-
intent_id: string;
|
|
36
|
-
agent_id: string;
|
|
37
|
-
action_type: Intent["action_type"];
|
|
38
|
-
amount: bigint;
|
|
39
|
-
target: string;
|
|
40
|
-
nonce: bigint;
|
|
41
|
-
timestamp?: number;
|
|
42
|
-
type?: "EXECUTE" | "RELEASE";
|
|
43
|
-
authorization_id?: string;
|
|
44
|
-
asset?: string;
|
|
45
|
-
metadata_hash?: string;
|
|
46
|
-
signature?: string;
|
|
47
|
-
depth?: number;
|
|
48
|
-
tool?: string;
|
|
49
|
-
tool_call?: boolean;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
export type StateBuilderInput = {
|
|
53
|
-
policy_version?: string;
|
|
54
|
-
period_id?: string;
|
|
55
|
-
agent_id: string;
|
|
56
|
-
allow_action_types?: Intent["action_type"][];
|
|
57
|
-
allow_assets?: string[];
|
|
58
|
-
allow_targets?: string[];
|
|
59
|
-
budget_limit?: bigint;
|
|
60
|
-
spent_in_period?: bigint;
|
|
61
|
-
max_amount_per_action?: bigint;
|
|
62
|
-
velocity_window_seconds?: number;
|
|
63
|
-
velocity_max_actions?: number;
|
|
64
|
-
replay_window_seconds?: number;
|
|
65
|
-
replay_max_nonces_per_agent?: number;
|
|
66
|
-
max_concurrent?: number;
|
|
67
|
-
max_depth?: number;
|
|
68
|
-
tool_window_seconds?: number;
|
|
69
|
-
tool_max_calls?: number;
|
|
70
|
-
};
|