Commit Graph
4 Commits
Author SHA1 Message Date
DongHyeonkaandClaude Opus 5 aa8ac35600 fix: make Browser RPC and Realtime own the physical work they report on
A server stream's registration was pruned on any settled close receipt.
`waitClosed()` rejecting, throwing synchronously, or not returning a
promise at all was absorbed into a fulfilled `undefined`, so the runtime
opened a second physical stream for the same operation while the first was
still running against the server. Only a fulfilled, contract-shaped
receipt confirms closure now; every negative receipt keeps the operation
DRAINING.

Cleanup also read foreign state outside the result boundary. A throwing
iterator `return` accessor replaced the already selected timeout with a
native `TypeError` and skipped the rest of the teardown, and the exported
lease decoder threw on a hostile `Symbol.asyncIterator`. Both reads move
inside their own boundaries, and the positive-close subscription is
installed before any fallible cleanup.

Composition validated the caller's registries before snapshotting them, so
a hostile accessor ran twice during validation, and rows hiding fields
behind a prototype or a non-enumerable key installed. Transport results
were checked for allowed own keys only, so own `{ok,message,encodedBytes}`
plus a prototype `injected` was a success and a missing `message` reached
a permissive schema as `undefined`.

In Realtime the tracked task was registered after the collaborator
returned. An authority that re-entered `close()` from inside its own
invocation saw an empty registry and got `{ok:true}` while its effect was
pending. The task is now registered first and the collaborator is invoked
a microtask later. A `scheduleTimeout` that threw was worse: the caller's
own catch treated it as an apply failure and started a recovery beside the
still-running effect, and `close()` rejected with a native `TypeError`. An
uninstallable deadline now fails closed as an expired one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 01:25:48 +09:00
DongHyeonkaandClaude Opus 5 bd90e0c983 fix: keep Browser RPC collaborator input and output inside the contract
RPC-RR-02. The server-stream path captured the generation fence outside its
protected boundary and raceWithin invoked clock.sleep outside a promise
boundary, so a synchronous throw from either escaped the Result contract and
skipped the listener and timer release. Both now run inside the boundary, and
release moved to finally.

RPC-RR-03. The runtime snapshotted its transports only after validating the
caller's raw objects, which ran their accessors first. It now decodes the
registry from own data descriptors before anything reads it — refusing an
accessor without invoking it and rejecting extra, inherited and symbol-keyed
fields — and validates that snapshot. Every installed binding registry is a
read facade over a private store instead of a frozen Map whose set, delete and
clear still worked.

RPC-RR-04. Transport results and stream frames are decoded per union variant
from own data descriptors into new frozen values. A throwing getter, an
inherited or extra field, a symbol key, an unknown failure code and an
out-of-range retryAfterMs all close as protocol failures instead of escaping.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 14:06:00 +09:00
DongHyeonkaandClaude Opus 5 8f67974f68 fix: install bounded Browser RPC stream leases
R-04: install the RPC contract bindings as exact immutable snapshots. Registry
and row data are copied from own data descriptors into frozen null-prototype
maps before validation, so a getter is never invoked, extra and symbol keys and
malformed descriptors are composition-time TypeErrors, and the runtime reads
only the snapshot. A post-validation mutation can no longer change replay
policy, deadlines, byte ceilings or transport selection.

R-01: bound transport stream cleanup. The generation is fenced and listeners
released immediately, and iterator.return() is awaited only within a cleanup
bound, so a non-cooperative iterator cannot keep the application generator, its
listeners or the total deadline alive. Unresolved cleanup stays observed.

R-05: reject oversized WebSocket text frames before allocating an encoded copy
and count UTF-8 bytes incrementally with an early exit, matching TextEncoder for
surrogate pairs and lone surrogates.

R-06: canonicalise clock and generation-fence failures into the closed Result
taxonomy instead of letting them escape as native rejections, with listener and
timer cleanup on every exit path.

Browser RPC remains AVAILABLE_NOT_COMPOSED; R-07 transport evidence is still
required before composition.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 00:00:15 +09:00
donghyeon-ka 6c52cdb916 feat: 기능 추가 과정중 2026-07-30 15:58:20 +09:00