@decidio/sdk 0.1.9 → 0.1.11
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 +134 -29
- package/dist/adapters/_gate.d.ts +25 -1
- package/dist/adapters/_gate.js +69 -26
- package/dist/adapters/langgraph.js +2 -15
- package/dist/adapters/openai.js +2 -5
- package/dist/adapters/temporal.js +18 -20
- package/dist/canonical.d.ts +18 -0
- package/dist/canonical.js +104 -0
- package/dist/cli.js +146 -272
- package/dist/errors.d.ts +12 -0
- package/dist/errors.js +18 -0
- package/dist/evidence.d.ts +41 -0
- package/dist/evidence.js +57 -0
- package/dist/guard.d.ts +29 -8
- package/dist/guard.js +28 -35
- package/dist/index.d.ts +19 -10
- package/dist/index.js +49 -113
- package/dist/inngest.js +6 -56
- package/dist/resume.d.ts +83 -26
- package/dist/resume.js +213 -150
- package/dist/signing.d.ts +72 -6
- package/dist/signing.js +107 -41
- package/openapi.yaml +47 -1
- package/package.json +3 -9
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
export class DecidioUnsafeArgumentsError extends Error {
|
|
2
|
+
decisionId;
|
|
3
|
+
constructor(message, decisionId) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.decisionId = decisionId;
|
|
6
|
+
this.name = "DecidioUnsafeArgumentsError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
const NOT_ROUTED = "The decision was NOT routed; nothing reached Decidio.";
|
|
10
|
+
function refuse(path, what, fix) {
|
|
11
|
+
throw new DecidioUnsafeArgumentsError(`[decidio] ${path} ${what} — the approval payload must be plain JSON so that what a human approves is exactly what executes; ${fix} ${NOT_ROUTED}`);
|
|
12
|
+
}
|
|
13
|
+
export function canonicalize(value, path = "args") {
|
|
14
|
+
return walk(value, path, new Set());
|
|
15
|
+
}
|
|
16
|
+
function walk(v, p, seen) {
|
|
17
|
+
if (v === null)
|
|
18
|
+
return null;
|
|
19
|
+
const t = typeof v;
|
|
20
|
+
if (t === "boolean" || t === "string")
|
|
21
|
+
return v;
|
|
22
|
+
if (t === "number") {
|
|
23
|
+
const n = v;
|
|
24
|
+
if (!Number.isFinite(n))
|
|
25
|
+
refuse(p, `is ${String(n)}`, "a non-finite number becomes null in JSON, so the approver and your function would see different values — pass a finite number or a string.");
|
|
26
|
+
if (Object.is(n, -0))
|
|
27
|
+
refuse(p, "is -0", "negative zero is indistinguishable from 0 after serialization — pass 0 if that is what you mean.");
|
|
28
|
+
return n;
|
|
29
|
+
}
|
|
30
|
+
if (t === "undefined")
|
|
31
|
+
refuse(p, "is undefined", "JSON drops or nullifies it — use null explicitly, or omit the field.");
|
|
32
|
+
if (t === "bigint")
|
|
33
|
+
refuse(p, "is a BigInt", "JSON cannot represent it — pass it as a string.");
|
|
34
|
+
if (t === "function")
|
|
35
|
+
refuse(p, "is a function", "it cannot be serialized for approval.");
|
|
36
|
+
if (t === "symbol")
|
|
37
|
+
refuse(p, "is a symbol", "it cannot be serialized for approval.");
|
|
38
|
+
const o = v;
|
|
39
|
+
if (seen.has(o))
|
|
40
|
+
refuse(p, "is part of a circular structure", "JSON cannot represent cycles — break the cycle or pass an id instead.");
|
|
41
|
+
seen.add(o);
|
|
42
|
+
try {
|
|
43
|
+
const proto = Object.getPrototypeOf(o);
|
|
44
|
+
if (Array.isArray(v)) {
|
|
45
|
+
if (proto !== Array.prototype)
|
|
46
|
+
refuse(p, "is an array subclass", "only plain arrays survive serialization with their behavior intact — pass a plain array.");
|
|
47
|
+
const arr = v;
|
|
48
|
+
const keys = Reflect.ownKeys(arr);
|
|
49
|
+
const expected = arr.length + 1;
|
|
50
|
+
if (keys.length !== expected) {
|
|
51
|
+
const extra = keys.filter((k) => k !== "length" && !(typeof k === "string" && /^\d+$/.test(k) && Number(k) < arr.length));
|
|
52
|
+
if (extra.length)
|
|
53
|
+
refuse(`${p}.${String(extra[0])}`, "is an extra property on an array", "JSON serializes arrays by index only, so it would be dropped — move it into an object.");
|
|
54
|
+
refuse(p, "is a sparse array (it has holes)", "each hole becomes null in JSON — fill the holes or pass a dense array.");
|
|
55
|
+
}
|
|
56
|
+
return arr.map((x, i) => walk(x, `${p}[${i}]`, seen));
|
|
57
|
+
}
|
|
58
|
+
if (proto !== Object.prototype && proto !== null) {
|
|
59
|
+
const name = o?.constructor?.name;
|
|
60
|
+
refuse(p, `is ${name && name !== "Object" ? `a ${name}` : "an exotic object"}`, "only plain JSON objects survive serialization with their meaning intact (a Map becomes {}, a Date becomes a string) — convert it to plain JSON first.");
|
|
61
|
+
}
|
|
62
|
+
if (proto === null)
|
|
63
|
+
refuse(p, "is a null-prototype object", "it round-trips into an ordinary object, changing its kind — pass a plain object.");
|
|
64
|
+
if (typeof o.toJSON === "function")
|
|
65
|
+
refuse(p, "defines toJSON()", "its serialized form would differ from the value the approval and signature cover — pass the already-serialized form.");
|
|
66
|
+
const out = {};
|
|
67
|
+
for (const key of Reflect.ownKeys(o)) {
|
|
68
|
+
if (typeof key === "symbol")
|
|
69
|
+
refuse(p, "has a symbol-keyed property", "JSON drops symbol keys — use string keys.");
|
|
70
|
+
const d = Object.getOwnPropertyDescriptor(o, key);
|
|
71
|
+
if (!("value" in d))
|
|
72
|
+
refuse(`${p}.${key}`, "is a getter/setter", "an accessor is evaluated at serialization time, so the approved value could differ from the executed one — pass a plain data property.");
|
|
73
|
+
if (!d.enumerable)
|
|
74
|
+
refuse(`${p}.${key}`, "is a non-enumerable property", "JSON drops it, so the approver would never see it — make it enumerable or remove it.");
|
|
75
|
+
out[key] = walk(d.value, `${p}.${key}`, seen);
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
seen.delete(o);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function deepFreeze(v) {
|
|
84
|
+
if (v === null || typeof v !== "object")
|
|
85
|
+
return v;
|
|
86
|
+
for (const k of Object.keys(v))
|
|
87
|
+
deepFreeze(v[k]);
|
|
88
|
+
return Object.freeze(v);
|
|
89
|
+
}
|
|
90
|
+
export function canonicalizeArgs(args) {
|
|
91
|
+
return args.map((a, i) => deepFreeze(walk(a, `args[${i}]`, new Set())));
|
|
92
|
+
}
|
|
93
|
+
export function canonicalJson(value) {
|
|
94
|
+
if (value === null)
|
|
95
|
+
return "null";
|
|
96
|
+
if (typeof value === "boolean" || typeof value === "number")
|
|
97
|
+
return JSON.stringify(value);
|
|
98
|
+
if (typeof value === "string")
|
|
99
|
+
return JSON.stringify(value);
|
|
100
|
+
if (Array.isArray(value))
|
|
101
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
102
|
+
const keys = Object.keys(value).sort();
|
|
103
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(value[k])}`).join(",")}}`;
|
|
104
|
+
}
|