@decidio/sdk 0.4.3 → 0.5.0
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/CHANGELOG.md +75 -0
- package/README.md +25 -21
- package/dist/cli.js +88 -32
- package/dist/resume.d.ts +8 -5
- package/openapi.yaml +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,81 @@ Release notes for the client SDK. The Python package `decidio` ships the same ch
|
|
|
4
4
|
version (the two are conformance-locked twins). Each entry names what changed, what a reader must do
|
|
5
5
|
on upgrade, and what proved it.
|
|
6
6
|
|
|
7
|
+
## 0.5.0 — `verify` pins by default
|
|
8
|
+
|
|
9
|
+
**Breaking, on purpose (founder ruling 2026-09-04, after the prodsec review of 0.4.3).** Every CLI verify door - `npx
|
|
10
|
+
@decidio/sdk verify`, the standalone `decidio-verify`, `python -m decidio verify` and `python -m decidio.verify` -
|
|
11
|
+
now REFUSES to run without `--issuer <did:key>` (exit 2, with the remedy printed). An unpinned VALID only proves the
|
|
12
|
+
file agrees with itself: the reviewer forged a self-signed "wire all funds" receipt and the exit code said yes. Scripts
|
|
13
|
+
trust exit status, not warnings, so the safe check is now the default and the structural check is a named choice:
|
|
14
|
+
`--allow-unpinned` runs it, and its verdict carries the UNPINNED warning as before. `--allow-unpinned` together with
|
|
15
|
+
`--issuer` is refused as ambiguous, like a disagreeing repeated pin.
|
|
16
|
+
|
|
17
|
+
- **What to change:** add `--issuer <your workspace DID>` to any scripted verify (the `receipt` command prints the
|
|
18
|
+
DID; the app's record-verify panel shows it), or `--allow-unpinned` where you genuinely want structure only.
|
|
19
|
+
- **Unchanged:** the LIBRARY (`verifyReceipt(vc)` / `verify_receipt(vc)`) still verifies unpinned when no issuer is
|
|
20
|
+
passed - a caller reads `issuer` in the result and decides; the CLIs are what scripts trust blindly.
|
|
21
|
+
- Vectors in all three suites: the refusal (exit 2, names `--issuer` and `--allow-unpinned`), the named unpinned run
|
|
22
|
+
reaching a verdict, both flags refused together. The usage pages, both READMEs and the `receipt` command's hint
|
|
23
|
+
say the new rule; the live selftest names its one unpinned run.
|
|
24
|
+
- **The sealed payload names two fields for what they are (schemaVersion 2).** `deciders` → `nominatedDeciders` (the
|
|
25
|
+
people nominated IN ADVANCE; the ratifier is who decided, so `nominated deciders: 0, ratified: yes` reads as it
|
|
26
|
+
should) and, inside `authority`, `appliedLimits` → `evaluatedLimits` (what the request was measured against - the
|
|
27
|
+
requested amount, the auto-approve threshold, the hard cap - not limits that "applied"). New records carry the v2
|
|
28
|
+
`payloadSchemaUri`; the canonicalization (`jcs-v1`) is unchanged, so verification is untouched. Both verifiers print
|
|
29
|
+
`nominated deciders: N` and read the v1 names too (every record sealed before 0.5.0 is lab test data). Founder
|
|
30
|
+
ruling 2026-09-04: rename now, while no customer record carries the old names.
|
|
31
|
+
- **From the 0.4.4 first-run audit (B+), the small items** (founder: go): quickstart checks connectivity BEFORE its
|
|
32
|
+
first disk write (a dead host leaves the directory untouched and says "Nothing was written"); `tokens list --mine`
|
|
33
|
+
after cleanup reads this directory's agents from the token recovery journal; `receipt` prints resolved identities
|
|
34
|
+
only on a terminal (a pipe or a log gets a one-line note; `--json` hands them over explicitly); the ④ frame says the
|
|
35
|
+
issuer DID was first seen over this connection and where to cross-check it (`<api>/api/did`, public, no sign-in);
|
|
36
|
+
the help page and README say what cleanup leaves behind; the README no longer calls the lab ONE SHARED workspace
|
|
37
|
+
(every invited tester has their own), explains the in-memory quota counter, lists `receipt --json` and `dev --port`,
|
|
38
|
+
and calls the journal append-only by behaviour, not by enforcement. Vectors: the dead-host tour, `--mine` from the
|
|
39
|
+
journal, the receipt in a pipe vs on a terminal.
|
|
40
|
+
- Two Lows from the prodsec retest of 0.4.4: `quickstart cleanup` with nothing to clean still ENDS the stored sign-in
|
|
41
|
+
(the early "Nothing to clean up here" return skipped it, contradicting the documented promise; vector in both
|
|
42
|
+
suites), and the resume module's comments say `lost_race` / `already_resolved` where the runtime does (they still
|
|
43
|
+
said `unknown`); `onResolved` is documented as firing for every outcome except `unknown`.
|
|
44
|
+
|
|
45
|
+
## 0.4.4 — the small findings of the 0.4.3 prodsec run: frames at 80 columns, a local install upgrades in place, a key-less .env survives the tour
|
|
46
|
+
|
|
47
|
+
An external prodsec reviewer ran 0.4.3 from a stranger's install (2026-09-04; grade B, "trust in the core mechanics
|
|
48
|
+
went up"). Its design questions (a mandatory `--issuer`, out-of-band issuer distribution, npm provenance) are founder
|
|
49
|
+
calls in the backlog; these are its small findings, each with a vector that was red first.
|
|
50
|
+
|
|
51
|
+
- **Frames never wider than the terminal.** The 0.4.2 frames had a width FLOOR of 84 columns, and the terminal driver
|
|
52
|
+
that gates the tours ran only at 300 columns - so on the most common width, 80, every border wrapped. The floor now
|
|
53
|
+
yields to the terminal, and both suites run the approval tour at 80 columns: no frame line wider than the window,
|
|
54
|
+
every frame line closes with its border.
|
|
55
|
+
- **The update gate knows a project-local install.** `npm install @decidio/sdk` then `npx @decidio/sdk …` (this
|
|
56
|
+
README's own path) runs the CLI from the project's node_modules; the gate offered `npm i -g` (global state nobody
|
|
57
|
+
asked for) and then re-ran the same old local file. Three shapes now, each upgraded the way it was installed:
|
|
58
|
+
project-local → `npm i @decidio/sdk@latest` in that project; global → `-g`; under npx → the `@latest` form,
|
|
59
|
+
printed. The re-run the gate spawns carries `DECIDIO_UPDATE_REEXEC=1` and never checks again, so a version that did
|
|
60
|
+
not move cannot loop the prompt. (Python: `pip install -U` already upgrades the interpreter that is running; it
|
|
61
|
+
gains the re-run marker.)
|
|
62
|
+
- **A key-less agent `.env` is left alone.** The tour used its own `.env.quickstart` only when `.env` held a signing
|
|
63
|
+
key; the SDK's `agentKey` is optional, so an agent configured without one had its agent fields overwritten and then
|
|
64
|
+
stripped by cleanup. ANY agent identity in `.env` (`DECIDIO_AGENT_ID`, `_DID`, `_KEY` or `DECIDIO_API_TOKEN`) now
|
|
65
|
+
sends the tour to its own file. Vector: such a file is byte-identical after the tour and its cleanup.
|
|
66
|
+
- **The ④ frame names what was sealed** without `--explain`: `decision payInvoice $86,000 · APPROVED · routed to
|
|
67
|
+
a human by policy (no auto-approve rule for this agent)` (REJECTED on the denial's record).
|
|
68
|
+
- A `.gitignore` the tour CREATES also ignores `node_modules/`; an existing file gets only the secret entries.
|
|
69
|
+
- `openapi.yaml` now states the release version (it read 0.1.11 beside a 0.4.3 package), and a seven-manifest
|
|
70
|
+
lockstep check (`packages/conformance/manifests.mjs`: both package.json, both package-lock.json, pyproject,
|
|
71
|
+
`__init__`, openapi) runs in CI and on the publish path - 0.4.3 shipped with both locks a version behind.
|
|
72
|
+
- Documented: the token recovery journal's vocabulary (`minted`, `retired how=persisted`, `retired how=revoked`,
|
|
73
|
+
`probe`) - `persisted` means the credential file holds the token and is its handle, not that it was revoked.
|
|
74
|
+
- **`logout` says what it does** (prodsec second pass on 0.4.3 - "A / A-", two Lows): it forgets the stored sign-in on
|
|
75
|
+
this computer; the session token itself stays valid until it expires - there is no server-side revocation yet, and no
|
|
76
|
+
wording implies one any more (`logout`, cleanup, the sign-in prompt, the usage page, both READMEs). Vectors: the
|
|
77
|
+
statement is asserted, and `logout` makes no API call.
|
|
78
|
+
- The shipped `.d.ts` state-machine comment names the terminal `discarded` state beside the chain that omitted it.
|
|
79
|
+
- Confirmed as intended, no change: the online `receipt` command prints the tokenized identities your access
|
|
80
|
+
covers ("Resolved for you"); the file keeps its tokens.
|
|
81
|
+
|
|
7
82
|
## 0.4.3 — the stale-install gate, frames you can read at a glance, and a phone door that lands
|
|
8
83
|
|
|
9
84
|
The founder's UAT round 2, second half (2026-09-04). Nobody is on 0.4.2 yet, so it was never tagged; this carries both.
|
package/README.md
CHANGED
|
@@ -29,17 +29,18 @@ prints the approve command and exits rather than approve its own request (CI can
|
|
|
29
29
|
|
|
30
30
|
## Quickstart — seven steps to your first sealed receipt
|
|
31
31
|
|
|
32
|
-
Under 30 minutes, no help needed. You'll need Node 20.6+ and a terminal.
|
|
32
|
+
Under 30 minutes for all seven steps (the tour itself is two minutes), no help needed. You'll need Node 20.6+ and a terminal.
|
|
33
33
|
|
|
34
34
|
**You'll need a Decidio workspace.** → Request one at [decidioai.com](https://decidioai.com/#early-access)
|
|
35
35
|
|
|
36
|
-
The hosted sandbox is synthetic data, safe to experiment in.
|
|
37
|
-
workspace-wide views like `tokens list` or `approvals` show every
|
|
38
|
-
|
|
36
|
+
The hosted sandbox is synthetic data, safe to experiment in. Every invited tester gets their OWN
|
|
37
|
+
workspace: workspace-wide views like `tokens list` or `approvals` show every agent and decision in
|
|
38
|
+
your workspace — yours alone unless you invite others into it. Production workspaces are per-tenant too.
|
|
39
39
|
|
|
40
40
|
Lab workspaces have a **governed-action allowance** (currently 30 actions/hour); hitting it raises
|
|
41
41
|
a typed `DecidioRateLimitError` naming the limit and reset time — nothing is routed or executed —
|
|
42
|
-
and `tokens list` shows your remaining allowance any time.
|
|
42
|
+
and `tokens list` shows your remaining allowance any time. The counter is in memory, per API instance: a
|
|
43
|
+
deploy resets it, so `30/30` right after a governed action means the API restarted, not that the action was free.
|
|
43
44
|
|
|
44
45
|
**PowerShell users** — the equivalents for every Bash idiom used below:
|
|
45
46
|
|
|
@@ -66,7 +67,7 @@ npx @decidio/sdk init --unique my-agent # --unique appends a random suffix: co
|
|
|
66
67
|
> Just ran `npx @decidio/sdk quickstart` in this directory and kept its agent? Its `.env` already
|
|
67
68
|
> works — skip this step and continue at step 3.
|
|
68
69
|
|
|
69
|
-
Every
|
|
70
|
+
Every network command talks to the hosted sandbox by default (`verify` is offline) and announces its target host on
|
|
70
71
|
stderr each run — set `DECIDIO_API_URL` (env or `.env`) to point at another Decidio instead.
|
|
71
72
|
|
|
72
73
|
`init` signs you into your workspace (email + password, or paste the sign-in link from your
|
|
@@ -152,9 +153,10 @@ npx @decidio/sdk verify --issuer <did:key:...> decidio-receipt-<decisionId>.json
|
|
|
152
153
|
```
|
|
153
154
|
|
|
154
155
|
Lead with the pin: `--issuer` binds the check to YOUR workspace's DID (the `receipt` command
|
|
155
|
-
prints it from the authenticated response, so it is already on your screen).
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
prints it from the authenticated response, so it is already on your screen). Since 0.5.0 the
|
|
157
|
+
CLI refuses to run without it: an UNPINNED check proves internal consistency only — any keyholder
|
|
158
|
+
could have issued such a file — so it is a named choice (`--allow-unpinned`), and its verdict says
|
|
159
|
+
so; pinned verification proves *who*. The download is an authenticated fetch from your
|
|
158
160
|
workspace; the *verification* is fully offline — the verifier ships inside this package (no second download, no registry probe, same
|
|
159
161
|
implementation as the standalone `@decidio/verify`). The receipt is a W3C Verifiable
|
|
160
162
|
Credential: signature and content binding verify with no Decidio account and no network — the
|
|
@@ -523,14 +525,14 @@ ever wanted it is an offline job with every writer stopped.
|
|
|
523
525
|
The cryptography is only half the evidence. These are the fields that an outside reader has
|
|
524
526
|
actually misread on a first run, and what each one means.
|
|
525
527
|
|
|
526
|
-
**`ratified: yes` next to `deciders: 0` is not a contradiction.** `
|
|
528
|
+
**`ratified: yes` next to `nominated deciders: 0` is not a contradiction.** `nominatedDeciders` is the list of people
|
|
527
529
|
nominated **in advance** to decide. When an agent's request is *routed* rather than pre-assigned,
|
|
528
530
|
that list is empty — nobody was nominated, because the request had not happened yet. The person who
|
|
529
531
|
actually decided is the **ratifier** (`ratifiedBy`). So a receipt for a human-approved action reads
|
|
530
|
-
`deciders: 0, ratified: yes`, and the human is recorded, in the field named for the role they
|
|
532
|
+
`nominated deciders: 0, ratified: yes`, and the human is recorded, in the field named for the role they
|
|
531
533
|
played. The verifier prints this explanation whenever the pair appears.
|
|
532
534
|
|
|
533
|
-
**`
|
|
535
|
+
**`evaluatedLimits` is what the request was MEASURED AGAINST, not a rule that fired.** (Receipts sealed before 0.5.0 spell these two `deciders` and `appliedLimits` - schemaVersion 1; the verifier reads both.) It holds up to
|
|
534
536
|
three entries, and only those that have a value: `requested_amount` (the amount actually asked for),
|
|
535
537
|
`auto_approve_threshold` and `hard_cap` (the ceilings on the agent's own token). So a decision routed
|
|
536
538
|
to a human because *no rule matched* can still list an `auto_approve_threshold` — that is the bar the
|
|
@@ -551,14 +553,14 @@ the roadmap; today the URN is an identifier only, and this note exists so it doe
|
|
|
551
553
|
|
|
552
554
|
**`prevHash` proves a pointer, not an ancestry.** A non-null `prevHash` shows this record was
|
|
553
555
|
sealed after a specific predecessor, but a single-file verification cannot check that predecessor
|
|
554
|
-
exists or is what it claims. Verify a chain (`verify a.json b.json c.json`) when the sequence
|
|
556
|
+
exists or is what it claims. Verify a chain (`verify --issuer <did> a.json b.json c.json`) when the sequence
|
|
555
557
|
matters.
|
|
556
558
|
|
|
557
559
|
**Pinning is trust-on-first-use.** The first time you fetch a receipt, the record *and* the issuer
|
|
558
560
|
DID arrive in the same authenticated response — so that first check trusts the connection, not the
|
|
559
561
|
key. What pinning buys is everything afterwards: record the DID once, out of band if you can, and
|
|
560
562
|
every later verification is checked against a key you chose rather than one you were handed. The
|
|
561
|
-
verifier
|
|
563
|
+
verifier refuses an unpinned run unless you ask for one (`--allow-unpinned`), and marks that verdict.
|
|
562
564
|
|
|
563
565
|
## Upgrading from 0.2.x to 0.3.0
|
|
564
566
|
|
|
@@ -638,20 +640,22 @@ that fits every other confirm error.
|
|
|
638
640
|
|
|
639
641
|
- `quickstart` — the guided two-minute tour (gate → approve → execute → proof)
|
|
640
642
|
- `--auto-approve-demo` approves the tour's own synthetic decision so it can run headless; it
|
|
641
|
-
then
|
|
643
|
+
then revokes the tour token and deletes its local credentials automatically (the inert
|
|
644
|
+
registration row and the permanent sealed record stay). Pass `--keep-agent` to keep them.
|
|
642
645
|
- An interactive run keeps the agent by default and offers cleanup at the end;
|
|
643
646
|
`quickstart cleanup` works later too.
|
|
644
647
|
- `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.
|
|
645
648
|
- `doctor` — config, connectivity, and what your current token can do
|
|
646
649
|
- `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)
|
|
647
|
-
- `receipt <id
|
|
650
|
+
- `receipt <id> [--json]` — download a sealed decision's Authority Receipt to a `.json` file (`--json`: stdout is the JSON result only; the identities your access covers print only on a terminal, or in the JSON)
|
|
648
651
|
- `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 depends on the verifier released alongside it, at the same version). 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.
|
|
649
652
|
- `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
|
|
650
653
|
- `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) Also ends the stored sign-in.
|
|
651
|
-
- **Update check.** Network commands ask the public npm registry once a day whether a newer version exists; if so, an interactive run asks `Upgrade now? [y/n]` first (`y` upgrades
|
|
654
|
+
- **Update check.** Network commands ask the public npm registry once a day whether a newer version exists; if so, an interactive run asks `Upgrade now? [y/n]` first (`y` upgrades the way you installed - `npm i @decidio/sdk@latest` in the project for a local install, `-g` for a global one, and under `npx` it prints the `@latest` form - then re-runs your command once; `n` exits), a headless run prints the notice and continues (`DECIDIO_REQUIRE_LATEST=1` makes it exit). `DECIDIO_NO_UPDATE_CHECK=1` skips it. Nothing is sent to Decidio; `verify` never checks.
|
|
655
|
+
- **The token recovery journal** (`.decidio-token-journal`, owner-only, gitignored; ids and timestamps, never a token value; append-only by the CLI's behaviour — not OS-enforced or tamper-evident). One JSON row per event: `minted` (a token was issued, written BEFORE its credential file), `retired how=persisted` (the credential file now holds it - the token is live and that file is its handle; not revoked), `retired how=revoked` (revoked server-side), `probe` (a durability check). `doctor` names rows still outstanding; `quickstart cleanup` sweeps them.
|
|
652
656
|
- `quickstart --phone web` — the QR at the human step opens the phone web rendering even when the Decidio app is installed (by default it opens the app when there is one).
|
|
653
|
-
- `login [email]` / `logout` — sign in ONCE (the CLI emails you a link; you paste it) and
|
|
654
|
-
- `dev [--target URL]` — local relay for the resume webhook (operator setups)
|
|
657
|
+
- `login [email]` / `logout` — sign in ONCE (the CLI emails you a link; you paste it) and forget that sign-in on this computer (the session token itself stays valid until it expires (up to 30 days) — there is no server-side revocation yet). The session is stored in your user profile, owner-only, bound to the terminal window that created it, for 30 days; `DECIDIO_SESSION_TOKEN` in the environment outranks it (headless shells).
|
|
658
|
+
- `dev [--target URL] [--port N]` — local relay for the resume webhook (operator setups)
|
|
655
659
|
|
|
656
660
|
The CLI's stream contract: **stdout carries results only** (safe to pipe — `--json` output is
|
|
657
661
|
pure JSON); errors, usage shown on *error* paths, and the per-run target-host announcement go
|
|
@@ -660,7 +664,7 @@ result and prints on stdout. Exit codes: 0 success · 1 failure or honest no-op
|
|
|
660
664
|
|
|
661
665
|
Commands that administer your workspace (`init`, `approvals`, `receipt`, `tokens`) use a **session**
|
|
662
666
|
sign-in: `login` stores it in your user profile (owner-only, bound to this terminal window, 30 days;
|
|
663
|
-
`logout` or `quickstart cleanup`
|
|
667
|
+
`logout` or `quickstart cleanup` forgets it here — the token itself stays valid until it expires), or `DECIDIO_SESSION_TOKEN` supplies it for headless shells. What lands on disk in `.env`: the agent's floor-limited
|
|
664
668
|
API token, its locally-generated private signing key, and a webhook secret (used only in
|
|
665
669
|
operator deployments) — `init` adds `.env` to `.gitignore` for you; move secrets to a secret
|
|
666
670
|
manager for production.
|
|
@@ -705,7 +709,7 @@ the next reader grades a promise, not a moving target:
|
|
|
705
709
|
- **The issuer DID is trust on first use.** The tour and `receipt` print your workspace's issuer DID
|
|
706
710
|
from the same authenticated response that carries the receipt. Pinned verification is only as strong
|
|
707
711
|
as where you keep that DID: record it through a channel you trust independently (your app's
|
|
708
|
-
record-verify panel
|
|
712
|
+
record-verify panel, and published in the open by the Decidio host you use at `<api>/api/did` — for the hosted sandbox https://decidio-api.onrender.com/api/did).
|
|
709
713
|
- **Verification is cryptographic, not schema validation.** `VALID` means the issuer's key signed these
|
|
710
714
|
canonical claims; the sealed-payload schema the receipt names by hash is not shipped, so a
|
|
711
715
|
structurally odd but correctly signed document is not refused on shape. Shipping the schema is on
|
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { readFileSync, writeFileSync, existsSync, appendFileSync, chmodSync, rmSync, openSync, writeSync, fsyncSync, closeSync, renameSync, mkdirSync } from "node:fs";
|
|
3
3
|
import { randomBytes } from "node:crypto";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
|
-
import { join as joinPath } from "node:path";
|
|
5
|
+
import { join as joinPath, resolve as resolvePath, sep as pathSep } from "node:path";
|
|
6
6
|
import { createServer } from "node:http";
|
|
7
7
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
8
8
|
import { createInterface } from "node:readline";
|
|
@@ -12,7 +12,7 @@ import { createRequire } from "node:module";
|
|
|
12
12
|
import { generateKeypair, assertSafeTransport, redactUrl } from "./signing.js";
|
|
13
13
|
import { hardenFile } from "./resume.js";
|
|
14
14
|
import { withApproval, DecidioRateLimitError, DecidioRejectedError, DecidioTimeoutError, didFromPrivateKey } from "./index.js";
|
|
15
|
-
import { verifyReceipt, runVerify } from "@decidio/verify";
|
|
15
|
+
import { verifyReceipt, runVerify, pinRequiredMessage } from "@decidio/verify";
|
|
16
16
|
const VERSION = (() => {
|
|
17
17
|
try {
|
|
18
18
|
return createRequire(import.meta.url)("../package.json").version;
|
|
@@ -378,12 +378,15 @@ function assertNotTracked(path) {
|
|
|
378
378
|
}
|
|
379
379
|
function ensureGitignore() {
|
|
380
380
|
const entries = [".env", ".env.quickstart", ".env.*.bak", ".env.*.tmp", ".decidio-pending/", ".decidio-token-journal"];
|
|
381
|
-
const
|
|
381
|
+
const creating = !existsSync(".gitignore");
|
|
382
|
+
if (creating)
|
|
383
|
+
entries.push("node_modules/");
|
|
384
|
+
const existing = creating ? "" : readFileSync(".gitignore", "utf8");
|
|
382
385
|
const missing = entries.filter((e) => !existing.split(/\r?\n/).some((l) => l.trim() === e || l.trim() === e.replace(/\/$/, "")));
|
|
383
386
|
if (!missing.length)
|
|
384
387
|
return;
|
|
385
388
|
appendFileSync(".gitignore", (existing && !existing.endsWith("\n") ? "\n" : "") + missing.join("\n") + "\n");
|
|
386
|
-
log(`✔ added ${missing.join(" + ")} to .gitignore (
|
|
389
|
+
log(`✔ ${creating ? "wrote" : "added"} ${missing.join(" + ")} to .gitignore (secret files here will not be committed)`);
|
|
387
390
|
}
|
|
388
391
|
function recordKindOf(credential) {
|
|
389
392
|
try {
|
|
@@ -396,7 +399,7 @@ function recordKindOf(credential) {
|
|
|
396
399
|
}
|
|
397
400
|
}
|
|
398
401
|
function sessionReminder() {
|
|
399
|
-
log(` ⚠ agent code launched from THIS terminal inherits your sign-in — run logout first (it
|
|
402
|
+
log(` ⚠ agent code launched from THIS terminal inherits your sign-in — run logout first (it forgets the sign-in here; the token itself stays valid until it expires); if you`);
|
|
400
403
|
log(` exported DECIDIO_SESSION_TOKEN, unset it ${dim("(bash/zsh: unset DECIDIO_SESSION_TOKEN · PowerShell: Remove-Item Env:DECIDIO_SESSION_TOKEN)")}`);
|
|
401
404
|
}
|
|
402
405
|
const TTY_OUT = !!process.stdout.isTTY && !process.env.NO_COLOR && process.env.TERM !== "dumb";
|
|
@@ -409,7 +412,7 @@ const visibleLength = (s) => s.replace(/\x1b\[[0-9;]*m|\x1b\]8;;[^\x1b]*\x1b\\/g
|
|
|
409
412
|
function frameWidth(rows) {
|
|
410
413
|
const cols = (process.stdout.columns ?? 0) > 20 ? process.stdout.columns : 120;
|
|
411
414
|
const widest = rows.reduce((m, r) => Math.max(m, visibleLength(r)), 0);
|
|
412
|
-
return Math.
|
|
415
|
+
return Math.min(cols - 1, Math.max(FRAME_WIDTH, widest + 4));
|
|
413
416
|
}
|
|
414
417
|
function block(n, title, rows, tone) {
|
|
415
418
|
const tint = tone === "choice" ? (process.env.TERM?.includes("256") || process.env.COLORTERM || process.env.WT_SESSION ? "38;5;208" : "33") : tone === "good" ? "32" : tone === "bad" ? "31" : null;
|
|
@@ -538,7 +541,7 @@ function endSession() {
|
|
|
538
541
|
}
|
|
539
542
|
}
|
|
540
543
|
async function loginFlow(purpose, emailArg) {
|
|
541
|
-
log(purpose === "Signing in" ? `Sign in once — 30 days, or until this terminal closes, logout, or cleanup.` : `${purpose} needs your Decidio workspace sign-in — once: 30 days, or until this terminal closes, logout, or cleanup.`);
|
|
544
|
+
log(purpose === "Signing in" ? `Sign in once — kept on this computer for 30 days, or until this terminal closes, logout, or cleanup.` : `${purpose} needs your Decidio workspace sign-in — once: kept on this computer for 30 days, or until this terminal closes, logout, or cleanup.`);
|
|
542
545
|
const email = emailArg ?? await ask(" email: ");
|
|
543
546
|
if (!email) {
|
|
544
547
|
log(" ✘ no email given - re-run when you have it.");
|
|
@@ -617,9 +620,12 @@ async function cmdLogin(emailArg) {
|
|
|
617
620
|
const token = await loginFlow("Signing in", emailArg);
|
|
618
621
|
return token ? 0 : 1;
|
|
619
622
|
}
|
|
623
|
+
const TRUTH_SESSION = "The session token itself stays valid until it expires (up to 30 days) — there is no server-side revocation yet";
|
|
620
624
|
function cmdLogout() {
|
|
621
|
-
if (endSession())
|
|
622
|
-
log("✔ signed out — the stored sign-in is removed.");
|
|
625
|
+
if (endSession()) {
|
|
626
|
+
log("✔ signed out — the stored sign-in is removed from this computer.");
|
|
627
|
+
log(` ${TRUTH_SESSION}; if you exported DECIDIO_SESSION_TOKEN or pasted the link anywhere else, those copies still work.`);
|
|
628
|
+
}
|
|
623
629
|
else
|
|
624
630
|
log("• no stored sign-in on this machine for this Decidio.");
|
|
625
631
|
if (process.env.DECIDIO_SESSION_TOKEN)
|
|
@@ -1034,7 +1040,10 @@ async function cmdReceipt(id, jsonMode = false) {
|
|
|
1034
1040
|
}
|
|
1035
1041
|
if (conf?.status)
|
|
1036
1042
|
log(` Sealed credential: the authority decision (its confirmation field reads "pending" forever — immutable). Current record evidence tier: ${conf.status}${conf.confirmed ? " (confirmed)" : ""}.`);
|
|
1037
|
-
if (resolved && Object.keys(resolved).length) {
|
|
1043
|
+
if (resolved && Object.keys(resolved).length && !process.stdout.isTTY) {
|
|
1044
|
+
log(` ${Object.keys(resolved).length} identity field(s) your access covers are shown only on a terminal (this is a pipe or a log) — use --json to receive them explicitly.`);
|
|
1045
|
+
}
|
|
1046
|
+
else if (resolved && Object.keys(resolved).length) {
|
|
1038
1047
|
log(` Resolved for you (${Object.keys(resolved).length} field${Object.keys(resolved).length === 1 ? "" : "s"} your access covers — the FILE keeps the tokens, so it stays verifiable and safe to hand on):`);
|
|
1039
1048
|
for (const [tok, val] of Object.entries(resolved))
|
|
1040
1049
|
log(` ${tok} → ${val}`);
|
|
@@ -1045,11 +1054,12 @@ async function cmdReceipt(id, jsonMode = false) {
|
|
|
1045
1054
|
log(``);
|
|
1046
1055
|
log(`${issuer}`);
|
|
1047
1056
|
log(``);
|
|
1057
|
+
log(` Cross-check it out of band — the same DID is published by this Decidio host at ${API}/api/did (public, no sign-in).`);
|
|
1048
1058
|
log(` Then verify, pinned to it (the same DID, inline — copy from the line above, not this one,`);
|
|
1049
1059
|
log(` which your terminal may wrap):`);
|
|
1050
1060
|
log(` npx @decidio/sdk verify --issuer ${issuer} ${name}`);
|
|
1051
|
-
log(`
|
|
1052
|
-
log(`
|
|
1061
|
+
log(` The verifier refuses to run without --issuer: a pin is what makes VALID mean "issued by your`);
|
|
1062
|
+
log(` workspace"; --allow-unpinned runs a structural check only, and says so.`);
|
|
1053
1063
|
}
|
|
1054
1064
|
else {
|
|
1055
1065
|
log(` To also prove WHO issued it, pin your workspace's issuer DID (shown in your app's record verify panel):`);
|
|
@@ -1059,6 +1069,7 @@ async function cmdReceipt(id, jsonMode = false) {
|
|
|
1059
1069
|
}
|
|
1060
1070
|
function cmdVerify(argsIn) {
|
|
1061
1071
|
let expectedIssuer;
|
|
1072
|
+
let allowUnpinned = false;
|
|
1062
1073
|
const files = [];
|
|
1063
1074
|
for (let i = 0; i < argsIn.length; i++) {
|
|
1064
1075
|
const a = argsIn[i];
|
|
@@ -1066,6 +1077,10 @@ function cmdVerify(argsIn) {
|
|
|
1066
1077
|
files.push(...argsIn.slice(i + 1));
|
|
1067
1078
|
break;
|
|
1068
1079
|
}
|
|
1080
|
+
if (a === "--allow-unpinned") {
|
|
1081
|
+
allowUnpinned = true;
|
|
1082
|
+
continue;
|
|
1083
|
+
}
|
|
1069
1084
|
if (a === "--issuer") {
|
|
1070
1085
|
const v = argsIn[i + 1];
|
|
1071
1086
|
if (v === undefined || v === "" || v.startsWith("-")) {
|
|
@@ -1096,10 +1111,18 @@ function cmdVerify(argsIn) {
|
|
|
1096
1111
|
}
|
|
1097
1112
|
}
|
|
1098
1113
|
if (!files.length) {
|
|
1099
|
-
err("usage: npx @decidio/sdk verify
|
|
1114
|
+
err("usage: npx @decidio/sdk verify --issuer <did:key> <receipt.json> [more.json ...] (or --allow-unpinned)");
|
|
1100
1115
|
err(" Verifies Authority Receipts OFFLINE — signature, content binding, and (for several files) chain order.");
|
|
1101
1116
|
return 2;
|
|
1102
1117
|
}
|
|
1118
|
+
if (expectedIssuer !== undefined && allowUnpinned) {
|
|
1119
|
+
err("✘ --allow-unpinned and --issuer together — an unpinned check and a pinned one are different questions; ask one.");
|
|
1120
|
+
return 2;
|
|
1121
|
+
}
|
|
1122
|
+
if (expectedIssuer === undefined && !allowUnpinned) {
|
|
1123
|
+
err(pinRequiredMessage("npx @decidio/sdk verify"));
|
|
1124
|
+
return 2;
|
|
1125
|
+
}
|
|
1103
1126
|
const texts = new Map();
|
|
1104
1127
|
for (const f of files) {
|
|
1105
1128
|
try {
|
|
@@ -1148,9 +1171,15 @@ async function cmdQuickstart(opts = {}) {
|
|
|
1148
1171
|
const session = await acquireSession("The quickstart");
|
|
1149
1172
|
if (!session)
|
|
1150
1173
|
return 1;
|
|
1151
|
-
const
|
|
1174
|
+
const prior = readEnvFile();
|
|
1175
|
+
const envFile = ["DECIDIO_AGENT_KEY", "DECIDIO_AGENT_ID", "DECIDIO_AGENT_DID", "DECIDIO_API_TOKEN"].some((k) => (prior[k] ?? "").trim()) ? ".env.quickstart" : ENV_PATH;
|
|
1152
1176
|
if (!assertNotTracked(envFile))
|
|
1153
1177
|
return 1;
|
|
1178
|
+
const reach = await api("/api/health", {}, "", true);
|
|
1179
|
+
if (!(reach.status >= 200 && reach.status < 300)) {
|
|
1180
|
+
err(`✘ cannot reach ${API}${reach.status ? ` (HTTP ${reach.status})` : " (no response)"} — check the network (or DECIDIO_API_URL) and re-run. Nothing was written.`);
|
|
1181
|
+
return 1;
|
|
1182
|
+
}
|
|
1154
1183
|
ensureGitignore();
|
|
1155
1184
|
let agentId = `quickstart-${randomBytes(5).toString("hex")}`;
|
|
1156
1185
|
if (EXPLAIN)
|
|
@@ -1470,8 +1499,9 @@ async function cmdQuickstart(opts = {}) {
|
|
|
1470
1499
|
}
|
|
1471
1500
|
block("④", "The record", [
|
|
1472
1501
|
`✔ VALID — ${fname} ${dim("(signature + content binding, offline, PINNED to your workspace issuer)")}`,
|
|
1502
|
+
`${bold("decision")} payInvoice $86,000 · REJECTED · routed to a human by policy (no auto-approve rule for this agent)`,
|
|
1473
1503
|
`${bold("record")} record kind: ${recordKindOf(rec.body.credential) ?? "?"} ${dim("— the denial is sealed too")}`,
|
|
1474
|
-
`${bold("issuer")} ${issuer} ${dim(
|
|
1504
|
+
`${bold("issuer")} ${issuer} ${dim(`· your trust anchor — first seen over this signed-in connection; cross-check ${API}/api/did, then verify --issuer <did> <receipt.json>`)}`,
|
|
1475
1505
|
], "bad");
|
|
1476
1506
|
denialVerified = true;
|
|
1477
1507
|
}
|
|
@@ -1554,8 +1584,9 @@ async function cmdQuickstart(opts = {}) {
|
|
|
1554
1584
|
const tier = detail.body?.record?.confirmation?.status;
|
|
1555
1585
|
block("④", "The receipt", [
|
|
1556
1586
|
`✔ VALID — ${fname} ${dim("(signature + content binding, offline, PINNED to your workspace issuer)")}`,
|
|
1587
|
+
`${bold("decision")} payInvoice $86,000 · APPROVED · routed to a human by policy (no auto-approve rule for this agent)`,
|
|
1557
1588
|
`${bold("record")} record kind: ${recordKindOf(rec.body.credential) ?? "?"}${tier ? ` · evidence: ${tier}` : ""} ${dim("— the receipt seals the DECISION; execution evidence lives on the record")}`,
|
|
1558
|
-
`${bold("issuer")} ${issuer} ${dim(
|
|
1589
|
+
`${bold("issuer")} ${issuer} ${dim(`· your trust anchor — first seen over this signed-in connection; cross-check ${API}/api/did, then verify --issuer <did> <receipt.json>`)}`,
|
|
1559
1590
|
], "good");
|
|
1560
1591
|
const secs = Math.round((Date.now() - t0) / 1000);
|
|
1561
1592
|
block("✔", `Done in ${secs}s`, [
|
|
@@ -1753,7 +1784,7 @@ async function quickstartCleanup(session, envFile) {
|
|
|
1753
1784
|
log(` ${process.platform === "win32" ? "Remove-Item -Recurse .decidio-pending" : "rm -r .decidio-pending"}`);
|
|
1754
1785
|
}
|
|
1755
1786
|
if (endSession())
|
|
1756
|
-
log(` ✔ signed out — the stored sign-in is
|
|
1787
|
+
log(` ✔ signed out — the stored sign-in is removed from this computer (the token itself stays valid until it expires; npx @decidio/sdk login to sign in again).`);
|
|
1757
1788
|
else if (process.env.DECIDIO_SESSION_TOKEN)
|
|
1758
1789
|
log(` • DECIDIO_SESSION_TOKEN is still exported in this shell — unset it when you are done.`);
|
|
1759
1790
|
return 0;
|
|
@@ -1769,7 +1800,16 @@ async function cmdTokens(sub, jti, mineOnly = false) {
|
|
|
1769
1800
|
err("usage: npx @decidio/sdk tokens revoke <jti> (find the jti with: npx @decidio/sdk tokens list)");
|
|
1770
1801
|
return 2;
|
|
1771
1802
|
}
|
|
1772
|
-
|
|
1803
|
+
const journalAgents = (() => {
|
|
1804
|
+
try {
|
|
1805
|
+
return [...new Set(journalRows().map((row) => row?.agentId).filter((a) => typeof a === "string" && a.length > 0))];
|
|
1806
|
+
}
|
|
1807
|
+
catch {
|
|
1808
|
+
return [];
|
|
1809
|
+
}
|
|
1810
|
+
})();
|
|
1811
|
+
const myAgents = process.env.DECIDIO_AGENT_ID ? [process.env.DECIDIO_AGENT_ID] : journalAgents;
|
|
1812
|
+
if (mineOnly && !myAgents.length) {
|
|
1773
1813
|
err(`✘ --mine needs an agent identity, and there is no DECIDIO_AGENT_ID in this directory's .env or your environment.`);
|
|
1774
1814
|
err(` Run this from your agent's directory (where init wrote .env), or drop --mine for the workspace-wide list.`);
|
|
1775
1815
|
return 2;
|
|
@@ -1809,8 +1849,8 @@ async function cmdTokens(sub, jti, mineOnly = false) {
|
|
|
1809
1849
|
return 1;
|
|
1810
1850
|
}
|
|
1811
1851
|
const all = r.body?.tokens ?? r.body ?? [];
|
|
1812
|
-
const myAgent = process.env.DECIDIO_AGENT_ID;
|
|
1813
|
-
const tokens = mineOnly ? all.filter((t) => t.agentId
|
|
1852
|
+
const myAgent = process.env.DECIDIO_AGENT_ID || (journalAgents.length ? `${journalAgents.join(", ")} (from the token recovery journal)` : undefined);
|
|
1853
|
+
const tokens = mineOnly ? all.filter((t) => myAgents.includes(t.agentId)) : all;
|
|
1814
1854
|
if (!Array.isArray(tokens) || !tokens.length) {
|
|
1815
1855
|
log(mineOnly ? `No agent tokens for "${myAgent}"${all.length ? ` (${all.length} elsewhere in this workspace — drop --mine to see them)` : ""}.` : "No agent tokens in this workspace.");
|
|
1816
1856
|
return 0;
|
|
@@ -1879,15 +1919,17 @@ THE TOUR
|
|
|
1879
1919
|
automated; ends with a keep-or-cleanup choice; --explain prints
|
|
1880
1920
|
the long narration of each step; --phone web makes the QR open
|
|
1881
1921
|
the phone web rendering even when the Decidio app is installed)
|
|
1882
|
-
npx @decidio/sdk quickstart cleanup revoke the tour's token + delete its local credentials
|
|
1922
|
+
npx @decidio/sdk quickstart cleanup revoke the tour's token + delete its local credentials (the inert
|
|
1923
|
+
registration row and the permanent sealed record stay)
|
|
1883
1924
|
(and end the stored sign-in)
|
|
1884
1925
|
npx @decidio/sdk quickstart --auto-approve-demo headless tour; cleans up after itself
|
|
1885
1926
|
(add --keep-agent to KEEP the tour agent + token)
|
|
1886
1927
|
|
|
1887
1928
|
SIGN IN (once)
|
|
1888
1929
|
npx @decidio/sdk login [email] sign in ONCE: the CLI emails you a link, you paste it; the sign-in
|
|
1889
|
-
|
|
1890
|
-
npx @decidio/sdk logout
|
|
1930
|
+
is kept on this computer 30 days, or until this terminal closes, logout, or cleanup
|
|
1931
|
+
npx @decidio/sdk logout forget the stored sign-in on this computer (the token itself stays valid
|
|
1932
|
+
until it expires — there is no server-side revocation yet)
|
|
1891
1933
|
|
|
1892
1934
|
YOUR AGENT AND ITS TOKENS
|
|
1893
1935
|
npx @decidio/sdk init [agentId] sign in, register the agent, mint its API token, write .env
|
|
@@ -1912,7 +1954,8 @@ APPROVALS AND RECEIPTS
|
|
|
1912
1954
|
... approve <id> -- "-50% cut, per the board"
|
|
1913
1955
|
... approve <id> --reason "-50% cut, per the board"
|
|
1914
1956
|
npx @decidio/sdk receipt <id> [--json] download a sealed decision's Authority Receipt (.json)
|
|
1915
|
-
npx @decidio/sdk verify
|
|
1957
|
+
npx @decidio/sdk verify --issuer <did> <receipt.json> [more...] verify receipts OFFLINE, pinned to your
|
|
1958
|
+
workspace's issuer (--allow-unpinned: structural check only)
|
|
1916
1959
|
|
|
1917
1960
|
DEVELOPMENT
|
|
1918
1961
|
npx @decidio/sdk dev [--target URL] [--port N] local relay for the resume webhook
|
|
@@ -1923,7 +1966,8 @@ NOTES
|
|
|
1923
1966
|
DECIDIO_API_URL says otherwise; the target host is announced on stderr each run. "verify" is
|
|
1924
1967
|
fully offline — it touches no network and reads no .env.
|
|
1925
1968
|
Update check: network commands ask the public npm registry once a day whether a newer version exists. If so,
|
|
1926
|
-
an interactive run asks "Upgrade now? [y/n]" first (y upgrades
|
|
1969
|
+
an interactive run asks "Upgrade now? [y/n]" first (y upgrades the way you installed - global, or in the
|
|
1970
|
+
project for a local install; under npx it prints the @latest form - and re-runs your command; n exits); a headless
|
|
1927
1971
|
run prints the notice and continues (DECIDIO_REQUIRE_LATEST=1 makes it exit). DECIDIO_NO_UPDATE_CHECK=1 skips
|
|
1928
1972
|
the check. Nothing is sent to Decidio; verify never checks.
|
|
1929
1973
|
Help: --help (or -h) after any command prints this page - one page for every command.
|
|
@@ -1976,13 +2020,23 @@ async function latestPublished() {
|
|
|
1976
2020
|
}
|
|
1977
2021
|
}
|
|
1978
2022
|
async function updateGate(cmd, argv) {
|
|
1979
|
-
if (!cmd || !GATED_CMDS.has(cmd) || SHELL_ENV.DECIDIO_NO_UPDATE_CHECK || VERSION === "unknown" || VERSION.includes("-"))
|
|
2023
|
+
if (!cmd || !GATED_CMDS.has(cmd) || SHELL_ENV.DECIDIO_NO_UPDATE_CHECK || SHELL_ENV.DECIDIO_UPDATE_REEXEC || VERSION === "unknown" || VERSION.includes("-"))
|
|
1980
2024
|
return null;
|
|
1981
2025
|
const latest = await latestPublished();
|
|
1982
2026
|
if (!latest || !semverNewer(latest, VERSION))
|
|
1983
2027
|
return null;
|
|
1984
|
-
const
|
|
1985
|
-
const
|
|
2028
|
+
const entry = process.argv[1] ?? "";
|
|
2029
|
+
const underNpx = /[\\/]_npx[\\/]/.test(entry);
|
|
2030
|
+
const localRoot = (() => {
|
|
2031
|
+
const m = /^(.*?)[\\/]node_modules[\\/]@decidio[\\/]sdk[\\/]/.exec(entry);
|
|
2032
|
+
if (!m)
|
|
2033
|
+
return null;
|
|
2034
|
+
const norm = (s) => (process.platform === "win32" ? s.toLowerCase() : s);
|
|
2035
|
+
const root = resolvePath(m[1]);
|
|
2036
|
+
const cwd = norm(process.cwd());
|
|
2037
|
+
return cwd === norm(root) || cwd.startsWith(norm(root) + pathSep) ? root : null;
|
|
2038
|
+
})();
|
|
2039
|
+
const upgradeCmd = underNpx ? `npx @decidio/sdk@latest ${argv.slice(2).join(" ")}` : localRoot ? "npm i @decidio/sdk@latest" : "npm i -g @decidio/sdk@latest";
|
|
1986
2040
|
const notice = `${latest} is available (you have ${VERSION}).`;
|
|
1987
2041
|
if (!(process.stdin.isTTY && process.stdout.isTTY)) {
|
|
1988
2042
|
err(`⚠ ${notice} Upgrade: ${upgradeCmd}`);
|
|
@@ -2005,14 +2059,14 @@ async function updateGate(cmd, argv) {
|
|
|
2005
2059
|
log(` would run: ${upgradeCmd}`);
|
|
2006
2060
|
return null;
|
|
2007
2061
|
}
|
|
2008
|
-
log(` upgrading: ${upgradeCmd}`);
|
|
2009
|
-
const up = spawnSync(process.platform === "win32" ? "npm.cmd" : "npm", ["i", "-g", "@decidio/sdk@latest"], { stdio: "inherit", shell: process.platform === "win32", env: SHELL_ENV });
|
|
2062
|
+
log(` upgrading: ${upgradeCmd}${localRoot ? ` (in ${localRoot})` : ""}`);
|
|
2063
|
+
const up = spawnSync(process.platform === "win32" ? "npm.cmd" : "npm", localRoot ? ["i", "@decidio/sdk@latest"] : ["i", "-g", "@decidio/sdk@latest"], { stdio: "inherit", shell: process.platform === "win32", env: SHELL_ENV, ...(localRoot ? { cwd: localRoot } : {}) });
|
|
2010
2064
|
if (up.status !== 0) {
|
|
2011
2065
|
err(`✘ the upgrade did not complete (exit ${up.status ?? "?"}) — run it by hand: ${upgradeCmd}`);
|
|
2012
2066
|
return 1;
|
|
2013
2067
|
}
|
|
2014
2068
|
log(` ✔ upgraded — re-running: decidio ${argv.slice(2).join(" ")}`);
|
|
2015
|
-
const rerun = spawnSync(process.execPath, [process.argv[1], ...argv.slice(2)], { stdio: "inherit", env: SHELL_ENV });
|
|
2069
|
+
const rerun = spawnSync(process.execPath, [process.argv[1], ...argv.slice(2)], { stdio: "inherit", env: { ...SHELL_ENV, DECIDIO_UPDATE_REEXEC: "1" } });
|
|
2016
2070
|
return rerun.status ?? 1;
|
|
2017
2071
|
}
|
|
2018
2072
|
async function main(argv) {
|
|
@@ -2042,7 +2096,7 @@ async function main(argv) {
|
|
|
2042
2096
|
doctor: { flags: [] },
|
|
2043
2097
|
approvals: { flags: ["--json", "--mine"], valued: ["--reason"] },
|
|
2044
2098
|
receipt: { flags: ["--json"] },
|
|
2045
|
-
verify: { flags: [], valued: ["--issuer"] },
|
|
2099
|
+
verify: { flags: ["--allow-unpinned"], valued: ["--issuer"] },
|
|
2046
2100
|
tokens: { flags: ["--mine"] },
|
|
2047
2101
|
dev: { flags: [], valued: ["--target", "--port"] },
|
|
2048
2102
|
};
|
|
@@ -2122,6 +2176,8 @@ async function main(argv) {
|
|
|
2122
2176
|
log(held.length
|
|
2123
2177
|
? `Nothing to clean up here: no tour credentials. The token recovery journal names ${held.length} live token(s) held by a credential file here (${held.map((j) => j.jti).join(", ")}) — not tour tokens, left alone; doctor lists them.`
|
|
2124
2178
|
: "Nothing to clean up here: no .env.quickstart, .env doesn't belong to a quickstart agent, and the token recovery journal has no live token to act on.");
|
|
2179
|
+
if (endSession())
|
|
2180
|
+
log(` ✔ signed out — the stored sign-in is removed from this computer (the token itself stays valid until it expires).`);
|
|
2125
2181
|
return 0;
|
|
2126
2182
|
}
|
|
2127
2183
|
const session = await acquireSession("Cleaning the quickstart up");
|
package/dist/resume.d.ts
CHANGED
|
@@ -37,6 +37,7 @@ export interface PendingAction {
|
|
|
37
37
|
/** The durable execution state machine (v2).
|
|
38
38
|
*
|
|
39
39
|
* parked → claimed_preinvoke → invoking → (executed_unreported | indeterminate) → reported
|
|
40
|
+
* plus the terminal `discarded` (a human or policy said NO: written from parked/claimed_preinvoke, never invoked).
|
|
40
41
|
*
|
|
41
42
|
* THE ONE RULE: nothing transitions BACK to parked once `invoking` has been written. Deletion
|
|
42
43
|
* is cleanup, never the single-use guarantee — that was the 0.1.x model, and a store whose
|
|
@@ -469,7 +470,7 @@ export type RecoveryState =
|
|
|
469
470
|
"parked"
|
|
470
471
|
/** Claimed by an attempt that never reached the invocation boundary, so the handler PROVABLY
|
|
471
472
|
* did not run — but the row is not parked either: `list()` excludes it and every ordinary
|
|
472
|
-
* signal answers `
|
|
473
|
+
* signal answers `lost_race`, so nothing will pick it up on its own. It is stalled until an
|
|
473
474
|
* operator releases it. Reporting this as "parked" (as 0.2.0's first draft did) told a UI an
|
|
474
475
|
* approved action was still queued to resume when in fact it had been silently dropped. */
|
|
475
476
|
| "claimed_stalled"
|
|
@@ -581,8 +582,9 @@ export interface ResumeConfig {
|
|
|
581
582
|
/** The callback URL Decidio should POST the verdict to (webhook transport). Sent
|
|
582
583
|
* on /agent/authorize so the decision carries it; omit when using the poll worker. */
|
|
583
584
|
resumeUrl?: string;
|
|
584
|
-
/** Fired
|
|
585
|
-
* no_handler), via either transport. Use it to log, close a
|
|
585
|
+
/** Fired for every outcome except `unknown` (executed / indeterminate / rejected / blocked /
|
|
586
|
+
* no_handler / already_resolved / lost_race), via either transport. Use it to log, close a
|
|
587
|
+
* listener, or exit a CLI.
|
|
586
588
|
* `indeterminate` is new in 0.2.0 and DOES fire here: a handler that ran and then threw is a
|
|
587
589
|
* terminal outcome for automation — nothing will retry it — so a listener that only handled
|
|
588
590
|
* the old four would silently miss the one case that needs a human. */
|
|
@@ -600,8 +602,9 @@ export interface ResumeController extends ResumeConfig {
|
|
|
600
602
|
/** Park a routed action WITH its integrity binding (prefer this over store.put — the
|
|
601
603
|
* binding is what lets resolve() refuse a tampered payload). */
|
|
602
604
|
park(p: Omit<PendingAction, "createdAt" | "argsSig">): Promise<void>;
|
|
603
|
-
/** Drive a parked decision to completion once a verdict is known. Idempotent:
|
|
604
|
-
*
|
|
605
|
+
/** Drive a parked decision to completion once a verdict is known. Idempotent: a second call
|
|
606
|
+
* after success answers `already_resolved` (no double-write); an id this SDK never parked
|
|
607
|
+
* answers `unknown`; a call that loses the pre-invocation claim answers `lost_race`. */
|
|
605
608
|
resolve(input: {
|
|
606
609
|
decisionId: string;
|
|
607
610
|
verdict: "approved" | "rejected" | "blocked";
|
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.
|
|
8
|
+
version: 0.5.0 # tracks the SDK release - checked by packages/conformance/manifests.mjs
|
|
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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decidio/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
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",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"prepublishOnly": "npm run typecheck && npm run build && node ../publish-boundary/check.mjs --only @decidio/sdk"
|
|
85
85
|
},
|
|
86
86
|
"dependencies": {
|
|
87
|
-
"@decidio/verify": "0.
|
|
87
|
+
"@decidio/verify": "0.5.0"
|
|
88
88
|
},
|
|
89
89
|
"author": "OmniTwin Technologies Inc.",
|
|
90
90
|
"bugs": {
|