@decidio/sdk 0.1.5 → 0.1.6
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 +15 -2
- package/dist/adapters/_gate.d.ts +0 -2
- package/dist/adapters/_gate.js +0 -15
- package/dist/adapters/langgraph.d.ts +0 -3
- package/dist/adapters/langgraph.js +0 -10
- package/dist/adapters/openai.d.ts +0 -4
- package/dist/adapters/openai.js +0 -20
- package/dist/adapters/temporal.d.ts +0 -1
- package/dist/adapters/temporal.js +1 -13
- package/dist/cli.js +27 -106
- package/dist/guard.d.ts +0 -14
- package/dist/guard.js +1 -18
- package/dist/index.d.ts +0 -41
- package/dist/index.js +3 -81
- package/dist/inngest.d.ts +0 -20
- package/dist/inngest.js +0 -26
- package/dist/resume.d.ts +5 -61
- package/dist/resume.js +70 -66
- package/dist/signing.d.ts +0 -12
- package/dist/signing.js +0 -25
- package/openapi.yaml +1 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -192,8 +192,21 @@ also allow-list resume hosts (default-deny). That's operator territory: if you r
|
|
|
192
192
|
Decidio (or we run a pilot with you), the webhook is set up then. Against the hosted sandbox,
|
|
193
193
|
use the worker — a webhook secret your server never learned would (correctly) fail closed.
|
|
194
194
|
|
|
195
|
-
Either way, re-execution is **single-use
|
|
196
|
-
duplicate signal
|
|
195
|
+
Either way, re-execution is **single-use on the happy path**: the parked entry is claimed before
|
|
196
|
+
execution and deleted on success, so a duplicate signal finds nothing to replay. Honest edge — if
|
|
197
|
+
your action succeeds but the process dies *before* the entry is cleared, the claim can be retried
|
|
198
|
+
(robust crash-recovery of stale claims is on the roadmap), so make your downstream action
|
|
199
|
+
idempotent if you need exactly-once under crashes.
|
|
200
|
+
|
|
201
|
+
**Payload integrity.** When a routed action is parked, the SDK signs its arguments (bound to the
|
|
202
|
+
decision id) with your agent key; on resume it **refuses to execute** if the parked payload was
|
|
203
|
+
modified, its signature stripped, or the file swapped into another decision's slot. That defends
|
|
204
|
+
against tampering by anything lacking your agent's key — note a *fully compromised agent host*
|
|
205
|
+
can bypass the guard entirely (it holds both the key and the function), so the durable guarantee
|
|
206
|
+
that closes that gap is server-sealed argument binding, auditable in the receipt itself, which is
|
|
207
|
+
on the roadmap. **Upgrading from ≤0.1.5:** entries parked by an older version carry no signature
|
|
208
|
+
and are refused once your agent has a key — drain pending approvals before upgrading, or delete
|
|
209
|
+
the parked files and re-run those actions.
|
|
197
210
|
|
|
198
211
|
### Already on a durable engine? Use its native wait
|
|
199
212
|
|
package/dist/adapters/_gate.d.ts
CHANGED
|
@@ -7,7 +7,5 @@ export interface GateAuth {
|
|
|
7
7
|
}
|
|
8
8
|
export declare function gateAuthorize(config: GuardConfig, ctx: ApprovalContext): Promise<GateAuth>;
|
|
9
9
|
export declare function gateConfirm(config: GuardConfig, decisionId: string, result: unknown): Promise<void>;
|
|
10
|
-
/** Normalize a resume payload ({verdict} | "approved" | ...) to a verdict string. */
|
|
11
10
|
export declare function verdictFrom(payload: any): string;
|
|
12
|
-
/** Throw the right error for a non-approving verdict; return for approved/auto_approved. */
|
|
13
11
|
export declare function assertVerdict(verdict: string, decisionId: string, reason?: string): void;
|
package/dist/adapters/_gate.js
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
// Shared adapter gate logic (TS) — the thin-adapter rule in code. Every engine adapter
|
|
2
|
-
// reuses these gate calls + verdict handling; only the suspend/resume translation
|
|
3
|
-
// differs. Pure/testable so the offline smoke covers our logic; the engine-primitive
|
|
4
|
-
// call in each adapter is a thin one-liner verified by typecheck against real types.
|
|
5
1
|
import { DecidioBlockedError, DecidioRejectedError, signBoundProof, signConfirm } from "../index.js";
|
|
6
2
|
export async function gateAuthorize(config, ctx) {
|
|
7
3
|
const f = config.fetchImpl ?? fetch;
|
|
8
|
-
// Fall back to the resume controller's URL (matches the core wrapper) so an adapter
|
|
9
|
-
// decision carries a callback URL even when only resume.resumeUrl was set.
|
|
10
4
|
const resumeUrl = config.resumeUrl ?? config.resume?.resumeUrl;
|
|
11
|
-
// Sign the bound identity proof like the core wrapper, so an adapter-path agent can PROVE
|
|
12
|
-
// its identity and auto-approve (not just route). No key → unsigned → server routes.
|
|
13
5
|
const requesterIdentity = (config.agentKey && config.agentDid)
|
|
14
6
|
? signBoundProof(config.agentKey, { agentId: config.agentId, did: config.agentDid, action: ctx.action, amount: ctx.amount, scope: ctx.scope, workspaceId: config.workspaceId })
|
|
15
7
|
: undefined;
|
|
@@ -29,10 +21,7 @@ export async function gateAuthorize(config, ctx) {
|
|
|
29
21
|
export async function gateConfirm(config, decisionId, result) {
|
|
30
22
|
const f = config.fetchImpl ?? fetch;
|
|
31
23
|
try {
|
|
32
|
-
// Sign the confirm too (bound to {action:"confirm", scope:decisionId}) so the adapter path
|
|
33
|
-
// earns application_confirmed; without a key the server caps it at agent_asserted.
|
|
34
24
|
const requesterIdentity = signConfirm(decisionId, config);
|
|
35
|
-
// Same no-silent-downgrade rule as the inline wrap (guard equivalent-path catch).
|
|
36
25
|
if (!requesterIdentity)
|
|
37
26
|
console.warn(`[decidio] execution evidence for ${decisionId} recorded as agent_asserted — no signing key configured, so the report can't be proven to come from this agent. Set DECIDIO_AGENT_KEY/DID to earn application_confirmed.`);
|
|
38
27
|
const res = await f(config.apiUrl.replace(/\/$/, "") + "/agent/confirm", {
|
|
@@ -40,8 +29,6 @@ export async function gateConfirm(config, decisionId, result) {
|
|
|
40
29
|
headers: { "content-type": "application/json", ...(config.apiToken ? { authorization: `Bearer ${config.apiToken}` } : {}) },
|
|
41
30
|
body: JSON.stringify({ decisionId, capturedResponse: result ?? null, source: "wrapper", ...(requesterIdentity ? { requesterIdentity } : {}) }),
|
|
42
31
|
});
|
|
43
|
-
// fetch only rejects on network error — surface a non-2xx (auth/500) too. Best-effort:
|
|
44
|
-
// log, don't throw (the action already happened).
|
|
45
32
|
if (!res.ok)
|
|
46
33
|
console.warn(`[decidio] confirm returned ${res.status} (action succeeded)`);
|
|
47
34
|
}
|
|
@@ -49,11 +36,9 @@ export async function gateConfirm(config, decisionId, result) {
|
|
|
49
36
|
console.warn(`[decidio] confirm failed (action succeeded): ${e.message}`);
|
|
50
37
|
}
|
|
51
38
|
}
|
|
52
|
-
/** Normalize a resume payload ({verdict} | "approved" | ...) to a verdict string. */
|
|
53
39
|
export function verdictFrom(payload) {
|
|
54
40
|
return (payload && typeof payload === "object" ? payload.verdict : payload) ?? "";
|
|
55
41
|
}
|
|
56
|
-
/** Throw the right error for a non-approving verdict; return for approved/auto_approved. */
|
|
57
42
|
export function assertVerdict(verdict, decisionId, reason) {
|
|
58
43
|
if (verdict === "approved" || verdict === "auto_approved")
|
|
59
44
|
return;
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { Command } from "@langchain/langgraph";
|
|
2
2
|
import { type GuardConfig, type ApprovalContext } from "../index.js";
|
|
3
|
-
/** Gate `run()` behind Decidio inside a LangGraph node. */
|
|
4
3
|
export declare function decidioGate<R>(config: GuardConfig, ctx: ApprovalContext, run: () => Promise<R>): Promise<R>;
|
|
5
|
-
/** Translate Decidio's signed resume webhook into the LangGraph resume Command. The host
|
|
6
|
-
* maps decisionId → thread_id and passes this to graph.invoke/stream. */
|
|
7
4
|
export declare function decidioResumeCommand(body: {
|
|
8
5
|
verdict?: string;
|
|
9
6
|
reason?: string;
|
|
@@ -1,19 +1,11 @@
|
|
|
1
|
-
// @decidio/sdk/langgraph — durable suspend via LangGraph's native `interrupt()`.
|
|
2
|
-
// `interrupt` finds the running graph itself (no handle needed), so the gate drops into
|
|
3
|
-
// a node: authorize → on route, interrupt() durably pauses (the checkpointer persists;
|
|
4
|
-
// the process can die) → on resume (Command(resume={verdict})) run the agent's own
|
|
5
|
-
// action + confirm. Decidio is the approval system; the host bridges its webhook to the
|
|
6
|
-
// graph resume (decidioResumeCommand). Requires @langchain/langgraph (optional peer dep).
|
|
7
1
|
import { interrupt, Command } from "@langchain/langgraph";
|
|
8
2
|
import { DecidioBlockedError } from "../index.js";
|
|
9
3
|
import { gateAuthorize, gateConfirm, verdictFrom, assertVerdict } from "./_gate.js";
|
|
10
|
-
/** Gate `run()` behind Decidio inside a LangGraph node. */
|
|
11
4
|
export async function decidioGate(config, ctx, run) {
|
|
12
5
|
const auth = await gateAuthorize(config, ctx);
|
|
13
6
|
if (auth.decision === "block")
|
|
14
7
|
throw new DecidioBlockedError(auth.reason ?? "blocked by policy", auth.decisionId);
|
|
15
8
|
if (auth.decision === "route") {
|
|
16
|
-
// Durable pause. On resume, interrupt() returns the Command(resume=...) payload.
|
|
17
9
|
const resumed = interrupt({ decidio: { decisionId: auth.decisionId, action: ctx.action, amount: ctx.amount, scope: ctx.scope } });
|
|
18
10
|
assertVerdict(verdictFrom(resumed), auth.decisionId, resumed?.reason);
|
|
19
11
|
}
|
|
@@ -21,8 +13,6 @@ export async function decidioGate(config, ctx, run) {
|
|
|
21
13
|
await gateConfirm(config, auth.decisionId, result);
|
|
22
14
|
return result;
|
|
23
15
|
}
|
|
24
|
-
/** Translate Decidio's signed resume webhook into the LangGraph resume Command. The host
|
|
25
|
-
* maps decisionId → thread_id and passes this to graph.invoke/stream. */
|
|
26
16
|
export function decidioResumeCommand(body) {
|
|
27
17
|
return new Command({ resume: { verdict: body.verdict, reason: body.reason } });
|
|
28
18
|
}
|
|
@@ -14,13 +14,9 @@ export interface Pending {
|
|
|
14
14
|
item: ToolApprovalItem;
|
|
15
15
|
decisionId: string;
|
|
16
16
|
}
|
|
17
|
-
/** Ask Decidio for each pending tool-approval on a RunState.
|
|
18
|
-
* proceed → state.approve(item); block → state.reject(item); route → pending (a human
|
|
19
|
-
* decides via Decidio; the host parks state.toString() + resumes via applyResume). */
|
|
20
17
|
export declare function gateInterruptions(config: GuardConfig, state: RunStateLike, describe: (item: ToolApprovalItem) => ApprovalContext): Promise<{
|
|
21
18
|
resolved: Resolved[];
|
|
22
19
|
pending: Pending[];
|
|
23
20
|
}>;
|
|
24
|
-
/** Apply a Decidio verdict to a resumed RunState (after RunState.fromString). */
|
|
25
21
|
export declare function applyResume(state: RunStateLike, item: ToolApprovalItem, verdict: string): void;
|
|
26
22
|
export {};
|
package/dist/adapters/openai.js
CHANGED
|
@@ -1,23 +1,4 @@
|
|
|
1
|
-
// @decidio/sdk/openai — gate OpenAI Agents SDK tool-approval interruptions through
|
|
2
|
-
// Decidio, with durable suspend via the SDK's native RunState serialization.
|
|
3
|
-
//
|
|
4
|
-
// The Agents loop surfaces tool approvals on the RunState (state.getInterruptions());
|
|
5
|
-
// you state.approve(item)/reject(item) then resume with run(agent, state). RunState
|
|
6
|
-
// serializes (state.toString() / RunState.fromString(agent, s)) — that's the durability:
|
|
7
|
-
// park the serialized state agent-side on `route`, resume on Decidio's approval.
|
|
8
|
-
//
|
|
9
|
-
// const state = result.state; // or RunState from the run
|
|
10
|
-
// const { resolved, pending } = await gateInterruptions(config, state, describe);
|
|
11
|
-
// if (pending.length) { await store.put(decisionId, state.toString()); return; } // suspend
|
|
12
|
-
// result = await run(agent, state); // all resolved → resume
|
|
13
|
-
// // on Decidio approval webhook: state = await RunState.fromString(agent, parked);
|
|
14
|
-
// // applyResume(state, item, verdict); result = await run(agent, state);
|
|
15
|
-
//
|
|
16
|
-
// Duck-typed on the state/item (no hard @openai/agents import) so it stays thin.
|
|
17
1
|
import { gateAuthorize } from "./_gate.js";
|
|
18
|
-
/** Ask Decidio for each pending tool-approval on a RunState.
|
|
19
|
-
* proceed → state.approve(item); block → state.reject(item); route → pending (a human
|
|
20
|
-
* decides via Decidio; the host parks state.toString() + resumes via applyResume). */
|
|
21
2
|
export async function gateInterruptions(config, state, describe) {
|
|
22
3
|
const resolved = [];
|
|
23
4
|
const pending = [];
|
|
@@ -36,7 +17,6 @@ export async function gateInterruptions(config, state, describe) {
|
|
|
36
17
|
}
|
|
37
18
|
return { resolved, pending };
|
|
38
19
|
}
|
|
39
|
-
/** Apply a Decidio verdict to a resumed RunState (after RunState.fromString). */
|
|
40
20
|
export function applyResume(state, item, verdict) {
|
|
41
21
|
if (verdict === "approved" || verdict === "auto_approved")
|
|
42
22
|
state.approve(item);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { type GuardConfig, type ApprovalContext } from "../index.js";
|
|
2
2
|
import { type GateAuth } from "./_gate.js";
|
|
3
3
|
export declare const decidioResumeSignal: import("@temporalio/workflow").SignalDefinition<[string, string, (string | undefined)?]>;
|
|
4
|
-
/** Call inside the workflow to wire the resume signal; returns the verdict store. */
|
|
5
4
|
export declare function installDecidioSignal(): Map<string, {
|
|
6
5
|
verdict: string;
|
|
7
6
|
reason?: string;
|
|
@@ -1,19 +1,7 @@
|
|
|
1
|
-
// @decidio/sdk/temporal — durable suspend via Temporal's native `condition()` + a
|
|
2
|
-
// Decidio resume signal. Strongest durability (the workflow survives worker restarts for
|
|
3
|
-
// days); the agent's action runs on resume. Requires @temporalio/workflow (optional peer).
|
|
4
|
-
//
|
|
5
|
-
// Inside your workflow:
|
|
6
|
-
// const verdicts = installDecidioSignal(); // wires the resume signal
|
|
7
|
-
// const out = await decidioGate(config, ctx, () => createOpp(o), verdicts, {
|
|
8
|
-
// authorize: () => executeActivity(authorizeActivity, ...), // HTTP belongs in activities
|
|
9
|
-
// confirm: (id, r) => executeActivity(confirmActivity, ...),
|
|
10
|
-
// });
|
|
11
|
-
// Decidio's webhook bridges to: handle.signal(decidioResumeSignal, decisionId, verdict, reason)
|
|
12
1
|
import { condition, defineSignal, setHandler } from "@temporalio/workflow";
|
|
13
2
|
import { DecidioBlockedError } from "../index.js";
|
|
14
3
|
import { gateAuthorize, gateConfirm, verdictFrom, assertVerdict } from "./_gate.js";
|
|
15
4
|
export const decidioResumeSignal = defineSignal("decidioResume");
|
|
16
|
-
/** Call inside the workflow to wire the resume signal; returns the verdict store. */
|
|
17
5
|
export function installDecidioSignal() {
|
|
18
6
|
const verdicts = new Map();
|
|
19
7
|
setHandler(decidioResumeSignal, (decisionId, verdict, reason) => { verdicts.set(decisionId, { verdict, reason }); });
|
|
@@ -24,7 +12,7 @@ export async function decidioGate(config, ctx, run, verdicts, opts = {}) {
|
|
|
24
12
|
if (auth.decision === "block")
|
|
25
13
|
throw new DecidioBlockedError(auth.reason ?? "blocked by policy", auth.decisionId);
|
|
26
14
|
if (auth.decision === "route") {
|
|
27
|
-
await condition(() => verdicts.has(auth.decisionId));
|
|
15
|
+
await condition(() => verdicts.has(auth.decisionId));
|
|
28
16
|
const v = verdicts.get(auth.decisionId);
|
|
29
17
|
assertVerdict(verdictFrom(v), auth.decisionId, v.reason);
|
|
30
18
|
}
|
package/dist/cli.js
CHANGED
|
@@ -1,23 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// @decidio/sdk CLI — the on-ramp. Run via `npx @decidio/sdk <cmd>` (or tsx in-repo).
|
|
3
|
-
// init [agentId] sign in, register the agent, mint its API token, write .env
|
|
4
|
-
// doctor check config + connectivity + durability readiness
|
|
5
|
-
// approvals list pending agent approvals; `approvals approve|reject <id> [reason]`
|
|
6
|
-
// receipt <id> download a sealed decision's Authority Receipt to a .json file
|
|
7
|
-
// dev [--target U] signal-only relay: forwards Decidio's webhook to your local agent URL
|
|
8
|
-
//
|
|
9
|
-
// TWO TOKEN KINDS, deliberately (self-serve design 2026-08-28): commands that ADMINISTER a
|
|
10
|
-
// workspace (init's register+mint, approvals, receipt) need the owner's SESSION token — acquired
|
|
11
|
-
// interactively per run or from DECIDIO_SESSION_TOKEN, held in memory, NEVER written to disk.
|
|
12
|
-
// What lands in .env as DECIDIO_API_TOKEN is the floor-limited AGENT token the runtime SDK
|
|
13
|
-
// needs (it can call /agent/* and nothing else). A session-capable secret must never live in
|
|
14
|
-
// an agent host's .env — that was the pre-publish design's hole.
|
|
15
|
-
//
|
|
16
|
-
// MESSAGE DISCIPLINE (founder bar 2026-08-29, from the Codex acceptance run): every failure
|
|
17
|
-
// tells the developer WHAT happened, WHY, and the EXACT next command. Every success says what
|
|
18
|
-
// changed, where, and what to do next. No stack traces for expected failures; no green ✔ for a
|
|
19
|
-
// no-op (the replay-approve bug: the server's idempotent alreadyResolved was reported as a
|
|
20
|
-
// fresh success — an authority CLI may never dress a no-op as an action).
|
|
21
2
|
import { readFileSync, writeFileSync, existsSync, appendFileSync } from "node:fs";
|
|
22
3
|
import { randomBytes } from "node:crypto";
|
|
23
4
|
import { createServer } from "node:http";
|
|
@@ -34,8 +15,6 @@ const VERSION = (() => {
|
|
|
34
15
|
return "unknown";
|
|
35
16
|
}
|
|
36
17
|
})();
|
|
37
|
-
// ---- environment: load the CWD .env FIRST (Codex finding: init writes .env, but the next
|
|
38
|
-
// command ignored it and pointed at localhost). Process env always wins — .env only fills gaps.
|
|
39
18
|
function readEnvFile() {
|
|
40
19
|
const out = {};
|
|
41
20
|
if (existsSync(".env"))
|
|
@@ -47,9 +26,6 @@ function readEnvFile() {
|
|
|
47
26
|
return out;
|
|
48
27
|
}
|
|
49
28
|
for (const [k, v] of Object.entries(readEnvFile())) {
|
|
50
|
-
// NEVER autoload the session token from disk: the whole design splits session (memory-only)
|
|
51
|
-
// from the floor-limited agent token (.env). Silently honoring a parked session token would
|
|
52
|
-
// reward exactly the credential-at-rest the split exists to prevent (guard catch 2026-08-29).
|
|
53
29
|
if (k === "DECIDIO_SESSION_TOKEN") {
|
|
54
30
|
console.log("⚠ ignoring DECIDIO_SESSION_TOKEN found in .env — session tokens belong in your shell or CI secret store, never on disk. Remove that line; export it in the shell instead.");
|
|
55
31
|
continue;
|
|
@@ -57,17 +33,11 @@ for (const [k, v] of Object.entries(readEnvFile())) {
|
|
|
57
33
|
if (process.env[k] === undefined)
|
|
58
34
|
process.env[k] = v;
|
|
59
35
|
}
|
|
60
|
-
// `quickstart` is the zero-config guided tour: with no DECIDIO_API_URL anywhere it targets the
|
|
61
|
-
// hosted sandbox (and says so); every other command keeps the localhost dev default.
|
|
62
36
|
const IS_QUICKSTART = process.argv[2] === "quickstart";
|
|
63
37
|
const API = (process.env.DECIDIO_API_URL ?? (IS_QUICKSTART ? "https://decidio-api.onrender.com" : "http://localhost:4000")).replace(/\/$/, "");
|
|
64
|
-
// Token is read AT CALL TIME, not captured at import: init mints a fresh agent token mid-run and
|
|
65
|
-
// its closing doctor must probe with THAT, not the pre-init env (round-3 Codex catch — the stale
|
|
66
|
-
// capture made init's own doctor print "token: REJECTED" seconds after a successful mint).
|
|
67
38
|
const TOKEN = () => process.env.DECIDIO_API_TOKEN;
|
|
68
39
|
const ENV_PATH = ".env";
|
|
69
40
|
const log = (...a) => console.log(...a);
|
|
70
|
-
/** Fetch wrapper that turns transport failures into guidance instead of a stack trace. */
|
|
71
41
|
async function api(path, init = {}, bearer) {
|
|
72
42
|
const auth = bearer ?? TOKEN();
|
|
73
43
|
let res;
|
|
@@ -102,10 +72,8 @@ function upsertEnv(updates) {
|
|
|
102
72
|
const body = Object.entries(merged).map(([k, v]) => `${k}=${v}`).join("\n") + "\n";
|
|
103
73
|
writeFileSync(ENV_PATH, body);
|
|
104
74
|
}
|
|
105
|
-
/** Keep secrets out of git without nagging: append the two entries to .gitignore when they're
|
|
106
|
-
* not already covered. Creating the file in a non-git directory is harmless. */
|
|
107
75
|
function ensureGitignore() {
|
|
108
|
-
const entries = [".env", ".decidio-pending/"];
|
|
76
|
+
const entries = [".env", ".env.quickstart", ".decidio-pending/"];
|
|
109
77
|
const existing = existsSync(".gitignore") ? readFileSync(".gitignore", "utf8") : "";
|
|
110
78
|
const missing = entries.filter((e) => !existing.split(/\r?\n/).some((l) => l.trim() === e || l.trim() === e.replace(/\/$/, "")));
|
|
111
79
|
if (!missing.length)
|
|
@@ -113,12 +81,10 @@ function ensureGitignore() {
|
|
|
113
81
|
appendFileSync(".gitignore", (existing && !existing.endsWith("\n") ? "\n" : "") + missing.join("\n") + "\n");
|
|
114
82
|
log(`✔ added ${missing.join(" + ")} to .gitignore (your agent key and parked actions stay out of git)`);
|
|
115
83
|
}
|
|
116
|
-
// ---- interactive input (session sign-in) ---------------------------------------------------
|
|
117
84
|
function ask(question, mute = false) {
|
|
118
85
|
return new Promise((resolve) => {
|
|
119
86
|
const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
120
87
|
if (mute) {
|
|
121
|
-
// Hide password echo: after the prompt renders, swallow subsequent output writes.
|
|
122
88
|
const anyRl = rl;
|
|
123
89
|
const write = anyRl._writeToOutput?.bind(rl);
|
|
124
90
|
anyRl._writeToOutput = (s) => { if (s.includes(question))
|
|
@@ -128,14 +94,9 @@ function ask(question, mute = false) {
|
|
|
128
94
|
process.stdout.write("\n"); resolve(answer.trim()); });
|
|
129
95
|
});
|
|
130
96
|
}
|
|
131
|
-
/** Owner SESSION token, memory-only. Sources in order: DECIDIO_SESSION_TOKEN env, interactive
|
|
132
|
-
* email+password (`/demo/login`), pasted sign-in link (`/demo/login-consume` — for the
|
|
133
|
-
* password-less accounts the welcome flow creates). Never persisted. */
|
|
134
97
|
async function acquireSession(purpose) {
|
|
135
98
|
if (process.env.DECIDIO_SESSION_TOKEN)
|
|
136
99
|
return process.env.DECIDIO_SESSION_TOKEN;
|
|
137
|
-
// Same rule the server applies to resume URLs: credentials never ride plaintext http to a
|
|
138
|
-
// non-loopback host. Refuse before prompting, not after collecting a password.
|
|
139
100
|
if (/^http:\/\//i.test(API) && !/^http:\/\/(localhost|127\.0\.0\.1|\[::1\])([:/]|$)/i.test(API)) {
|
|
140
101
|
log(`✘ refusing to send sign-in credentials over plaintext http to ${API}.`);
|
|
141
102
|
log(` Use https (the hosted sandbox is https://decidio-api.onrender.com) or localhost for local dev.`);
|
|
@@ -179,14 +140,10 @@ async function acquireSession(purpose) {
|
|
|
179
140
|
}
|
|
180
141
|
async function cmdInit(agentId) {
|
|
181
142
|
log(`Registering agent "${agentId}" with Decidio at ${API}…`);
|
|
182
|
-
// CLIENT-SIDE KEYGEN: generate the Ed25519 keypair LOCALLY and register only the PUBLIC
|
|
183
|
-
// did:key. The private key NEVER crosses the wire — it goes straight to this host's env.
|
|
184
143
|
const existingKey = readEnvFile().DECIDIO_AGENT_KEY;
|
|
185
144
|
const sourceSystem = process.env.DECIDIO_SOURCE_SYSTEM || readEnvFile().DECIDIO_SOURCE_SYSTEM;
|
|
186
145
|
const { did: localDid, privateKeyBase64: localKey } = generateKeypair();
|
|
187
146
|
const registerBody = JSON.stringify({ agentId, did: localDid, ...(sourceSystem ? { sourceSystem } : {}) });
|
|
188
|
-
// First try whatever bearer the env already carries (the local-dev / operator path — a static
|
|
189
|
-
// admin token, or dev-open auth). On 401/403 fall through to the self-serve session flow.
|
|
190
147
|
let session = null;
|
|
191
148
|
let r = await api("/api/agents/register", { method: "POST", body: registerBody });
|
|
192
149
|
if (r.status === 401 || r.status === 403) {
|
|
@@ -197,10 +154,6 @@ async function cmdInit(agentId) {
|
|
|
197
154
|
}
|
|
198
155
|
let did, privateKey;
|
|
199
156
|
if (r.status === 409) {
|
|
200
|
-
// Already registered to a server-held did. WITHOUT the original private key on this machine
|
|
201
|
-
// this is a HARD STOP, not a degraded continue (round-3 Codex catch: init used to write a
|
|
202
|
-
// keyless .env and print happy-path next steps — every request then routed unproven and
|
|
203
|
-
// execution evidence silently capped at agent_asserted).
|
|
204
157
|
did = r.body?.did;
|
|
205
158
|
if (existingKey) {
|
|
206
159
|
log(`• "${agentId}" is already registered in this workspace (did ${did}) — reusing it with the DECIDIO_AGENT_KEY already in .env.`);
|
|
@@ -227,10 +180,6 @@ async function cmdInit(agentId) {
|
|
|
227
180
|
log(` Fix the cause above and re-run — init is safe to repeat.`);
|
|
228
181
|
return 1;
|
|
229
182
|
}
|
|
230
|
-
// Mint the runtime credential: the floor-limited AGENT token (can call /agent/* only). This is
|
|
231
|
-
// what .env gets — the session token stays in memory and dies with this process. On boxes
|
|
232
|
-
// without the tester-lab fence (plain local dev) the mint endpoint refuses; that's fine —
|
|
233
|
-
// local dev auths with its existing env and we say so instead of failing the init.
|
|
234
183
|
let agentToken, workspaceId;
|
|
235
184
|
const mintBearer = session ?? TOKEN();
|
|
236
185
|
if (mintBearer) {
|
|
@@ -256,15 +205,12 @@ async function cmdInit(agentId) {
|
|
|
256
205
|
if (did)
|
|
257
206
|
envOut.DECIDIO_AGENT_DID = did;
|
|
258
207
|
if (privateKey)
|
|
259
|
-
envOut.DECIDIO_AGENT_KEY = privateKey;
|
|
208
|
+
envOut.DECIDIO_AGENT_KEY = privateKey;
|
|
260
209
|
if (agentToken)
|
|
261
|
-
envOut.DECIDIO_API_TOKEN = agentToken;
|
|
210
|
+
envOut.DECIDIO_API_TOKEN = agentToken;
|
|
262
211
|
if (workspaceId)
|
|
263
212
|
envOut.DECIDIO_WORKSPACE_ID = workspaceId;
|
|
264
213
|
upsertEnv(envOut);
|
|
265
|
-
// The closing doctor (and anything else this process does) must see what init just wrote —
|
|
266
|
-
// OVERWRITE process.env with the fresh values (round-3 Codex catch: doctor probed with the
|
|
267
|
-
// stale pre-init token and reported the mint it just watched as REJECTED).
|
|
268
214
|
for (const [k, v] of Object.entries(envOut))
|
|
269
215
|
process.env[k] = v;
|
|
270
216
|
log(`✔ wrote ${ENV_PATH} — every decidio command reads it from this directory (your script loads it with node --env-file=.env)`);
|
|
@@ -305,27 +251,17 @@ async function cmdDoctor() {
|
|
|
305
251
|
line(!!(process.env.DECIDIO_AGENT_ID || env.DECIDIO_AGENT_ID), `agent id: ${process.env.DECIDIO_AGENT_ID || env.DECIDIO_AGENT_ID || "(unset — run: npx @decidio/sdk init <agent-name>)"}`);
|
|
306
252
|
const secret = process.env.DECIDIO_WEBHOOK_SECRET || env.DECIDIO_WEBHOOK_SECRET;
|
|
307
253
|
line(!!secret, secret ? "webhook secret: set — operator-only transport (inactive against the hosted sandbox; the poll worker guard.worker() is the self-serve resume path)" : "webhook secret: MISSING — the resume webhook (operator deployments) would fail closed; init generates one");
|
|
308
|
-
// Signing key — REQUIRED to be ELIGIBLE for auto-approve. Whether anything auto-approves is
|
|
309
|
-
// the workspace policy's call, not this key's (Codex finding: the old wording claimed
|
|
310
|
-
// "auto-approve is in effect" while deny-by-default routed everything).
|
|
311
254
|
const signing = (process.env.DECIDIO_AGENT_KEY || env.DECIDIO_AGENT_KEY) && (process.env.DECIDIO_AGENT_DID || env.DECIDIO_AGENT_DID);
|
|
312
255
|
if (signing)
|
|
313
256
|
log("✔ signing key: set — the agent can prove its identity, making it ELIGIBLE for policy auto-approve. Whether a rule matches is your workspace policy's decision; with no matching rule every request routes to a human (deny-by-default).");
|
|
314
257
|
else
|
|
315
258
|
log("⚠ signing key: MISSING (DECIDIO_AGENT_KEY/DID) — the agent can't prove identity, so EVERY request routes to a human. Run `npx @decidio/sdk init` to generate one.");
|
|
316
|
-
// Reachability and token role are SEPARATE checks (the old probe called any <500 green, so a
|
|
317
|
-
// wrong-kind token looked healthy). /api/health is unauthenticated truth about reachability;
|
|
318
|
-
// /api/records then classifies the token.
|
|
319
259
|
const h = await api("/api/health", {}, "");
|
|
320
260
|
line(h.status >= 200 && h.status < 300, `connectivity: API reachable (${h.status})`);
|
|
321
261
|
const rec = await api("/api/records");
|
|
322
262
|
if (rec.status >= 200 && rec.status < 300)
|
|
323
263
|
log("✔ token scope: session/admin (init, approvals and receipt work from this env)");
|
|
324
264
|
else if (rec.status === 401) {
|
|
325
|
-
// A 401 off the agent floor could also mean an INVALID token (Codex catch: don't
|
|
326
|
-
// false-green expiry). Disambiguate with a floor probe: a valid agent token gets 404 for an
|
|
327
|
-
// unknown decision (authorized, not found); an invalid one gets 401 again. A rejection here
|
|
328
|
-
// flips the overall verdict — "all green" after a red line was round 3's other doctor bug.
|
|
329
265
|
const probe = await api("/agent/status/doctor-scope-probe");
|
|
330
266
|
if (probe.status === 404)
|
|
331
267
|
log("✔ token scope: agent floor — correct for the runtime SDK (approvals/receipt will ask you to sign in)");
|
|
@@ -340,8 +276,6 @@ async function cmdDoctor() {
|
|
|
340
276
|
return ok ? 0 : 1;
|
|
341
277
|
}
|
|
342
278
|
async function cmdApprovals(sub, id, reason) {
|
|
343
|
-
// Approvals are a HUMAN act on the workspace — session scope, never the agent token. If the
|
|
344
|
-
// env's token is agent-floor (401 on /api/*), sign in interactively; memory-only as always.
|
|
345
279
|
const jsonMode = sub === "--json";
|
|
346
280
|
if (jsonMode)
|
|
347
281
|
sub = undefined;
|
|
@@ -360,9 +294,6 @@ async function cmdApprovals(sub, id, reason) {
|
|
|
360
294
|
}
|
|
361
295
|
const r = await api(`/api/decisions/${encodeURIComponent(id)}/${sub === "approve" ? "approve" : "reject"}`, { method: "POST", body: JSON.stringify({ reason: reason ?? undefined }) }, bearer);
|
|
362
296
|
if (r.status >= 200 && r.status < 300) {
|
|
363
|
-
// HONESTY over green checkmarks (Codex finding: the server resolves each decision exactly
|
|
364
|
-
// once and answers replays idempotently — a replay is a NO-OP and must read as one, never
|
|
365
|
-
// as a fresh approval). Non-zero exit so scripts can tell the difference.
|
|
366
297
|
if (r.body?.alreadyResolved) {
|
|
367
298
|
log(`• ${id} was already resolved — nothing changed.`);
|
|
368
299
|
log(` Decisions are single-use: a second ${sub} can never re-execute the action (that's the replay protection working).`);
|
|
@@ -392,7 +323,6 @@ async function cmdApprovals(sub, id, reason) {
|
|
|
392
323
|
const cards = r.body?.cards ?? [];
|
|
393
324
|
const pending = cards.filter((c) => c.isAgentRequest && c.status !== "sealed");
|
|
394
325
|
if (jsonMode) {
|
|
395
|
-
// Stable machine output (round-3 ask): agents scripting the queue parse this, not prose.
|
|
396
326
|
log(JSON.stringify(pending.map((c) => ({ decisionId: c.id, action: c.agentRequest?.action ?? null, amount: c.agentRequest?.amount ?? null, title: c.title ?? null })), null, 2));
|
|
397
327
|
return 0;
|
|
398
328
|
}
|
|
@@ -418,8 +348,6 @@ async function cmdReceipt(id, jsonMode = false) {
|
|
|
418
348
|
log(" Find decision ids via `npx @decidio/sdk approvals` or in your Decidio app.");
|
|
419
349
|
return 2;
|
|
420
350
|
}
|
|
421
|
-
// Receipts are workspace data — session scope, like approvals. (The record-detail /verify
|
|
422
|
-
// route carries the credential; the plain record route does not — Codex finding.)
|
|
423
351
|
let bearer;
|
|
424
352
|
let r = await api(`/api/records/${encodeURIComponent(id)}/verify`);
|
|
425
353
|
if (r.status === 401 || r.status === 403) {
|
|
@@ -439,11 +367,18 @@ async function cmdReceipt(id, jsonMode = false) {
|
|
|
439
367
|
return 1;
|
|
440
368
|
}
|
|
441
369
|
const name = `decidio-receipt-${id.replace(/[^A-Za-z0-9_-]/g, "")}.json`;
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
370
|
+
const nextBytes = JSON.stringify(r.body.credential, null, 2) + "\n";
|
|
371
|
+
if (existsSync(name)) {
|
|
372
|
+
if (readFileSync(name, "utf8") !== nextBytes) {
|
|
373
|
+
log(`✘ ${name} already exists with DIFFERENT bytes — refusing to overwrite an audit artifact. Move it aside or delete it, then re-run.`);
|
|
374
|
+
return 1;
|
|
375
|
+
}
|
|
376
|
+
log(`• ${name} already exists (byte-identical — receipts are immutable); leaving it in place.`);
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
writeFileSync(name, nextBytes);
|
|
380
|
+
log(`✔ wrote ${name} (downloaded from your workspace — the download is online; VERIFYING it is offline)`);
|
|
381
|
+
}
|
|
447
382
|
const detail = await api(`/api/records/${encodeURIComponent(id)}`, {}, bearer);
|
|
448
383
|
const conf = detail.body?.record?.confirmation;
|
|
449
384
|
if (jsonMode) {
|
|
@@ -452,19 +387,12 @@ async function cmdReceipt(id, jsonMode = false) {
|
|
|
452
387
|
}
|
|
453
388
|
if (conf?.status)
|
|
454
389
|
log(` Sealed credential: the authority decision (its confirmation field reads "pending" forever — immutable). Current record evidence tier: ${conf.status}${conf.confirmed ? " (confirmed)" : ""}.`);
|
|
455
|
-
// Emit EXACTLY the deterministic command we document (round-4 catch: the README and this hint
|
|
456
|
-
// disagreed, and bare npx re-probes the registry) — the verifier ships INSIDE this package.
|
|
457
390
|
log(` Verify it offline — no Decidio account or network needed (the verifier is built in):`);
|
|
458
391
|
log(` npx @decidio/sdk verify ${name}`);
|
|
459
392
|
log(` To also prove WHO issued it, pin your workspace's issuer DID (shown in your app's record verify panel):`);
|
|
460
393
|
log(` npx @decidio/sdk verify --issuer <did:key:...> ${name}`);
|
|
461
394
|
return 0;
|
|
462
395
|
}
|
|
463
|
-
// ---- verify (embedded) ----------------------------------------------------------------------
|
|
464
|
-
/** Duplicate-key-refusing parse — the SAME semantics as the decidio-verify CLI (RFC 8785 §3.1:
|
|
465
|
-
* duplicate keys make different parsers disagree on which value is real; refuse instead).
|
|
466
|
-
* Conformance-locked: the cross-language runner asserts this door refuses the same vectors for
|
|
467
|
-
* the same reasons as decidio-verify and python -m decidio.verify. */
|
|
468
396
|
function parseReceiptStrict(text, file) {
|
|
469
397
|
const value = JSON.parse(text);
|
|
470
398
|
const stack = [];
|
|
@@ -514,6 +442,10 @@ function parseReceiptStrict(text, file) {
|
|
|
514
442
|
}
|
|
515
443
|
function cmdVerify(argsIn) {
|
|
516
444
|
const issIdx = argsIn.indexOf("--issuer");
|
|
445
|
+
if (issIdx >= 0 && (argsIn[issIdx + 1] === undefined || argsIn[issIdx + 1].startsWith("--"))) {
|
|
446
|
+
log("✘ --issuer needs a did:key value.");
|
|
447
|
+
return 2;
|
|
448
|
+
}
|
|
517
449
|
const expectedIssuer = issIdx >= 0 ? argsIn[issIdx + 1] : undefined;
|
|
518
450
|
const files = argsIn.filter((a, i) => !a.startsWith("--") && !(issIdx >= 0 && i === issIdx + 1));
|
|
519
451
|
if (!files.length) {
|
|
@@ -544,7 +476,6 @@ function cmdVerify(argsIn) {
|
|
|
544
476
|
}
|
|
545
477
|
return allOk ? 0 : 1;
|
|
546
478
|
}
|
|
547
|
-
// ---- quickstart (the guided two-minute tour) ------------------------------------------------
|
|
548
479
|
async function cmdQuickstart() {
|
|
549
480
|
const t0 = Date.now();
|
|
550
481
|
log("Decidio quickstart — gate an action, route it to a human, execute on approval, hold the proof.");
|
|
@@ -553,9 +484,6 @@ async function cmdQuickstart() {
|
|
|
553
484
|
const session = await acquireSession("The quickstart");
|
|
554
485
|
if (!session)
|
|
555
486
|
return 1;
|
|
556
|
-
// 1. A collision-proof agent, registered fresh every run (round-4 ask: idempotent sample
|
|
557
|
-
// registration). Key generated locally; floor-limited token minted; .env written so the
|
|
558
|
-
// take-away snippet works in this directory afterward.
|
|
559
487
|
const agentId = `quickstart-${randomBytes(2).toString("hex")}`;
|
|
560
488
|
log(`① Registering agent "${agentId}" (fresh name each run — no collisions)…`);
|
|
561
489
|
const { did, privateKeyBase64 } = generateKeypair();
|
|
@@ -574,9 +502,6 @@ async function cmdQuickstart() {
|
|
|
574
502
|
DECIDIO_AGENT_KEY: privateKeyBase64, DECIDIO_API_TOKEN: mint.body.token,
|
|
575
503
|
DECIDIO_WORKSPACE_ID: mint.body.workspaceId ?? "", DECIDIO_WEBHOOK_SECRET: readEnvFile().DECIDIO_WEBHOOK_SECRET || randomBytes(32).toString("hex"),
|
|
576
504
|
};
|
|
577
|
-
// NEVER clobber a real agent's key (guard catch: keys never leave this machine — overwriting
|
|
578
|
-
// .env's DECIDIO_AGENT_KEY orphans that agent irrecoverably). A directory that already holds
|
|
579
|
-
// one gets a TOUR-SCOPED env file instead.
|
|
580
505
|
let envFile = ENV_PATH;
|
|
581
506
|
if (readEnvFile().DECIDIO_AGENT_KEY) {
|
|
582
507
|
envFile = ".env.quickstart";
|
|
@@ -589,11 +514,11 @@ async function cmdQuickstart() {
|
|
|
589
514
|
ensureGitignore();
|
|
590
515
|
log(` ✔ key generated locally (public half registered) · floor-limited token minted · ${envFile} written`);
|
|
591
516
|
log("");
|
|
592
|
-
|
|
593
|
-
log(`② Gating a demo action with ONE line — guard.protect(payInvoice, …):`);
|
|
517
|
+
log(`② Gating a demo action — the tour runs this (blocking mode: watch it live):`);
|
|
594
518
|
log("");
|
|
595
519
|
log(` const payInvoice = guard.protect(payInvoiceRaw,`);
|
|
596
|
-
log(` (inv) => ({ action: "payInvoice", amount: inv.amount, scope: "Invoice" }))
|
|
520
|
+
log(` (inv) => ({ action: "payInvoice", amount: inv.amount, scope: "Invoice" }),`);
|
|
521
|
+
log(` { mode: "blocking" }); // your real agent typically omits this — durable + a worker`);
|
|
597
522
|
log("");
|
|
598
523
|
let decisionId = "";
|
|
599
524
|
const cfg = {
|
|
@@ -615,7 +540,6 @@ async function cmdQuickstart() {
|
|
|
615
540
|
log(` ✔ $86,000 payInvoice ROUTED to a human (decision ${decisionId}) — this agent has no auto-approve rule,`);
|
|
616
541
|
log(` so deny-by-default sends everything to a person. Nothing executes until someone decides.`);
|
|
617
542
|
log("");
|
|
618
|
-
// 3. The human moment — approve right here, or from the Decidio app.
|
|
619
543
|
if (process.stdin.isTTY) {
|
|
620
544
|
await ask(`③ YOU are the approver. Press Enter to approve it here (or approve it in your Decidio queue first, then Enter): `);
|
|
621
545
|
}
|
|
@@ -628,7 +552,6 @@ async function cmdQuickstart() {
|
|
|
628
552
|
return 1;
|
|
629
553
|
}
|
|
630
554
|
log(` ✔ approved${ap.body?.alreadyResolved ? " (it was already approved from your app — even better)" : ""}`);
|
|
631
|
-
// 4. The blocking wrapper wakes and runs the agent's own function.
|
|
632
555
|
let result;
|
|
633
556
|
try {
|
|
634
557
|
result = await running;
|
|
@@ -639,7 +562,6 @@ async function cmdQuickstart() {
|
|
|
639
562
|
}
|
|
640
563
|
log(` ✔ your function executed AFTER approval: ${JSON.stringify(result)}`);
|
|
641
564
|
log("");
|
|
642
|
-
// 5. The proof: download the receipt and verify it offline, all in-process.
|
|
643
565
|
log("④ Downloading the sealed Authority Receipt and verifying it OFFLINE…");
|
|
644
566
|
const rec = await api(`/api/records/${encodeURIComponent(decisionId)}/verify`, {}, session);
|
|
645
567
|
if (!(rec.status >= 200 && rec.status < 300) || !rec.body?.credential) {
|
|
@@ -649,25 +571,24 @@ async function cmdQuickstart() {
|
|
|
649
571
|
const fname = `decidio-receipt-${decisionId.replace(/[^A-Za-z0-9_-]/g, "")}.json`;
|
|
650
572
|
writeFileSync(fname, JSON.stringify(rec.body.credential, null, 2) + "\n");
|
|
651
573
|
const v = verifyReceipt(rec.body.credential, undefined);
|
|
652
|
-
log(` ${v.valid ? "✔ VALID" : "✘ INVALID"} — ${fname} (signature + content binding
|
|
574
|
+
log(` ${v.valid ? "✔ VALID" : "✘ INVALID"} — ${fname} (signature + content binding, offline; issuer UNPINNED — bind it with: npx @decidio/sdk verify --issuer <your workspace DID>)`);
|
|
653
575
|
const detail = await api(`/api/records/${encodeURIComponent(decisionId)}`, {}, session);
|
|
654
576
|
const tier = detail.body?.record?.confirmation?.status;
|
|
655
577
|
if (tier)
|
|
656
|
-
log(` ✔ execution evidence on the record: ${tier} — the wrapper's report
|
|
578
|
+
log(` ✔ execution evidence on the record: ${tier} — the wrapper's signed report. (The receipt seals the DECISION; execution evidence accrues on the record — see the README.)`);
|
|
657
579
|
log("");
|
|
658
580
|
const secs = Math.round((Date.now() - t0) / 1000);
|
|
659
|
-
log(`Done in ${secs}s: gated → routed to a human → approved → executed once →
|
|
581
|
+
log(`Done in ${secs}s: gated → routed to a human → approved → your function executed once → an authority-decision receipt you can verify offline.`);
|
|
660
582
|
log("");
|
|
661
583
|
log("Take it to your real agent — the .env here already works:");
|
|
662
584
|
log(` import { guard } from "@decidio/sdk";`);
|
|
663
585
|
log(` const doIt = guard.protect(doItRaw, (a) => ({ action: "yourAction", amount: a.amount, scope: "YourScope" }));`);
|
|
586
|
+
log(` // production is DURABLE mode (default) + one worker: guard.worker(); // README has the full pattern`);
|
|
664
587
|
log("");
|
|
665
|
-
log("Next: the README's 7-step quickstart (durable
|
|
588
|
+
log(`Your workspace + queue: ${API.replace("decidio-api.onrender.com", "app.decidioai.com")} · Next: the README's 7-step quickstart (durable, workers, adapters).`);
|
|
666
589
|
return v.valid ? 0 : 1;
|
|
667
590
|
}
|
|
668
591
|
function cmdDev(target, port) {
|
|
669
|
-
// Signal-only relay: receive Decidio's webhook here (a stable URL) and forward it to
|
|
670
|
-
// your local agent's resume listener. Useful when the agent isn't directly reachable.
|
|
671
592
|
log(`decidio dev relay on http://localhost:${port}/decidio/resume → forwarding to ${target}`);
|
|
672
593
|
createServer((req, res) => {
|
|
673
594
|
if (req.method !== "POST") {
|
package/dist/guard.d.ts
CHANGED
|
@@ -8,22 +8,14 @@ export interface GuardFacadeConfig {
|
|
|
8
8
|
resumeUrl?: string;
|
|
9
9
|
webhookSecret?: string;
|
|
10
10
|
pendingDir?: string;
|
|
11
|
-
/** Local signing key (DECIDIO_AGENT_KEY) + public did (DECIDIO_AGENT_DID), from `init`.
|
|
12
|
-
* When present the gate authorizes against a PROVEN identity, not a claimed label. */
|
|
13
11
|
agentKey?: string;
|
|
14
12
|
agentDid?: string;
|
|
15
|
-
/** The workspace this agent acts in (DECIDIO_WORKSPACE_ID). Bound into the signed proof (D-B3.6)
|
|
16
|
-
* so a captured proof can't be replayed in another tenant. */
|
|
17
13
|
workspaceId?: string;
|
|
18
|
-
/** Base URL of the Decidio app (DECIDIO_APP_URL) — used to deep-link a routed decision. */
|
|
19
14
|
appUrl?: string;
|
|
20
15
|
fetchImpl?: typeof fetch;
|
|
21
16
|
}
|
|
22
17
|
export interface ProtectOptions {
|
|
23
|
-
/** null/"generic" → the core gate; engine adapters (langgraph/openai/temporal/inngest/mcp)
|
|
24
|
-
* live in @decidio/sdk/adapters and take the engine handle. */
|
|
25
18
|
adapter?: "generic" | "langgraph" | "openai" | "temporal" | "inngest" | "mcp";
|
|
26
|
-
/** "durable" (default) suspends for async approval; "blocking" polls (short approvals only). */
|
|
27
19
|
mode?: "durable" | "blocking";
|
|
28
20
|
}
|
|
29
21
|
export declare class Guard {
|
|
@@ -31,13 +23,8 @@ export declare class Guard {
|
|
|
31
23
|
resume?: ResumeController;
|
|
32
24
|
constructor(config: GuardFacadeConfig, resume?: ResumeController);
|
|
33
25
|
static fromEnv(): Guard;
|
|
34
|
-
/** Mount on a node/Express/Fastify route: `app.post("/decidio/resume", guard.resumeHandler())`. */
|
|
35
26
|
resumeHandler(): import("http").RequestListener;
|
|
36
|
-
/** Mount on a Next.js/Hono/CF/Bun route: `export const POST = (req) => guard.resumeFetchHandler()(req)`. */
|
|
37
27
|
resumeFetchHandler(): (req: Request) => Promise<Response>;
|
|
38
|
-
/** Durable resume with NOTHING to host: a background watcher polls Decidio outward and finishes
|
|
39
|
-
* any parked approval — so a suspended action survives a process restart. One line at startup.
|
|
40
|
-
* Production may swap this for the webhook (resumeUrl); the suspend/resume shape is identical. */
|
|
41
28
|
worker(opts?: {
|
|
42
29
|
intervalMs?: number;
|
|
43
30
|
timeoutMs?: number;
|
|
@@ -48,5 +35,4 @@ export declare class Guard {
|
|
|
48
35
|
private ensureResume;
|
|
49
36
|
protect<A extends any[], R>(fn: (...a: A) => Promise<R>, describe: (...a: A) => ApprovalContext, opts?: ProtectOptions): (...a: A) => Promise<R>;
|
|
50
37
|
}
|
|
51
|
-
/** Preconfigured singleton — `import { guard } from "@decidio/sdk"`. */
|
|
52
38
|
export declare const guard: Guard;
|