/** * design/80 D-1 §2 (slice 1a.2) — the engine-only **vetted canonical serializer** used to mint a stable, * opaque {@link boundInputHashOf | boundInputHash} over a pending tool call's arguments. * * ## Why a bespoke serializer (and why engine-ONLY) * The package's only runtime deps are typebox + the MCP SDK — there is no canonical/stable JSON serializer, * and `JSON.stringify` is **not** canonical (object key order is insertion order, `-0`/`NaN`/`Infinity` are * mishandled, and it offers no type-tagging). The red-team r3 ruling (design/80 §7) is the load-bearing * constraint: **never let two runtimes each compute a canonical form and compare** — a `JSON.stringify` * divergence (or a serializer-version drift across a worker upgrade) would produce a false mismatch and * fail-closed a *legitimate* approval. So the boundInputHash is **server-minted opaque**: the engine computes * it ONCE at suspend-mint with THIS serializer, persists it on the checkpoint, and a resume verifies by * **opaque string equality** against the persisted value (it never re-serializes the resume-side args). This * serializer therefore only needs to be *internally deterministic* (same value → same bytes within one * runtime); it is deliberately NOT exported from the package index, so no cross-wire consumer (the SDK / * service) can be tempted to recompute it — they treat the hash as opaque and echo it verbatim. * * ## Canonical form * A total function (never throws) that emits a **type-tagged, length-prefixed** string so distinct values of * distinct types can never collide (`number 1` ≠ `string "1"` ≠ `boolean`, and `{"a":"b"}` ≠ `{"ab":""}`): * - `null` → `N`, `undefined` → `U` (encoded distinctly — `{a:undefined}` must not collide with `{}`) * - boolean → `b1` / `b0` * - number → `n:`; finite via `String(x)` (shortest round-trippable, deterministic per value); * `-0` normalized to `0`; `NaN`/`±Infinity` get explicit distinct tokens (never collapsed to `null`) * - bigint → `i:` * - string → `s:` — **length-prefixed, NOT normalized**: equal inputs map to equal output and * distinct inputs (e.g. a non-NFC string vs its NFC form) map to distinct output. The serializer never * silently Unicode-normalizes (that would let two different argument payloads share one hash). * - array → `a:[…]` — order preserved (semantic) * - object → `o:{:=…}` — **own enumerable keys sorted** by UTF-16 code unit, so * `{a,b}` and `{b,a}` canonicalize identically. (Duplicate keys cannot exist on a live JS object — * last-wins already happened at parse/construction — so they need no special handling here.) * - function / symbol → tagged tokens (`fn` / `y:`) so the serializer stays total; these never appear * in design/37 post-hook tool args (which are JSON values), but encoding rather than throwing keeps a * weird arg from silently degrading a durable suspend to a non-durable fallback. */ export declare function canonicalize(value: unknown): string; /** * design/80 D-1 §2 — the server-minted **opaque** boundInputHash: a SHA-256 (hex) over {@link canonicalize}. * Bound at suspend-mint to the pending tool call's input (`pendingAction.args`) and persisted on the * checkpoint; a resume verifies the echoed value by string equality (never re-hashing the resume-side args). * The hex digest is fixed-length and opaque — exactly what the operator sees / the D-G signer signs. */ export declare function boundInputHashOf(value: unknown): string; //# sourceMappingURL=canonical-json.d.ts.map