@decidio/sdk 0.3.0 → 0.3.5
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 +119 -13
- package/dist/adapters/_gate.js +3 -2
- package/dist/adapters/openai.d.ts +1 -1
- package/dist/cli.js +292 -29
- package/dist/errors.js +5 -1
- package/dist/evidence.d.ts +9 -0
- package/dist/evidence.js +5 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +10 -7
- package/dist/inngest.js +2 -0
- package/dist/resume.d.ts +58 -27
- package/dist/resume.js +273 -161
- package/dist/signing.d.ts +4 -0
- package/dist/signing.js +23 -2
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readFileSync, writeFileSync, existsSync, appendFileSync, chmodSync, rmSync } from "node:fs";
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, appendFileSync, chmodSync, rmSync, openSync, writeSync, fsyncSync, closeSync, renameSync } from "node:fs";
|
|
3
3
|
import { randomBytes } from "node:crypto";
|
|
4
4
|
import { createServer } from "node:http";
|
|
5
5
|
import { execFileSync } from "node:child_process";
|
|
@@ -38,7 +38,10 @@ function readEnvFile() {
|
|
|
38
38
|
}
|
|
39
39
|
return out;
|
|
40
40
|
}
|
|
41
|
-
const
|
|
41
|
+
const wantsHelp = (s) => s === "help" || s === "--help" || s === "-h";
|
|
42
|
+
const ARGV_TAIL = process.argv.slice(2);
|
|
43
|
+
const OFFLINE_CMD = ["verify", "--version", "-v", "-V", "version", undefined].includes(ARGV_TAIL[0])
|
|
44
|
+
|| wantsHelp(ARGV_TAIL[0]) || wantsHelp(ARGV_TAIL[1]);
|
|
42
45
|
if (!OFFLINE_CMD) {
|
|
43
46
|
readEnvFile();
|
|
44
47
|
if (envUnreadable) {
|
|
@@ -109,7 +112,30 @@ async function api(path, init = {}, bearer) {
|
|
|
109
112
|
return { status: res.status, body };
|
|
110
113
|
}
|
|
111
114
|
function writeSecretFile(path, body) {
|
|
112
|
-
|
|
115
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
116
|
+
const fd = openSync(tmp, "w", 0o600);
|
|
117
|
+
try {
|
|
118
|
+
writeSync(fd, body);
|
|
119
|
+
fsyncSync(fd);
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
closeSync(fd);
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
chmodSync(tmp, 0o600);
|
|
126
|
+
}
|
|
127
|
+
catch { }
|
|
128
|
+
hardenFile(tmp);
|
|
129
|
+
try {
|
|
130
|
+
renameSync(tmp, path);
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
133
|
+
try {
|
|
134
|
+
rmSync(tmp, { force: true });
|
|
135
|
+
}
|
|
136
|
+
catch { }
|
|
137
|
+
throw e;
|
|
138
|
+
}
|
|
113
139
|
try {
|
|
114
140
|
chmodSync(path, 0o600);
|
|
115
141
|
}
|
|
@@ -119,6 +145,83 @@ function writeSecretFile(path, body) {
|
|
|
119
145
|
console.warn(`⚠ could not restrict ${path} to owner-only on this Windows volume (${why}) — it keeps the directory's inherited permissions. For production, move these values to a secret manager.`);
|
|
120
146
|
}
|
|
121
147
|
}
|
|
148
|
+
async function revokeFreshMint(jti, bearer, writeError, target) {
|
|
149
|
+
err(`✘ could not write ${target} (${writeError?.message ?? writeError}). The token minted a moment ago (jti ${jti}) is LIVE until revoked - revoking it now.`);
|
|
150
|
+
const rv = await api(`/api/workspace/agent-token/${encodeURIComponent(jti)}`, { method: "DELETE" }, bearer);
|
|
151
|
+
if (rv.status >= 200 && rv.status < 300) {
|
|
152
|
+
journalRetire(jti, "revoked");
|
|
153
|
+
err(` ✔ the token minted a moment ago (jti ${jti}) was REVOKED again, so nothing live was left behind. Fix the cause above and re-run - it will mint a fresh one.`);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
let known = false;
|
|
157
|
+
try {
|
|
158
|
+
known = journalOutstanding().some((j) => j.jti === jti);
|
|
159
|
+
}
|
|
160
|
+
catch { }
|
|
161
|
+
if (known)
|
|
162
|
+
err(` Its id is kept in ${JOURNAL_PATH} (owner-only, append-only) until it is revoked - \`quickstart cleanup\` retries it, and \`doctor\` names it.`);
|
|
163
|
+
else
|
|
164
|
+
err(` Its id is NOT recorded anywhere but this terminal (the journal write is what failed) - write it down now and revoke it by hand.`);
|
|
165
|
+
err(` ⚠ the token minted a moment ago (jti ${jti}) is LIVE and could NOT be revoked (${rv.status}). Revoke it by hand, or re-run init - the server keeps one active token per agent, so a fresh mint supersedes it:`);
|
|
166
|
+
err(` npx @decidio/sdk tokens revoke ${jti}`);
|
|
167
|
+
err(` npx @decidio/sdk tokens list --mine`);
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
const JOURNAL_PATH = ".decidio-token-journal";
|
|
171
|
+
function journalAppend(entry) {
|
|
172
|
+
const fresh = !existsSync(JOURNAL_PATH);
|
|
173
|
+
const fd = openSync(JOURNAL_PATH, "a", 0o600);
|
|
174
|
+
try {
|
|
175
|
+
writeSync(fd, JSON.stringify({ ...entry, at: new Date().toISOString() }) + "\n");
|
|
176
|
+
fsyncSync(fd);
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
closeSync(fd);
|
|
180
|
+
}
|
|
181
|
+
if (fresh) {
|
|
182
|
+
try {
|
|
183
|
+
chmodSync(JOURNAL_PATH, 0o600);
|
|
184
|
+
}
|
|
185
|
+
catch { }
|
|
186
|
+
hardenFile(JOURNAL_PATH);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function journalProbe() {
|
|
190
|
+
try {
|
|
191
|
+
journalAppend({ event: "probe" });
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
catch (e) {
|
|
195
|
+
return String(e?.message ?? e);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function journalMinted(jti, agentId, label) { journalAppend({ event: "minted", jti, agentId, label, apiUrl: API }); }
|
|
199
|
+
function journalRetire(jti, how) { try {
|
|
200
|
+
journalAppend({ event: "retired", jti, how });
|
|
201
|
+
}
|
|
202
|
+
catch { } }
|
|
203
|
+
function journalOutstanding() {
|
|
204
|
+
if (!existsSync(JOURNAL_PATH))
|
|
205
|
+
return [];
|
|
206
|
+
const minted = new Map();
|
|
207
|
+
for (const line of readFileSync(JOURNAL_PATH, "utf8").split(/\r?\n/)) {
|
|
208
|
+
if (!line.trim())
|
|
209
|
+
continue;
|
|
210
|
+
let e;
|
|
211
|
+
try {
|
|
212
|
+
e = JSON.parse(line);
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (e?.event === "minted" && typeof e.jti === "string")
|
|
218
|
+
minted.set(e.jti, { jti: e.jti, agentId: e.agentId, label: e.label, apiUrl: e.apiUrl, at: e.at });
|
|
219
|
+
if (e?.event === "retired" && typeof e.jti === "string")
|
|
220
|
+
minted.delete(e.jti);
|
|
221
|
+
}
|
|
222
|
+
return [...minted.values()];
|
|
223
|
+
}
|
|
224
|
+
const REFUSE_MINT_NO_JOURNAL = (why) => `✘ could not write the token recovery journal (${JOURNAL_PATH}: ${why}) - refusing to mint a credential this directory cannot record a revocation handle for. Fix the directory's permissions or free space and re-run. Nothing was minted.`;
|
|
122
225
|
function upsertEnv(updates) {
|
|
123
226
|
const remaining = { ...updates };
|
|
124
227
|
const updatedKeys = new Set();
|
|
@@ -168,7 +271,7 @@ function assertNotTracked(path) {
|
|
|
168
271
|
return false;
|
|
169
272
|
}
|
|
170
273
|
function ensureGitignore() {
|
|
171
|
-
const entries = [".env", ".env.quickstart", ".env.*.bak", ".decidio-pending/"];
|
|
274
|
+
const entries = [".env", ".env.quickstart", ".env.*.bak", ".env.*.tmp", ".decidio-pending/", ".decidio-token-journal"];
|
|
172
275
|
const existing = existsSync(".gitignore") ? readFileSync(".gitignore", "utf8") : "";
|
|
173
276
|
const missing = entries.filter((e) => !existing.split(/\r?\n/).some((l) => l.trim() === e || l.trim() === e.replace(/\/$/, "")));
|
|
174
277
|
if (!missing.length)
|
|
@@ -307,16 +410,33 @@ async function cmdInit(agentId, opts = {}) {
|
|
|
307
410
|
let agentToken, workspaceId, tokenJti;
|
|
308
411
|
const mintBearer = session ?? TOKEN();
|
|
309
412
|
if (mintBearer) {
|
|
413
|
+
ensureGitignore();
|
|
414
|
+
const noJournal = journalProbe();
|
|
415
|
+
if (noJournal) {
|
|
416
|
+
err(REFUSE_MINT_NO_JOURNAL(noJournal));
|
|
417
|
+
return 1;
|
|
418
|
+
}
|
|
310
419
|
const expiresInDays = opts.expiresDays ?? 90;
|
|
311
420
|
const mint = await api("/api/workspace/agent-token", { method: "POST", body: JSON.stringify({ agentId, label: "sdk-cli", expiresInDays }) }, mintBearer);
|
|
312
421
|
if (mint.status >= 200 && mint.status < 300 && mint.body?.token) {
|
|
313
422
|
agentToken = mint.body.token;
|
|
314
423
|
workspaceId = mint.body.workspaceId;
|
|
315
424
|
tokenJti = mint.body.jti;
|
|
425
|
+
if (tokenJti) {
|
|
426
|
+
try {
|
|
427
|
+
journalMinted(tokenJti, agentId, "sdk-cli");
|
|
428
|
+
}
|
|
429
|
+
catch (e) {
|
|
430
|
+
await revokeFreshMint(tokenJti, mintBearer, e, JOURNAL_PATH);
|
|
431
|
+
return 1;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
316
434
|
log(`✔ minted the agent's API token — floor-limited: it can request authorization, poll status and report confirmation; it cannot administer the workspace or approve anything${mint.body.expiresAt ? ` (expires ${mint.body.expiresAt})` : ` (${expiresInDays} days)`}`);
|
|
317
435
|
const superseded = Array.isArray(mint.body.supersededJtis) ? mint.body.supersededJtis : [];
|
|
318
|
-
for (const s of superseded)
|
|
436
|
+
for (const s of superseded) {
|
|
437
|
+
journalRetire(s, "revoked");
|
|
319
438
|
log(` • this replaces the agent's previous token (jti ${s}) — now REVOKED: the server keeps one active token per agent. Anything still using it will 401.`);
|
|
439
|
+
}
|
|
320
440
|
}
|
|
321
441
|
else if (session) {
|
|
322
442
|
err(`✘ could not mint the agent's API token (${mint.status}): ${JSON.stringify(mint.body)}`);
|
|
@@ -341,8 +461,21 @@ async function cmdInit(agentId, opts = {}) {
|
|
|
341
461
|
envOut.DECIDIO_WORKSPACE_ID = workspaceId;
|
|
342
462
|
if (tokenJti)
|
|
343
463
|
envOut.DECIDIO_TOKEN_JTI = tokenJti;
|
|
344
|
-
|
|
345
|
-
|
|
464
|
+
try {
|
|
465
|
+
prepareEnvForWrite();
|
|
466
|
+
upsertEnv(envOut);
|
|
467
|
+
if (tokenJti)
|
|
468
|
+
journalRetire(tokenJti, "persisted");
|
|
469
|
+
}
|
|
470
|
+
catch (e) {
|
|
471
|
+
if (agentToken && tokenJti)
|
|
472
|
+
await revokeFreshMint(tokenJti, mintBearer, e, ENV_PATH);
|
|
473
|
+
else if (agentToken)
|
|
474
|
+
err(`✘ could not write ${ENV_PATH} (${e?.message ?? e}). The token minted a moment ago is LIVE and the server returned no revocation id - find and revoke it: npx @decidio/sdk tokens list --mine`);
|
|
475
|
+
else
|
|
476
|
+
err(`✘ could not write ${ENV_PATH} (${e?.message ?? e}). No token was minted this run.`);
|
|
477
|
+
return 1;
|
|
478
|
+
}
|
|
346
479
|
for (const [k, v] of Object.entries(envOut))
|
|
347
480
|
process.env[k] = v;
|
|
348
481
|
log(`✔ wrote ${ENV_PATH} (owner-only where the OS supports it) — every decidio command reads it from this directory (your script loads it with node --env-file=.env)`);
|
|
@@ -381,6 +514,17 @@ async function cmdDoctor() {
|
|
|
381
514
|
let warned = 0;
|
|
382
515
|
const line = (good, label) => { log(`${good ? "✔" : "✘"} ${label}`); ok = ok && good; };
|
|
383
516
|
const warn = (label) => { warned++; log(`⚠ ${label}`); };
|
|
517
|
+
let journalRows = [];
|
|
518
|
+
try {
|
|
519
|
+
journalRows = journalOutstanding();
|
|
520
|
+
}
|
|
521
|
+
catch (e) {
|
|
522
|
+
warn(`token recovery journal (${JOURNAL_PATH}) could not be read (${e?.message ?? e}) — it may hold live token ids; fix its permissions and re-run doctor.`);
|
|
523
|
+
}
|
|
524
|
+
for (const j of journalRows) {
|
|
525
|
+
const where = j.apiUrl && j.apiUrl !== API ? `DECIDIO_API_URL=${j.apiUrl} ` : "";
|
|
526
|
+
warn(`token recovery journal: jti ${j.jti} (${j.label ?? "token"}, agent ${j.agentId ?? "?"}, minted ${j.at ?? "?"} against ${j.apiUrl ?? API}) has no recorded revocation in this directory's journal — it may still be live. Revoke it, or confirm it is gone: ${where}npx @decidio/sdk quickstart cleanup (or: ${where}npx @decidio/sdk tokens revoke ${j.jti})`);
|
|
527
|
+
}
|
|
384
528
|
log(`✔ API URL: ${API}${(process.env.DECIDIO_API_URL || env.DECIDIO_API_URL) ? "" : " (default: the hosted sandbox — set DECIDIO_API_URL to point elsewhere)"}`);
|
|
385
529
|
line(!!(process.env.DECIDIO_AGENT_ID || env.DECIDIO_AGENT_ID), `agent id: ${process.env.DECIDIO_AGENT_ID || env.DECIDIO_AGENT_ID || "(unset — run: npx @decidio/sdk init <agent-name>)"}`);
|
|
386
530
|
const secret = process.env.DECIDIO_WEBHOOK_SECRET || env.DECIDIO_WEBHOOK_SECRET;
|
|
@@ -399,7 +543,7 @@ async function cmdDoctor() {
|
|
|
399
543
|
else if (derived !== storedDid)
|
|
400
544
|
line(false, `signing key: DOES NOT MATCH this agent's DID — the key here derives ${derived}, but .env registers ${storedDid}. Signed requests will not be recognised as this agent (auto-approve and application_confirmed evidence will both fail). Restore the original key, or re-run \`npx @decidio/sdk init\` to register a fresh one.`);
|
|
401
545
|
else
|
|
402
|
-
log("✔ signing key: set and MATCHES the registered DID — the agent can prove its identity, making it ELIGIBLE for policy auto-approve. Whether a rule matches is your workspace policy's decision; with no matching rule every request routes to a human (
|
|
546
|
+
log("✔ signing key: set and MATCHES the registered DID — the agent can prove its identity, making it ELIGIBLE for policy auto-approve. Whether a rule matches is your workspace policy's decision; with no matching rule every request routes to a human (routing-by-default - a block is different: a named rule, or the universal backstop when no active policy governs agents at all).");
|
|
403
547
|
}
|
|
404
548
|
else
|
|
405
549
|
warn("signing key: MISSING (DECIDIO_AGENT_KEY/DID) — the agent can't prove identity, so EVERY request routes to a human. Run `npx @decidio/sdk init` to generate one.");
|
|
@@ -597,10 +741,13 @@ async function cmdReceipt(id, jsonMode = false) {
|
|
|
597
741
|
}
|
|
598
742
|
log(` Verify it offline — no Decidio account or network needed (the verifier is built in):`);
|
|
599
743
|
if (issuer) {
|
|
744
|
+
log(` Record your workspace's issuer DID — copy this line, and pin it forever:`);
|
|
745
|
+
log(``);
|
|
746
|
+
log(`${issuer}`);
|
|
747
|
+
log(``);
|
|
748
|
+
log(` Then verify, pinned to it (the same DID, inline — copy from the line above, not this one,`);
|
|
749
|
+
log(` which your terminal may wrap):`);
|
|
600
750
|
log(` npx @decidio/sdk verify --issuer ${issuer} ${name}`);
|
|
601
|
-
log(` That pins the check to your workspace's issuer DID (read from the authenticated`);
|
|
602
|
-
log(` response above — record it once, then pin it forever):`);
|
|
603
|
-
log(` ${issuer}`);
|
|
604
751
|
log(` Without --issuer the verifier proves the receipt is internally consistent, but NOT who`);
|
|
605
752
|
log(` issued it; it says so in its output.`);
|
|
606
753
|
}
|
|
@@ -621,6 +768,10 @@ function cmdVerify(argsIn) {
|
|
|
621
768
|
err("✘ --issuer needs a did:key value (a bare or EMPTY --issuer must not silently verify unpinned).");
|
|
622
769
|
return 2;
|
|
623
770
|
}
|
|
771
|
+
if (expectedIssuer !== undefined && expectedIssuer !== v) {
|
|
772
|
+
err(`✘ --issuer given twice with different values (${expectedIssuer} then ${v}) — refusing to choose which one you meant to pin to.`);
|
|
773
|
+
return 2;
|
|
774
|
+
}
|
|
624
775
|
expectedIssuer = v;
|
|
625
776
|
i++;
|
|
626
777
|
}
|
|
@@ -630,6 +781,10 @@ function cmdVerify(argsIn) {
|
|
|
630
781
|
err("✘ --issuer needs a did:key value.");
|
|
631
782
|
return 2;
|
|
632
783
|
}
|
|
784
|
+
if (expectedIssuer !== undefined && expectedIssuer !== v) {
|
|
785
|
+
err(`✘ --issuer given twice with different values (${expectedIssuer} then ${v}) — refusing to choose which one you meant to pin to.`);
|
|
786
|
+
return 2;
|
|
787
|
+
}
|
|
633
788
|
expectedIssuer = v;
|
|
634
789
|
}
|
|
635
790
|
else if (!a.startsWith("-")) {
|
|
@@ -695,11 +850,35 @@ async function cmdQuickstart(opts = {}) {
|
|
|
695
850
|
err(` Nothing was written to this directory.`);
|
|
696
851
|
return 1;
|
|
697
852
|
}
|
|
853
|
+
let prevTourFile = null;
|
|
854
|
+
if (envFile !== ENV_PATH && existsSync(envFile)) {
|
|
855
|
+
try {
|
|
856
|
+
prevTourFile = readFileSync(envFile, "utf8");
|
|
857
|
+
}
|
|
858
|
+
catch (e) {
|
|
859
|
+
err(`✘ ${envFile} exists but could not be read (${e?.message ?? e}) - a previous tour's credentials may be in it, and this tour would have to replace it. Refusing to mint a new token on top of a file this command cannot read: fix its permissions or move it aside, then re-run. Nothing was minted.`);
|
|
860
|
+
return 1;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
const noJournal = journalProbe();
|
|
864
|
+
if (noJournal) {
|
|
865
|
+
err(REFUSE_MINT_NO_JOURNAL(noJournal));
|
|
866
|
+
return 1;
|
|
867
|
+
}
|
|
698
868
|
const mint = await api("/api/workspace/agent-token", { method: "POST", body: JSON.stringify({ agentId, label: "quickstart", expiresInDays: 7 }) }, session);
|
|
699
869
|
if (!(mint.status >= 200 && mint.status < 300) || !mint.body?.token) {
|
|
700
870
|
err(`✘ token mint failed (${mint.status}): ${JSON.stringify(mint.body)}`);
|
|
701
871
|
return 1;
|
|
702
872
|
}
|
|
873
|
+
if (mint.body.jti) {
|
|
874
|
+
try {
|
|
875
|
+
journalMinted(mint.body.jti, agentId, "quickstart");
|
|
876
|
+
}
|
|
877
|
+
catch (e) {
|
|
878
|
+
await revokeFreshMint(mint.body.jti, session, e, JOURNAL_PATH);
|
|
879
|
+
return 1;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
703
882
|
const envOut = {
|
|
704
883
|
DECIDIO_API_URL: API, DECIDIO_AGENT_ID: agentId, DECIDIO_AGENT_DID: reg.body?.did ?? did,
|
|
705
884
|
DECIDIO_AGENT_KEY: privateKeyBase64, DECIDIO_API_TOKEN: mint.body.token,
|
|
@@ -707,18 +886,18 @@ async function cmdQuickstart(opts = {}) {
|
|
|
707
886
|
...(mint.body.jti ? { DECIDIO_TOKEN_JTI: mint.body.jti } : {}),
|
|
708
887
|
};
|
|
709
888
|
if (envFile !== ENV_PATH) {
|
|
710
|
-
if (
|
|
711
|
-
const prev =
|
|
889
|
+
if (prevTourFile !== null) {
|
|
890
|
+
const prev = prevTourFile;
|
|
712
891
|
const prevId = prev.match(/^DECIDIO_AGENT_ID=(.+)$/m)?.[1];
|
|
713
892
|
const prevJti = prev.match(/^DECIDIO_TOKEN_JTI=(.+)$/m)?.[1];
|
|
893
|
+
const carried = new Set((prev.match(/^DECIDIO_ORPHANED_TOKEN_JTI=(.+)$/m)?.[1] ?? "").split(",").map((s) => s.trim()).filter(Boolean));
|
|
714
894
|
if (prevId && prevId !== agentId) {
|
|
715
895
|
if (prevJti) {
|
|
716
896
|
const rv = await api(`/api/workspace/agent-token/${encodeURIComponent(prevJti)}`, { method: "DELETE" }, session);
|
|
717
897
|
if (rv.status >= 200 && rv.status < 300)
|
|
718
898
|
log(` ✔ the previous tour agent (${prevId}) had its token REVOKED before this tour replaced it — no live credential is left behind`);
|
|
719
899
|
else {
|
|
720
|
-
|
|
721
|
-
envOut.DECIDIO_ORPHANED_TOKEN_JTI = [...new Set(carried)].join(",");
|
|
900
|
+
carried.add(prevJti);
|
|
722
901
|
log(` ⚠ could not revoke the previous tour's token (jti ${prevJti}, ${rv.status}) — it is still LIVE. Its id is kept in ${envFile} as DECIDIO_ORPHANED_TOKEN_JTI, and \`quickstart cleanup\` will retry it; or revoke it by hand: npx @decidio/sdk tokens revoke ${prevJti}`);
|
|
723
902
|
}
|
|
724
903
|
}
|
|
@@ -726,12 +905,38 @@ async function cmdQuickstart(opts = {}) {
|
|
|
726
905
|
log(` ⚠ the previous tour file carried no revocation id, so its token (if any) must be revoked from your Decidio app.`);
|
|
727
906
|
}
|
|
728
907
|
}
|
|
908
|
+
if (carried.size) {
|
|
909
|
+
envOut.DECIDIO_ORPHANED_TOKEN_JTI = [...carried].join(",");
|
|
910
|
+
log(` • ${carried.size} orphaned token id(s) from earlier replacements carried forward — \`quickstart cleanup\` will retry them.`);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
try {
|
|
914
|
+
writeSecretFile(envFile, Object.entries(envOut).map(([k, v]) => `${k}=${v}`).join("\n") + "\n");
|
|
915
|
+
if (mint.body.jti)
|
|
916
|
+
journalRetire(mint.body.jti, "persisted");
|
|
917
|
+
}
|
|
918
|
+
catch (e) {
|
|
919
|
+
if (mint.body?.jti)
|
|
920
|
+
await revokeFreshMint(mint.body.jti, session, e, envFile);
|
|
921
|
+
else
|
|
922
|
+
err(`✘ could not write ${envFile} (${e?.message ?? e}). The tour token minted a moment ago is LIVE and the server returned no revocation id - find and revoke it: npx @decidio/sdk tokens list --mine`);
|
|
923
|
+
return 1;
|
|
729
924
|
}
|
|
730
|
-
writeSecretFile(envFile, Object.entries(envOut).map(([k, v]) => `${k}=${v}`).join("\n") + "\n");
|
|
731
925
|
log(` ✔ this directory already has an agent in .env — tour credentials went to ${envFile} (your agent is untouched)`);
|
|
732
926
|
}
|
|
733
927
|
else {
|
|
734
|
-
|
|
928
|
+
try {
|
|
929
|
+
upsertEnv(envOut);
|
|
930
|
+
if (mint.body.jti)
|
|
931
|
+
journalRetire(mint.body.jti, "persisted");
|
|
932
|
+
}
|
|
933
|
+
catch (e) {
|
|
934
|
+
if (mint.body?.jti)
|
|
935
|
+
await revokeFreshMint(mint.body.jti, session, e, envFile);
|
|
936
|
+
else
|
|
937
|
+
err(`✘ could not write ${envFile} (${e?.message ?? e}). The tour token minted a moment ago is LIVE and the server returned no revocation id - find and revoke it: npx @decidio/sdk tokens list --mine`);
|
|
938
|
+
return 1;
|
|
939
|
+
}
|
|
735
940
|
}
|
|
736
941
|
const failTour = async (code) => {
|
|
737
942
|
log("");
|
|
@@ -776,7 +981,9 @@ async function cmdQuickstart(opts = {}) {
|
|
|
776
981
|
return failTour(1);
|
|
777
982
|
}
|
|
778
983
|
log(` ✔ $86,000 payInvoice ROUTED to a human (decision ${decisionId}) — this agent has no auto-approve rule,`);
|
|
779
|
-
log(` so
|
|
984
|
+
log(` so the policy ROUTES it to a person (routing-by-default). That is distinct from a BLOCK, which no human can`);
|
|
985
|
+
log(` override: a block is a named rule, or the universal backstop when no active policy governs agents at all.`);
|
|
986
|
+
log(` Nothing executes until someone decides.`);
|
|
780
987
|
log("");
|
|
781
988
|
const interactive = process.stdin.isTTY;
|
|
782
989
|
if (interactive) {
|
|
@@ -857,7 +1064,10 @@ async function cmdQuickstart(opts = {}) {
|
|
|
857
1064
|
log(` const doIt = guard.protect(doItRaw, (a) => ({ action: "yourAction", amount: a.amount, scope: "YourScope" }));`);
|
|
858
1065
|
log(` // production is DURABLE mode (default) + one worker: guard.worker(); // README has the full pattern`);
|
|
859
1066
|
log("");
|
|
860
|
-
log(`Your workspace + queue: ${API.replace("decidio-api.onrender.com", "app.decidioai.com")}
|
|
1067
|
+
log(`Your workspace + queue: ${API.replace("decidio-api.onrender.com", "app.decidioai.com")}`);
|
|
1068
|
+
log(`Next: the README section "Production mode: durable async resume" (the worker; the engine adapters follow it).`);
|
|
1069
|
+
log(`The README's seven-step quickstart is this same blocking flow in your own code - if you keep this agent, its .env`);
|
|
1070
|
+
log(`already works here, so skip that flow's init step.`);
|
|
861
1071
|
log(`Built by OmniTwin — decidioai.com · Putting this in front of real approvals? decidioai.com/#early-access`);
|
|
862
1072
|
let doCleanup = false;
|
|
863
1073
|
if (interactive) {
|
|
@@ -876,14 +1086,47 @@ async function cmdQuickstart(opts = {}) {
|
|
|
876
1086
|
async function quickstartCleanup(session, envFile) {
|
|
877
1087
|
const env = (() => {
|
|
878
1088
|
const out = {};
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
const
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1089
|
+
try {
|
|
1090
|
+
if (existsSync(envFile))
|
|
1091
|
+
for (const line of readFileSync(envFile, "utf8").split(/\r?\n/)) {
|
|
1092
|
+
const m = line.match(/^\s*([A-Z_]+)=(.*)$/);
|
|
1093
|
+
if (m)
|
|
1094
|
+
out[m[1]] = m[2];
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
catch (e) {
|
|
1098
|
+
err(` ⚠ ${envFile} could not be read (${e?.message ?? e}) - sweeping the token recovery journal instead.`);
|
|
1099
|
+
}
|
|
885
1100
|
return out;
|
|
886
1101
|
})();
|
|
1102
|
+
let journalRevoked = 0;
|
|
1103
|
+
const journalStuck = [];
|
|
1104
|
+
for (const j of journalOutstanding()) {
|
|
1105
|
+
if (j.apiUrl && j.apiUrl !== API) {
|
|
1106
|
+
journalStuck.push(j.jti);
|
|
1107
|
+
err(` ⚠ journalled token ${j.jti} was minted against ${j.apiUrl}, not ${API} — revoke it there: DECIDIO_API_URL=${j.apiUrl} npx @decidio/sdk tokens revoke ${j.jti}`);
|
|
1108
|
+
continue;
|
|
1109
|
+
}
|
|
1110
|
+
const rv = await api(`/api/workspace/agent-token/${encodeURIComponent(j.jti)}`, { method: "DELETE" }, session);
|
|
1111
|
+
if (rv.status >= 200 && rv.status < 300) {
|
|
1112
|
+
journalRetire(j.jti, "revoked");
|
|
1113
|
+
journalRevoked++;
|
|
1114
|
+
if (rv.body?.changed === false)
|
|
1115
|
+
log(` • journalled token ${j.jti} was already revoked — retiring its journal entry.`);
|
|
1116
|
+
else
|
|
1117
|
+
log(` ✔ journalled token revoked (jti ${j.jti}${j.label ? `, ${j.label}` : ""}) — its credential file never landed, so this was its only handle`);
|
|
1118
|
+
}
|
|
1119
|
+
else {
|
|
1120
|
+
journalStuck.push(j.jti);
|
|
1121
|
+
err(` ⚠ journalled token ${j.jti} could NOT be revoked (${rv.status}) — it is still live; its id stays in ${JOURNAL_PATH}.`);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
if (journalStuck.length) {
|
|
1125
|
+
err(`✘ ${journalStuck.length} journalled token(s) are STILL LIVE and could not be revoked. Nothing was deleted; retry, or revoke by hand:`);
|
|
1126
|
+
for (const jti of journalStuck)
|
|
1127
|
+
err(` npx @decidio/sdk tokens revoke ${jti}`);
|
|
1128
|
+
return 1;
|
|
1129
|
+
}
|
|
887
1130
|
const orphans = (env.DECIDIO_ORPHANED_TOKEN_JTI ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
888
1131
|
const stillOrphaned = [];
|
|
889
1132
|
for (const jti of orphans) {
|
|
@@ -914,6 +1157,9 @@ async function quickstartCleanup(session, envFile) {
|
|
|
914
1157
|
return 1;
|
|
915
1158
|
}
|
|
916
1159
|
}
|
|
1160
|
+
else if (journalRevoked > 0 && !existsSync(envFile) || (journalRevoked > 0 && Object.keys(env).length === 0)) {
|
|
1161
|
+
log(` • no credential file to clean: the journal sweep revoked ${journalRevoked} token(s) this directory owed.`);
|
|
1162
|
+
}
|
|
917
1163
|
else {
|
|
918
1164
|
err(`✘ no DECIDIO_TOKEN_JTI in ${envFile} (older tour) — this tour's token is STILL LIVE and this`);
|
|
919
1165
|
err(` command cannot revoke it. Nothing was deleted. Find and revoke it, then re-run cleanup:`);
|
|
@@ -922,9 +1168,18 @@ async function quickstartCleanup(session, envFile) {
|
|
|
922
1168
|
err(` (it also expires on its own within 7 days)`);
|
|
923
1169
|
return 1;
|
|
924
1170
|
}
|
|
1171
|
+
let envRaw = null;
|
|
925
1172
|
if (existsSync(envFile)) {
|
|
1173
|
+
try {
|
|
1174
|
+
envRaw = readFileSync(envFile, "utf8");
|
|
1175
|
+
}
|
|
1176
|
+
catch (e) {
|
|
1177
|
+
err(` ⚠ ${envFile} could not be read (${e?.message ?? e}) — left as it is; remove it by hand once it is readable.`);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
if (envRaw !== null) {
|
|
926
1181
|
const TOUR_KEYS = new Set(["DECIDIO_API_URL", "DECIDIO_AGENT_ID", "DECIDIO_AGENT_DID", "DECIDIO_AGENT_KEY", "DECIDIO_API_TOKEN", "DECIDIO_WORKSPACE_ID", "DECIDIO_TOKEN_JTI", "DECIDIO_ORPHANED_TOKEN_JTI"]);
|
|
927
|
-
const kept =
|
|
1182
|
+
const kept = envRaw.split(/\r?\n/).filter((line) => {
|
|
928
1183
|
const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)=/);
|
|
929
1184
|
return m ? !TOUR_KEYS.has(m[1]) : line.trim() !== "";
|
|
930
1185
|
});
|
|
@@ -948,6 +1203,12 @@ async function quickstartCleanup(session, envFile) {
|
|
|
948
1203
|
}
|
|
949
1204
|
log(` • What remains, by design: the sealed decision record (records are permanent — that is the product)`);
|
|
950
1205
|
log(` and the agent's registration row (inert without key or token; server-side deregistration is on the roadmap).`);
|
|
1206
|
+
if (existsSync(".decidio-pending")) {
|
|
1207
|
+
log(` • Also kept: .decidio-pending/ — this agent's own execution ledger: whatever this agent parked (call arguments,`);
|
|
1208
|
+
log(` execution evidence; never credentials, by design). Gitignored; owner-only where the OS supports it. If nothing is`);
|
|
1209
|
+
log(` pending and you do not want the history, remove it:`);
|
|
1210
|
+
log(` ${process.platform === "win32" ? "Remove-Item -Recurse .decidio-pending" : "rm -r .decidio-pending"}`);
|
|
1211
|
+
}
|
|
951
1212
|
return 0;
|
|
952
1213
|
}
|
|
953
1214
|
async function cmdTokens(sub, jti, mineOnly = false) {
|
|
@@ -980,10 +1241,12 @@ async function cmdTokens(sub, jti, mineOnly = false) {
|
|
|
980
1241
|
const rev = await api(`/api/workspace/agent-token/${encodeURIComponent(jti)}`, { method: "DELETE" }, bearer);
|
|
981
1242
|
if (rev.status >= 200 && rev.status < 300) {
|
|
982
1243
|
if (rev.body?.changed === false) {
|
|
1244
|
+
journalRetire(jti, "revoked");
|
|
983
1245
|
log(`• ${jti} was already revoked — nothing changed.`);
|
|
984
1246
|
log(` Revocation is permanent; there is no re-revoke. See it: npx @decidio/sdk tokens list`);
|
|
985
1247
|
return 1;
|
|
986
1248
|
}
|
|
1249
|
+
journalRetire(jti, "revoked");
|
|
987
1250
|
log(`✔ revoked ${jti} — the token is dead now (its agent stays registered; mint a new one with init).`);
|
|
988
1251
|
return 0;
|
|
989
1252
|
}
|
|
@@ -1095,7 +1358,6 @@ Docs: the README that shipped with this package — node_modules/@decidio/sdk/RE
|
|
|
1095
1358
|
(quickstart, durable resume, workers, adapters).`;
|
|
1096
1359
|
async function main(argv) {
|
|
1097
1360
|
const [cmd, a, b, ...rest] = argv.slice(2);
|
|
1098
|
-
const wantsHelp = (s) => s === "help" || s === "--help" || s === "-h";
|
|
1099
1361
|
if (cmd === "--version" || cmd === "-v" || cmd === "-V" || cmd === "version") {
|
|
1100
1362
|
log(VERSION);
|
|
1101
1363
|
return 0;
|
|
@@ -1174,9 +1436,10 @@ async function main(argv) {
|
|
|
1174
1436
|
case "quickstart": {
|
|
1175
1437
|
if (argsIn.includes("cleanup")) {
|
|
1176
1438
|
const envFile = existsSync(".env.quickstart") ? ".env.quickstart"
|
|
1177
|
-
: (readEnvFile().DECIDIO_AGENT_ID ?? "").startsWith("quickstart-") ? ENV_PATH
|
|
1439
|
+
: (readEnvFile().DECIDIO_AGENT_ID ?? "").startsWith("quickstart-") ? ENV_PATH
|
|
1440
|
+
: journalOutstanding().length ? ".env.quickstart" : undefined;
|
|
1178
1441
|
if (!envFile) {
|
|
1179
|
-
log("Nothing to clean up here: no .env.quickstart,
|
|
1442
|
+
log("Nothing to clean up here: no .env.quickstart, .env doesn't belong to a quickstart agent, and the token recovery journal owes nothing.");
|
|
1180
1443
|
return 0;
|
|
1181
1444
|
}
|
|
1182
1445
|
const session = await acquireSession("Cleaning the quickstart up");
|
package/dist/errors.js
CHANGED
|
@@ -26,7 +26,11 @@ export class DecidioHttpError extends Error {
|
|
|
26
26
|
(credential
|
|
27
27
|
? ` — the credential was rejected. The agent token may have expired or been revoked (re-running \`init\` for the same agent revokes the previous one). Mint a fresh one: npx @decidio/sdk init <agent-name>, then restart this process.`
|
|
28
28
|
: retryable
|
|
29
|
-
?
|
|
29
|
+
? (/\/agent\/confirm/.test(endpoint)
|
|
30
|
+
? ` — this looks transient. Your action ALREADY RAN; do NOT re-run it — only its report failed to land, and the door's own note says whether a report was kept to re-send.`
|
|
31
|
+
: /\/agent\/status/.test(endpoint)
|
|
32
|
+
? ` — this looks transient; polling again is safe (a status read changes nothing).`
|
|
33
|
+
: ` — this looks transient; retrying is safe (a retry creates a NEW request and can never double-execute).`)
|
|
30
34
|
: ` — the server refused the request itself, so retrying it unchanged will fail the same way. Fix the request (or the decision it names) first.`) +
|
|
31
35
|
sideEffectNote(endpoint, typeof body?.code === "string" ? body.code : undefined));
|
|
32
36
|
this.status = status;
|
package/dist/evidence.d.ts
CHANGED
|
@@ -3,6 +3,15 @@
|
|
|
3
3
|
* we do not understand is never treated as evidence. */
|
|
4
4
|
export declare const EVIDENCE_TIERS: readonly ["pending", "execution_unconfirmed", "agent_asserted", "sensor_observed", "application_confirmed", "independently_reconciled"];
|
|
5
5
|
export type EvidenceTier = (typeof EVIDENCE_TIERS)[number];
|
|
6
|
+
/** Whether a recorded tier attests a COMMITTED execution - the same set the live reader uses, so a
|
|
7
|
+
* tombstone that kept only the tier can still answer the question honestly (retest V030-07). */
|
|
8
|
+
export declare function tierConfirms(tier: string | undefined | null): boolean;
|
|
9
|
+
/** The tier a `reported` tombstone records. The server caps a confirming tier whose proof failed to
|
|
10
|
+
* `execution_unconfirmed` (apps/api agent-confirm.ts, lint-locked CONFIRM-TRUTH), so a body that
|
|
11
|
+
* says otherwise is one the current server cannot emit; the tombstone applies the same rule, so
|
|
12
|
+
* reconfirm() - which derives from the tier - can never answer "confirmed" where resolve() answered
|
|
13
|
+
* "not confirmed" over the same record (invariant-guard, 0.3.3). Every other tier is stored verbatim. */
|
|
14
|
+
export declare function tombstoneTier(ev: EvidenceOutcome): string;
|
|
6
15
|
export interface EvidenceOutcome {
|
|
7
16
|
/** Decidio accepted the report and named the tier it recorded. */
|
|
8
17
|
reportRecorded: boolean;
|
package/dist/evidence.js
CHANGED
|
@@ -7,6 +7,10 @@ export const EVIDENCE_TIERS = [
|
|
|
7
7
|
"independently_reconciled",
|
|
8
8
|
];
|
|
9
9
|
const CONFIRMING = ["sensor_observed", "application_confirmed", "independently_reconciled"];
|
|
10
|
+
export function tierConfirms(tier) { return typeof tier === "string" && CONFIRMING.includes(tier); }
|
|
11
|
+
export function tombstoneTier(ev) {
|
|
12
|
+
return ev.executionReportVerified === false && tierConfirms(ev.evidenceTier) ? "execution_unconfirmed" : (ev.evidenceTier ?? "recorded");
|
|
13
|
+
}
|
|
10
14
|
export function describeTierGap(capturedResponse) {
|
|
11
15
|
const r = (capturedResponse ?? {});
|
|
12
16
|
const id = r.id ?? r.recordId ?? r.Id ?? null;
|
|
@@ -47,7 +51,7 @@ export function readConfirmResponse(body, opts) {
|
|
|
47
51
|
outcome.executionReportReason = rpt.reason;
|
|
48
52
|
if (proofExplicitlyFailed) {
|
|
49
53
|
const why = typeof rpt.reason === "string" && rpt.reason ? ` (${rpt.reason})` : "";
|
|
50
|
-
outcome.diagnostic = `the signed execution report did NOT verify${why}, so this is NOT a confirmed execution — the gate reported tier "${tier}", and this SDK refuses to read an unproven attestation as confirmation. Your action DID run;
|
|
54
|
+
outcome.diagnostic = `the signed execution report did NOT verify${why}, so this is NOT a confirmed execution — the gate reported tier "${tier}", and this SDK refuses to read an unproven attestation as confirmation. Your action DID run and the report IS recorded at that tier - there is nothing to re-send, and re-sending would not change the verdict; the record stands as unverified. Check the agent's key/DID/registration (npx @decidio/sdk doctor) so the NEXT execution verifies.`;
|
|
51
55
|
}
|
|
52
56
|
}
|
|
53
57
|
return outcome;
|
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,10 @@ export { readConfirmResponse, buildConfirmBody, describeTierGap, EVIDENCE_TIERS
|
|
|
12
12
|
export type { EvidenceOutcome, EvidenceTier } from "./evidence.js";
|
|
13
13
|
export type { RequesterIdentity } from "./signing.js";
|
|
14
14
|
export interface GuardConfig {
|
|
15
|
-
/** Base URL of the Decidio gate
|
|
15
|
+
/** Base URL of the Decidio gate. The hosted sandbox is "https://decidio-api.onrender.com",
|
|
16
|
+
* which is also the default when this is unset; a self-hosted deployment is your own origin.
|
|
17
|
+
* (This example named a host the product does not use — it shipped in the .d.ts, so it was the
|
|
18
|
+
* first URL a typing-aware editor suggested to anyone configuring the SDK.) */
|
|
16
19
|
apiUrl: string;
|
|
17
20
|
/** The agent identity registered with Decidio (onboarding wizard), e.g. "salesforce/SalesforceBot". */
|
|
18
21
|
agentId: string;
|
|
@@ -100,6 +103,11 @@ export declare class DecidioNetworkError extends Error {
|
|
|
100
103
|
code: string | undefined;
|
|
101
104
|
constructor(endpoint: string, op: string, code: string | undefined, cause: unknown);
|
|
102
105
|
}
|
|
106
|
+
/** THE ONE DESCRIBE VALIDATION (prodsec V033-05). The core wrapper refused a blank `action` and a
|
|
107
|
+
* negative / non-finite `amount` before routing; the adapter gate canonicalized and posted them, so
|
|
108
|
+
* every engine door reached the mock authorization path with a request the core would have refused.
|
|
109
|
+
* Exported so `adapters/_gate.ts` calls the same function rather than a second copy that can drift. */
|
|
110
|
+
export declare function assertDescribed(ctx: ApprovalContext): void;
|
|
103
111
|
/**
|
|
104
112
|
* Wrap an async action behind the Decidio gate. `describe` extracts the approval
|
|
105
113
|
* context (action, amount, scope) from the call arguments. Returns a function with
|
package/dist/index.js
CHANGED
|
@@ -78,6 +78,14 @@ async function reach(f, url, op, init) {
|
|
|
78
78
|
throw new DecidioNetworkError(url, op, e?.cause?.code ?? e?.code, e);
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
+
export function assertDescribed(ctx) {
|
|
82
|
+
if (typeof ctx?.action !== "string" || ctx.action.trim().length === 0) {
|
|
83
|
+
throw new Error(`[decidio] describe(...) must return an \`action\` string naming what is being asked (got ${JSON.stringify(ctx?.action ?? null)}). Nothing was routed or executed.`);
|
|
84
|
+
}
|
|
85
|
+
if (typeof ctx.amount !== "number" || !Number.isFinite(ctx.amount) || ctx.amount < 0) {
|
|
86
|
+
throw new Error(`[decidio] describe(...) must return a finite, non-negative \`amount\` — the policy decides on it, so a request without one is not a request Decidio can rule on (got ${JSON.stringify(ctx.amount ?? null)}). Use 0 for an action with no monetary value. Nothing was routed or executed.`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
81
89
|
async function post(cfg, path, body) {
|
|
82
90
|
const f = cfg.fetchImpl ?? fetch;
|
|
83
91
|
assertSafeTransport(cfg.apiUrl, carriesCredential(cfg));
|
|
@@ -138,16 +146,11 @@ export function withApproval(fn, describe, config, opts) {
|
|
|
138
146
|
}
|
|
139
147
|
throw e;
|
|
140
148
|
}
|
|
149
|
+
ctx = canonicalize(ctx, "describe(...)");
|
|
141
150
|
const requesterIdentity = (config.agentKey && config.agentDid)
|
|
142
151
|
? signBoundProof(config.agentKey, { agentId: config.agentId, did: config.agentDid, action: ctx.action, amount: ctx.amount, scope: ctx.scope, workspaceId: config.workspaceId })
|
|
143
152
|
: undefined;
|
|
144
|
-
|
|
145
|
-
if (typeof ctx?.action !== "string" || ctx.action.trim().length === 0) {
|
|
146
|
-
throw new Error(`[decidio] describe(...) must return an \`action\` string naming what is being asked (got ${JSON.stringify(ctx?.action ?? null)}). Nothing was routed or executed.`);
|
|
147
|
-
}
|
|
148
|
-
if (typeof ctx.amount !== "number" || !Number.isFinite(ctx.amount) || ctx.amount < 0) {
|
|
149
|
-
throw new Error(`[decidio] describe(...) must return a finite, non-negative \`amount\` — the policy decides on it, so a request without one is not a request Decidio can rule on (got ${JSON.stringify(ctx.amount ?? null)}). Use 0 for an action with no monetary value. Nothing was routed or executed.`);
|
|
150
|
-
}
|
|
153
|
+
assertDescribed(ctx);
|
|
151
154
|
const auth = await post(config, "/agent/authorize", {
|
|
152
155
|
requester: config.agentId, action: ctx.action, amount: ctx.amount,
|
|
153
156
|
scope: ctx.scope, sourceSystem: config.sourceSystem,
|
package/dist/inngest.js
CHANGED
|
@@ -28,6 +28,8 @@ export async function decidioGateStep(args) {
|
|
|
28
28
|
export function createInngestResumeBridge(opts) {
|
|
29
29
|
const secret = opts.webhookSecret ?? "";
|
|
30
30
|
const eventName = opts.eventName ?? DECIDIO_RESUME_EVENT;
|
|
31
|
+
if (!secret && opts.allowUnsigned)
|
|
32
|
+
console.warn("[decidio] inngest resume bridge: allowUnsigned=true and no webhookSecret — resume signals are NOT authenticated; anyone who can reach this endpoint can emit a resume event. Development only.");
|
|
31
33
|
return (req, res) => {
|
|
32
34
|
if (req.method !== "POST") {
|
|
33
35
|
res.statusCode = 405;
|