@oxdeai/sdk 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -0
- package/dist/adapters.d.ts +24 -0
- package/dist/adapters.js +104 -0
- package/dist/adapters.js.map +1 -0
- package/dist/builders.d.ts +4 -0
- package/dist/builders.js +76 -0
- package/dist/builders.js.map +1 -0
- package/dist/client.d.ts +29 -0
- package/dist/client.js +70 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/sdk.test.d.ts +1 -0
- package/dist/sdk.test.js +71 -0
- package/dist/sdk.test.js.map +1 -0
- package/dist/types.d.ts +66 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +15 -0
- package/src/adapters.ts +109 -0
- package/src/builders.ts +80 -0
- package/src/client.ts +106 -0
- package/src/index.ts +6 -0
- package/src/sdk.test.ts +83 -0
- package/src/types.ts +70 -0
- package/tsconfig.json +9 -0
package/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# @oxdeai/sdk
|
|
2
|
+
|
|
3
|
+
Developer-facing integration layer on top of `@oxdeai/core`.
|
|
4
|
+
|
|
5
|
+
## What It Adds
|
|
6
|
+
|
|
7
|
+
- Intent/state builder helpers
|
|
8
|
+
- Typed client wrapper for common flow: evaluate + persist + verify
|
|
9
|
+
- Runtime adapters (in-memory and file-based)
|
|
10
|
+
|
|
11
|
+
## Quick Example
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { PolicyEngine } from "@oxdeai/core";
|
|
15
|
+
import {
|
|
16
|
+
OxDeAIClient,
|
|
17
|
+
buildState,
|
|
18
|
+
buildIntent,
|
|
19
|
+
InMemoryStateAdapter,
|
|
20
|
+
InMemoryAuditAdapter
|
|
21
|
+
} from "@oxdeai/sdk";
|
|
22
|
+
|
|
23
|
+
const engine = new PolicyEngine({
|
|
24
|
+
policy_version: "v1",
|
|
25
|
+
engine_secret: "dev-secret",
|
|
26
|
+
authorization_ttl_seconds: 120
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const stateAdapter = new InMemoryStateAdapter(
|
|
30
|
+
buildState({
|
|
31
|
+
policy_version: "v1",
|
|
32
|
+
agent_id: "agent-1",
|
|
33
|
+
allow_action_types: ["PROVISION"],
|
|
34
|
+
allow_targets: ["us-east-1"]
|
|
35
|
+
})
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const auditAdapter = new InMemoryAuditAdapter();
|
|
39
|
+
|
|
40
|
+
const client = new OxDeAIClient({
|
|
41
|
+
engine,
|
|
42
|
+
stateAdapter,
|
|
43
|
+
auditAdapter,
|
|
44
|
+
clock: { now: () => 1770000000 }
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const intent = buildIntent({
|
|
48
|
+
intent_id: "intent-1",
|
|
49
|
+
agent_id: "agent-1",
|
|
50
|
+
action_type: "PROVISION",
|
|
51
|
+
amount: 320n,
|
|
52
|
+
target: "us-east-1",
|
|
53
|
+
nonce: 1n
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const result = await client.evaluateAndCommit(intent);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Main Exports
|
|
60
|
+
|
|
61
|
+
- `buildIntent`, `buildState`
|
|
62
|
+
- `OxDeAIClient`
|
|
63
|
+
- `InMemoryStateAdapter`, `InMemoryAuditAdapter`
|
|
64
|
+
- `JsonFileStateAdapter`, `NdjsonFileAuditAdapter`
|
|
65
|
+
|
|
66
|
+
## Scripts
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pnpm -C packages/sdk build
|
|
70
|
+
pnpm -C packages/sdk test
|
|
71
|
+
```
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { State } from "@oxdeai/core";
|
|
2
|
+
import type { AuditAdapter, StateAdapter } from "./types.js";
|
|
3
|
+
export declare class InMemoryStateAdapter implements StateAdapter {
|
|
4
|
+
private state;
|
|
5
|
+
constructor(initial: State);
|
|
6
|
+
load(): State;
|
|
7
|
+
save(state: State): void;
|
|
8
|
+
}
|
|
9
|
+
export declare class InMemoryAuditAdapter implements AuditAdapter {
|
|
10
|
+
private readonly events;
|
|
11
|
+
append(events: readonly unknown[]): void;
|
|
12
|
+
snapshot(): unknown[];
|
|
13
|
+
}
|
|
14
|
+
export declare class JsonFileStateAdapter implements StateAdapter {
|
|
15
|
+
private readonly path;
|
|
16
|
+
constructor(path: string);
|
|
17
|
+
load(): Promise<State>;
|
|
18
|
+
save(state: State): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
export declare class NdjsonFileAuditAdapter implements AuditAdapter {
|
|
21
|
+
private readonly path;
|
|
22
|
+
constructor(path: string);
|
|
23
|
+
append(events: readonly unknown[]): Promise<void>;
|
|
24
|
+
}
|
package/dist/adapters.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { appendFile, mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
function canonicalize(value) {
|
|
5
|
+
if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string")
|
|
6
|
+
return value;
|
|
7
|
+
if (typeof value === "bigint")
|
|
8
|
+
return `${value.toString()}n`;
|
|
9
|
+
if (Array.isArray(value))
|
|
10
|
+
return value.map((v) => canonicalize(v));
|
|
11
|
+
if (typeof value === "object") {
|
|
12
|
+
const obj = value;
|
|
13
|
+
const out = {};
|
|
14
|
+
for (const key of Object.keys(obj).sort())
|
|
15
|
+
out[key] = canonicalize(obj[key]);
|
|
16
|
+
return out;
|
|
17
|
+
}
|
|
18
|
+
return String(value);
|
|
19
|
+
}
|
|
20
|
+
function stringifyCanonical(value) {
|
|
21
|
+
return JSON.stringify(canonicalize(value));
|
|
22
|
+
}
|
|
23
|
+
function parseBigIntLike(value) {
|
|
24
|
+
if (typeof value === "bigint")
|
|
25
|
+
return value;
|
|
26
|
+
if (typeof value === "number")
|
|
27
|
+
return BigInt(value);
|
|
28
|
+
if (typeof value === "string") {
|
|
29
|
+
const s = value.endsWith("n") ? value.slice(0, -1) : value;
|
|
30
|
+
return BigInt(s);
|
|
31
|
+
}
|
|
32
|
+
throw new Error("invalid bigint-like value");
|
|
33
|
+
}
|
|
34
|
+
function mapRecordBigInt(input) {
|
|
35
|
+
const rec = (input ?? {});
|
|
36
|
+
const out = {};
|
|
37
|
+
for (const key of Object.keys(rec))
|
|
38
|
+
out[key] = parseBigIntLike(rec[key]);
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
function normalizeStateBigInts(raw) {
|
|
42
|
+
const s = raw;
|
|
43
|
+
return {
|
|
44
|
+
...s,
|
|
45
|
+
budget: {
|
|
46
|
+
...s.budget,
|
|
47
|
+
budget_limit: mapRecordBigInt(s.budget?.budget_limit),
|
|
48
|
+
spent_in_period: mapRecordBigInt(s.budget?.spent_in_period)
|
|
49
|
+
},
|
|
50
|
+
max_amount_per_action: mapRecordBigInt(s.max_amount_per_action)
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export class InMemoryStateAdapter {
|
|
54
|
+
state;
|
|
55
|
+
constructor(initial) {
|
|
56
|
+
this.state = structuredClone(initial);
|
|
57
|
+
}
|
|
58
|
+
load() {
|
|
59
|
+
return structuredClone(this.state);
|
|
60
|
+
}
|
|
61
|
+
save(state) {
|
|
62
|
+
this.state = structuredClone(state);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export class InMemoryAuditAdapter {
|
|
66
|
+
events = [];
|
|
67
|
+
append(events) {
|
|
68
|
+
for (const event of events)
|
|
69
|
+
this.events.push(structuredClone(event));
|
|
70
|
+
}
|
|
71
|
+
snapshot() {
|
|
72
|
+
return structuredClone(this.events);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export class JsonFileStateAdapter {
|
|
76
|
+
path;
|
|
77
|
+
constructor(path) {
|
|
78
|
+
this.path = path;
|
|
79
|
+
}
|
|
80
|
+
async load() {
|
|
81
|
+
const txt = await readFile(this.path, "utf8");
|
|
82
|
+
return normalizeStateBigInts(JSON.parse(txt));
|
|
83
|
+
}
|
|
84
|
+
async save(state) {
|
|
85
|
+
await mkdir(dirname(this.path), { recursive: true });
|
|
86
|
+
const tmp = `${this.path}.tmp-${randomUUID()}`;
|
|
87
|
+
await writeFile(tmp, `${stringifyCanonical(state)}\n`, "utf8");
|
|
88
|
+
await rename(tmp, this.path);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export class NdjsonFileAuditAdapter {
|
|
92
|
+
path;
|
|
93
|
+
constructor(path) {
|
|
94
|
+
this.path = path;
|
|
95
|
+
}
|
|
96
|
+
async append(events) {
|
|
97
|
+
if (events.length === 0)
|
|
98
|
+
return;
|
|
99
|
+
await mkdir(dirname(this.path), { recursive: true });
|
|
100
|
+
const body = events.map((e) => stringifyCanonical(e)).join("\n") + "\n";
|
|
101
|
+
await appendFile(this.path, body, "utf8");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=adapters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapters.js","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":"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"}
|
package/dist/builders.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export function buildIntent(input) {
|
|
2
|
+
const base = {
|
|
3
|
+
intent_id: input.intent_id,
|
|
4
|
+
agent_id: input.agent_id,
|
|
5
|
+
action_type: input.action_type,
|
|
6
|
+
amount: input.amount,
|
|
7
|
+
asset: input.asset,
|
|
8
|
+
target: input.target,
|
|
9
|
+
timestamp: input.timestamp ?? 0,
|
|
10
|
+
metadata_hash: input.metadata_hash ?? "0x" + "0".repeat(64),
|
|
11
|
+
nonce: input.nonce,
|
|
12
|
+
signature: input.signature ?? "sdk-signature-placeholder",
|
|
13
|
+
depth: input.depth,
|
|
14
|
+
tool: input.tool,
|
|
15
|
+
tool_call: input.tool_call
|
|
16
|
+
};
|
|
17
|
+
if (input.type === "RELEASE") {
|
|
18
|
+
if (!input.authorization_id)
|
|
19
|
+
throw new Error("authorization_id is required for RELEASE intent");
|
|
20
|
+
return {
|
|
21
|
+
...base,
|
|
22
|
+
type: "RELEASE",
|
|
23
|
+
authorization_id: input.authorization_id
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
...base,
|
|
28
|
+
type: "EXECUTE",
|
|
29
|
+
authorization_id: input.authorization_id
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function buildState(input) {
|
|
33
|
+
const agent = input.agent_id;
|
|
34
|
+
return {
|
|
35
|
+
policy_version: input.policy_version ?? "v1",
|
|
36
|
+
period_id: input.period_id ?? "default-period",
|
|
37
|
+
kill_switch: { global: false, agents: {} },
|
|
38
|
+
allowlists: {
|
|
39
|
+
action_types: input.allow_action_types ?? ["PROVISION"],
|
|
40
|
+
assets: input.allow_assets ?? [],
|
|
41
|
+
targets: input.allow_targets ?? []
|
|
42
|
+
},
|
|
43
|
+
budget: {
|
|
44
|
+
budget_limit: { [agent]: input.budget_limit ?? 1000000n },
|
|
45
|
+
spent_in_period: { [agent]: input.spent_in_period ?? 0n }
|
|
46
|
+
},
|
|
47
|
+
max_amount_per_action: { [agent]: input.max_amount_per_action ?? 1000000n },
|
|
48
|
+
velocity: {
|
|
49
|
+
config: {
|
|
50
|
+
window_seconds: input.velocity_window_seconds ?? 60,
|
|
51
|
+
max_actions: input.velocity_max_actions ?? 1000
|
|
52
|
+
},
|
|
53
|
+
counters: {}
|
|
54
|
+
},
|
|
55
|
+
replay: {
|
|
56
|
+
window_seconds: input.replay_window_seconds ?? 3600,
|
|
57
|
+
max_nonces_per_agent: input.replay_max_nonces_per_agent ?? 256,
|
|
58
|
+
nonces: {}
|
|
59
|
+
},
|
|
60
|
+
concurrency: {
|
|
61
|
+
max_concurrent: { [agent]: input.max_concurrent ?? 16 },
|
|
62
|
+
active: {},
|
|
63
|
+
active_auths: {}
|
|
64
|
+
},
|
|
65
|
+
recursion: {
|
|
66
|
+
max_depth: { [agent]: input.max_depth ?? 8 }
|
|
67
|
+
},
|
|
68
|
+
tool_limits: {
|
|
69
|
+
window_seconds: input.tool_window_seconds ?? 60,
|
|
70
|
+
max_calls: { [agent]: input.tool_max_calls ?? 1000 },
|
|
71
|
+
max_calls_by_tool: {},
|
|
72
|
+
calls: {}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=builders.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builders.js","sourceRoot":"","sources":["../src/builders.ts"],"names":[],"mappings":"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"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { PolicyEngine } from "@oxdeai/core";
|
|
2
|
+
import type { Authorization, Intent } from "@oxdeai/core";
|
|
3
|
+
import type { AuditAdapter, ClockAdapter, EvaluateAndCommitResult, StateAdapter, VerifyBundleResult } from "./types.js";
|
|
4
|
+
type ClientOptions = {
|
|
5
|
+
engine: PolicyEngine;
|
|
6
|
+
stateAdapter: StateAdapter;
|
|
7
|
+
auditAdapter?: AuditAdapter;
|
|
8
|
+
clock?: ClockAdapter;
|
|
9
|
+
};
|
|
10
|
+
type EvaluateInput = Omit<Intent, "timestamp"> & {
|
|
11
|
+
timestamp?: number;
|
|
12
|
+
};
|
|
13
|
+
export declare class OxDeAIClient {
|
|
14
|
+
private readonly engine;
|
|
15
|
+
private readonly stateAdapter;
|
|
16
|
+
private readonly auditAdapter?;
|
|
17
|
+
private readonly clock;
|
|
18
|
+
private auditCursor;
|
|
19
|
+
constructor(opts: ClientOptions);
|
|
20
|
+
evaluateAndCommit(input: EvaluateInput): Promise<EvaluateAndCommitResult>;
|
|
21
|
+
verifyAuthorization(intent: Intent, authorization: Authorization): Promise<{
|
|
22
|
+
valid: boolean;
|
|
23
|
+
reason?: string;
|
|
24
|
+
}>;
|
|
25
|
+
verifyCurrentArtifacts(opts?: {
|
|
26
|
+
mode?: "strict" | "best-effort";
|
|
27
|
+
}): Promise<VerifyBundleResult>;
|
|
28
|
+
}
|
|
29
|
+
export {};
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { encodeCanonicalState, encodeEnvelope, verifyAuditEvents, verifyEnvelope, verifySnapshot } from "@oxdeai/core";
|
|
2
|
+
function collectNewEvents(engine, cursor) {
|
|
3
|
+
const all = engine.audit.snapshot();
|
|
4
|
+
const events = all.slice(cursor);
|
|
5
|
+
return { nextCursor: all.length, events };
|
|
6
|
+
}
|
|
7
|
+
export class OxDeAIClient {
|
|
8
|
+
engine;
|
|
9
|
+
stateAdapter;
|
|
10
|
+
auditAdapter;
|
|
11
|
+
clock;
|
|
12
|
+
auditCursor = 0;
|
|
13
|
+
constructor(opts) {
|
|
14
|
+
this.engine = opts.engine;
|
|
15
|
+
this.stateAdapter = opts.stateAdapter;
|
|
16
|
+
this.auditAdapter = opts.auditAdapter;
|
|
17
|
+
this.clock = opts.clock ?? { now: () => Math.floor(Date.now() / 1000) };
|
|
18
|
+
}
|
|
19
|
+
async evaluateAndCommit(input) {
|
|
20
|
+
const state = await this.stateAdapter.load();
|
|
21
|
+
const resolvedTimestamp = input.timestamp === undefined || input.timestamp === 0
|
|
22
|
+
? this.clock.now()
|
|
23
|
+
: input.timestamp;
|
|
24
|
+
const intent = {
|
|
25
|
+
...input,
|
|
26
|
+
timestamp: resolvedTimestamp
|
|
27
|
+
};
|
|
28
|
+
const out = this.engine.evaluatePure(intent, state, { mode: "fail-fast" });
|
|
29
|
+
const emitted = collectNewEvents(this.engine, this.auditCursor);
|
|
30
|
+
this.auditCursor = emitted.nextCursor;
|
|
31
|
+
if (this.auditAdapter)
|
|
32
|
+
await this.auditAdapter.append(emitted.events);
|
|
33
|
+
if (out.decision === "ALLOW") {
|
|
34
|
+
await this.stateAdapter.save(out.nextState);
|
|
35
|
+
const output = {
|
|
36
|
+
decision: "ALLOW",
|
|
37
|
+
reasons: [],
|
|
38
|
+
authorization: out.authorization
|
|
39
|
+
};
|
|
40
|
+
return { output, state: out.nextState, auditEvents: emitted.events };
|
|
41
|
+
}
|
|
42
|
+
const output = {
|
|
43
|
+
decision: "DENY",
|
|
44
|
+
reasons: out.reasons
|
|
45
|
+
};
|
|
46
|
+
return { output, state, auditEvents: emitted.events };
|
|
47
|
+
}
|
|
48
|
+
async verifyAuthorization(intent, authorization) {
|
|
49
|
+
const state = await this.stateAdapter.load();
|
|
50
|
+
const result = this.engine.verifyAuthorization(intent, authorization, state, intent.timestamp);
|
|
51
|
+
return result.valid ? { valid: true } : { valid: false, reason: result.reason };
|
|
52
|
+
}
|
|
53
|
+
async verifyCurrentArtifacts(opts) {
|
|
54
|
+
const state = await this.stateAdapter.load();
|
|
55
|
+
const snapshotBytes = encodeCanonicalState(this.engine.exportState(state));
|
|
56
|
+
const snapshot = verifySnapshot(snapshotBytes);
|
|
57
|
+
const allEvents = this.engine.audit.snapshot();
|
|
58
|
+
const audit = verifyAuditEvents(allEvents, {
|
|
59
|
+
mode: opts?.mode ?? "best-effort"
|
|
60
|
+
});
|
|
61
|
+
const envelopeBytes = encodeEnvelope({
|
|
62
|
+
formatVersion: 1,
|
|
63
|
+
snapshot: snapshotBytes,
|
|
64
|
+
events: allEvents
|
|
65
|
+
});
|
|
66
|
+
const envelope = verifyEnvelope(envelopeBytes, { mode: opts?.mode ?? "best-effort" });
|
|
67
|
+
return { snapshot, audit, envelope };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/sdk.test.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
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 { OxDeAIClient } from "./client.js";
|
|
7
|
+
test("builder helpers create engine-compatible intent/state", () => {
|
|
8
|
+
const state = buildState({
|
|
9
|
+
policy_version: "v1",
|
|
10
|
+
agent_id: "agent-1",
|
|
11
|
+
allow_action_types: ["PROVISION"],
|
|
12
|
+
allow_targets: ["us-east-1"]
|
|
13
|
+
});
|
|
14
|
+
const intent = buildIntent({
|
|
15
|
+
intent_id: "intent-1",
|
|
16
|
+
agent_id: "agent-1",
|
|
17
|
+
action_type: "PROVISION",
|
|
18
|
+
amount: 100n,
|
|
19
|
+
target: "us-east-1",
|
|
20
|
+
nonce: 1n,
|
|
21
|
+
timestamp: 1_770_000_000
|
|
22
|
+
});
|
|
23
|
+
const engine = new PolicyEngine({
|
|
24
|
+
policy_version: "v1",
|
|
25
|
+
engine_secret: "dev-secret",
|
|
26
|
+
authorization_ttl_seconds: 60
|
|
27
|
+
});
|
|
28
|
+
const out = engine.evaluatePure(intent, state, { mode: "fail-fast" });
|
|
29
|
+
assert.equal(out.decision, "ALLOW");
|
|
30
|
+
});
|
|
31
|
+
test("OxDeAIClient evaluate+persist+verify flow", async () => {
|
|
32
|
+
const engine = new PolicyEngine({
|
|
33
|
+
policy_version: "v1",
|
|
34
|
+
engine_secret: "dev-secret",
|
|
35
|
+
authorization_ttl_seconds: 60
|
|
36
|
+
});
|
|
37
|
+
const stateAdapter = new InMemoryStateAdapter(buildState({
|
|
38
|
+
policy_version: "v1",
|
|
39
|
+
agent_id: "agent-1",
|
|
40
|
+
allow_action_types: ["PROVISION"],
|
|
41
|
+
allow_targets: ["us-east-1"]
|
|
42
|
+
}));
|
|
43
|
+
const auditAdapter = new InMemoryAuditAdapter();
|
|
44
|
+
const client = new OxDeAIClient({
|
|
45
|
+
engine,
|
|
46
|
+
stateAdapter,
|
|
47
|
+
auditAdapter,
|
|
48
|
+
clock: { now: () => 1_770_000_000 }
|
|
49
|
+
});
|
|
50
|
+
const intent = buildIntent({
|
|
51
|
+
intent_id: "intent-2",
|
|
52
|
+
agent_id: "agent-1",
|
|
53
|
+
action_type: "PROVISION",
|
|
54
|
+
amount: 320n,
|
|
55
|
+
target: "us-east-1",
|
|
56
|
+
nonce: 2n
|
|
57
|
+
});
|
|
58
|
+
const res = await client.evaluateAndCommit(intent);
|
|
59
|
+
assert.equal(res.output.decision, "ALLOW");
|
|
60
|
+
if (res.output.decision !== "ALLOW")
|
|
61
|
+
return;
|
|
62
|
+
assert.ok(res.auditEvents.length >= 3);
|
|
63
|
+
assert.equal(auditAdapter.snapshot().length, res.auditEvents.length);
|
|
64
|
+
const auth = await client.verifyAuthorization({ ...intent, timestamp: 1_770_000_000 }, res.output.authorization);
|
|
65
|
+
assert.equal(auth.valid, true);
|
|
66
|
+
const verify = await client.verifyCurrentArtifacts({ mode: "best-effort" });
|
|
67
|
+
assert.equal(verify.snapshot.status, "ok");
|
|
68
|
+
assert.equal(verify.audit.status, "ok");
|
|
69
|
+
assert.equal(verify.envelope.status, "ok");
|
|
70
|
+
});
|
|
71
|
+
//# sourceMappingURL=sdk.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sdk.test.js","sourceRoot":"","sources":["../src/sdk.test.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Authorization, Intent, State, VerificationResult } from "@oxdeai/core";
|
|
2
|
+
export type MaybePromise<T> = T | Promise<T>;
|
|
3
|
+
export type EvaluateDecision = {
|
|
4
|
+
decision: "ALLOW";
|
|
5
|
+
reasons: [];
|
|
6
|
+
authorization: Authorization;
|
|
7
|
+
} | {
|
|
8
|
+
decision: "DENY";
|
|
9
|
+
reasons: string[];
|
|
10
|
+
};
|
|
11
|
+
export type EvaluateAndCommitResult = {
|
|
12
|
+
output: EvaluateDecision;
|
|
13
|
+
state: State;
|
|
14
|
+
auditEvents: unknown[];
|
|
15
|
+
};
|
|
16
|
+
export type VerifyBundleResult = {
|
|
17
|
+
snapshot: VerificationResult;
|
|
18
|
+
audit: VerificationResult;
|
|
19
|
+
envelope: VerificationResult;
|
|
20
|
+
};
|
|
21
|
+
export interface StateAdapter {
|
|
22
|
+
load(): MaybePromise<State>;
|
|
23
|
+
save(state: State): MaybePromise<void>;
|
|
24
|
+
}
|
|
25
|
+
export interface AuditAdapter {
|
|
26
|
+
append(events: readonly unknown[]): MaybePromise<void>;
|
|
27
|
+
}
|
|
28
|
+
export interface ClockAdapter {
|
|
29
|
+
now(): number;
|
|
30
|
+
}
|
|
31
|
+
export type IntentBuilderInput = {
|
|
32
|
+
intent_id: string;
|
|
33
|
+
agent_id: string;
|
|
34
|
+
action_type: Intent["action_type"];
|
|
35
|
+
amount: bigint;
|
|
36
|
+
target: string;
|
|
37
|
+
nonce: bigint;
|
|
38
|
+
timestamp?: number;
|
|
39
|
+
type?: "EXECUTE" | "RELEASE";
|
|
40
|
+
authorization_id?: string;
|
|
41
|
+
asset?: string;
|
|
42
|
+
metadata_hash?: string;
|
|
43
|
+
signature?: string;
|
|
44
|
+
depth?: number;
|
|
45
|
+
tool?: string;
|
|
46
|
+
tool_call?: boolean;
|
|
47
|
+
};
|
|
48
|
+
export type StateBuilderInput = {
|
|
49
|
+
policy_version?: string;
|
|
50
|
+
period_id?: string;
|
|
51
|
+
agent_id: string;
|
|
52
|
+
allow_action_types?: Intent["action_type"][];
|
|
53
|
+
allow_assets?: string[];
|
|
54
|
+
allow_targets?: string[];
|
|
55
|
+
budget_limit?: bigint;
|
|
56
|
+
spent_in_period?: bigint;
|
|
57
|
+
max_amount_per_action?: bigint;
|
|
58
|
+
velocity_window_seconds?: number;
|
|
59
|
+
velocity_max_actions?: number;
|
|
60
|
+
replay_window_seconds?: number;
|
|
61
|
+
replay_max_nonces_per_agent?: number;
|
|
62
|
+
max_concurrent?: number;
|
|
63
|
+
max_depth?: number;
|
|
64
|
+
tool_window_seconds?: number;
|
|
65
|
+
tool_max_calls?: number;
|
|
66
|
+
};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@oxdeai/sdk",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@oxdeai/core": "^1.0.1"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
12
|
+
"build": "rm -rf dist && tsc -p tsconfig.json",
|
|
13
|
+
"test": "pnpm build && node --test \"dist/**/*.test.js\""
|
|
14
|
+
}
|
|
15
|
+
}
|
package/src/adapters.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { appendFile, mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
|
|
5
|
+
import type { State } from "@oxdeai/core";
|
|
6
|
+
|
|
7
|
+
import type { AuditAdapter, StateAdapter } from "./types.js";
|
|
8
|
+
|
|
9
|
+
function canonicalize(value: unknown): unknown {
|
|
10
|
+
if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string") return value;
|
|
11
|
+
if (typeof value === "bigint") return `${value.toString()}n`;
|
|
12
|
+
if (Array.isArray(value)) return value.map((v) => canonicalize(v));
|
|
13
|
+
if (typeof value === "object") {
|
|
14
|
+
const obj = value as Record<string, unknown>;
|
|
15
|
+
const out: Record<string, unknown> = {};
|
|
16
|
+
for (const key of Object.keys(obj).sort()) out[key] = canonicalize(obj[key]);
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
return String(value);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function stringifyCanonical(value: unknown): string {
|
|
23
|
+
return JSON.stringify(canonicalize(value));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function parseBigIntLike(value: unknown): bigint {
|
|
27
|
+
if (typeof value === "bigint") return value;
|
|
28
|
+
if (typeof value === "number") return BigInt(value);
|
|
29
|
+
if (typeof value === "string") {
|
|
30
|
+
const s = value.endsWith("n") ? value.slice(0, -1) : value;
|
|
31
|
+
return BigInt(s);
|
|
32
|
+
}
|
|
33
|
+
throw new Error("invalid bigint-like value");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function mapRecordBigInt(input: unknown): Record<string, bigint> {
|
|
37
|
+
const rec = (input ?? {}) as Record<string, unknown>;
|
|
38
|
+
const out: Record<string, bigint> = {};
|
|
39
|
+
for (const key of Object.keys(rec)) out[key] = parseBigIntLike(rec[key]);
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function normalizeStateBigInts(raw: unknown): State {
|
|
44
|
+
const s = raw as State;
|
|
45
|
+
return {
|
|
46
|
+
...s,
|
|
47
|
+
budget: {
|
|
48
|
+
...s.budget,
|
|
49
|
+
budget_limit: mapRecordBigInt((s as any).budget?.budget_limit),
|
|
50
|
+
spent_in_period: mapRecordBigInt((s as any).budget?.spent_in_period)
|
|
51
|
+
},
|
|
52
|
+
max_amount_per_action: mapRecordBigInt((s as any).max_amount_per_action)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export class InMemoryStateAdapter implements StateAdapter {
|
|
57
|
+
private state: State;
|
|
58
|
+
|
|
59
|
+
constructor(initial: State) {
|
|
60
|
+
this.state = structuredClone(initial);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
load(): State {
|
|
64
|
+
return structuredClone(this.state);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
save(state: State): void {
|
|
68
|
+
this.state = structuredClone(state);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export class InMemoryAuditAdapter implements AuditAdapter {
|
|
73
|
+
private readonly events: unknown[] = [];
|
|
74
|
+
|
|
75
|
+
append(events: readonly unknown[]): void {
|
|
76
|
+
for (const event of events) this.events.push(structuredClone(event));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
snapshot(): unknown[] {
|
|
80
|
+
return structuredClone(this.events);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export class JsonFileStateAdapter implements StateAdapter {
|
|
85
|
+
constructor(private readonly path: string) {}
|
|
86
|
+
|
|
87
|
+
async load(): Promise<State> {
|
|
88
|
+
const txt = await readFile(this.path, "utf8");
|
|
89
|
+
return normalizeStateBigInts(JSON.parse(txt));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async save(state: State): Promise<void> {
|
|
93
|
+
await mkdir(dirname(this.path), { recursive: true });
|
|
94
|
+
const tmp = `${this.path}.tmp-${randomUUID()}`;
|
|
95
|
+
await writeFile(tmp, `${stringifyCanonical(state)}\n`, "utf8");
|
|
96
|
+
await rename(tmp, this.path);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export class NdjsonFileAuditAdapter implements AuditAdapter {
|
|
101
|
+
constructor(private readonly path: string) {}
|
|
102
|
+
|
|
103
|
+
async append(events: readonly unknown[]): Promise<void> {
|
|
104
|
+
if (events.length === 0) return;
|
|
105
|
+
await mkdir(dirname(this.path), { recursive: true });
|
|
106
|
+
const body = events.map((e) => stringifyCanonical(e)).join("\n") + "\n";
|
|
107
|
+
await appendFile(this.path, body, "utf8");
|
|
108
|
+
}
|
|
109
|
+
}
|
package/src/builders.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Intent, State } from "@oxdeai/core";
|
|
2
|
+
import type { IntentBuilderInput, StateBuilderInput } from "./types.js";
|
|
3
|
+
|
|
4
|
+
export function buildIntent(input: IntentBuilderInput): Intent {
|
|
5
|
+
const base = {
|
|
6
|
+
intent_id: input.intent_id,
|
|
7
|
+
agent_id: input.agent_id,
|
|
8
|
+
action_type: input.action_type,
|
|
9
|
+
amount: input.amount,
|
|
10
|
+
asset: input.asset,
|
|
11
|
+
target: input.target,
|
|
12
|
+
timestamp: input.timestamp ?? 0,
|
|
13
|
+
metadata_hash: input.metadata_hash ?? "0x" + "0".repeat(64),
|
|
14
|
+
nonce: input.nonce,
|
|
15
|
+
signature: input.signature ?? "sdk-signature-placeholder",
|
|
16
|
+
depth: input.depth,
|
|
17
|
+
tool: input.tool,
|
|
18
|
+
tool_call: input.tool_call
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
if (input.type === "RELEASE") {
|
|
22
|
+
if (!input.authorization_id) throw new Error("authorization_id is required for RELEASE intent");
|
|
23
|
+
return {
|
|
24
|
+
...base,
|
|
25
|
+
type: "RELEASE",
|
|
26
|
+
authorization_id: input.authorization_id
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
...base,
|
|
32
|
+
type: "EXECUTE",
|
|
33
|
+
authorization_id: input.authorization_id
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function buildState(input: StateBuilderInput): State {
|
|
38
|
+
const agent = input.agent_id;
|
|
39
|
+
return {
|
|
40
|
+
policy_version: input.policy_version ?? "v1",
|
|
41
|
+
period_id: input.period_id ?? "default-period",
|
|
42
|
+
kill_switch: { global: false, agents: {} },
|
|
43
|
+
allowlists: {
|
|
44
|
+
action_types: input.allow_action_types ?? ["PROVISION"],
|
|
45
|
+
assets: input.allow_assets ?? [],
|
|
46
|
+
targets: input.allow_targets ?? []
|
|
47
|
+
},
|
|
48
|
+
budget: {
|
|
49
|
+
budget_limit: { [agent]: input.budget_limit ?? 1_000_000n },
|
|
50
|
+
spent_in_period: { [agent]: input.spent_in_period ?? 0n }
|
|
51
|
+
},
|
|
52
|
+
max_amount_per_action: { [agent]: input.max_amount_per_action ?? 1_000_000n },
|
|
53
|
+
velocity: {
|
|
54
|
+
config: {
|
|
55
|
+
window_seconds: input.velocity_window_seconds ?? 60,
|
|
56
|
+
max_actions: input.velocity_max_actions ?? 1000
|
|
57
|
+
},
|
|
58
|
+
counters: {}
|
|
59
|
+
},
|
|
60
|
+
replay: {
|
|
61
|
+
window_seconds: input.replay_window_seconds ?? 3600,
|
|
62
|
+
max_nonces_per_agent: input.replay_max_nonces_per_agent ?? 256,
|
|
63
|
+
nonces: {}
|
|
64
|
+
},
|
|
65
|
+
concurrency: {
|
|
66
|
+
max_concurrent: { [agent]: input.max_concurrent ?? 16 },
|
|
67
|
+
active: {},
|
|
68
|
+
active_auths: {}
|
|
69
|
+
},
|
|
70
|
+
recursion: {
|
|
71
|
+
max_depth: { [agent]: input.max_depth ?? 8 }
|
|
72
|
+
},
|
|
73
|
+
tool_limits: {
|
|
74
|
+
window_seconds: input.tool_window_seconds ?? 60,
|
|
75
|
+
max_calls: { [agent]: input.tool_max_calls ?? 1000 },
|
|
76
|
+
max_calls_by_tool: {},
|
|
77
|
+
calls: {}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
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/index.ts
ADDED
package/src/sdk.test.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
};
|