attenu-guard 0.4.0 → 0.6.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/CHANGELOG.md CHANGED
@@ -6,7 +6,275 @@ follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
- ## [0.4.0] 2026-08-31
9
+ ## [0.6.0] - 2026-09-02
10
+
11
+ ### Added
12
+ - **The bundle-level interop test vectors, and the structured failures they are scored
13
+ against.** The delegation-chain vectors under `test/fixtures/vectors/` have always let this
14
+ package score its own *token* verifier against the Python reference. There was no equivalent
15
+ for the *bundle* verifier, which is where the offline-verifiability claim actually lands: an
16
+ auditor checks a published ledger with no engine, no service and no vendor in the loop.
17
+ `test/fixtures/vectors/bundles/bundle_vectors_v1.json` is that file, copied byte for byte from
18
+ the Python repository (`tests/vectors/bundles/bundle_vectors_v1.json`, whose single writer is
19
+ `tests/vectors/generate_bundles.py`). Eight cases, every one derived from a single valid
20
+ schema-v2 bundle by exactly ONE change so each isolates one rule: `valid_bundle_v2` (accept),
21
+ `reject_params_mismatch`, `reject_outcome_without_allow`, `reject_outcome_before_allow`,
22
+ `reject_duplicate_outcome`, `reject_duplicate_call_id`, `reject_rehashed_chain` (one entry
23
+ edited and every later hash recomputed — the rewrite a hash chain alone cannot catch, which
24
+ only the signed anchor does) and `reject_tampered_entry` (the same edit with nothing
25
+ re-hashed, which fails AT that entry). All eight score here exactly as they score in Python,
26
+ with byte-identical `failures` strings. `tools/gen_fixtures.py` re-copies the file from the
27
+ installed Python package once a release ships it, so CI's existing fixture-drift check starts
28
+ guarding it with no workflow edit.
29
+ - **`verifyBundle()` reports `failure_details`, the structured twin of `failures`** (additive;
30
+ `failures` is byte-identical to before, because other implementations parse those strings).
31
+ One entry per string, same order, same count: `{reason, seq, node, call_id, detail}`, so a
32
+ conformance suite can assert WHICH check failed and WHERE instead of matching prose. `reason`
33
+ is the token before the colon in the message, except at the two historical sites whose message
34
+ names a node there (`unreadable_authority`, `unreadable_granted`), which state their reason
35
+ explicitly. Positions match the Python implementation's: the second sighting for a re-used
36
+ `call_id`, the `outcome` entry for every allow/outcome binding failure, and null for a
37
+ genuinely chain-level failure such as a signed anchor that no longer matches the ledger head.
38
+ Every failure in `src/evidence.ts` now goes through one collector, so a message cannot be
39
+ added without its twin — `test/bundle-vectors.test.ts` asserts the two lists stay in step at
40
+ every failure site in the module (including the ones no vector exercises) and traps a direct
41
+ append. The `execution_binding` sub-report's own shape is unchanged: the twins ride alongside
42
+ it, never inside it.
43
+
44
+ ## [0.5.0] - 2026-08-31
45
+
46
+ ### Fixed
47
+ - **Adapter mirror of the Python batch-1/batch-2 adversarial review.** The TS package ships
48
+ exactly one adapter surface (`src/adapters/langgraph.ts` — `package.json`'s `exports` map
49
+ declares nothing besides `.` and `./adapters/langgraph`; no A2A, no generic wrapper, no
50
+ separate LangChain.js seam), and execution binding was already fully wired into both
51
+ `guardNode` and `guardTool` as of 0.4.0. Each Python defect class was checked against this
52
+ specific adapter, against pinned `@langchain/core@1.2.9`/`@langchain/langgraph@1.4.13` source
53
+ (installed and grepped directly, not assumed), rather than ported by analogy — see the module
54
+ doc comment's own "Adversarial review" section for the full per-class evidence. Six of the
55
+ seven classes came back genuinely inapplicable to this adapter's architecture (no composable
56
+ middleware chain in either pinned framework package; one wrapper per call, no second gate; no
57
+ cross-hook correlation map to collide or grow unbounded; `isDeferredResult` already covers
58
+ JavaScript's whole lazy-result landscape; no external multi-phase hook dispatch to lose an
59
+ event across; `src/`'s only runtime import is a lazy `@langchain/langgraph`, correctly
60
+ undeclared as a hard dependency, matching the README's own "zero runtime dependencies" claim).
61
+ One real, TS-specific gap was found in the snapshot-commitment family and fixed:
62
+ - `snapshotParams`/`snapshotToolParams`'s fallback, taken when `structuredClone` cannot clone
63
+ the value being snapshotted (a function, a class instance it refuses, anything sharing an
64
+ object graph with one of those), was a bare shallow copy — `{args: [...args]}` makes a fresh
65
+ OUTER array, but every element INSIDE it is the same live reference as the real call
66
+ arguments. Reproduced directly before fixing: `snapshot.args[0] === liveArg` was `true`, and
67
+ a mutation of `liveArg` after the call was visible through the "snapshot" — violating the
68
+ adapter's own documented guarantee ("an IMMUTABLE snapshot... taken BEFORE the wrapped
69
+ callable runs"). Checked the specific `toJSON` vector first: `structuredClone` does NOT
70
+ consult a `toJSON` method or any other user-overridable protocol the way Python's
71
+ `copy.deepcopy` consults `__deepcopy__` (verified empirically — a hostile class's own
72
+ `toJSON` returning fabricated data is simply ignored, and the clone is never the same
73
+ OBJECT reference for that specific case); only the FAILURE path aliased for a hostile
74
+ `toJSON`. See the release-gate correction below for why "a successful clone never aliases"
75
+ was still wrong as a general claim (a `SharedArrayBuffer` clones "successfully" to a
76
+ DIFFERENT object that shares the SAME underlying memory).
77
+ - Fixed with a new `freeze()` function (exported for direct testing, the same reason every
78
+ Python adapter's own `_freeze()` is imported directly by its tests — the audit log never
79
+ exposes the raw snapshot value, only its hash, so "does this alias" is not otherwise
80
+ observable): safe JSON-primitive leaves pass through verbatim, plain objects/arrays are
81
+ rebuilt fresh and recursively, and anything else (a function, a class instance, a
82
+ `Map`/`Set`/`Date`/`RegExp`, a `Symbol`, a `BigInt`) becomes a safe string representation —
83
+ never the live reference. This is `structuredClone`'s support matrix happening to overlap
84
+ with what the audit log's own JCS canonicalizer (`params.ts`) can hash, unconditionally,
85
+ matching the same invariant every Python adapter's `_freeze()` already holds, rather than
86
+ scoped narrowly to only the cases proven to reach a hash mismatch. Guards a circular
87
+ reference with a `WeakSet` (`structuredClone` handles cycles natively; the whole point of
88
+ this function is the cases it could NOT handle, one of which could still be cyclic). A
89
+ welcome side effect: because `freeze()` sanitizes an otherwise-unsupported value BEFORE it
90
+ is ever handed to `params.ts`'s `commit()`, a call that used to commit no hash at all
91
+ (`paramsHashReason: "unsupported"`) now commits a real, verifiable one.
92
+ - Tests added in `test/adapter-langgraph.test.ts`: two direct unit tests on `freeze()` itself
93
+ (never aliases the unclonable value's own case; never aliases a mutable SIBLING sharing the
94
+ same object graph as an unclonable value — the mixed case), one guarding the circular
95
+ reference, and one end-to-end test per wrapper (`guardNode`, `guardTool`) driving an
96
+ unclonable argument through the real call path and asserting a genuine, matching
97
+ `authorizedParamsHash`/`invokedParamsHash` pair is committed rather than `"unsupported"`.
98
+ - **Delta review, two more edits to `freeze()` itself:**
99
+ - **Medium-low, required:** the plain-object branch built the rebuilt object with an
100
+ `out[k] = freeze(v, seen)` accumulation loop. A plain `JSON.parse('{"__proto__": {...}}')`
101
+ result genuinely has `"__proto__"` as an OWN, ENUMERABLE data property (`Object.keys`
102
+ lists it — JSON has no notion of prototypes, so this is reachable from ordinary untrusted
103
+ input, not a contrived shape) — but assigning through `out[k] = v` for that specific key
104
+ name does not create a data property at all; it sets the accumulator's own `[[Prototype]]`
105
+ via `Object.prototype`'s own `__proto__` accessor instead. The key then vanished from the
106
+ rebuilt object's own enumerable keys entirely — a params-commitment completeness gap
107
+ (substitution on that key would be invisible to a params mismatch) that `structuredClone`'s
108
+ own success path, and Python's `_freeze()`, do not have. Fixed with
109
+ `Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, freeze(v, seen)]))`, which
110
+ always defines genuine data properties, `__proto__` included. Test added: a
111
+ `JSON.parse`-created `__proto__` own key beside an unclonable sibling (forcing the
112
+ fallback) — the key survives into the snapshot with its value, and the rebuilt object's
113
+ own prototype is unaffected.
114
+ - **Nit, ride-along:** the array branch used `value.map((v) => freeze(v, seen))` —
115
+ `Array.prototype.map` SKIPS a hole in a sparse array rather than visiting it, so a hole
116
+ would survive into the snapshot as a hole too, unlike every other absent value `freeze()`
117
+ turns into a plain `null`. Fixed with `Array.from(value, (v) => freeze(v, seen))`, which
118
+ visits every index up to `.length`, densifying a hole to `undefined` (then `null`, same as
119
+ any other `undefined`). Test added: `freeze([1, , 3])` equals `[1, null, 3]`, with a real
120
+ (densified) element at index 1, not a hole.
121
+ - **Release-gate correction (CRITICAL + HIGH): `freeze()` was still only a FALLBACK, run only
122
+ when `structuredClone` THREW — a successful clone bypassed it entirely, and "a successful
123
+ clone never aliases" (asserted above) was not actually true.** Three bypasses, each
124
+ reproduced directly before fixing: (1) a circular object clones successfully —
125
+ `structuredClone` handles cycles natively — so `freeze()` never ran on it at all; the
126
+ circularity then reached `params.ts`'s own hash-commitment walk, which has NO cycle guard,
127
+ and crashed with `RangeError: Maximum call stack size exceeded` before authorization or the
128
+ tool body ever ran. (2) A sparse array clones successfully too, holes preserved, bypassing
129
+ `freeze()`'s own densification (added above) entirely — it reached `params.ts` as
130
+ `paramsHashReason: "unsupported"` instead of a real, densified, hashable snapshot. (3) A
131
+ `SharedArrayBuffer` clones to a DISTINCT wrapper object that shares the SAME underlying
132
+ memory, by design — a "successful" clone that is not independent at all. Fixed by making
133
+ `freeze()` the ONLY snapshot path, unconditionally: `structuredClone` is no longer called
134
+ anywhere in this adapter. Also fixed in the same pass: the cycle guard (`seen`) was a
135
+ single, MUTABLE `WeakSet` shared across the whole call, added to but never removed from —
136
+ so a DAG's repeated sibling reference (the SAME object appearing twice as two different
137
+ keys' values, never as its own ancestor) was wrongly reported `"<circular>"` on its second
138
+ occurrence, reproduced directly: `freeze({a: shared, b: shared})` came back
139
+ `{"a": {...}, "b": "<circular>"}`. Renamed to `active`, a PATH-ACTIVE `ReadonlySet` — a
140
+ fresh `Set` unioned in at each recursive call, never mutated in place or shared across
141
+ sibling branches. Separately, HIGH: the array and object branches (`Array.from`/`.map()` and
142
+ `Object.entries()`) invoke the value's OWN protocols — a hostile `[Symbol.iterator]`
143
+ override can yield ANYTHING regardless of an array's real indexed properties (reproduced:
144
+ `[1, , 3]` with a hostile iterator froze as `[999]`), and a getter is INVOKED by
145
+ `Object.entries()`, with no guarantee of being invoked only once (reproduced: with an
146
+ unclonable sibling forcing the old fallback, a getter with a side effect was observed three
147
+ times across the old clone-attempt/freeze/body sequence, and the committed snapshot was the
148
+ SECOND of three observations, not the first). Fixed: both branches now walk
149
+ `Object.getOwnPropertyDescriptor` directly (arrays by a `.length`-bounded index loop, objects
150
+ by `Object.keys`) — pure introspection, never invoking user code — and an accessor property
151
+ (`.get`/`.set` present) is encoded as the literal string `"<accessor>"` rather than read at
152
+ all. **A regression caught and fixed before this same commit landed:** rewriting the
153
+ object branch's write-back re-introduced the EXACT `out[key] = value` bug the `__proto__`
154
+ fix above had already closed (a bracket assignment to the literal key `"__proto__"` sets the
155
+ accumulator's prototype instead of a data property) — caught by running the existing
156
+ `__proto__` test against the rewrite, not assumed fixed; corrected with
157
+ `Object.defineProperty` in the loop instead. Six new tests added, each driving the REAL
158
+ wrapper (`guardNode`/`guardTool`), not `freeze()` directly — the earlier circular test
159
+ guarded the wrong path (it passed even while the actual wrapper crashed): circular input,
160
+ sparse array, hostile custom iterator, getter, `SharedArrayBuffer`, and a DAG's repeated
161
+ reference, all via `test/adapter-langgraph.test.ts`.
162
+ - **Release-gate correction (HIGH): `isDeferredResult` missed a plain `AsyncIterable`.** The
163
+ async branch required the result to have its OWN `.next` method, matching a self-iterating
164
+ async generator — but the JavaScript async-iterable protocol only requires a callable
165
+ `[Symbol.asyncIterator]()`, which can return a SEPARATE object that has `.next`, without the
166
+ iterable itself ever having one. Reproduced directly before fixing: a plain object
167
+ implementing only `[Symbol.asyncIterator]()` was recorded `BodyState.RETURNED`, not
168
+ `DEFERRED`. Fixed: the async check no longer requires an own `.next` (the sync check, which
169
+ DOES require it, was deliberately left alone — dropping it there would misdetect a plain
170
+ `Array`/`Set`/`Map` as deferred, since those implement `Symbol.iterator` too without their
171
+ contents being lazily produced; there is no equivalent JavaScript built-in that implements
172
+ `Symbol.asyncIterator` over already-computed values, so this asymmetry is not itself a gap).
173
+ The module doc comment's own "whole lazy-result landscape" claim is narrowed to list exactly
174
+ what `isDeferredResult` checks, rather than asserting completeness. Two tests added: the
175
+ async-iterable case now scores `DEFERRED`; a plain array result is confirmed to still score
176
+ `RETURNED` (pinning that the sync branch's requirement was deliberately kept).
177
+ - **Release-gate correction (HIGH): three disagreeing version fields.** `package.json` said
178
+ `0.4.0`; `package-lock.json`'s root `"version"` said `0.3.1` (stale since before the 0.4.0
179
+ bump — `npm install` never re-synced it); `src/version.ts`'s exported `VERSION` — the
180
+ constant `guard.ts` and both `adapters/langgraph.ts` wrappers use to attribute every v2
181
+ ledger entry's `adapter.version` field — said `0.3.0`. The release workflow
182
+ (`.github/workflows/release.yml`) only ever checks the pushed tag against `package.json`, so
183
+ it would have published while shipped ledger attribution was still wrong. Every v2 ledger
184
+ entry produced by the shipped 0.4.0 release has been misreporting `adapter.version` as
185
+ `"0.3.0"`. Fixed: all three aligned to the CURRENT `0.4.0` (no version bump as part of this
186
+ fix — that is the operator's call at release time). Added
187
+ `test/version-consistency.test.ts`, a new CI-run test (not only a release-time check)
188
+ asserting `package.json`, `package-lock.json`'s root version (both the top-level field and
189
+ its `packages[""].version` copy, which have drifted independently before), and the exported
190
+ `VERSION` all agree, every run, not only at tag time.
191
+ - **Release-gate finding, MEDIUM: `test/wire-vectors.test.ts` enumerated and scored 19 of the
192
+ 20 published interop vectors, silently.** `reject_unsafe_integer` — anticipated in this
193
+ package's own `[0.3.1]` CHANGELOG entry below ("will show 20 vectors... once the Python
194
+ package ships them") and shipped by Python's own `[0.9.0]` — has had its fixture file
195
+ present on disk (byte-identical to Python's) since, but `VECTOR_NAMES` itself was never
196
+ updated to include it, and the test's own count assertion (`19`) masked the omission rather
197
+ than catching it. Fixed: added to `VECTOR_NAMES`, the count and test name updated to `20`,
198
+ and the stale `.github/workflows/ci.yml` comment ("19-vector... >=0.8 ships this") corrected
199
+ to `20-vector`/`>=0.9` (Python's own `[0.9.0]` CHANGELOG entry is where
200
+ `reject_unsafe_integer.json` shipped, matching the pip constraint
201
+ `attenu-guard>=0.9,<0.10` already pinned two lines below that comment).
202
+ - **Release-gate finding, LOW: the interop matrix had no early warning for the NEXT Python
203
+ minor.** `ci.yml`'s `interop` job pins `attenu-guard>=0.9,<0.10` deliberately — the committed
204
+ fixtures match that release, and the job's own last step re-generates and diffs them, so
205
+ pinning to it is correct, not stale. But nothing in this repo would notice a 0.10.0 that
206
+ breaks wire compatibility until someone widened that pin by hand. Added a second job,
207
+ `interop-next`, that runs the same cross-language suite against `attenu-guard>=0.10,<0.11` —
208
+ gated on a `pip index versions` check so it reports success without running anything while
209
+ 0.10.0 is unpublished (confirmed against the live PyPI index: 0.9.0 is current, no 0.10.x
210
+ yet), and starts actually exercising the suite the moment the operator ships it, with no
211
+ workflow edit required either way.
212
+ - **D14 — `Guard.check()` registered a `PRE_HOOK_ONLY` allow as pending, wedging `complete()`
213
+ forever.** Mirrors the fix landing in the Python `attenu-guard` reference implementation
214
+ (`guard.py`, same defect, same root cause): `registerPending` ran unconditionally for every
215
+ `schemaVersion: 2` allow, in both the normal commit path and the `CommittedAuditError` path,
216
+ with no regard for `capture`. A bare `check()` (or any explicit `capture: Capture.PRE_HOOK_ONLY`)
217
+ is an honest promise of NO terminal observation — nothing is ever going to call `recordOutcome`
218
+ for it — yet it was registered pending exactly like a `WRAPPER_SYNC`/`WRAPPER_ASYNC`/
219
+ `FRAMEWORK_POST_HOOK` allow, so `complete()` refused forever for a node with only
220
+ `PRE_HOOK_ONLY` calls. The offline verifier already treated a missing `PRE_HOOK_ONLY` outcome
221
+ as merely `unobserved` (`evidence.ts`'s execution-binding report), so runtime and offline
222
+ semantics disagreed. Fixed: a call is now registered pending only when its capture is one of
223
+ `WRAPPER_SYNC`/`WRAPPER_ASYNC`/`FRAMEWORK_POST_HOOK` — in both the normal path and the
224
+ `CommittedAuditError` path. A bare/`PRE_HOOK_ONLY` allow never enters the pending set, so
225
+ `complete()` finalizes immediately, and the verifier's `unobserved` classification for it now
226
+ matches the runtime's own view.
227
+ - **Re-gate correction (HIGH): `freeze()` still executed attacker-controlled code BEFORE
228
+ authorization, on three separate exotic-value paths, all reproduced directly before this fix.**
229
+ (1) A `Proxy` is not inert under reflection: `Object.getPrototypeOf`, `Object.keys`, and
230
+ `Object.getOwnPropertyDescriptor` are each real, user-definable traps — walking an ordinary
231
+ Proxy through the property-descriptor logic fired four of them before authorization was ever
232
+ decided, and `Array.isArray` on a REVOKED Proxy throws `TypeError` outright rather than
233
+ degrading cleanly. (2) The bottom fallback still called `String(value)` for anything not a
234
+ plain object/array — a boxed primitive (`new Number(...)`) with a hostile `Symbol.toPrimitive`,
235
+ or a `TypedArray` with a hostile own `toString`, each ran attacker code exactly once per
236
+ snapshot. (3) No general safety net: an unanticipated reflection failure would have propagated
237
+ an exception out of a snapshot taken before authorization, rather than degrading. None of this
238
+ was an aliasing or authorization-bypass defect — ordinary frozen/accessor-only objects never
239
+ leaked references, and policy evaluation still fully controlled whether the wrapped body ran —
240
+ the defect was pre-authorization code execution and exception propagation. Fixed: `freeze()`
241
+ now recognizes a Proxy FIRST, via `util.types.isProxy` (an internal engine-slot check that
242
+ invokes nothing, live or revoked — verified directly), before `Array.isArray` or any other
243
+ reflection; every value that is not a safe JSON primitive and not a plain object/array (a
244
+ Proxy, a boxed primitive, a TypedArray/`ArrayBuffer`/`SharedArrayBuffer`/`DataView`, a
245
+ `Map`/`Set`/`Date`/`RegExp`, a function, a `Symbol`, a `BigInt`, or anything else) becomes the
246
+ new `FREEZE_UNSUPPORTED` sentinel instead of a string — never `String()`, never any other
247
+ protocol; the whole reflective walk runs inside one `try`/`catch`, degrading any other
248
+ reflection failure the same way. `paramsHashReason: "unsupported"` for the whole call, never a
249
+ partial commitment — the same degradation this library already uses for an out-of-domain
250
+ number, not a new failure mode.
251
+ - **Re-gate correction (MEDIUM): `"<accessor>"` was a real commitment collision, and so was
252
+ `"<circular>"` by the identical reasoning.** Two real wrapper calls — one with an enumerable
253
+ getter, one with the literal string `"<accessor>"` as an ordinary value — produced the
254
+ IDENTICAL `authorizedParamsHash`, reproduced directly: an evidence-integrity ambiguity in a
255
+ supposedly cryptographic commitment (two materially different inputs, one commitment), even
256
+ though the getter itself correctly stayed uninvoked and this was never an authorization
257
+ bypass. Audited the sibling sentinel `"<circular>"` for the exact same collision class rather
258
+ than leaving it unexamined — it has the identical problem (a genuinely circular object and a
259
+ plain object holding the literal string `"<circular>"` produce the same commitment); there is
260
+ no reason a cycle's position makes the collision infeasible, so it gets the same fix. Both an
261
+ accessor property and a genuine cycle now become `FREEZE_UNSUPPORTED` (above) — the same
262
+ private `Symbol` every other unrepresentable value degrades to, which cannot equal any real
263
+ call argument by construction. Regression test added: zero getter calls, no hash,
264
+ `params_hash_reason: "unsupported"`; plus a direct test that a real getter-bearing object and
265
+ the old literal-string sentinel no longer freeze to the same shape.
266
+ - **Re-gate correction (MEDIUM): `interop-next`'s "is 0.10.x published yet?" check failed OPEN.**
267
+ `set -eu` alone does not catch a failing `pip index versions` inside an
268
+ `if PIPE | grep -q ...; then` compound command — a compound `if` condition is exempt from
269
+ `set -e` by design, and without `pipefail` a pipeline's exit status is its LAST command's
270
+ only. Reproduced directly: a simulated exit 42 from the query flowed straight through to
271
+ `published=false`, the same output as "0.10.x genuinely does not exist yet" — a PyPI outage
272
+ silently read as a fact about what has been released, with the step still exiting 0. Fixed by
273
+ extracting the check into `tools/check-next-python-published.sh`, which captures the query's
274
+ exit code in its own statement (not inside an `if`) and exits non-zero — without ever printing
275
+ `false` — before reaching the match logic. Added a probe step to `ci.yml`'s `interop-next` job,
276
+ run on every CI invocation, that points the script at a stub `python3` simulating a query
277
+ failure and asserts the script fails rather than reporting `false`.
10
278
 
11
279
  ### Added
12
280
  - **Execution binding**, opt-in per chain via `Guard.issue(agentId, authority, {schemaVersion: 2})`
package/README.md CHANGED
@@ -178,6 +178,17 @@ dual-signing mode. The 19 committed interop vectors include the separating cases
178
178
  for number spelling, raw Unicode, UTF-16 member ordering, large integers,
179
179
  duplicates, non-finite values and an unmarked canonical header.
180
180
 
181
+ `test/fixtures/vectors/bundles/bundle_vectors_v1.json` is the second, bundle-level
182
+ suite: whole evidence bundles for `verifyBundle`, the check an auditor runs on a
183
+ published ledger with no engine and no vendor in the loop. The token vectors pin
184
+ what a delegation token means; these pin what the LEDGER of a run has to satisfy.
185
+ A bundle verifier reports a LIST of failures rather than one reject reason, so
186
+ each rejecting case declares the minimal set of `{reason, seq, node}` that MUST
187
+ appear, at that exact position. A conformant verifier may report more, never
188
+ fewer and never elsewhere. `verifyBundle` returns those positions as
189
+ `failure_details`, the structured twin of `failures`: one
190
+ `{reason, seq, node, call_id, detail}` entry per string, in the same order.
191
+
181
192
  ## What it does not do
182
193
 
183
194
  - It does not decide what permissions a task needs. You state them; this library
@@ -51,6 +51,69 @@
51
51
  * adapter behaves exactly as it did before 0.9.0: no `capture`/`authorizedParams`, no
52
52
  * `recordOutcome` call. Every other framework adapter is unchanged in this release.
53
53
  *
54
+ * ## Adversarial review: the Python batch-1/batch-2 defect classes, checked against this
55
+ * adapter specifically (not assumed absent by analogy)
56
+ *
57
+ * The Python `attenu-guard` adapters went through two rounds of adversarial review that found
58
+ * several defect classes across their (many) framework adapters. This TS package ships exactly
59
+ * ONE adapter surface (this file — verified against `package.json`'s own `exports` map, which
60
+ * declares nothing besides `.` and `./adapters/langgraph`), so each class was checked against
61
+ * THIS adapter specifically, not inherited by assumption:
62
+ *
63
+ * - **Composable middleware / sibling short-circuit or retry** (a sibling wrapper positioned
64
+ * closer to the tool body than this one, able to fabricate or repeat what it observes) — NOT
65
+ * APPLICABLE. Verified directly against pinned `@langchain/core@1.2.9` and
66
+ * `@langchain/langgraph@1.4.13` (installed and grepped, not read off documentation): zero
67
+ * `"middleware"` hits anywhere near tool invocation in either package, and
68
+ * `ToolNode.prototype.runTool` (`dist/prebuilt/tool_node.js`) calls `tool.invoke(toolCall,
69
+ * runtime)` directly — `guardTool`'s `Proxy` IS what gets called, nothing sits between it and
70
+ * `ToolNode`. Neither framework has a composable per-call hook chain the way LangChain-Python's
71
+ * `create_agent(middleware=[...])` or AG2's `FunctionTool.register()` do.
72
+ * - **Double authorization via a second, independent gate** (e.g. Python's `claude_sdk`
73
+ * adapter's `can_use_tool` calling `authorize()` a second time for the same call) — NOT
74
+ * APPLICABLE. There is no second entry point here: `guardNode`/`guardTool` are each the ONLY
75
+ * caller-facing wrapper for their call, and there is nothing in either framework analogous to
76
+ * a second permission callback for a call this adapter already gated.
77
+ * - **Snapshot double-evaluation / narrow-projection commitment** — NOT APPLICABLE in the
78
+ * double-evaluation shape (`snapshotParams`/`snapshotToolParams` already compute ONE snapshot,
79
+ * reused unchanged for both `authorizedParams` and `invokedParams`), but a DIFFERENT,
80
+ * TS-specific gap in the same family was found and fixed — see `freeze()`'s own doc comment
81
+ * above and the CHANGELOG.
82
+ * - **Correlation-key collision across hooks** (e.g. Python's `claude_sdk` `tool_use_id`
83
+ * collision) — NOT APPLICABLE. `guard.recordOutcome` is called synchronously inside the same
84
+ * closure that owns the whole call, from `authorize()`'s own returned `Decision.callId` — no
85
+ * external pending-map keyed by a framework-supplied correlation id exists to collide on.
86
+ * - **Lazy-result detection gaps** (e.g. Python's `smolagents` adapter missing a coroutine or a
87
+ * `concurrent.futures.Future`) — `isDeferredResult` catches generators (an object with its own
88
+ * `.next` AND `[Symbol.iterator]` — "self-iterating", the shape a native generator has, but
89
+ * deliberately NOT the shape a plain `Array`/`Set`/`Map` has, since those implement
90
+ * `[Symbol.iterator]` too without an own `.next`, and their contents are already fully
91
+ * computed), a genuine ASYNC ITERABLE (anything implementing a callable
92
+ * `[Symbol.asyncIterator]`, self-iterating async generators included — see the RELEASE-GATE
93
+ * CORRECTION on this function's own body for why this does NOT require an own `.next` the way
94
+ * the sync branch does), and anything thenable. A plain (non-`async`) function that manually
95
+ * returns a bare `Promise` is also caught correctly, via the thenable check, in the sync
96
+ * branch of both wrappers. This is NOT a claim of covering "the whole lazy-result landscape" —
97
+ * only what this function's own checks actually implement, listed above; a class implementing
98
+ * some OTHER deferred-consumption protocol this function does not check for would not be
99
+ * caught.
100
+ * - **Lost-terminal-event / "fires unconditionally" false claims** (e.g. Python's `strands`
101
+ * adapter's before-hook interrupt paths) — NOT APPLICABLE. There is no external, multi-phase
102
+ * hook-dispatch loop for an event to be lost across; one wrapper function's own `try`/`catch`
103
+ * (or `await`ed async path) owns authorize-through-`recordOutcome` for the whole call
104
+ * synchronously. Structurally this adapter was already closest to Python's own `langgraph.py`
105
+ * reference wiring, not any of the adapters that needed this class of fix.
106
+ * - **Unbounded correlation cache** (Python's `claude_sdk` `_recentVerdicts`) — NOT APPLICABLE,
107
+ * for the same reason as the correlation-collision point above: no cache or pending-map exists
108
+ * in this adapter to bound.
109
+ * - **Wrong dependency declaration** (Python's `semantic-kernel` `protobuf` lesson: check what
110
+ * the RESOLVED version actually requires, not what is assumed) — checked: `src/` imports only
111
+ * `@langchain/langgraph` (lazily, in `isLangGraphAvailable()`); it never imports
112
+ * `@langchain/core` at all (only this file's own tests do, to build fixtures). `package.json`
113
+ * declares zero `dependencies` and no `peerDependencies` — matching the README's own "zero
114
+ * runtime dependencies" claim — and both `@langchain/core`/`@langchain/langgraph` are correctly
115
+ * `devDependencies`-only. Nothing this package's `src/` needs at runtime is undeclared.
116
+ *
54
117
  * ## Delegation
55
118
  *
56
119
  * Handing work to a sub-agent is the delegation moment. `delegateTo` mints the
@@ -67,6 +130,7 @@
67
130
  */
68
131
  import { type Guard } from "../guard.js";
69
132
  import type { Authority } from "../authority.js";
133
+ import type { Json } from "../canonical.js";
70
134
  import type { Context } from "../ceilings.js";
71
135
  import { type Decision } from "../reasons.js";
72
136
  /** Options shared by every guarded wrapper. */
@@ -90,6 +154,129 @@ export type GuardedNode<F extends (...args: any[]) => any> = F & {
90
154
  readonly toolScope: string;
91
155
  readonly unwrapped: F;
92
156
  };
157
+ /**
158
+ * A private, freeze()-only sentinel for "could not be represented as a JSON leaf" — a Proxy, an
159
+ * accessor property, a genuine cycle, a boxed primitive, a TypedArray, a function, or anything
160
+ * else this module does not know how to rebuild as plain JSON. NEVER a JSON-representable
161
+ * value (a string, `null`, …): a second release-gate finding showed a literal string sentinel
162
+ * (`"<accessor>"`) genuinely COLLIDES — a real getter-bearing object and a plain object holding
163
+ * the literal string `"<accessor>"` produced the IDENTICAL `authorizedParamsHash`, an
164
+ * evidence-integrity ambiguity in a supposedly cryptographic commitment (two materially
165
+ * different inputs, one commitment). A fresh, private `Symbol` cannot equal any real call
166
+ * argument, so it cannot collide with one — and it makes the SAME degradation apply uniformly
167
+ * everywhere this function cannot represent something, rather than inventing a new
168
+ * JSON-shaped sentinel (with its own collision risk) per case.
169
+ *
170
+ * Declared as `Json` even though a `Symbol` is not one — a deliberate escape from that type's
171
+ * nominal domain, not an oversight: `canonical.ts`'s own JCS `serialize()` already runtime-checks
172
+ * `typeof` for exactly this reason (its switch handles `"undefined"`/`"bigint"`/`"symbol"`/
173
+ * `"function"` despite `CJson`'s declared type not admitting any of them either), because the
174
+ * type system cannot fully describe this module's actual runtime domain. Once this sentinel
175
+ * reaches `params.ts`'s `commit()` — inside a plain object or array, same as any other frozen
176
+ * leaf — `canonicalBytes` hits that `"symbol"` case, throws `UnsupportedTypeError`, and `commit()`
177
+ * turns that into `paramsHashReason: "unsupported"` for the WHOLE params value, never a partial
178
+ * or per-field one: there is no such thing as "this one nested field is unsupported," only
179
+ * "this whole call's arguments are, or are not, representable."
180
+ *
181
+ * Exported for the same reason `freeze` itself is: not part of this adapter's semantic
182
+ * contract, but its own tests need to assert directly that a given leaf became this exact
183
+ * sentinel (by identity — nothing else can equal it) rather than inferring it indirectly.
184
+ */
185
+ export declare const FREEZE_UNSUPPORTED: Json;
186
+ /**
187
+ * A genuinely immutable, fully decoupled rebuild of `value` — the ONE, UNCONDITIONAL sanitizer
188
+ * every snapshot in this adapter goes through. Safe JSON-primitive leaves
189
+ * (`string`/`number`/`boolean`/`null`) pass through verbatim; plain objects and arrays are
190
+ * rebuilt fresh, recursively, by inspecting their REAL own property descriptors directly
191
+ * (`Object.getOwnPropertyDescriptor`), never by invoking anything the value itself controls (a
192
+ * getter, an iterator, a copy protocol, a Proxy trap, a `toString`/`valueOf`/`Symbol.toPrimitive`
193
+ * override); anything this function cannot represent becomes `UNSUPPORTED` (above) — never a
194
+ * string, never the live object.
195
+ *
196
+ * RELEASE-GATE CORRECTION (CRITICAL): this used to run ONLY as a fallback, after
197
+ * `structuredClone` had already been tried and had THROWN — the previous revision of this
198
+ * comment documented that carefully, but never asked whether `structuredClone` SUCCEEDING was
199
+ * itself a sufficient guarantee. It is not, on three counts, each reproduced directly before
200
+ * that fix: (1) a circular object clones successfully — `structuredClone` handles cycles
201
+ * natively — so this function never ran on it at all, and the circularity later reached
202
+ * `params.ts`'s own cycle-guard-less hash walk and crashed with `RangeError`; (2) a sparse
203
+ * array clones successfully too, bypassing this function's own densification; (3) a
204
+ * `SharedArrayBuffer` clones to a DISTINCT wrapper object sharing the SAME underlying memory —
205
+ * a "successful" clone that is not independent at all. Fixed by making this function the ONLY
206
+ * snapshot path, unconditionally — `structuredClone` is not called anywhere in this adapter.
207
+ *
208
+ * A SECOND release-gate pass then found that "pure introspection" was not fully true either —
209
+ * three more code-execution paths, all reproduced directly before this fix:
210
+ *
211
+ * 1. A `Proxy` is not inert under reflection. `Object.getPrototypeOf`, `Object.keys`
212
+ * (`[[OwnPropertyKeys]]` + a `[[GetOwnProperty]]` per key to check enumerability), and
213
+ * `Object.getOwnPropertyDescriptor` are each real, user-definable traps — reproduced
214
+ * directly: walking an ordinary handler-tracked Proxy through the OLD version of this
215
+ * function fired four separate traps before authorization was ever decided. `Array.isArray`
216
+ * is worse: called on a REVOKED Proxy, it throws `TypeError` outright (its spec algorithm,
217
+ * `IsArray`, unwraps `[[ProxyTarget]]`, which does not exist on a revoked handle) —
218
+ * reproduced directly. Fixed: `require("node:util").types.isProxy(value)` recognizes a
219
+ * Proxy — live OR revoked — via an internal engine slot, invoking NOTHING (verified
220
+ * directly: zero trap calls, no throw on a revoked handle either) — checked FIRST, before
221
+ * `Array.isArray` or any other reflection, and routed straight to `UNSUPPORTED`.
222
+ * 2. The bottom fallback used `String(value)` for anything not a plain object/array — a boxed
223
+ * primitive (`new Number(...)`) with a hostile `Symbol.toPrimitive`, or a `TypedArray` with
224
+ * a hostile own `toString`, each ran attacker code exactly once per snapshot, reproduced
225
+ * directly both ways — BEFORE `Guard.check` had decided allow or deny. The same is true, in
226
+ * principle, of ANY object-typed exotic value (a function's own `.toString` is just as
227
+ * overridable) — there is no way to distinguish "safe to stringify" from "hostile" by
228
+ * inspection alone, so none of them are stringified any more. Fixed: every value that is
229
+ * not a safe JSON primitive and not a plain object/array — a Proxy, a boxed primitive, a
230
+ * TypedArray/`ArrayBuffer`/`SharedArrayBuffer`/`DataView`, a `Map`/`Set`/`Date`/`RegExp`, a
231
+ * function, a `Symbol`, a `BigInt`, anything else — becomes `UNSUPPORTED` (never `String()`,
232
+ * never any other protocol) — see the confirmed-good note above: stringification itself
233
+ * was never unsafe as a RESULT (a `SharedArrayBuffer` never retained live memory as a
234
+ * string), the defect was invoking attacker-controlled code to PRODUCE that string before
235
+ * authorization ran, and `UNSUPPORTED` avoids that entirely rather than picking a "safer"
236
+ * string.
237
+ * 3. Any OTHER reflection failure — an exotic value this pass did not specifically anticipate,
238
+ * still throwing from `Object.getPrototypeOf`/`Object.keys`/`Object.getOwnPropertyDescriptor`
239
+ * despite the Proxy check above — must degrade the same way, not propagate an exception out
240
+ * of a snapshot taken before authorization. The whole reflective walk (everything past the
241
+ * Proxy/primitive fast paths) runs inside one `try`/`catch`; any throw there becomes
242
+ * `UNSUPPORTED` too.
243
+ *
244
+ * `active` is the PATH-ACTIVE cycle guard: the set of containers on the CURRENT recursion path,
245
+ * passed as a NEW `Set` at each recursive call rather than mutated in place and shared across
246
+ * sibling branches (an earlier revision DID share one mutable `WeakSet` across the whole call,
247
+ * which meant a DAG's repeated reference — the SAME object appearing twice as sibling values,
248
+ * never as its own ancestor — was wrongly flagged on its second occurrence; reproduced directly
249
+ * before that fix too). A genuine cycle's own leaf value is `UNSUPPORTED`, not a literal string
250
+ * `"<circular>"` — audited for the same collision class as `"<accessor>"` below, and it has the
251
+ * identical problem: a self-referential object and a plain object holding the literal string
252
+ * `"<circular>"` would otherwise produce the same commitment. There is no position-based reason
253
+ * a cycle's collision is any less real than an accessor's, so it gets the same fix.
254
+ *
255
+ * The property-descriptor walk ALSO closes a separate, protocol-driven gap: the previous
256
+ * revision used `Array.from`/`.map()` (which invoke `[Symbol.iterator]()` — a hostile array's
257
+ * own override can yield ANYTHING regardless of its real indexed properties; reproduced
258
+ * directly: `[1, , 3]` with a hostile iterator froze as `[999]`) and `Object.entries()` (which
259
+ * reads each property's VALUE directly, invoking a getter if one is defined there — reproduced
260
+ * directly: a getter with a side effect was observed three times across the old clone-attempt/
261
+ * freeze/body sequence, and the committed snapshot was the SECOND of three observations, not the
262
+ * first). `Object.getOwnPropertyDescriptor` and a `.length`-bounded index loop are pure
263
+ * introspection — they never invoke user code — and an accessor property (`.get`/`.set` present)
264
+ * becomes `UNSUPPORTED` rather than read at all: a getter can have arbitrary side effects, throw,
265
+ * or return something different on every call, so there is no single "correct" observation of it
266
+ * to commit, and (release-gate correction) the earlier `"<accessor>"` string sentinel this
267
+ * function used instead genuinely collided — reproduced directly: a real getter-bearing object
268
+ * and a plain object holding the literal string `"<accessor>"` produced the identical
269
+ * `authorizedParamsHash`. Both now degrade the same commitment to `unsupported` via `UNSUPPORTED`
270
+ * (see its own doc comment above), never a JSON-representable stand-in.
271
+ *
272
+ * Exported — not part of this adapter's semantic contract (it is an internal sanitizer, not a
273
+ * feature callers configure), but its own aliasing-safety invariant is worth a direct unit
274
+ * test in isolation, the same way every Python adapter's `_freeze()` is imported directly by
275
+ * its own tests: the audit log never exposes the raw snapshot value it produces (only its
276
+ * hash — see `params.ts`'s own doc comment), so "does this alias a live mutable object" is
277
+ * not otherwise observable from outside this module.
278
+ */
279
+ export declare function freeze(value: unknown, active?: ReadonlySet<unknown>): Json;
93
280
  /**
94
281
  * Wrap a callable so every call is authorized through `guard` first.
95
282
  *
@@ -1 +1 @@
1
- {"version":3,"file":"langgraph.d.ts","sourceRoot":"","sources":["../../../src/adapters/langgraph.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"langgraph.d.ts","sourceRoot":"","sources":["../../../src/adapters/langgraph.ts"],"names":[],"mappings":"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"}