@decidio/sdk 0.1.8 → 0.1.10

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 CHANGED
@@ -23,7 +23,10 @@ prints the approve command and exits rather than approve its own request (CI can
23
23
 
24
24
  Under 30 minutes, no help needed. You'll need Node 20.6+, a terminal, and a Decidio workspace
25
25
  (request one at [decidioai.com](https://decidioai.com/#early-access) — the hosted sandbox is
26
- synthetic data, safe to experiment in). Lab workspaces have a **governed-action allowance**
26
+ synthetic data, safe to experiment in — and it is ONE SHARED workspace: workspace-wide views
27
+ like `tokens list` or `approvals` show every lab member's agents and decisions there, which is
28
+ the shared-lab boundary working as designed; production workspaces are per-tenant). Lab
29
+ workspaces have a **governed-action allowance**
27
30
  (currently 30 actions/hour); hitting it raises a typed `DecidioRateLimitError` naming the limit
28
31
  and reset time — nothing is routed or executed — and `tokens list` shows your remaining
29
32
  allowance any time.
@@ -56,7 +59,9 @@ stderr each run — set `DECIDIO_API_URL` (env or `.env`) to point at another De
56
59
  `init` signs you into your workspace (email + password, or paste the sign-in link from your
57
60
  workspace email), generates the agent's Ed25519 keypair **locally** (only the public `did:key`
58
61
  is sent), registers the agent, mints its **floor-limited API token** (limited to the agent runtime — request authorization, poll status, report signed confirmation; it cannot administer the workspace or approve anything — and your workspace session never touches the agent's disk), and writes `.env`.
59
- Every later command reads `.env` from this directory automatically.
62
+ Every later command reads `.env` from this directory automatically. Re-running init for an
63
+ agent that already has a token REPLACES it — the server keeps one active token per agent, so
64
+ the previous one is revoked and init says so.
60
65
 
61
66
  What `.env` contains after a successful init: `DECIDIO_API_URL`, `DECIDIO_AGENT_ID`,
62
67
  `DECIDIO_AGENT_DID` + `DECIDIO_AGENT_KEY` (the local keypair — the key is what earns
@@ -121,11 +126,14 @@ becomes the record's execution evidence.
121
126
 
122
127
  ```bash
123
128
  npx @decidio/sdk receipt <decisionId>
124
- npx @decidio/sdk verify decidio-receipt-<decisionId>.json
129
+ npx @decidio/sdk verify --issuer <did:key:...> decidio-receipt-<decisionId>.json
125
130
  ```
126
131
 
127
- The download is an authenticated fetch from your workspace; the *verification* is fully
128
- offline — the verifier ships inside this package (no second download, no registry probe, same
132
+ Lead with the pin: `--issuer` binds the check to YOUR workspace's DID (the `receipt` command
133
+ prints it from the authenticated response, so it is already on your screen). An UNPINNED
134
+ `verify` proves internal consistency only — any keyholder could have issued such a file — and
135
+ says so; pinned verification proves *who*. The download is an authenticated fetch from your
136
+ workspace; the *verification* is fully offline — the verifier ships inside this package (no second download, no registry probe, same
129
137
  implementation as the standalone `@decidio/verify`). The receipt is a W3C Verifiable
130
138
  Credential: signature and content binding verify with no Decidio account and no network — the
131
139
  evidence is yours, not ours. To also prove *who* issued it, pin your workspace's issuer:
@@ -133,7 +141,10 @@ evidence is yours, not ours. To also prove *who* issued it, pin your workspace's
133
141
  record verify panel — or simply read it off the `receipt` command's output, which prints your
134
142
  workspace's issuer DID from the authenticated response so you never have to hunt for it).
135
143
 
136
- One receipt proves itself. Pass several files and their **ordering** is verified too: the first
144
+ One receipt proves itself. Pass several files and their **ordering** is verified too — in
145
+ **seal order** (each receipt's `prevHash` links to its predecessor's `contentHash`), which is
146
+ the order decisions were *sealed*, not the order of their displayed decision numbers (two
147
+ decisions created together can seal in either order). The first
137
148
  file is the anchor and each subsequent receipt must follow it, so verifying any consecutive
138
149
  *segment* works — you are told whether the segment starts at your workspace's genesis or
139
150
  mid-history, and a set spanning two different issuers is refused outright.
@@ -144,9 +155,12 @@ why the credential's own `confirmation.status` field reads `"pending"` forever.
144
155
  evidence that arrives *after* the seal (your function's captured result) lives on the
145
156
  **record**, not inside the credential bytes: the `receipt` command prints the record's current
146
157
  tier next to the file, and with the signing key `init` wrote you'll see `application_confirmed`
147
- — the wrapper's report was cryptographically bound to your agent. (No signing key configured?
148
- The server caps the evidence at the weaker `agent_asserted`, and the SDK warns at confirm
149
- time.) Immutable seal + accruing evidence is the design, not an omission — corrections and
158
+ — the wrapper's report carried a signed identity proof bound to your agent, this decision, and
159
+ a freshness window. Precisely: the proof authenticates WHO is confirming WHAT decision — it
160
+ does not yet sign the captured response bytes themselves (that binding is on the receipt-v2
161
+ roadmap), so treat the tier as proven reporter identity, not a cryptographic hash of the
162
+ result. (No signing key configured? The server caps the evidence at the weaker
163
+ `agent_asserted`, and the SDK warns at confirm time.) Immutable seal + accruing evidence is the design, not an omission — corrections and
150
164
  later facts supersede, they never rewrite.
151
165
 
152
166
  One sharing note: a receipt is **not a secret, but it is not metadata-free** — it names the
@@ -162,7 +176,9 @@ npx @decidio/sdk approvals approve <the same decisionId>
162
176
  Decisions are single-use: the CLI reports "already resolved — nothing changed" and exits
163
177
  non-zero. The server answers replays idempotently — a second approve can never re-execute the
164
178
  action. (The durable resume path has the same property: parked actions are claimed exactly
165
- once, and its webhook handler fails closed without a verified signature.)
179
+ once, and its webhook handler fails closed without a verified signature — unless you
180
+ explicitly set `allowUnsigned: true`, which removes webhook authenticity and makes the
181
+ controller say so loudly at startup; never use it on an internet-reachable endpoint.)
166
182
 
167
183
  That's the whole contract: gate → human decision → your execution → owned, verifiable evidence.
168
184
 
@@ -230,6 +246,13 @@ also allow-list resume hosts (default-deny). That's operator territory: if you r
230
246
  Decidio (or we run a pilot with you), the webhook is set up then. Against the hosted sandbox,
231
247
  use the worker — a webhook secret your server never learned would (correctly) fail closed.
232
248
 
249
+ **The supported argument data model is plain JSON**: `null`, booleans, strings, finite
250
+ numbers, arrays, and plain objects. Anything that cannot survive JSON with its meaning intact
251
+ — `NaN`/`Infinity`, `undefined`, `BigInt`, `Map`/`Set`/`Date`, class instances, functions,
252
+ circular structures — is **refused before the decision is routed or the payload signed**
253
+ (typed error naming the offending path), because a value that silently drifts in serialization
254
+ would mean a human approves one thing and your function executes another.
255
+
233
256
  Either way, re-execution is **single-use on the happy path**: the parked entry is claimed before
234
257
  execution and deleted on success, so a duplicate signal finds nothing to replay. Honest edge — if
235
258
  your action succeeds but the process dies *before* the entry is cleared, the claim can be retried
@@ -258,7 +281,9 @@ If your agent runs on Inngest/Temporal/LangGraph, the engine *is* the durable st
258
281
  `@decidio/sdk/inngest` maps the gate onto `step.waitForEvent`; `@decidio/sdk/langgraph` is a
259
282
  drop-in via `interrupt()`; `@decidio/sdk/temporal` uses `condition` + signal;
260
283
  `@decidio/sdk/openai` gates RunState approvals. All optional peer dependencies — the core SDK
261
- imports none of them. Each adapter's shipped `.d.ts` header documents its wiring; `npx
284
+ imports none of them — install the engine you use alongside the SDK (`npm i inngest`,
285
+ `@langchain/langgraph`, `@temporalio/workflow`, or `@openai/agents`); nothing is downloaded
286
+ for engines you don't. Each adapter's shipped `.d.ts` header documents its wiring; `npx
262
287
  @decidio/sdk quickstart` is the fastest runnable end-to-end.
263
288
 
264
289
  ## One surface, every runtime — and Python too
@@ -275,14 +300,15 @@ imports none of them. Each adapter's shipped `.d.ts` header documents its wiring
275
300
  - `doctor` — config, connectivity, and what your current token can do
276
301
  - `approvals [--mine] [--json]` / `approvals approve|reject <id> [reason]` — the human side, from your terminal (the list is workspace-wide; `--mine` narrows it to this directory's agent)
277
302
  - `receipt <id>` — download a sealed decision's Authority Receipt to a `.json` file
278
- - `verify [--issuer <did>] <file>...` — verify receipts offline (the canonical verifier; the standalone `@decidio/verify` package is the same implementation, for auditors who want zero Decidio dependency). Unknown flags are refused, never ignored.
279
- - `tokens list` / `tokens revoke <jti>` — see and kill agent tokens (values are never re-shown; the jti is the revocation handle). Each row carries its status — `active`, `expired`, or `REVOKED <date>` — so the list confirms the revocations it performs; re-revoking is an honest no-op (exit 1)
303
+ - `verify [--issuer <did>] <file>...` — verify receipts offline (the canonical verifier; the standalone `@decidio/verify` package is the same implementation, for auditors who want zero Decidio dependency — the sdk's embedded copy is pinned to the previous release, so message wording may trail it one version; verdicts and semantics are conformance-locked identical). Unknown flags are refused, never ignored. Fully offline AND fully decoupled: it touches no network and reads no `.env` — a receipt verifies identically from an empty directory.
304
+ - `tokens list [--mine]` / `tokens revoke <jti>` — see and kill agent tokens (values are never re-shown; the jti is the revocation handle). Each row carries its status — `active`, `expired`, or `REVOKED <date>` — so the list confirms the revocations it performs; re-revoking is an honest no-op (exit 1). The list is workspace-wide (a shared workspace shows every member's agents); `--mine` narrows to this directory's agent
280
305
  - `quickstart cleanup` — revoke the tour's token and delete its local credentials. What stays, honestly: sealed records (permanent by design) and the agent's registration row (inert without key or token; server-side deregistration is on the roadmap)
281
306
  - `dev [--target URL]` — local relay for the resume webhook (operator setups)
282
307
 
283
308
  The CLI's stream contract: **stdout carries results only** (safe to pipe — `--json` output is
284
- pure JSON); errors, usage guidance, and the per-run target-host announcement go to **stderr**.
285
- Exit codes: 0 success · 1 failure or honest no-op · 2 usage error.
309
+ pure JSON); errors, usage shown on *error* paths, and the per-run target-host announcement go
310
+ to **stderr**. Asked-for help (an explicit `--help`, or invoking the bare CLI) is itself a
311
+ result and prints on stdout. Exit codes: 0 success · 1 failure or honest no-op · 2 usage error.
286
312
 
287
313
  Commands that administer your workspace (`init`, `approvals`, `receipt`) use a **session**
288
314
  sign-in held in memory for that run. What lands on disk in `.env`: the agent's floor-limited
@@ -297,7 +323,8 @@ manager for production.
297
323
  - `DecidioSuspendedError` — (durable mode) the action was routed and is parked for async approval; the process may exit and resume later. Not a failure.
298
324
  - `DecidioTimeoutError` — (blocking mode only) no human decided within `pollTimeoutMs` (default 10 min); the decision stays open in Decidio, nothing executed locally.
299
325
 
300
- Every error carries the `decisionId`, so a suspended or timed-out action is always findable in
326
+ Every error raised after a decision exists carries the `decisionId`, so a suspended or
327
+ timed-out action is always findable in
301
328
  your queue.
302
329
 
303
330
  ## Notes
@@ -7,5 +7,7 @@ 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. */
10
11
  export declare function verdictFrom(payload: any): string;
12
+ /** Throw the right error for a non-approving verdict; return for approved/auto_approved. */
11
13
  export declare function assertVerdict(verdict: string, decisionId: string, reason?: string): void;
@@ -1,5 +1,7 @@
1
- import { DecidioBlockedError, DecidioRejectedError, signBoundProof, signConfirm } from "../index.js";
1
+ import { DecidioBlockedError, DecidioRejectedError, signBoundProof, signConfirm, assertJsonSafe, assertConfirmRecorded, assertSafeTransport } from "../index.js";
2
2
  export async function gateAuthorize(config, ctx) {
3
+ assertJsonSafe(ctx, "describe(...)");
4
+ assertSafeTransport(config.apiUrl, !!config.apiToken);
3
5
  const f = config.fetchImpl ?? fetch;
4
6
  const resumeUrl = config.resumeUrl ?? config.resume?.resumeUrl;
5
7
  const requesterIdentity = (config.agentKey && config.agentDid)
@@ -20,6 +22,13 @@ export async function gateAuthorize(config, ctx) {
20
22
  }
21
23
  export async function gateConfirm(config, decisionId, result) {
22
24
  const f = config.fetchImpl ?? fetch;
25
+ try {
26
+ assertSafeTransport(config.apiUrl, !!config.apiToken);
27
+ }
28
+ catch (e) {
29
+ console.warn(`[decidio] confirm failed (action succeeded): ${e.message}`);
30
+ return;
31
+ }
23
32
  try {
24
33
  const requesterIdentity = signConfirm(decisionId, config);
25
34
  if (!requesterIdentity)
@@ -29,8 +38,11 @@ export async function gateConfirm(config, decisionId, result) {
29
38
  headers: { "content-type": "application/json", ...(config.apiToken ? { authorization: `Bearer ${config.apiToken}` } : {}) },
30
39
  body: JSON.stringify({ decisionId, capturedResponse: result ?? null, source: "wrapper", ...(requesterIdentity ? { requesterIdentity } : {}) }),
31
40
  });
32
- if (!res.ok)
41
+ if (!res.ok) {
33
42
  console.warn(`[decidio] confirm returned ${res.status} (action succeeded)`);
43
+ return;
44
+ }
45
+ assertConfirmRecorded(await res.json().catch(() => null), decisionId, !!requesterIdentity);
34
46
  }
35
47
  catch (e) {
36
48
  console.warn(`[decidio] confirm failed (action succeeded): ${e.message}`);
@@ -1,6 +1,19 @@
1
1
  import { Command } from "@langchain/langgraph";
2
2
  import { type GuardConfig, type ApprovalContext } from "../index.js";
3
+ /**
4
+ * @decidio/sdk/langgraph — durable suspend via LangGraph's native `interrupt()`.
5
+ * `interrupt` finds the running graph itself (no handle needed), so the gate drops into
6
+ * a node: authorize → on route, interrupt() durably pauses (the checkpointer persists;
7
+ * the process can die) → on resume (Command(resume={verdict})) run the agent's own
8
+ * action + confirm. Decidio is the approval system; the host bridges its webhook to the
9
+ * graph resume (decidioResumeCommand). Requires @langchain/langgraph (optional peer dep).
10
+ *
11
+ * (This wiring doc ships in the .d.ts — the README points here.)
12
+ */
13
+ /** Gate `run()` behind Decidio inside a LangGraph node. */
3
14
  export declare function decidioGate<R>(config: GuardConfig, ctx: ApprovalContext, run: () => Promise<R>): Promise<R>;
15
+ /** Translate Decidio's signed resume webhook into the LangGraph resume Command. The host
16
+ * maps decisionId → thread_id and passes this to graph.invoke/stream. */
4
17
  export declare function decidioResumeCommand(body: {
5
18
  verdict?: string;
6
19
  reason?: string;
@@ -5,6 +5,26 @@ interface RunStateLike {
5
5
  approve(item: ToolApprovalItem): void;
6
6
  reject(item: ToolApprovalItem): void;
7
7
  }
8
+ /**
9
+ * @decidio/sdk/openai — gate OpenAI Agents SDK tool-approval interruptions through
10
+ * Decidio, with durable suspend via the SDK's native RunState serialization.
11
+ *
12
+ * The Agents loop surfaces tool approvals on the RunState (state.getInterruptions());
13
+ * you state.approve(item)/reject(item) then resume with run(agent, state). RunState
14
+ * serializes (state.toString() / RunState.fromString(agent, s)) — that's the durability:
15
+ * park the serialized state agent-side on `route`, resume on Decidio's approval.
16
+ *
17
+ * const state = result.state; // or RunState from the run
18
+ * const { resolved, pending } = await gateInterruptions(config, state, describe);
19
+ * if (pending.length) { await store.put(decisionId, state.toString()); return; } // suspend
20
+ * result = await run(agent, state); // all resolved → resume
21
+ * // on Decidio approval webhook: state = await RunState.fromString(agent, parked);
22
+ * // applyResume(state, item, verdict); result = await run(agent, state);
23
+ *
24
+ * Duck-typed on the state/item (no hard @openai/agents import) so it stays thin.
25
+ *
26
+ * (This wiring doc ships in the .d.ts — the README points here.)
27
+ */
8
28
  export interface Resolved {
9
29
  item: ToolApprovalItem;
10
30
  verdict: "approved" | "blocked";
@@ -14,9 +34,13 @@ export interface Pending {
14
34
  item: ToolApprovalItem;
15
35
  decisionId: string;
16
36
  }
37
+ /** Ask Decidio for each pending tool-approval on a RunState.
38
+ * proceed → state.approve(item); block → state.reject(item); route → pending (a human
39
+ * decides via Decidio; the host parks state.toString() + resumes via applyResume). */
17
40
  export declare function gateInterruptions(config: GuardConfig, state: RunStateLike, describe: (item: ToolApprovalItem) => ApprovalContext): Promise<{
18
41
  resolved: Resolved[];
19
42
  pending: Pending[];
20
43
  }>;
44
+ /** Apply a Decidio verdict to a resumed RunState (after RunState.fromString). */
21
45
  export declare function applyResume(state: RunStateLike, item: ToolApprovalItem, verdict: string): void;
22
46
  export {};
@@ -1,6 +1,22 @@
1
1
  import { type GuardConfig, type ApprovalContext } from "../index.js";
2
2
  import { type GateAuth } from "./_gate.js";
3
+ /**
4
+ * @decidio/sdk/temporal — durable suspend via Temporal's native `condition()` + a
5
+ * Decidio resume signal. Strongest durability (the workflow survives worker restarts for
6
+ * days); the agent's action runs on resume. Requires @temporalio/workflow (optional peer).
7
+ *
8
+ * Inside your workflow:
9
+ * const verdicts = installDecidioSignal(); // wires the resume signal
10
+ * const out = await decidioGate(config, ctx, () => createOpp(o), verdicts, {
11
+ * authorize: () => executeActivity(authorizeActivity, ...), // HTTP belongs in activities
12
+ * confirm: (id, r) => executeActivity(confirmActivity, ...),
13
+ * });
14
+ * Decidio's webhook bridges to: handle.signal(decidioResumeSignal, decisionId, verdict, reason)
15
+ *
16
+ * (This wiring doc ships in the .d.ts — the README points here.)
17
+ */
3
18
  export declare const decidioResumeSignal: import("@temporalio/workflow").SignalDefinition<[string, string, (string | undefined)?]>;
19
+ /** Call inside the workflow to wire the resume signal; returns the verdict store. */
4
20
  export declare function installDecidioSignal(): Map<string, {
5
21
  verdict: string;
6
22
  reason?: string;