@decidio/sdk 0.1.6 → 0.1.8

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
@@ -11,22 +11,29 @@ npx @decidio/sdk quickstart
11
11
  One command, guided: it registers a fresh agent (key generated locally), gates a demo action
12
12
  with the same one line you'll use on your real function, routes it to a human — **you** — lets
13
13
  you approve right there (or from your Decidio app), executes on approval, then downloads the
14
- sealed Authority Receipt and verifies it offline in front of you. Sign in when prompted, or
15
- export `DECIDIO_SESSION_TOKEN` for headless terminals. No config: with no `DECIDIO_API_URL` set
16
- it targets the hosted synthetic sandbox.
14
+ sealed Authority Receipt and verifies it offline in front of you, pinned to your workspace's
15
+ issuer. It mints a **7-day** tour token (`init` mints 90-day tokens — different jobs, different
16
+ lifetimes) and ends by offering to clean itself up (`quickstart cleanup` works later too).
17
+ Sign in when prompted. No config: with no `DECIDIO_API_URL` set it targets the hosted synthetic
18
+ sandbox. Headless, the tour **stops at the routed decision** — approving is a human act, so it
19
+ prints the approve command and exits rather than approve its own request (CI can pass
20
+ `--auto-approve-demo`, which labels that authority as automated).
17
21
 
18
22
  ## Quickstart — seven steps to your first sealed receipt
19
23
 
20
24
  Under 30 minutes, no help needed. You'll need Node 20.6+, a terminal, and a Decidio workspace
21
25
  (request one at [decidioai.com](https://decidioai.com/#early-access) — the hosted sandbox is
22
- synthetic data, safe to experiment in).
26
+ synthetic data, safe to experiment in). Lab workspaces have a **governed-action allowance**
27
+ (currently 30 actions/hour); hitting it raises a typed `DecidioRateLimitError` naming the limit
28
+ and reset time — nothing is routed or executed — and `tokens list` shows your remaining
29
+ allowance any time.
23
30
 
24
31
  **PowerShell users** — the equivalents for every Bash idiom used below:
25
32
 
26
33
  ```powershell
27
34
  mkdir decidio-quickstart; cd decidio-quickstart # instead of mkdir ... && cd ...
28
35
  $env:DECIDIO_API_URL = "https://decidio-api.onrender.com" # instead of export NAME=value
29
- $env:DECIDIO_SOURCE_SYSTEM = "salesforce"; npx @decidio/sdk init my-sf-agent
36
+ $env:DECIDIO_AGENT_ID = "my-agent"; npx @decidio/sdk init
30
37
  # instead of VAR=value npx ... (inline env)
31
38
  ```
32
39
 
@@ -37,13 +44,15 @@ mkdir decidio-quickstart && cd decidio-quickstart
37
44
  npm install @decidio/sdk
38
45
  ```
39
46
 
40
- **2. Point at your Decidio and register your agent**
47
+ **2. Register your agent**
41
48
 
42
49
  ```bash
43
- export DECIDIO_API_URL=https://decidio-api.onrender.com # the hosted sandbox
44
50
  npx @decidio/sdk init --unique my-agent # --unique appends a random suffix: collision-proof
45
51
  ```
46
52
 
53
+ Every CLI command talks to the hosted sandbox by default and announces its target host on
54
+ stderr each run — set `DECIDIO_API_URL` (env or `.env`) to point at another Decidio instead.
55
+
47
56
  `init` signs you into your workspace (email + password, or paste the sign-in link from your
48
57
  workspace email), generates the agent's Ed25519 keypair **locally** (only the public `did:key`
49
58
  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`.
@@ -51,7 +60,8 @@ Every later command reads `.env` from this directory automatically.
51
60
 
52
61
  What `.env` contains after a successful init: `DECIDIO_API_URL`, `DECIDIO_AGENT_ID`,
53
62
  `DECIDIO_AGENT_DID` + `DECIDIO_AGENT_KEY` (the local keypair — the key is what earns
54
- `application_confirmed` evidence later), `DECIDIO_API_TOKEN` (the floor-limited agent token),
63
+ `application_confirmed` evidence later), `DECIDIO_API_TOKEN` (the floor-limited agent token —
64
+ 90 days by default, `--expires-days N` to change),
55
65
  `DECIDIO_WORKSPACE_ID`, and `DECIDIO_WEBHOOK_SECRET` (operator deployments only). Pick any
56
66
  agent name — if it's already taken in your workspace, `init` stops cleanly and suggests a
57
67
  fresh one (or pass `--unique` to auto-append a random suffix — collision-proof for shared
@@ -88,11 +98,9 @@ node --env-file=.env quickstart.mjs
88
98
  A brand-new agent matches **no** auto-approve rule, so Decidio **routes every request to a
89
99
  human by default** — that is the point: nothing executes without either a named policy rule or
90
100
  a person. (Routing-by-default is distinct from a policy **block**, which refuses the action
91
- outright — no human can approve a blocked request.) The script prints the decision id and waits. (To later see a policy `proceed`,
92
- register a **new** agent in the sandbox's pre-policied lane — an existing agent's lane can't be
93
- changed: `DECIDIO_SOURCE_SYSTEM=salesforce npx @decidio/sdk init my-sf-agent`. That lane's
94
- named rule auto-approves amounts under $1M when the request carries the agent's signed
95
- identity proof, so even this $86k example would proceed there.)
101
+ outright — no human can approve a blocked request. Policy `proceed` rules — named auto-approve
102
+ for requests carrying the agent's signed identity proof — are configured per workspace by its
103
+ owner.) The script prints the decision id and waits.
96
104
 
97
105
  **5. Approve it** — in a second terminal (same directory; replace `<decisionId>` with the id
98
106
  step 4 printed — angle brackets are placeholders, never typed literally). Approvals are a
@@ -122,8 +130,13 @@ implementation as the standalone `@decidio/verify`). The receipt is a W3C Verifi
122
130
  Credential: signature and content binding verify with no Decidio account and no network — the
123
131
  evidence is yours, not ours. To also prove *who* issued it, pin your workspace's issuer:
124
132
  `npx @decidio/sdk verify --issuer <did:key:...> receipt.json` (the DID is shown in your app's
125
- record verify panel). One receipt proves itself; chain order across receipts is verified when
126
- you pass several files at once.
133
+ record verify panel — or simply read it off the `receipt` command's output, which prints your
134
+ workspace's issuer DID from the authenticated response so you never have to hunt for it).
135
+
136
+ One receipt proves itself. Pass several files and their **ordering** is verified too: the first
137
+ file is the anchor and each subsequent receipt must follow it, so verifying any consecutive
138
+ *segment* works — you are told whether the segment starts at your workspace's genesis or
139
+ mid-history, and a set spanning two different issuers is refused outright.
127
140
 
128
141
  What the credential contains, honestly: the sealed **authority decision** — who decided, what
129
142
  was authorized, when, under which policy. It seals at decision time and is immutable — which is
@@ -136,6 +149,10 @@ The server caps the evidence at the weaker `agent_asserted`, and the SDK warns a
136
149
  time.) Immutable seal + accruing evidence is the design, not an omission — corrections and
137
150
  later facts supersede, they never rewrite.
138
151
 
152
+ One sharing note: a receipt is **not a secret, but it is not metadata-free** — it names the
153
+ action, amount limits, policy, agent and issuer identifiers, and the decision reason. Share it
154
+ with auditors and counterparties deliberately, the way you would any business record.
155
+
139
156
  **7. Prove a replay is rejected:**
140
157
 
141
158
  ```bash
@@ -168,15 +185,36 @@ waiting: it parks its call arguments agent-side (`.decidio-pending/` by default
168
185
  stores none of your downstream payload) and throws `DecidioSuspendedError`. The process may
169
186
  exit. Two ways the parked action resumes when a human decides:
170
187
 
171
- **The worker (self-serve — start here).** One line in any long-lived process:
188
+ **The worker (self-serve — start here).** No inbound URL, no shared secrets, and it resumes work
189
+ the current process never started. Here is a complete `agent.mjs` you can run:
172
190
 
173
191
  ```js
174
192
  import { guard } from "@decidio/sdk";
175
- guard.worker(); // polls Decidio outward, re-executes parked actions on approval, exactly once
193
+
194
+ const payInvoiceRaw = async (invoice) => ({ paid: true, id: invoice.id });
195
+
196
+ // Declare the action name so the wrapper registers at LOAD time, not on first call —
197
+ // that is what lets a restarted process resume actions approved while it was down.
198
+ const payInvoice = guard.protect(
199
+ payInvoiceRaw,
200
+ (invoice) => ({ action: "payInvoice", amount: invoice.amount, scope: "Invoice" }),
201
+ { action: "payInvoice" },
202
+ );
203
+
204
+ guard.worker(); // polls Decidio, re-executes parked actions on approval, one execution each
205
+
206
+ // New work suspends immediately; approvals land later, through the same worker.
207
+ try { await payInvoice({ id: "INV-1", amount: 86_000 }); }
208
+ catch (e) { console.log(e.name); } // DecidioSuspendedError — expected, not a failure
176
209
  ```
177
210
 
178
- No inbound URL, no shared secrets, survives restarts. This is the transport to use against the
179
- hosted sandbox.
211
+ Run it with `node --env-file=.env agent.mjs`, approve from another terminal, and the running
212
+ worker executes it. Kill the process, restart it, then approve: it still executes, because the
213
+ action was registered at load. **If you can't declare the name at wrap time** (a dynamic
214
+ `describe`), register the raw function explicitly at startup instead —
215
+ `guard.register("payInvoice", payInvoiceRaw)` — before calling `guard.worker()`. Without one of
216
+ those two, a cold worker has an empty registry and reports `no_handler` (the parked action is
217
+ kept, not lost, and the message names the exact fix).
180
218
 
181
219
  **The signed webhook (operator deployments).** Decidio POSTs a signed verdict to your agent's
182
220
  `resumeUrl`; the handler verifies the HMAC signature fail-closed, then re-runs your function:
@@ -198,6 +236,12 @@ your action succeeds but the process dies *before* the entry is cleared, the cla
198
236
  (robust crash-recovery of stale claims is on the roadmap), so make your downstream action
199
237
  idempotent if you need exactly-once under crashes.
200
238
 
239
+ **Parked arguments must be JSON-serializable.** A suspended action is signed and then written to
240
+ disk as JSON, so values that don't survive `JSON.stringify` round-tripping — `Date`, `Map`,
241
+ `undefined`, `NaN`, class instances with custom `toJSON` — come back changed and fail the
242
+ integrity check on resume (a refusal, never a wrong execution). Pass plain data; rehydrate
243
+ inside your function.
244
+
201
245
  **Payload integrity.** When a routed action is parked, the SDK signs its arguments (bound to the
202
246
  decision id) with your agent key; on resume it **refuses to execute** if the parked payload was
203
247
  modified, its signature stripped, or the file swapped into another decision's slot. That defends
@@ -227,13 +271,19 @@ imports none of them. Each adapter's shipped `.d.ts` header documents its wiring
227
271
  `npx @decidio/sdk <cmd>`:
228
272
 
229
273
  - `quickstart` — the guided two-minute tour (gate → approve → execute → proof)
230
- - `init [agentId] [--unique]` — sign in, register, mint the agent token, write `.env` (+ `.gitignore` entries)
274
+ - `init [agentId] [--unique] [--force] [--expires-days N]` — sign in, register, mint the agent token, write `.env` (+ `.gitignore` entries). Refuses to overwrite a *different* agent's key in this directory; `--force` backs the old `.env` up first.
231
275
  - `doctor` — config, connectivity, and what your current token can do
232
- - `approvals` / `approvals approve|reject <id> [reason]` — the human side, from your terminal
276
+ - `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)
233
277
  - `receipt <id>` — download a sealed decision's Authority Receipt to a `.json` file
234
- - `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)
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)
280
+ - `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)
235
281
  - `dev [--target URL]` — local relay for the resume webhook (operator setups)
236
282
 
283
+ 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.
286
+
237
287
  Commands that administer your workspace (`init`, `approvals`, `receipt`) use a **session**
238
288
  sign-in held in memory for that run. What lands on disk in `.env`: the agent's floor-limited
239
289
  API token, its locally-generated private signing key, and a webhook secret (used only in