@decidio/sdk 0.2.2 → 0.3.3
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 +189 -12
- package/dist/adapters/_gate.js +1 -1
- package/dist/adapters/langgraph.d.ts +2 -2
- package/dist/adapters/langgraph.js +2 -2
- package/dist/adapters/openai.d.ts +14 -4
- package/dist/adapters/openai.js +3 -2
- package/dist/adapters/temporal.d.ts +2 -2
- package/dist/adapters/temporal.js +7 -2
- package/dist/cli.js +97 -24
- package/dist/errors.js +12 -4
- package/dist/evidence.d.ts +9 -0
- package/dist/evidence.js +13 -7
- package/dist/index.d.ts +5 -2
- package/dist/index.js +2 -2
- package/dist/inngest.d.ts +10 -2
- package/dist/inngest.js +2 -2
- package/dist/resume.d.ts +164 -27
- package/dist/resume.js +432 -201
- package/dist/signing.d.ts +8 -1
- package/dist/signing.js +18 -23
- package/openapi.yaml +38 -0
- package/package.json +3 -8
package/dist/signing.d.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
/** Sorted-key JCS — must match apps/api/src/canonical.ts byte-for-byte.
|
|
1
|
+
/** Sorted-key JCS — must match apps/api/src/canonical.ts byte-for-byte.
|
|
2
|
+
*
|
|
3
|
+
* ONE IMPLEMENTATION (F-006). This was a module-local copy that reached `JSON.stringify` for every
|
|
4
|
+
* primitive, so it applied NONE of the strict rules the exported canonicalizer enforces — most
|
|
5
|
+
* sharply, it emitted an escaped lone surrogate instead of refusing it. The bytes a bound proof
|
|
6
|
+
* SIGNS and the bytes the digest BINDS are the two places that must never disagree with the value
|
|
7
|
+
* model, and they were the two places that did. Python already delegated (`signing.canonicalize` →
|
|
8
|
+
* `canonical_json`); this is the twin-parity repair on the TypeScript side. */
|
|
2
9
|
export declare function canonicalize(v: unknown): string;
|
|
3
10
|
/** Generate a fresh Ed25519 keypair LOCALLY. Returns the public `did:key` (register this)
|
|
4
11
|
* and the PKCS8 private key base64 (keep this in the agent host's env as DECIDIO_AGENT_KEY —
|
package/dist/signing.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { generateKeyPairSync, createPrivateKey, createPublicKey, sign as edSign, verify as edVerify, createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { canonicalJson } from "./canonical.js";
|
|
2
3
|
const B58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
3
4
|
function b58encode(buf) {
|
|
4
5
|
const digits = [0];
|
|
@@ -26,12 +27,7 @@ function b58encode(buf) {
|
|
|
26
27
|
return str;
|
|
27
28
|
}
|
|
28
29
|
export function canonicalize(v) {
|
|
29
|
-
|
|
30
|
-
return JSON.stringify(v);
|
|
31
|
-
if (Array.isArray(v))
|
|
32
|
-
return `[${v.map(canonicalize).join(",")}]`;
|
|
33
|
-
const o = v;
|
|
34
|
-
return `{${Object.keys(o).sort().map((k) => `${JSON.stringify(k)}:${canonicalize(o[k])}`).join(",")}}`;
|
|
30
|
+
return canonicalJson(v, "canonicalize(...)");
|
|
35
31
|
}
|
|
36
32
|
export function didFromPrivateKey(privateKeyBase64) {
|
|
37
33
|
const priv = createPrivateKey({ key: Buffer.from(privateKeyBase64, "base64"), format: "der", type: "pkcs8" });
|
|
@@ -111,20 +107,6 @@ export function assertJsonSafe(value, path = "args") {
|
|
|
111
107
|
walk(value, path);
|
|
112
108
|
}
|
|
113
109
|
export function assertSafeTransport(url, hasCredential) {
|
|
114
|
-
if (!hasCredential) {
|
|
115
|
-
let probe = null;
|
|
116
|
-
try {
|
|
117
|
-
probe = new URL(url);
|
|
118
|
-
}
|
|
119
|
-
catch {
|
|
120
|
-
probe = null;
|
|
121
|
-
}
|
|
122
|
-
if (probe && (probe.username || probe.password)) {
|
|
123
|
-
throw new Error(`[decidio] the gate URL carries credentials in the URL itself (${probe.protocol}//user:***@${probe.host}). Decidio authenticates with a bearer token, so this is either a mistake or a secret you did not mean to put on the wire — and it is sent on every request regardless of whether an api token is configured. Remove the user:password from the URL. Nothing was sent.`);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
if (!hasCredential)
|
|
127
|
-
return;
|
|
128
110
|
let u;
|
|
129
111
|
try {
|
|
130
112
|
u = new URL(url);
|
|
@@ -132,9 +114,9 @@ export function assertSafeTransport(url, hasCredential) {
|
|
|
132
114
|
catch {
|
|
133
115
|
const spelled = url.replace(/[\t\r\n]/g, "").replace(/^[\s\u0000-\u001f\u0085\ufeff]+/, "");
|
|
134
116
|
if (/^http:/i.test(spelled)) {
|
|
135
|
-
throw new Error(`[decidio] refusing to send credentials over plaintext http to ${JSON.stringify(url)} — that is not a complete absolute URL, so where the request would actually connect is undefined (the path is appended to it, which can turn it into a remote host). Set a full https URL, or http://localhost for local dev. Nothing was sent.`);
|
|
117
|
+
throw new Error(`[decidio] refusing to ${hasCredential ? "send credentials" : "send a request"} over plaintext http to ${JSON.stringify(url)} — that is not a complete absolute URL, so where the request would actually connect is undefined (the path is appended to it, which can turn it into a remote host). Set a full https URL, or http://localhost for local dev. Nothing was sent.`);
|
|
136
118
|
}
|
|
137
|
-
|
|
119
|
+
throw new Error(`[decidio] the gate URL ${JSON.stringify(url)} is not an absolute URL, so there is no host to send to — where a request would actually connect depends entirely on whichever transport resolves it. Decidio needs a full https URL (or http://localhost for local dev). Nothing was sent.`);
|
|
138
120
|
}
|
|
139
121
|
if (u.protocol !== "http:" && u.protocol !== "https:") {
|
|
140
122
|
throw new Error(`[decidio] the gate URL uses the ${u.protocol.replace(":", "")} scheme, which is not a Decidio endpoint — use https (or http://localhost for local dev). Nothing was sent.`);
|
|
@@ -142,13 +124,26 @@ export function assertSafeTransport(url, hasCredential) {
|
|
|
142
124
|
if (u.username || u.password) {
|
|
143
125
|
throw new Error(`[decidio] the gate URL carries credentials in the URL itself (${u.protocol}//user:***@${u.host}). Decidio authenticates with a bearer token; remove the user:password from the URL. Nothing was sent.`);
|
|
144
126
|
}
|
|
127
|
+
if (u.hostname === "") {
|
|
128
|
+
throw new Error(`[decidio] the gate URL ${JSON.stringify(url)} has no host — there is nowhere for the request to go. Decidio needs a full https URL (or http://localhost for local dev). Nothing was sent.`);
|
|
129
|
+
}
|
|
130
|
+
{
|
|
131
|
+
const spelled = url.replace(/[\t\r\n]/g, "").replace(/^[\s\u0000-\u001f\u0085\ufeff]+/, "").toLowerCase();
|
|
132
|
+
const origin = `${u.protocol}//${u.host}`.toLowerCase();
|
|
133
|
+
const next = spelled.charAt(origin.length);
|
|
134
|
+
if (!spelled.startsWith(origin) || (next !== "" && next !== "/" && next !== "?" && next !== "#" && next !== ":")) {
|
|
135
|
+
throw new Error(`[decidio] the gate URL ${JSON.stringify(url)} is not written in canonical form — this parser reads it as ${origin}, but the raw spelling is what a transport receives, and a parser that normalizes differently can read it as another host entirely. Write it as ${origin}. Nothing was sent.`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
145
138
|
if (u.protocol !== "http:")
|
|
146
139
|
return;
|
|
147
140
|
const host = u.hostname.toLowerCase();
|
|
148
141
|
const local = host === "localhost" || host === "127.0.0.1" || host === "[::1]" || host === "::1";
|
|
149
142
|
if (local)
|
|
150
143
|
return;
|
|
151
|
-
throw new Error(
|
|
144
|
+
throw new Error(hasCredential
|
|
145
|
+
? `[decidio] refusing to send credentials over plaintext http to ${u.origin} — use https, or localhost for local dev. Nothing was sent.`
|
|
146
|
+
: `[decidio] refusing to talk to ${u.origin} over plaintext http — Decidio is https, and an unauthenticated plaintext reply cannot be trusted to carry an authority decision. Use https, or localhost for local dev. Nothing was sent.`);
|
|
152
147
|
}
|
|
153
148
|
export const carriesCredential = (c) => !!c.apiToken || !!(c.agentKey && c.agentDid);
|
|
154
149
|
export function signExecutionReport(privateKeyBase64, fields) {
|
package/openapi.yaml
CHANGED
|
@@ -260,6 +260,13 @@ paths:
|
|
|
260
260
|
content:
|
|
261
261
|
application/json:
|
|
262
262
|
schema: { $ref: "#/components/schemas/StatusResponse" }
|
|
263
|
+
"400":
|
|
264
|
+
description: >
|
|
265
|
+
`INVALID_DECISION_ID` - the id is not a usable decision id (empty, whitespace-padded,
|
|
266
|
+
control characters, a path, or over 128 characters). Deliberately NOT the 404 below: a
|
|
267
|
+
malformed id is not a decision that might exist somewhere else, and answering "unknown
|
|
268
|
+
decision" would send you looking for something that could never have been issued. The
|
|
269
|
+
check runs BEFORE any lookup, so this response reveals nothing about existence.
|
|
263
270
|
"401": { description: missing/invalid agent token }
|
|
264
271
|
"404": { description: unknown decision }
|
|
265
272
|
/agent/confirm:
|
|
@@ -272,6 +279,14 @@ paths:
|
|
|
272
279
|
evidence. The caller does not choose it and this endpoint does not always grant the top
|
|
273
280
|
one. The credential sealed at decision time is immutable; evidence accrues on the record,
|
|
274
281
|
never inside the sealed bytes.
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
A SUPPLIED-BUT-REFUSED executionReport CAPS THE TIER at `execution_unconfirmed`, whatever
|
|
285
|
+
else the report would have earned — and deliberately BELOW `agent_asserted`. A failed
|
|
286
|
+
attestation is weaker evidence than never claiming one; the opposite ordering would reward
|
|
287
|
+
sending a forged report over sending none. The server verifies the report and derives the
|
|
288
|
+
tier on the SAME path, so a refused attestation can no longer land on a receipt beside
|
|
289
|
+
`application_confirmed`.
|
|
275
290
|
requestBody:
|
|
276
291
|
required: true
|
|
277
292
|
content:
|
|
@@ -287,6 +302,29 @@ paths:
|
|
|
287
302
|
application/json:
|
|
288
303
|
schema: { $ref: "#/components/schemas/ConfirmResponse" }
|
|
289
304
|
"401": { description: missing/invalid agent token }
|
|
305
|
+
"404": { description: unknown decision, or one this agent is not bound to }
|
|
306
|
+
"409":
|
|
307
|
+
description: >
|
|
308
|
+
The gate refuses to attach execution evidence to this decision AT ALL. This is not a
|
|
309
|
+
report failure, and a client must not treat it as one — handle it separately from a
|
|
310
|
+
5xx. `code` is either:
|
|
311
|
+
|
|
312
|
+
`TERMINALLY_DENIED` — a human or a policy DENIED this decision. If your action ran, it
|
|
313
|
+
ran against a refusal: do NOT re-run it, and investigate how it executed without an
|
|
314
|
+
approval. A denial is terminal and cannot be reconciled or retried.
|
|
315
|
+
|
|
316
|
+
`NOT_AUTHORIZED` — this is not an approved, sealed decision, so there is nothing for
|
|
317
|
+
evidence to attach to yet. If you are confirming early, wait for the approval. If your
|
|
318
|
+
action already ran, it ran WITHOUT authority.
|
|
319
|
+
|
|
320
|
+
Not retryable in either case: the same request will be refused the same way.
|
|
321
|
+
content:
|
|
322
|
+
application/json:
|
|
323
|
+
schema:
|
|
324
|
+
type: object
|
|
325
|
+
properties:
|
|
326
|
+
code: { type: string, enum: [TERMINALLY_DENIED, NOT_AUTHORIZED] }
|
|
327
|
+
message: { type: string }
|
|
290
328
|
x-webhooks:
|
|
291
329
|
resume:
|
|
292
330
|
post:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decidio/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.3",
|
|
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",
|
|
@@ -55,11 +55,6 @@
|
|
|
55
55
|
"verifiable-credentials"
|
|
56
56
|
],
|
|
57
57
|
"homepage": "https://decidioai.com/developers/",
|
|
58
|
-
"repository": {
|
|
59
|
-
"type": "git",
|
|
60
|
-
"url": "git+https://github.com/OmniTwin/Decidio.git",
|
|
61
|
-
"directory": "packages/decidio-guard"
|
|
62
|
-
},
|
|
63
58
|
"publishConfig": {
|
|
64
59
|
"access": "public"
|
|
65
60
|
},
|
|
@@ -84,10 +79,10 @@
|
|
|
84
79
|
"scripts": {
|
|
85
80
|
"typecheck": "tsc --noEmit",
|
|
86
81
|
"build": "node -e \"fs.rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json && tsc -p tsconfig.types.json",
|
|
87
|
-
"prepublishOnly": "npm run typecheck && npm run build"
|
|
82
|
+
"prepublishOnly": "npm run typecheck && npm run build && node ../publish-boundary/check.mjs --only @decidio/sdk"
|
|
88
83
|
},
|
|
89
84
|
"dependencies": {
|
|
90
|
-
"@decidio/verify": "0.
|
|
85
|
+
"@decidio/verify": "0.3.3"
|
|
91
86
|
},
|
|
92
87
|
"author": "OmniTwin Technologies Inc.",
|
|
93
88
|
"bugs": {
|