fix(dev): bound route-invocation render history by count and bytes end to end - #699
fix(dev): bound route-invocation render history by count and bytes end to end#699ScriptedAlchemy wants to merge 6 commits into
Conversation
🦋 Changeset detectedLatest commit: a7f3b53 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Producer, IPC reply, live replay, completed envelope, history, and the Workbench buffer share RENDER_EVENT_RETENTION (256 events, 1 MiB JSON). Envelopes report evictedEvents; cancellation keeps the latest document after eviction; SSE replay is paced by drain instead of the live queue.
…om summaries, count only live arrivals during replay
3bb5240 to
a7f3b53
Compare
…ion, pinned-newest module test, history ring, HTTP full-window replay
|
Closing as a duplicate of #715, which also fixes #681. Both PRs bound producer, IPC, replay, completed/cancelled/failed envelopes and history by event count and bytes and pin the final outcome/correlation. #715 survives because it goes further on the producer side: the child no longer carries any Ported from this PR to #715 (a643d08): the real-compiled-child |
…ion, pinned-newest module test, history ring, HTTP full-window replay
…ion, pinned-newest module test, history ring, HTTP full-window replay
…d to end (#715) One render-history window (`route-invocation-render-history.ts`: at most 256 events and 2 MiB serialized, always keeping the newest event and the newest document-bearing event) bounds the child/IPC producer, the service replay buffer, completed/cancelled/failed envelopes, GET reads, history, the `final` message, the Workbench live window, and runtime-run envelopes. Envelopes whose events were evicted carry `retention`; the stream sends `truncated` once; `document`, `result`, `outcome`, and `correlationId` are never truncated. Stream replay is paced by socket drain. Compiled routes' progress reports reach the Workbench as `progress` render events. Survivor of the #699 / #715 duplicate; #699's unique tests (compiled-child reconnect and cancel after eviction, pinned-newest, history ring, HTTP full-window replay) are ported here. Closes #681
Closes #681
What
One retention policy for an invocation's render history, applied everywhere it is kept:
RENDER_EVENT_RETENTION(maxEvents: 256,maxBytes: 1 MiBof JSON) andretainRenderEvent/retainRenderEventslive inroute-invocation-result.ts, re-exported fromcontracts/invocations.tsfor the Workbench. Oldest-first eviction; the newest event is never evicted, so a completed stream always keeps itscompleteevent whatever its size.document,result,outcome, and correlation identifiers sit outside the window and are never truncated (no plugin-result limit is introduced).renderCompiled) retains in place while streaming and reportsevictedEvents.unit-rendersurfaces.RetainedRenderEventswindow pluslatestDocumenttracked outside it; the completed envelope re-applies the bound and carriesevictedEvents(absent when nothing was evicted). Cancelled and failed runs keep the retained window and the latest document, even after the event that carried it was evicted.invocationSummaryomitsevictedEvents.#stream) is paced by socket drain instead of being burst into the live-consumer queue — a reconnect whose retained window exceeded the 256 KiB / 128-record queue limits previously destroyed the response (TypeError: terminated), which made the reconnect-after-eviction acceptance impossible. Live frames arriving behind a draining replay are held to the same 256 KiB / 128-record bound on the current backlog.state.retained), the client schema acceptsevictedEvents, and the raw view says how many events were evicted.Replay order is now:
truncatedmarker (if any), the retained render window, kernel trace messages,final. No consumer depended on trace/render interleaving.Tests
tests/route-invocation-retention.test.ts(unit, no sleeps): count-bound with outcome/correlation kept and history ring eviction; byte-bound with 128 KiB snapshots; the newest event survives at any size; failed run keeps retained history and summaries omitevictedEvents; a full 256-render replay served throughRouteInvocationRoutesover HTTP; reconnect after eviction replaystruncated+ bounded window with nofinal, then cancellation keepsevictedEvents, correlation, and the latest document after the shell was evicted.tests/route-invocation-dev-server.test.ts: new case through a real compiled CLI child (cli:flood, 300 Suspense boundaries settling one per macrotask). Completed envelope,GETread, and post-completion stream replay are bounded by bytes (well under 256 events) and agree onevictedEvents; the final document andresultare intact. A gated run: live stream past eviction, reconnect replaystruncated+ window only, cancel behind the gate yieldscancelledwith nooutcome, bounded events with the shell evicted,evictedEvents, and the latest document.packages/workbench/tests/invocation-model.test.ts: count eviction reportsretained.evicted; byte eviction with 128 KiB snapshots.Local gate (branch contains
origin/main@ 5fc8de3; final tree 3bb5240)One earlier full-suite run hit a load-induced 30 s timeout in an unrelated child-spawning test (
install.test.ts, thenroute-invocation-service.test.ts"resolves a.jsimport"); both pass standalone and the final full run above is clean.Deslop: Claude, 3 edits (dropped the redundant
eventsalias from the Workbench running state; early return in the childrender; replacedfindLastwith the retained tail).Coordination
#686 owns streaming; retention is settled here — see the comments on #681 and #686. Any chunked Flight delivery should feed
publishRenderone event at a time and reuse this primitive; the SSE route keeps its live-consumer limits.Self-review
Reviewer:
gpt-5.6-sol-max(change-risk-reviewer), three passes.invocationSummaryleakedevictedEventsinto strict Workbench list decoding — fixed + test.JSON.stringify(...).lengthis UTF-16 code units, not UTF-8 bytes — dismissed: the bound protects the dev server's heap, and V8 stores non-Latin1 strings two-byte, so code units track resident size more closely than UTF-8; the unit is stated on the constant.retainedHistory(record)shared by cancelled and failed paths) + test, docs, changeset.unit-renderstill collects the full stream in the harness before the IPC bound — dismissed:renderRouteEventsis the public test-harness contract and returns every event by design; IPC is bounded here and the runtime caps a stream at 10 000 events.Pass 3: no remaining merge risks.