@decidio/sdk 0.7.0 → 0.8.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/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ export type { PendingAction, PendingStore, ResumeController, ResumeOutcome, Appr
6
6
  export { guard, Guard } from "./guard.js";
7
7
  export type { GuardFacadeConfig, ProtectOptions } from "./guard.js";
8
8
  export { DecidioHttpError, decisionIdProblem } from "./errors.js";
9
- export { generateKeypair, didFromPrivateKey, signBoundProof, signConfirm, signExecutionReport, verifyExecutionReport, resultDigest, assertSafeTransport, carriesCredential, bindingMaterial, requestCommitment, idempotencyKey, committedRequestJson } from "./signing.js";
9
+ export { generateKeypair, didFromPrivateKey, signBoundProof, signConfirm, signExecutionReport, verifyExecutionReport, resultDigest, assertSafeTransport, carriesCredential, bindingMaterial, boundScope, requestCommitment, idempotencyKey, committedRequestJson } from "./signing.js";
10
10
  export type { BindingMaterial, CommittedRequest } from "./signing.js";
11
11
  export { canonicalize, canonicalizeArgs, canonicalJson, DecidioUnsafeArgumentsError } from "./canonical.js";
12
12
  export type { JsonValue, JsonPrimitive } from "./canonical.js";
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ export { DecidioSuspendedError, DecidioDeniedError, DecidioRecoveryUnavailableEr
6
6
  export { guard, Guard } from "./guard.js";
7
7
  export { DecidioHttpError, decisionIdProblem } from "./errors.js";
8
8
  import { DecidioHttpError, decisionIdProblem } from "./errors.js";
9
- export { generateKeypair, didFromPrivateKey, signBoundProof, signConfirm, signExecutionReport, verifyExecutionReport, resultDigest, assertSafeTransport, carriesCredential, bindingMaterial, requestCommitment, idempotencyKey, committedRequestJson } from "./signing.js";
9
+ export { generateKeypair, didFromPrivateKey, signBoundProof, signConfirm, signExecutionReport, verifyExecutionReport, resultDigest, assertSafeTransport, carriesCredential, bindingMaterial, boundScope, requestCommitment, idempotencyKey, committedRequestJson } from "./signing.js";
10
10
  export { canonicalize, canonicalizeArgs, canonicalJson, DecidioUnsafeArgumentsError } from "./canonical.js";
11
11
  export { readConfirmResponse, buildConfirmBody, describeTierGap, EVIDENCE_TIERS } from "./evidence.js";
12
12
  export class DecidioBlockedError extends Error {
@@ -55,6 +55,8 @@ export function approvalRefusal(check) {
55
55
  case "commitment_missing": return new DecidioApprovalUnverifiedError(id, "commitment_missing", check.reason ?? "Decidio answered without the request commitment this SDK sent");
56
56
  case "unverifiable": return new DecidioApprovalUnverifiedError(id, "unverifiable", check.reason ?? "the approval could not be tied to this request");
57
57
  case "pending": return new DecidioBlockedError(`not approved - ${check.reason ?? `Decidio records ${id} as pending`}`, id);
58
+ case "spent": return new DecidioBlockedError(`not executed - ${check.reason ?? `the approval for ${id} is already spent`}`, id);
59
+ case "expired": return new DecidioBlockedError(`not executed - ${check.reason ?? `the approval for ${id} has expired`}`, id);
58
60
  default: return new DecidioBlockedError(check.reason ?? `not approved (Decidio answered ${JSON.stringify(check.state)})`, id);
59
61
  }
60
62
  }
package/dist/resume.d.ts CHANGED
@@ -701,11 +701,18 @@ export declare function verifyResumeSignature(secret: string, body: string, head
701
701
  * called it: the claim was what kept anyone from noticing the disagreement. */
702
702
  export declare function assertConfirmRecorded(body: any, decisionId: string, signed: boolean): string;
703
703
  /** What GET /agent/status answers. `requestCommitment` (0.7.0) is the commitment the decision was
704
- * authorized with, or null for a decision whose request was never bound. */
704
+ * authorized with, or null for a decision whose request was never bound. `spent` and `grantExpiresAt` (0.8.0,
705
+ * mini-approvals): the grant is ONE-TIME - `spent` once an execution was recorded on it, `grantExpiresAt` when
706
+ * it lapses (the seal's time plus the gate's TTL) for a granted decision, null otherwise. A gate that predates
707
+ * them answers neither. */
705
708
  export interface DecisionStatus {
706
709
  state: string;
710
+ decisionId?: string;
711
+ receiptId?: string | null;
707
712
  reason?: string | null;
708
713
  requestCommitment?: string | null;
714
+ spent?: boolean;
715
+ grantExpiresAt?: string | null;
709
716
  }
710
717
  type StatusConfig = {
711
718
  apiUrl: string;
@@ -717,11 +724,14 @@ type StatusConfig = {
717
724
  /** Why an approval check did not pass.
718
725
  * pending / unrecognized_state — Decidio records no approval (yet): nothing may run.
719
726
  * rejected / blocked — Decidio's final answer is no.
727
+ * spent — the approval was already used: an execution is recorded on it (one approval
728
+ * covers one action, once) - ask again.
729
+ * expired — the approval lapsed (its grant's time passed) before anything ran - ask again.
720
730
  * request_changed — the approval is for a DIFFERENT request than the one about to run.
721
731
  * commitment_missing — this SDK bound the request, the gate answered without a commitment.
722
732
  * unverifiable — Decidio holds a commitment this process cannot recompute (no or
723
733
  * unreadable key material), or answered a malformed one. */
724
- export type ApprovalCheckCode = "pending" | "unrecognized_state" | "rejected" | "blocked" | "request_changed" | "commitment_missing" | "unverifiable";
734
+ export type ApprovalCheckCode = "pending" | "unrecognized_state" | "rejected" | "blocked" | "spent" | "expired" | "request_changed" | "commitment_missing" | "unverifiable";
725
735
  /** The outcome of the one check. Plain JSON on purpose: an engine that runs it as an activity or a
726
736
  * step (Temporal, Inngest) memoizes it across its serialization boundary. */
727
737
  export interface ApprovalCheck {
package/dist/resume.js CHANGED
@@ -839,6 +839,18 @@ export async function checkApproval(cfg, decisionId, request, opts = {}) {
839
839
  return no("pending", `Decidio records decision ${decisionId} as pending - no human has approved it`);
840
840
  if (state !== "approved" && state !== "auto_approved")
841
841
  return no("unrecognized_state", `Decidio answered state ${JSON.stringify(st?.state ?? null)} for decision ${decisionId}, which is not an approval this SDK recognises (an answer it does not understand is never permission)`);
842
+ if (st?.spent === true)
843
+ return no("spent", `decision ${decisionId} was approved once and that approval is already spent (an execution is recorded on it) - ask again for a fresh approval; nothing was executed`);
844
+ if (typeof st?.grantExpiresAt === "string") {
845
+ const until = Date.parse(st.grantExpiresAt);
846
+ if (!Number.isFinite(until))
847
+ return no("unverifiable", `Decidio answered a malformed grant expiry for decision ${decisionId} (${JSON.stringify(st.grantExpiresAt)}), so the approval cannot be known to be in force`);
848
+ if (until <= Date.now())
849
+ return no("expired", `decision ${decisionId}'s approval expired at ${st.grantExpiresAt} before anything ran - ask again for a fresh approval; nothing was executed`);
850
+ }
851
+ else if (typeof st?.spent === "boolean" && st?.grantExpiresAt === null) {
852
+ return no("expired", `decision ${decisionId} is approved but Decidio names no expiry for its grant (its age is unknown) - ask again for a fresh approval; nothing was executed`);
853
+ }
842
854
  const server = st?.requestCommitment;
843
855
  if (server !== undefined && server !== null) {
844
856
  if (typeof server !== "string" || !/^[0-9a-f]{64}$/.test(server))
package/dist/signing.d.ts CHANGED
@@ -126,6 +126,11 @@ export declare function requestCommitment(material: BindingMaterial, request: Co
126
126
  /** lowercase-hex idempotency key over an execution identity (e.g. LangGraph's thread + task +
127
127
  * commitment), keyed so the host's thread ids never reach Decidio in the clear. */
128
128
  export declare function idempotencyKey(material: BindingMaterial, identity: Record<string, JsonValue>): string;
129
+ /** The Claude Code hook's request scope: `cmd-` + 16 hex of a KEYED digest over the command text and its subject
130
+ * (chat approvals follow-up, invariant-guard 2026-10-15). The text may carry a credential the display redacts; an
131
+ * unkeyed digest sealed beside the redacted text would be an offline oracle for a weak password. Keyed under its own
132
+ * domain, so it is deterministic for this agent and nothing to anyone without the key material. */
133
+ export declare function boundScope(material: BindingMaterial, text: string): string;
129
134
  /** A signed, DOMAIN-SEPARATED, versioned attestation that THIS agent reported THIS captured
130
135
  * response for THIS decision. Distinct from the identity proof (`signConfirm`) in every field
131
136
  * that matters, so a report can never be lifted from one decision onto another: the decision id,
package/dist/signing.js CHANGED
@@ -198,6 +198,9 @@ export function requestCommitment(material, request) {
198
198
  export function idempotencyKey(material, identity) {
199
199
  return createHmac("sha256", bindingSubkey(material, "decidio.idempotency-key.v1")).update(canonicalJson(identity, "idempotency identity"), "utf8").digest("hex");
200
200
  }
201
+ export function boundScope(material, text) {
202
+ return "cmd-" + createHmac("sha256", bindingSubkey(material, "decidio.hook-scope.v1")).update(text, "utf8").digest("hex").slice(0, 16);
203
+ }
201
204
  export function signExecutionReport(privateKeyBase64, fields) {
202
205
  const key = createPrivateKey({ key: Buffer.from(privateKeyBase64, "base64"), format: "der", type: "pkcs8" });
203
206
  const nonce = fields.nonce ?? randomUUID();
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.7.0 # tracks the SDK release - checked by packages/conformance/manifests.mjs
8
+ version: 0.8.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.7.0",
3
+ "version": "0.8.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.7.0"
87
+ "@decidio/verify": "0.8.0"
88
88
  },
89
89
  "author": "OmniTwin Technologies Inc.",
90
90
  "bugs": {