Skip to content

test(signals): reader seat — the slot table and shadow differential as a CI spec (S0+M1, byte-identical) - #4438

Merged
ryansolid merged 11 commits into
nextfrom
seat/spec
Oct 30, 2026
Merged

ryansolid merged 11 commits into
nextfrom
seat/spec

Conversation

@ryansolid

Copy link
Copy Markdown
Member

S0 + M1 of documentation/plans/reader-seat-rebuild.md, landed on their own as a CI spec. The S1 swap that was to follow (#4430) is stopped — the record and the decision are the design doc's new §8 "S1 result and decision (2026-10-29)", which ships in this PR. Supersedes #4395 (the original S0 draft, before the M1 re-baseline).

What it is

  • The slot table as an executable spec. packages/signals/src/core/seat.ts (test-only, __TEST__) computes the reader seat — {world, lane, ahead, posture} per pass — and the slot table from the brief: for every read, which world the reader sees (screen C, staging P, lane Lv/Dv, truth T, NR, E) and which marks it raises, each row carrying the rule id it is derived from (design doc §2.3, §6).
  • The shadow differential. Under __TEST__ every read, verdictValue, laneRead and every recompute head/tail runs the table beside today's deciders (frameRead, readerSeesCommitted, serve, laneRead, verdictRead, route, the born-held arm, …) and compares slot, marks, seating and born-held decision; a disagreement is recorded as a mismatch class (kind · row · expected → actual · detail). Nothing in the production path changes behaviour: every call is if (__TEST__)-guarded.
  • INV-seat (invariants.ts, INTERNALS-ASYNC-STATE.md): INV-seat-1..3 — a leaf never raises its world beyond its reads (amended per D-27 for the born-node read), posture is never inherited, the shadow agrees with the deciders.
  • Harness. tests/setup/seat-shadow-report.ts (vitest setup, on when SEAT_SHADOW_REPORT=<file> is set; SEAT_SHADOW_STRICT=1 throws at the first disagreement), scripts/seat-shadow-summary.mjs (aggregates to a mismatch table), shadowKnown (the filed next bugs carried by name), and SIGNALS_TEST_DEFINE=true pnpm --filter @solidjs/signals build to emit a dev dist with __TEST__ on for the solid/web/generator runs (design doc §3.4). The default build is unchanged.

How to run

cd packages/signals
SEAT_SHADOW_REPORT=/tmp/seat-signals.jsonl pnpm vitest run
node scripts/seat-shadow-summary.mjs /tmp/seat-signals.jsonl [--rows]

For solid / web / the generator, build the signals dist with SIGNALS_TEST_DEFINE=true first and set SEAT_SHADOW_REPORT the same way (§3.4); rebuild plain (pnpm build) before measuring size.

Results

Re-run on this branch (head e300252's tree, verified identical to seat/s1-swap @ 4c413e1):

run reads compared passes seated mismatch classes
signals suite (393 files, 5,994 passed · 7 expected fail · 2 skipped) 965,367 242,301 0

M1's full table (design doc §4.3, measured at 4c413e1): signals 0 · visibility oracle 0 · solid 0 · web 0 · generator 2,000 × seeds 7 / 3289 / 91501 (every pair) 0 — zero mismatch classes everywhere besides the four shadowKnown entries, each a filed bug on next with a generator replay in its issue: #4426 (V1' P→C), #4427 (V6 Lv→P), #4428 (B2 born held via the owner's stale flag), #4429 (Vl4 probing leak). The 50k × 3 S0 run and its residue are in §4.2.

Byte-identity (pnpm build, default __TEST__ false; scripts/size of this branch against a clean origin/next @ 5e68925 build, same native compiler): 22 / 22 scenarios identical in both brotli and minified bytes (core floor 7,415 / 19,908; + isPending/latest 11,026 / 30,408; compiled floor 9,961 / 27,271; server floor 1,275 / 3,255; …). packages/{solid,web}/dist diff -rq empty; packages/signals/dist/{prod,observe} differ only in statement braces and import order left where __TEST__ blocks were removed (the minifier erases both — the scenario bundles are the proof). Added dist/types/core/seat.d.ts is types only.

Why it lands alone

S1 (the swap, #4430) was built through M5 and stopped on its gate: the table replaces the read/seating deciders (≈ −540 B minified in core.js) but S1 is additive overall — +294 B brotli on the core floor, +1,061 B on + isPending/latest over rc.20 — because the growth lives in the seam/reveal/hold layer the table does not touch, and the 50k differential found two regressions there (3289 #39609 at M3a, 7 #45702 at M3b) plus one class of 20 awaiting a ruling. Design doc §8 has the size table, the deletions, the inventory with milestone attribution, the method lessons (2k generator cases are not evidence for a ruling; size is measured before design) and the salvage list. §5 is marked stopped; the brief's §4 is superseded by §8.

What this PR keeps is the part that proved itself: a spec of today's read decision that CI can run against the deciders, with 0 classes, at 0 bytes.

Commits

Cherry-picked from seat/s1-swap — S0 (3609040, 40208a0, b71aafe, 4c6a2ee, 7718526, 697217f) and M1 (4c413e1) — onto next; the resulting tree is identical to 4c413e1 (git diff empty). Plus the §8 doc commit. Changesets: .changeset/reader-seat-shadow.md, .changeset/reader-seat-s1-rebaseline.md (both @solidjs/signals: patch, test-only).

Public API changes

None.

ryansolid and others added 7 commits October 29, 2026 21:25
… under __TEST__, differential, then swap

Co-authored-by: Cursor <[email protected]>
…r __TEST__ beside the existing deciders

core/seat.ts computes the reader seat (world/lane/ahead/posture) and the
slot table (U/F/L/D/V/Vl/Vp/A/B rows) beside today's deciders; under
__TEST__ every read and every recompute compares the two and records
disagreements (INV-seat-1..3) into a registry (SEAT_SHADOW_REPORT /
__SEAT_SHADOW_STRICT__). Nothing reaches a build: every statement is
behind __TEST__ (false in every rollup define); SIGNALS_TEST_DEFINE=true
builds a dev dist with it on for the solid/web/generator differential.

Design doc §3/§6/§7 updated with the harness as built, the table rows
found wrong against a rule (fixed in seat.ts), and the two-readings
items the differential leaves open.

Co-authored-by: Claude via Cursor <[email protected]>
…d published-inputs table rows, results in the design doc

The 50,000-case generator runs (seeds 3289, 91501, 7, every pair) found two
table rows wrong against a rule, fixed in seat.ts (test-only):

- L4 / L2 / V1' (D-41): a held node's staged error throws for a leaf or a
  born-into frame reader; the rows said P.
- V3 (F-9, #3305): a verdict reader's plain read of a node staged and in
  flight with INPUTS_PUBLISHED is the flight's (V4', NR), not the staging.

Design doc §4.2 records the differential results (suites, 2k, 50k) and §6
the two rows; RULES-INDEX regenerated for the new #3305 citation. Build
output unchanged (size harness: 15/15 bundling scenarios byte-identical).

Co-authored-by: Claude via Cursor <[email protected]>
Co-authored-by: Cursor <[email protected]>
…ost-fix re-run, plain-build abort indices, untraced residue

The 50k row counted seed 3289's classes (133) against the three-seed
reads/passes/instances; the aggregate is 220 classes / 8,821 instances
before the two 50k table fixes and 209 / 8,706 re-run with the table as
committed (the 11 classes the fixes cover go, none appear). The infinite
loop abort on each seed is verified against a dist built without
SIGNALS_TEST_DEFINE, at the same case index on both builds (7: 1158,
3289: 3963, 91501: 5807). Classes no §6 finding or §7 item names
literally (623 instances / 43 classes) are listed and left untraced.

Co-authored-by: Claude via Cursor <[email protected]>
…is the hold's own work, not a persisted posture; swap pins corrected

Co-authored-by: Cursor <[email protected]>
…t @ 5e68925

Table fixes (design doc §6.1): V1'' staged error → E (D-41); the verdict seat
is the hold the pass's own node's verdict lane names (vhold); the branch world
(creatorsWorld) walks the owner chain of running passes (#4341); ahead only
from a lane or verdict seat; verdict reads leave marks and do not raise;
Vl4' (latest of a node staged and in flight serves the staging); D8 needs a
value (u f is NR); lane rows for a boundary's structure are a derivation's
(A15/#4283 — today is right, renderLeaf is EFFECT_RENDER); fallback's
read(b._fb) brackets the seat (shadowSeatSave/Restore, #4337); INV-seat-1
amended per D-27 (a leaf that read a born node or the hold's own staging is
in the hold's world).

shadowKnown allow-list of next bugs the swap fixes by construction, each
filed with a generator replay: #4426 (V1' P→C), #4427 (V6 Lv→P), #4428 (B2
born held via the owner-context creator), #4429 (probing leaks into a pulled
pass; Vl4 world). §7 F-transitive reopened: today is a third reading;
one-level and transitive each differ from it in disjoint born-held shapes.

Differential: signals 393 files / solid / web / visibility oracle / generator
2k × {7, 3289, 91501} — zero classes besides the known entries; zero screen
differences against next recordings. Build output byte-identical to next
(scripts/size: every scenario equal in brotli and minified).

Co-authored-by: Claude via Cursor <[email protected]>
@changeset-bot

changeset-bot Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a4794c4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 15 packages
Name Type
@solidjs/signals Patch
test-integration Patch
@solidjs/web Patch
@solidjs/babel-plugin Patch
@solidjs/compiler Patch
@solidjs/diagnostics Patch
@solidjs/element Patch
@solidjs/h Patch
@solidjs/html Patch
solid-js Patch
@solidjs/universal Patch
board-example Patch
canvas-example Patch
server-dbmon-example Patch
todos-server-example Patch

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

…-29): the swap is stopped, S0+M1 land alone as the spec

Appends §8 with the measured record from #4430: the four-column size table
and the per-module floor breakdown after the levers; the deletions proven,
not subsumed and undecidable; the 50k differential inventory with
milestone attribution (20 of one D-37 class at M3b, two next bugs closed,
two regressions — 3289 #39609 at M3a, 7 #45702 at M3b); the decision (the
table replaces the deciders, ≈ −540 B in core.js, but S1 is additive —
+294 B brotli on the core floor, +1,061 B on + isPending/latest — because
the growth lives in the seam/reveal/hold layer the table does not touch);
method lessons (2k is not evidence for a ruling; size is measured before
design); what is salvaged and what the next decision is. §5 marked
stopped; the brief's §4 is superseded by §8.

Co-authored-by: Claude via Cursor <[email protected]>
Co-authored-by: Cursor <[email protected]>
@github-actions

Copy link
Copy Markdown

Size (brotli, eager entry chunk)

scenario head vs base minified vs base minified vs recorded cap lazy chunks (not counted)
signals: core floor (createSignal/Memo/Effect/Root/flush) 7.42 KB 0 B 0 B +1 B 7.43 KB ✅
signals: + createStore 14.88 KB 0 B 0 B +1 B 14.96 KB ✅
signals: + isPending/latest 11.03 KB 0 B 0 B +1 B 11.05 KB ✅
signals: + until/refresh (truth readers, no optimism) 8.04 KB 0 B 0 B +1 B 8.06 KB ✅
app: render + one signal (the simple-app floor) 9.77 KB 0 B 0 B +1 B 9.79 KB ✅
app: hydrating (no stores) with Show/For/Loading/Errored/lazy 18.71 KB 0 B 0 B +1 B 18.72 KB ✅ lazy-page.js 0.04 KB
app: hydrating + every store primitive family 32.41 KB 0 B 0 B +1 B 32.42 KB ✅ lazy-page.js 0.04 KB
app: CSR with Show/For/Loading/Errored/lazy 13.42 KB 0 B 0 B +1 B 13.43 KB ✅ lazy-page.js 0.04 KB
app: CSR, observe tier (same app on the observe artifacts) 15.19 KB 0 B 0 B +1 B 15.20 KB ✅ lazy-page.js 0.04 KB
app: CSR, observe tier + attribution engine enabled 29.73 KB 0 B 0 B +1 B 29.79 KB ✅ lazy-page.js 0.04 KB
app: compiled floor (one template, one text hole, one delegated click) 9.96 KB 0 B 0 B +1 B 9.98 KB ✅
app: compiled CSR (JSX todo app: spread/merge/omit, events, class/style, keyed For, Show, Loading + lazy, store) 26.26 KB 0 B 0 B +1 B 26.31 KB ✅ stats.js 0.18 KB
app: compiled hydrating (the same JSX todo app through hydrate(), compiled hydratable) 32.46 KB 0 B 0 B +1 B 32.46 KB ⚠️ over by 3 B, 19 B minified headroom stats.js 0.20 KB
frames: eager client consumer (frames client + transport, lazy codec) 11.75 KB 0 B 0 B 0 B 11.77 KB ✅
page: base server components (hydrating + dynamic + frames + sf reference) 33.66 KB 0 B 0 B +1 B 33.68 KB ✅ assets.js 0.78 KB, decode.js 7.09 KB, lazy-page.js 0.04 KB, regions.js 1.06 KB, trace.js 9.03 KB, wire.js 3.47 KB
page: live server components (base + live/GET + action + isPending/latest) 38.70 KB 0 B 0 B +1 B 38.75 KB ✅ assets.js 0.78 KB, decode.js 7.09 KB, lazy-page.js 0.04 KB, regions.js 1.06 KB, trace.js 9.03 KB, wire.js 3.47 KB
page: compiled base server components (the base page as JSX: templates with class/style/attributes/events, For/Show; no spread) 35.66 KB 0 B 0 B +1 B 35.72 KB ✅ assets.js 0.78 KB, decode.js 7.09 KB, regions.js 1.06 KB, sc-comments.js 0.20 KB, trace.js 8.99 KB, wire.js 3.47 KB
page: compiled live server components (the compiled base page + live/GET + action + isPending/latest) 42.65 KB 0 B 0 B +1 B 42.65 KB ⚠️ over by 4 B, 19 B minified headroom eager (counted): web.js 22.77 KB; assets.js 0.78 KB, decode.js 7.09 KB, regions.js 1.07 KB, sc-comments.js 0.19 KB, trace.js 9.07 KB, wire.js 3.47 KB
page: base + router (base page + @solidjs/router: createRouter, two routes, preload, useNavigate) 41.17 KB 0 B 0 B +1 B 41.16 KB ⚠️ over by 7 B, 19 B minified headroom assets.js 0.78 KB, decode.js 7.09 KB, lazy-page.js 0.04 KB, regions.js 1.06 KB, server.js 1.03 KB, serverForms.js 3.57 KB, trace.js 9.02 KB, wire.js 3.48 KB
page: live + router (live page + @solidjs/router: createRouter, two routes, preload, useNavigate) 48.31 KB 0 B 0 B +1 B 48.31 KB ✅ eager (counted): client.js 27.89 KB; assets.js 0.78 KB, decode.js 7.09 KB, lazy-page.js 0.04 KB, regions.js 1.07 KB, server.js 1.03 KB, serverForms.js 3.30 KB, trace.js 9.00 KB, wire.js 3.49 KB
server: floor (getRequestEvent + isServer) 1.27 KB 0 B 0 B 0 B 1.29 KB ✅
server: renderToString (the server-render floor) 22.79 KB 0 B 0 B 0 B 22.80 KB ✅

⚠️ Over the brotli cap within the minified allowance (passes)

  • app: compiled hydrating (the same JSX todo app through hydrate(), compiled hydratable): over brotli cap by 3 B; minified 100,730 B vs 100,729 B recorded with the cap (+1 B) — 19 B of the 20 B minified allowance left; +0 B minified over this PR's base
  • page: compiled live server components (the compiled base page + live/GET + action + isPending/latest): over brotli cap by 4 B; minified 125,267 B vs 125,266 B recorded with the cap (+1 B) — 19 B of the 20 B minified allowance left; +0 B minified over this PR's base
  • page: base + router (base page + @solidjs/router: createRouter, two routes, preload, useNavigate): over brotli cap by 7 B; minified 125,417 B vs 125,416 B recorded with the cap (+1 B) — 19 B of the 20 B minified allowance left; +0 B minified over this PR's base

Bundled with Rolldown (what Vite ships), brotli q11, decimal KB. A scenario fails only when it is over its brotli cap and its minified size is more than 20 B over the minified recorded with the cap; over the cap within that allowance is brotli layout noise and passes with a warning. Caps and their recorded minified in scripts/size/scenarios.js; the floor and page caps in floor-caps.json are frozen (lower only, or Size-Exception: in the PR body). npm run ratchet lowers caps per RC; it never raises one (scripts/size/README.md).

@coveralls

coveralls commented Oct 30, 2026 •

Copy link
Copy Markdown

Coverage Report for CI Build 41019788916

Coverage remained the same at 78.259%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 1359
Covered Lines: 1125
Line Coverage: 82.78%
Relevant Branches: 1088
Covered Branches: 790
Branch Coverage: 72.61%
Branches in Coverage %: Yes
Coverage Strength: 30.01 hits per line

💛 - Coveralls

@codspeed

codspeed Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Merging this PR will regress 2 benchmarks

2 warnings

⚠️ 2 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

⚠️ Base and head ran on different runner types

Some benchmarks with significant performance changes were compared across different runner types, so the changes might not come from the code. Run base and head on the same runner type, or use CodSpeed Macro Runners for more stable results.

Learn more about Macro Runners

Open the report in CodSpeed to investigate

⚡ 2 improved benchmarks
❌ 2 regressed benchmarks
✅ 196 untouched benchmarks
⏩ 5 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
❌ projection derive: write one NESTED field (reference) 2.1 ms 2.4 ms -11.9%
❌ memo + sync render effect only (reference) 30.1 ms 31.8 ms -5.54%
⚡ createStore setter: delete + set one root key (#3044 overlay) 2.1 ms 1.8 ms +16.35%
⚡ arrow in template 12 ms 11 ms +9.39%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing seat/spec (a4794c4) with next (5e68925)

Open in CodSpeed

Footnotes

  1. 5 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

… __TEST__ shadow puts it past 5 s under coverage
@ryansolid
ryansolid merged commit e56a4a0 into next Oct 30, 2026
8 of 9 checks passed
ryansolid added a commit that referenced this pull request Oct 30, 2026
… at its stop (#4448)

Follow-ups from #4438's review.

- verify-release-artifacts.mjs (run by release.mjs before anything is
  published) fails when a fixed package's tarball carries
  `__SEAT_SHADOW__`, the mark a SIGNALS_TEST_DEFINE=true build leaves in
  @solidjs/signals' dist/dev-shared.js. A shadow build is a test build
  (rollup.config.js: "never for measuring or publishing").
- reader-seat-brief.md §4 says the swap stopped and points at
  reader-seat-rebuild.md §8, which supersedes it.


Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

Co-authored-by: Claude <[email protected]>
ryansolid pushed a commit that referenced this pull request Oct 30, 2026
Resolves verdict.ts beside #4438's shadow hook (the window's state is
restored before the shadow's decision point) and the generated
RULES-INDEX.md (regenerated).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
ryansolid pushed a commit that referenced this pull request Oct 30, 2026
…ask's flush

The reader-seat table (#4438) predicted NotReady (row D3) for a memo with
no value reading a first answer staged in the flush that re-asks it; under
D-22 (amended 2026-10-30) the memo derives from it once. Row D3' predicts
the staging (P), and `read` records the slot it serves. __TEST__ only:
dist is unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
ryansolid pushed a commit that referenced this pull request Oct 30, 2026
ryansolid added a commit that referenced this pull request Oct 30, 2026
…s answered, then re-asked (D-22, F-9) (#4459)

* fix(signals): a first answer landing in the tick a write re-asks it is answered, then re-asked (D-22, F-9)

An async memo's first answer resolves, and in the same synchronous turn a
plain write re-asks it; the promise reaction runs after the write, so the
landing and the re-ask are processed in one flush. The runtime treated the
answer as never produced:

- `asyncWrite` dropped it outright when the write's notification had marked
  the node dirty (a stale-answer guard meant for re-asks).
- Otherwise it landed staged, the re-ask left the node pending and
  uninitialized, and `isPending` gave it no verdict (`false` from the
  flush's probe, never re-run), while an identity memo or a derived store
  over it never derived from it (`latest` of either threw: [ROOT-L]).

Now a first answer always lands; `isPending` of a node answered and
re-asked in one flush is the re-ask's verdict (true); and a memo with no
value of its own that reads it derives from the answer once, then re-runs
on the missed-wake latch and waits on the re-ask holding it (a verdict
engine hook, so the hello-world floor pays only the call). Amends D-22's
2026-10-28 clause per the 2026-10-30 ruling: a plain write re-asking the
memo carries the answer like an action's.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

* fix(signals): a first answer landing in the tick a write re-asks it is answered, then re-asked (D-22, F-9)

An async memo's first answer resolves, and in the same synchronous turn a
plain write re-asks it; the promise reaction runs after the write, so the
landing and the re-ask are processed in one flush. The runtime treated the
answer as never produced:

- `asyncWrite` dropped it outright when the write's notification had marked
  the node dirty (a stale-answer guard meant for re-asks).
- Otherwise it landed staged, the re-ask left the node pending and
  uninitialized, and `isPending` gave it no verdict (`false` from the
  flush's probe, never re-run), while an identity memo or a derived store
  over it never derived from it (`latest` of either threw: [ROOT-L]).

Now a first answer always lands; `isPending` of a node answered and
re-asked in one flush is the re-ask's verdict (true); and a memo with no
value of its own that reads it derives from the answer once, then re-runs
on the missed-wake latch and waits on the re-ask holding it (in `read`'s
pending path, behind the verdict engine's hook so the floor folds it).
Amends D-22's 2026-10-28 clause per the 2026-10-30 ruling: a plain write re-asking the
memo carries the answer like an action's.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

* test(signals): the seat shadow seats a first answer served in its re-ask's flush

The reader-seat table (#4438) predicted NotReady (row D3) for a memo with
no value reading a first answer staged in the flush that re-asks it; under
D-22 (amended 2026-10-30) the memo derives from it once. Row D3' predicts
the staging (P), and `read` records the slot it serves. __TEST__ only:
dist is unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

* chore(size): raise caps for the same-tick first answer

isPending/latest +37 B brotli (+151 B minified) and the compiled live
page: the first-answer serve in `read`'s pending path and the
`isPending` verdict for it. The observe-attribution and compiled
scenarios carry the core half (+10 B minified on every floor; the rest
is brotli layout). Caps at measured + 10 B rounded up to 0.01 KB;
recorded minified = measured.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

* chore(size): raise the live page's frozen floor for the same-tick first answer

38.76 KB -> 38.79 KB, recorded minified 117,227 -> 117,379 B, measured
at 38,778 B brotli. Size-Exception in the PR body.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

* fix(signals): serve an answered-then-reasked first answer only to a reader waiting on it, off lanes, at any depth

read()'s D-22 branch served the staged first answer to a reader that did
not wait on it (a first pass: the seat table's D3 row predicts NR) and to
a verdict lane's pass (V6: D-37, a lane sees the screen plus its own
guesses). It now requires passLane === null and that the reader wait on
the source a read of the node parks on (_error.source): the node itself
one level down, the async it derives from at any depth, so
identity(identity(m)) reads as m does (E2 at depth 2). The seat's D3'
row says the same (!inSeat, the parked-on source, no staging of its own).

handleAsync's answered-then-reasked guard is the verdict engine's
(holdWorkHook): plain-only bundles keep the plain return.

D-23 exception 1: an answered-then-reasked node is not a first load.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

* fix(signals): type the parked-on source read in read() and the D3' row

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

* chore(size): re-record #4459's caps over next @ 131ceb8

isPending/latest, compiled live SC and live + router grow with the
verdict-engine branch (+159..+161 B minified); no frozen floor moves:
plain-only bundles are unchanged in minified bytes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

* chore(size): re-record the live page floor's minified for #4459

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

* fix(signals): serve an answered-then-reasked first answer one pass a flush, out of line

read()'s D-22 arm served a reader whose pass then threw anyway (a read
beside the served one parked on the same source): the throw re-parked it
on that source, the missed-wake latch re-ran it, and the re-run qualified
again — a synchronous loop within one flush (async-gen 91501 #749,
minimized: a boundary's children memo over two Shows that both park on
the re-asked memo). The arm now stamps the served pass's clock on the
reader (`_x._served`) and serves a reader once a flush; the re-run throws
and waits. The seat's D3' row says the same.

The arm moves out of read() into `servesStaged`, called behind
`observeFlightHook` on the pending-throw path, so read() itself grows by
a call (bytecode 1318 -> 1170; next 1139).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

* fix(signals): an answered-then-reasked first answer reaches a reader its landing released

servesStaged served a reader with no value only while it still waited on
the source. When the memo's input is read through latest() or a sync
memo, the write's re-ask reaches the memo after its landing ran, and the
landing had already released the identity memo's wait: it ran with no
value, waiting on nothing, and threw, so latest(identity(m)) and a derived
store stayed on [ROOT-L] while latest(m) showed the answer (async-gen E2/E3
3289 #310, 91501 #1993, #2609). D-22 as amended says a derivation with no
value of its own reads like the memo at any depth; which the flush ran
first is not the reader's. A reader that is not pending is served too
(one served pass a flush, as before); one pending on another source is
not. The seat's D3' row says the same.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

* chore(size): re-measure the answered-then-reasked serve over next @ 230c670

Live server components 38.85 -> 38.96 KB (117,747; Size-Exception in the PR
body), isPending/latest 11.17 -> 11.26 KB (31,002), compiled live
42.79 -> 42.85 KB (125,887); live + router re-records minified (143,150).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

* chore(size): re-measure the answered-then-reasked serve over next @ d0fc285

Takes next's ledger (recorded after #4471/#4479) and re-records only the
scenarios this PR moves: isPending/latest 11.19 -> 11.28 KB (31,053), live
server components 38.91 -> 38.96 KB (117,793; Size-Exception in the PR body),
compiled live 42.83 -> 42.87 KB (125,938), live + router 48.51 -> 48.58 KB
(143,201).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

---------

Co-authored-by: Claude <[email protected]>
ryansolid added a commit that referenced this pull request Oct 30, 2026
…des the write's flush (#4495) (#4499)

* fix(signals): a first answer landing in the tick a write re-asks it is answered, then re-asked (D-22, F-9)

An async memo's first answer resolves, and in the same synchronous turn a
plain write re-asks it; the promise reaction runs after the write, so the
landing and the re-ask are processed in one flush. The runtime treated the
answer as never produced:

- `asyncWrite` dropped it outright when the write's notification had marked
  the node dirty (a stale-answer guard meant for re-asks).
- Otherwise it landed staged, the re-ask left the node pending and
  uninitialized, and `isPending` gave it no verdict (`false` from the
  flush's probe, never re-run), while an identity memo or a derived store
  over it never derived from it (`latest` of either threw: [ROOT-L]).

Now a first answer always lands; `isPending` of a node answered and
re-asked in one flush is the re-ask's verdict (true); and a memo with no
value of its own that reads it derives from the answer once, then re-runs
on the missed-wake latch and waits on the re-ask holding it (a verdict
engine hook, so the hello-world floor pays only the call). Amends D-22's
2026-10-28 clause per the 2026-10-30 ruling: a plain write re-asking the
memo carries the answer like an action's.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

* fix(signals): a first answer landing in the tick a write re-asks it is answered, then re-asked (D-22, F-9)

An async memo's first answer resolves, and in the same synchronous turn a
plain write re-asks it; the promise reaction runs after the write, so the
landing and the re-ask are processed in one flush. The runtime treated the
answer as never produced:

- `asyncWrite` dropped it outright when the write's notification had marked
  the node dirty (a stale-answer guard meant for re-asks).
- Otherwise it landed staged, the re-ask left the node pending and
  uninitialized, and `isPending` gave it no verdict (`false` from the
  flush's probe, never re-run), while an identity memo or a derived store
  over it never derived from it (`latest` of either threw: [ROOT-L]).

Now a first answer always lands; `isPending` of a node answered and
re-asked in one flush is the re-ask's verdict (true); and a memo with no
value of its own that reads it derives from the answer once, then re-runs
on the missed-wake latch and waits on the re-ask holding it (in `read`'s
pending path, behind the verdict engine's hook so the floor folds it).
Amends D-22's 2026-10-28 clause per the 2026-10-30 ruling: a plain write re-asking the
memo carries the answer like an action's.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

* test(signals): the seat shadow seats a first answer served in its re-ask's flush

The reader-seat table (#4438) predicted NotReady (row D3) for a memo with
no value reading a first answer staged in the flush that re-asks it; under
D-22 (amended 2026-10-30) the memo derives from it once. Row D3' predicts
the staging (P), and `read` records the slot it serves. __TEST__ only:
dist is unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

* chore(size): raise caps for the same-tick first answer

isPending/latest +37 B brotli (+151 B minified) and the compiled live
page: the first-answer serve in `read`'s pending path and the
`isPending` verdict for it. The observe-attribution and compiled
scenarios carry the core half (+10 B minified on every floor; the rest
is brotli layout). Caps at measured + 10 B rounded up to 0.01 KB;
recorded minified = measured.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

* chore(size): raise the live page's frozen floor for the same-tick first answer

38.76 KB -> 38.79 KB, recorded minified 117,227 -> 117,379 B, measured
at 38,778 B brotli. Size-Exception in the PR body.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

* fix(signals): serve an answered-then-reasked first answer only to a reader waiting on it, off lanes, at any depth

read()'s D-22 branch served the staged first answer to a reader that did
not wait on it (a first pass: the seat table's D3 row predicts NR) and to
a verdict lane's pass (V6: D-37, a lane sees the screen plus its own
guesses). It now requires passLane === null and that the reader wait on
the source a read of the node parks on (_error.source): the node itself
one level down, the async it derives from at any depth, so
identity(identity(m)) reads as m does (E2 at depth 2). The seat's D3'
row says the same (!inSeat, the parked-on source, no staging of its own).

handleAsync's answered-then-reasked guard is the verdict engine's
(holdWorkHook): plain-only bundles keep the plain return.

D-23 exception 1: an answered-then-reasked node is not a first load.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

* fix(signals): type the parked-on source read in read() and the D3' row

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

* chore(size): re-record #4459's caps over next @ 131ceb8

isPending/latest, compiled live SC and live + router grow with the
verdict-engine branch (+159..+161 B minified); no frozen floor moves:
plain-only bundles are unchanged in minified bytes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

* chore(size): re-record the live page floor's minified for #4459

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

* fix(signals): serve an answered-then-reasked first answer one pass a flush, out of line

read()'s D-22 arm served a reader whose pass then threw anyway (a read
beside the served one parked on the same source): the throw re-parked it
on that source, the missed-wake latch re-ran it, and the re-run qualified
again — a synchronous loop within one flush (async-gen 91501 #749,
minimized: a boundary's children memo over two Shows that both park on
the re-asked memo). The arm now stamps the served pass's clock on the
reader (`_x._served`) and serves a reader once a flush; the re-run throws
and waits. The seat's D3' row says the same.

The arm moves out of read() into `servesStaged`, called behind
`observeFlightHook` on the pending-throw path, so read() itself grows by
a call (bytecode 1318 -> 1170; next 1139).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

* fix(signals): an answered-then-reasked first answer reaches a reader its landing released

servesStaged served a reader with no value only while it still waited on
the source. When the memo's input is read through latest() or a sync
memo, the write's re-ask reaches the memo after its landing ran, and the
landing had already released the identity memo's wait: it ran with no
value, waiting on nothing, and threw, so latest(identity(m)) and a derived
store stayed on [ROOT-L] while latest(m) showed the answer (async-gen E2/E3
3289 #310, 91501 #1993, #2609). D-22 as amended says a derivation with no
value of its own reads like the memo at any depth; which the flush ran
first is not the reader's. A reader that is not pending is served too
(one served pass a flush, as before); one pending on another source is
not. The seat's D3' row says the same.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

* chore(size): re-measure the answered-then-reasked serve over next @ 230c670

Live server components 38.85 -> 38.96 KB (117,747; Size-Exception in the PR
body), isPending/latest 11.17 -> 11.26 KB (31,002), compiled live
42.79 -> 42.85 KB (125,887); live + router re-records minified (143,150).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

* chore(size): re-measure the answered-then-reasked serve over next @ d0fc285

Takes next's ledger (recorded after #4471/#4479) and re-records only the
scenarios this PR moves: isPending/latest 11.19 -> 11.28 KB (31,053), live
server components 38.91 -> 38.96 KB (117,793; Size-Exception in the PR body),
compiled live 42.83 -> 42.87 KB (125,938), live + router 48.51 -> 48.58 KB
(143,201).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

* fix(signals): every answer resolved in the tick a write re-asks it rides the write's flush (#4495)

Two async memos m1, m2 answer, then a write re-asks both, in one tick. m1's
landing flushed synchronously (`asyncWrite`), and that flush carried the
queued write: it re-asked m2 before m2's answer's reaction ran, so the answer
arrived superseded (`_inFlight !== result`) and was dropped. A memo over both
then had no value and `latest` of it threw (D-22 says 13).

A landing that arrives while a write's flush is queued (a staged value in
`pendingNodes`, captured before the landing stages its own) no longer flushes:
the write's flush carries it with every answer whose reaction is queued ahead
of it (F-10), each answered, then re-asked (D-22, F-9). Only `latest` and
`isPending` tell the difference, so it is gated on the verdict engine
(`holdWorkHook`); a stream's next pull still waits on its landing's flush.

Size: isPending/latest 11.28 -> 11.30 KB (31,087 B), compiled live server
components 42.87 -> 42.90 KB (125,978 B); hello world unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2

---------

Co-authored-by: Claude <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants