@decidio/sdk 0.1.1 → 0.1.2

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
@@ -4,9 +4,10 @@ One line to put a human-or-policy approval gate in front of any AI-agent action
4
4
 
5
5
  ## Quickstart — seven steps to your first sealed receipt
6
6
 
7
- Under 30 minutes, no help needed. You'll need Node 20+, a terminal, and a Decidio workspace
7
+ Under 30 minutes, no help needed. You'll need Node 20.6+, a terminal, and a Decidio workspace
8
8
  (request one at [decidioai.com](https://decidioai.com/#early-access) — the hosted sandbox is
9
- synthetic data, safe to experiment in).
9
+ synthetic data, safe to experiment in). Shell commands below are Bash; on PowerShell, `export
10
+ NAME=value` becomes `$env:NAME="value"`.
10
11
 
11
12
  **1. Install**
12
13
 
package/dist/cli.js CHANGED
@@ -22,7 +22,16 @@ import { readFileSync, writeFileSync, existsSync, appendFileSync } from "node:fs
22
22
  import { randomBytes } from "node:crypto";
23
23
  import { createServer } from "node:http";
24
24
  import { createInterface } from "node:readline";
25
+ import { createRequire } from "node:module";
25
26
  import { generateKeypair } from "./signing.js";
27
+ const VERSION = (() => {
28
+ try {
29
+ return createRequire(import.meta.url)("../package.json").version;
30
+ }
31
+ catch {
32
+ return "unknown";
33
+ }
34
+ })();
26
35
  // ---- environment: load the CWD .env FIRST (Codex finding: init writes .env, but the next
27
36
  // command ignored it and pointed at localhost). Process env always wins — .env only fills gaps.
28
37
  function readEnvFile() {
@@ -239,25 +248,31 @@ async function cmdInit(agentId) {
239
248
  if (workspaceId)
240
249
  envOut.DECIDIO_WORKSPACE_ID = workspaceId;
241
250
  upsertEnv(envOut);
242
- log(`✔ wrote ${ENV_PATH} — every decidio command and the SDK read it from this directory`);
251
+ log(`✔ wrote ${ENV_PATH} — every decidio command reads it from this directory (your script loads it with node --env-file=.env)`);
243
252
  ensureGitignore();
244
253
  if (privateKey)
245
254
  log(` (the agent's private signing key is in there — for production, move it to your secret manager)`);
246
255
  log("");
247
256
  log("Next: protect one function and watch it route. Two steps:");
248
257
  log("");
249
- log(" 1. Save this as quickstart.mjs:");
258
+ log(" 1. Save this as quickstart.mjs (the same snippet as the README's step 3):");
250
259
  log("");
251
260
  log(` import { guard } from "@decidio/sdk";`);
252
- log(` const payInvoiceRaw = async (inv) => ({ paid: true, id: inv.id }); // your real action`);
253
- log(` const payInvoice = guard.protect(payInvoiceRaw,`);
254
- log(` (inv) => ({ action: "payInvoice", amount: inv.amount, scope: "Invoice" }),`);
255
- log(` { mode: "blocking" }); // blocking = watch it happen live; durable is the production mode`);
256
- log(` await payInvoice({ id: "INV-1", amount: 86000 });`);
261
+ log("");
262
+ log(` const payInvoiceRaw = async (invoice) => ({ paid: true, id: invoice.id }); // your real action`);
263
+ log("");
264
+ log(` const payInvoice = guard.protect(`);
265
+ log(` payInvoiceRaw,`);
266
+ log(` (invoice) => ({ action: "payInvoice", amount: invoice.amount, scope: "Invoice" }),`);
267
+ log(` { mode: "blocking" }, // blocking = watch it happen live; durable is the production mode`);
268
+ log(` );`);
269
+ log("");
270
+ log(` const result = await payInvoice({ id: "INV-2026-001", amount: 86_000 });`);
271
+ log(` console.log("executed after approval:", result);`);
257
272
  log("");
258
273
  log(" 2. Run it, approve it, watch it complete:");
259
274
  log("");
260
- log(` node --env-file=.env quickstart.mjs # routes to a human and waits`);
275
+ log(` node --env-file=.env quickstart.mjs # routes to a human, prints the decision id, waits`);
261
276
  log(` npx @decidio/sdk approvals # (second terminal) see it pending`);
262
277
  log(` npx @decidio/sdk approvals approve <id> # approve -> your function runs -> sealed`);
263
278
  log("");
@@ -287,8 +302,18 @@ async function cmdDoctor() {
287
302
  const rec = await api("/api/records");
288
303
  if (rec.status >= 200 && rec.status < 300)
289
304
  log("✔ token scope: session/admin (init, approvals and receipt work from this env)");
290
- else if (rec.status === 401)
291
- log("✔ token scope: agent floor correct for the runtime SDK (approvals/receipt will ask you to sign in)");
305
+ else if (rec.status === 401) {
306
+ // A 401 off the agent floor could also mean an INVALID token (Codex catch: don't
307
+ // false-green expiry). Disambiguate with a floor probe: a valid agent token gets 404 for an
308
+ // unknown decision (authorized, not found); an invalid one gets 401 again.
309
+ const probe = await api("/agent/status/doctor-scope-probe");
310
+ if (probe.status === 404)
311
+ log("✔ token scope: agent floor — correct for the runtime SDK (approvals/receipt will ask you to sign in)");
312
+ else if (probe.status === 401)
313
+ log("✘ token: REJECTED on the agent floor too — it's invalid or expired. Re-run `npx @decidio/sdk init` to mint a fresh one.");
314
+ else
315
+ log(`⚠ token scope: agent-floor probe returned ${probe.status} — inconclusive`);
316
+ }
292
317
  else
293
318
  log(`⚠ token scope: unexpected ${rec.status} from /api/records`);
294
319
  log(ok ? "\ndoctor: all green." : "\ndoctor: fix the ✘ items above, then re-run `npx @decidio/sdk doctor`.");
@@ -386,6 +411,13 @@ async function cmdReceipt(id) {
386
411
  const name = `decidio-receipt-${id.replace(/[^A-Za-z0-9_-]/g, "")}.json`;
387
412
  writeFileSync(name, JSON.stringify(r.body.credential, null, 2) + "\n");
388
413
  log(`✔ wrote ${name}`);
414
+ // The credential seals at DECISION time (immutable), so execution evidence that arrived later
415
+ // lives on the record — surface the CURRENT tier here so the reader doesn't need a second
416
+ // lookup to see it (Codex catch).
417
+ const detail = await api(`/api/records/${encodeURIComponent(id)}`, {}, bearer);
418
+ const conf = detail.body?.record?.confirmation;
419
+ if (conf?.status)
420
+ log(` Sealed credential: the authority decision (its confirmation field reads "pending" forever — immutable). Current record evidence tier: ${conf.status}${conf.confirmed ? " (confirmed)" : ""}.`);
389
421
  log(` Verify it offline — no Decidio account or network needed:`);
390
422
  log(` npx @decidio/verify ${name}`);
391
423
  log(` To also prove WHO issued it, pin your workspace's issuer DID (shown in your app's record verify panel):`);
@@ -428,11 +460,16 @@ const USAGE = `decidio — the Decidio SDK command line
428
460
  npx @decidio/sdk approvals reject <id> [reason]
429
461
  npx @decidio/sdk receipt <id> download a sealed decision's Authority Receipt (.json)
430
462
  npx @decidio/sdk dev [--target URL] [--port N] local relay for the resume webhook
463
+ npx @decidio/sdk --version print the CLI version
431
464
 
432
465
  Docs: the README that shipped with this package (quickstart, durable resume, adapters).`;
433
466
  async function main(argv) {
434
467
  const [cmd, a, b, ...rest] = argv.slice(2);
435
468
  const wantsHelp = (s) => s === "help" || s === "--help" || s === "-h";
469
+ if (cmd === "--version" || cmd === "-v" || cmd === "version") {
470
+ log(VERSION);
471
+ return 0;
472
+ }
436
473
  if (!cmd || wantsHelp(cmd)) {
437
474
  log(USAGE);
438
475
  return 0;
package/dist/index.js CHANGED
@@ -122,13 +122,16 @@ export function withApproval(fn, describe, config) {
122
122
  throw new DecidioBlockedError(auth.reason ?? "blocked by policy", auth.decisionId);
123
123
  // 2. routed → either SUSPEND for async resume (durable) or BLOCK-poll (interactive)
124
124
  if (auth.decision === "route") {
125
- // Hand back a link straight to the decision. An agent that suspends and says only "waiting"
126
- // leaves the operator to go hunting through a queue; one that prints the URL does not.
125
+ // ALWAYS hand back the decision id an agent that suspends and says only "waiting" leaves
126
+ // the operator hunting through a queue, and an agent that says NOTHING (the pre-0.1.2 bug:
127
+ // the print was gated on appUrl, which init never writes) leaves them staring at a silent
128
+ // terminal. The Codex acceptance run failed exactly this claim; the id line is unconditional
129
+ // now, with the deep link when appUrl is configured and the CLI command when not.
127
130
  const approvalUrl = decisionUrl(config.appUrl, auth.decisionId);
128
131
  if (config.onPending)
129
132
  config.onPending({ decisionId: auth.decisionId, receiptId: auth.receiptId, url: approvalUrl });
130
- else if (approvalUrl)
131
- console.log(`[decidio] ${ctx.action} is awaiting approval — approve it at ${approvalUrl}`);
133
+ else
134
+ console.log(`[decidio] ${ctx.action} is awaiting approval (decision ${auth.decisionId}) — approve it ${approvalUrl ? `at ${approvalUrl}` : `in your Decidio queue, or: npx @decidio/sdk approvals approve ${auth.decisionId}`}`);
132
135
  // Durable transport: park the args agent-side, register the raw fn so a (cold)
133
136
  // resume can replay it, and SUSPEND. The process may exit now; the controller's
134
137
  // webhook handler or poll worker re-executes THIS fn when a human approves.
package/openapi.yaml CHANGED
@@ -5,7 +5,7 @@
5
5
  openapi: 3.1.0
6
6
  info:
7
7
  title: Decidio Agent Gate
8
- version: 0.1.0
8
+ version: 0.1.2
9
9
  description: >
10
10
  Authorize an AI-agent action (proceed | route | block), confirm the captured outcome, and
11
11
  receive the signed resume webhook when a routed action is decided. Every outcome seals a
@@ -154,7 +154,10 @@ paths:
154
154
  "404": { description: unknown decision }
155
155
  /agent/confirm:
156
156
  post:
157
- summary: Report the captured execution outcome (seals the receipt's evidence tier)
157
+ summary: >
158
+ Report the captured execution outcome — recorded as the RECORD's evidence tier
159
+ (application_confirmed). The credential sealed at decision time is immutable; evidence
160
+ accrues on the record, never inside the sealed bytes.
158
161
  requestBody:
159
162
  required: true
160
163
  content:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decidio/sdk",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "One-line approval gate for AI-agent actions. Wrap a risky tool call with guard.protect(); Decidio's policy engine decides proceed | route | block, a human approves routed actions, and every outcome is sealed as a verifiable Authority Receipt.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",