Commit Graph
2 Commits
Author SHA1 Message Date
DongHyeonkaandClaude Opus 5 df18349682 fix: validate the snapshot that installs, not the object that was shown
Three trust boundaries checked a caller's object and then read it again to
use it. Between those two reads an accessor or a Proxy can answer
differently, so the value that passed validation and the value that was
installed were not the same value.

A credential owner's answer was read field by field outside the auth
boundary: a throwing `kind` getter escaped into the transport catch and an
auth outage reached operators as `NETWORK_FAILURE`. Contract composition
validated a contribution and then copied it, so a policy that answered
10,000 to the ceiling check and 999,999 to the copy installed the second
value. The cursor runtime validated its profile once and re-read it on
every page, so raising `maxPages` after construction widened a cap that
had already been checked.

`src/contracts/exact-snapshot.ts` is the one descriptor-based decoder they
now share: every property is read exactly once, an accessor, a symbol, an
inherited or non-enumerable field and a throwing trap all resolve to a
typed failure, and validation runs on the owned copy.

Separately, the `responseBody: NONE` probe awaited a bare `read()`. The
deadline produced a bounded public result while the raw reader kept its
lease, so the body stayed locked and the outer compensator could not
cancel it. The probe now takes the operation lifetime and owns the cancel
and the lock release itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 01:25:12 +09:00
DongHyeonkaandClaude Opus 5 f4bfdf0365 fix: close the live V3 authority findings from the adapter re-review
LIVE-01. A credential collaborator that returns UNAVAILABLE, throws, rejects
or answers off-contract is an outage of the auth integration, not evidence
about the user's session. Each of those now closes as AUTH_INTEGRATION_FAILURE
with zero fetches, so the composition root's logout path stays reserved for a
genuinely absent session. The synchronous and asynchronous failure sites share
one classifier.

LIVE-02 / LIVE-03. Object.freeze(new Map(...)) freezes the wrapper, not the
backing store, so an exported registry could still be cleared or replaced after
composition. Both the installed REST auth profile registry and the composed
HTTP/event lookups are now read facades over private stores, and every composed
row is an exact own-data snapshot that rejects accessors, inherited and
symbol-keyed fields.

LIVE-04. The total deadline now bounds the physical waits rather than being
checked between them: dispatch and response admission race the attempt signal,
the bounded reader takes that signal, and an abandoned operation is still
observed once so a late native rejection cannot surface unhandled. A body that
completes after the deadline or the caller owns the execution is no longer
admitted; a stale generation keeps its more specific SCOPE_FENCED verdict.

LIVE-05. DEADLINE is no longer treated as a caller-owned cancellation, so a
timeout reaches api.request.failed exactly once while caller, route, scope and
shutdown aborts stay excluded.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 13:43:59 +09:00