Skip to content

test(signals): reader seat shadow — new seat/slot implementation under __TEST__, differential against the existing deciders - #4395

Closed
ryansolid wants to merge 6 commits into
nextfrom
seat/rebuild-beside
Closed

ryansolid wants to merge 6 commits into
nextfrom
seat/rebuild-beside

Conversation

@ryansolid

@ryansolid ryansolid commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

Step S0 of documentation/plans/reader-seat-rebuild.md (brief: reader-seat-brief.md §4). Draft — not for merge. The swap (S1) and the cleanup (S2) are separate PRs, gated on the rulings listed under Two readings below.

What is shadowed

packages/signals/src/core/seat.ts is the new layer: the seat (openSeat — world S < Fl < H that only raises, lane, display-ahead, posture from the node alone; bornHeld — row B2) and the slot table (slot(c, el) — rows U1–U4, F1, A1, L1–L8, D1–D8, V1–V6, Vl1–Vl5, Vp1–Vp2, with the primed rows the differential added). Everything is behind __TEST__ ("false" in every rollup define and size script).

Under __TEST__:

  • every read runs once for real and slot() predicts beside it at the deciders' decision point (fast block, slow path after the pull, verdictValue's window, laneRead); each return site of the old deciders records the slot it served (shadowActual). Compared: slot identity (C / P / Lv / Dv / T / NR / E), and the reader's world state after the read for non-leaf postures (INV-seat-3).
  • every recompute opens a seat beside the head seating (setPassLane / laneRoot / mount) and compares lane, ahead and inherited world (row B1), and in the finally the born-held decision and the lane the pass ends in (row B2). A leaf never raises its world (INV-seat-1); posture is recomputed from the node at open and close (INV-seat-2).
  • raises the old code applies outside read (joinPass/joinPassAt, stagedRead, the boundary fallback swap) are mirrored into the seat.

Mismatches are recorded, not thrown (__SEAT_SHADOW_STRICT__ throws): SEAT_SHADOW_REPORT=<file> makes the signals/solid/web vitest configs append one JSON line per test file; packages/signals/scripts/seat-shadow-summary.mjs aggregates. SIGNALS_TEST_DEFINE=true pnpm --filter @solidjs/signals build emits a dev dist with __TEST__ on so solid, web and the generator (which consume dist/dev.js) run the shadow; the default build is unchanged.

Docs: design doc §3 (harness as built), §4.2 (results), §6 (table rows found wrong against a rule — fixed in seat.ts, the brief's table untouched), §7 (two readings); INTERNALS-ASYNC-STATE.md §5 gains the INV-seat family; invariants.ts points at it.

Suite results

run reads compared passes seated mismatch classes instances
signals suite (376 test files; re-run with the table as committed) 965,386 238,343 40 151
visibility oracle (3 files: posture / oracle / store) 11,379 8,322 0 0
solid suite (49 test files) 2,389 1,546 0 0
web suite (27 .spec.ts files; the 153 .spec.tsx files fail to load in this worktree on next too — no @solidjs/compiler binary) 254,902 155,740 25 74
generator, 2,000 cases × seeds 3289 / 91501 / 7, every pair (E1 E2 E2e E3 E6 E7 E8 E9, owned + unowned via E7) 6,969,627 5,153,328 143 4,127
generator, 50,000 cases × seeds 3289 / 91501 / 7, every pair — table before the two 50k fixes below 67,328,944 43,755,988 220 8,821
generator, 50,000 cases × seeds 3289 / 91501 / 7, every pair — re-run with the table as committed 67,328,944 43,755,988 209 8,706

Every suite is green with the shadow on. The 50k run is deterministic (same reads/passes on repeat; the re-run with the committed table loses exactly the 11 classes / 115 instances the two fixes cover and gains none) and added two table rows (L4/L2/V1' staged error → E, D-41; V3 under published inputs in flight → the flight's V4', F-9 / #3305) and no new two-readings item; the §7 buckets scale roughly linearly from 2k to 50k (F-ahead-posture 1,461 → 2,922; F-vlane 1,054 → 2,355; F-vworld 1,101 → 2,308; F-transitive 301 → 684; B2 display-ahead residue 104 → 257; F-mount 97 → 180).

Generator findings on next, not shadow findings (reproduced on the plain build without the shadow; not minimized or filed here):

  • all three seeds abort before 50,000 cases with Potential Infinite Loop Detected. Kept alive by scheduled work; last staged node: … — seed 7 inside the first 2,000 cases (computed), 3289 at ~427 s (value), 91501 at ~638 s (signal). Verified against a dist built without SIGNALS_TEST_DEFINE (no shadow in the bundle): each aborts at the same case index on both builds — seed 7 at case 1158 (in the E8 remount run), 3289 at case 3963 (the base run), 91501 at case 5807 (the E8 remount run). The campaign does not print the index; a throwaway replay of its survey loop (same fc.sample stream, same per-case run order) did — not committed. Repro: ASYNC_GEN=1 ASYNC_GEN_SEED=<seed> ASYNC_GEN_CASES=50000 ASYNC_GEN_MODE=survey ASYNC_GEN_PAIRS=E1,E2,E2e,E3,E6,E7,E8,E9 pnpm exec vitest run test/async-gen/campaign.spec.ts in packages/web.
  • seeds 3289 and 91501 each report one equivalence/E7 signature (owned vs unowned screens) in survey mode, on both builds.

Mismatch triage

No class was found where the NEW slot is right and today is wrong by a rule, so no issue was filed. Every slot disagreement traced either to a table gap (the table was fixed in seat.ts; design doc §6) or to one of the two-readings items below (design doc §7), which need a ruling before the swap. Counts are signals / web / generator-2k.

row(s) shape which side rule status
U4 untracked read of a memo with _shownError (write/retry over an error) today (E) D-41, #4110 table fixed
Vl1' latest() by a render effect outside the hold's flush, verdict lane blocked today (C, stale reader) #3460 / A15 table fixed
Vl1, Vl4, Vp* staged error inside latest throws; isPending never throws, error last today D-41 table fixed
L6, D4''' lane work reading a lane derivation in flight today (NR) D-37 table fixed
L7' leaf held by the flush's tx reading a lane derivation's staging today (P) D-2 table fixed
V6 a verdict pass's derivation (today a verdict lane's CONFIG_OVERRIDE value) — (new row) D-25, #4050 row added; residue under F-vlane
B2 leaf clause render effect under a verdict-lane mount reading a staging is born held today D-27, #4166 table fixed (mark SR)
B2 exclusion nested-derivation-left-pending exclusion vs a verdict reader's direct mount today #4203 #4340 #3851 table fixed (creatorDirect)
D1 / L1 staged + uninitialized + in flight → NR today F-9 table fixed
D2 held, nothing staged → C with the join today D-2 table fixed
L4 / L2 / V1' leaf or born-into frame reader of a held node whose staging is an error today (E) D-41 table fixed (50k)
V3 verdict reader's plain read of a node staged + in flight with INPUTS_PUBLISHED today (NR via V4') F-9, #3305 table fixed (50k)
F-vlane — V6 / V4' / V1 / V2 / L2 / Vl1' under oldVlane a node whose staging sits in a verdict lane is seated as the verdict lane's on its next pass (incl. pending re-pass after #3884 clears CONFIG_VERDICT, and the #3540 boundary swap); the brief gives verdicts no node set two readings D-25, #3884, #3540, #4337 15 / 11 / 1,054 instances; ruling needed: how posture persists without a node set
F-leaf — B1 inherited world, B2 born held (old true, new false, world 0) a render effect that read a born node is JOINED today and its children are born held; INV-seat-1 keeps a leaf at S two readings A29 vs D-1 / INV-seat-1 41 / 0 / 0
F-mount — B1 ahead (old true, new false; creator running false) today's display-ahead mount is module state (laneRoot), covering runWithOwner creations (boundary fallback memo); the brief says creator = context two readings A29 "children" 22 / 3 / 97
F-ahead-posture — B1 ahead (old false, new true) brief: ahead = creator verdict posture or lane; today only a lane/passLane two readings D-27 1 / 29 / 1,277 (+184 the #4341 narrowing, V in world ≥ Fl)
F-vworld — equal slot, world after the read old 1|2 new 0 (Vl/Vp/V, D5) verdict reads set STAGED_READ/JOINED on the reader today; the table gives marks only two readings D-1 vs D-23/A10 40 / 25 / 1,101
F-transitive — B2 born held (old false, new true; world 2, ahead false) world inherited transitively through running creators vs today's one-level creatorPass(oldcontext) JOINED two readings A29 14 / 2 / 301
residue L7 Dv vs Lv + lane seat at pass end (spec-async-semantics ×2); Vl1' C vs P with the reader in world H (generator 50k ×10) and V1' P vs C for a render-effect verdict reader born into the hold (stagedScreen arm, 50k ×2); B2 under a display-ahead mount (generator 50k ×257, 36 classes) untraced — listed in the summaries, not classified
residue, 50k (literal reading) the 50k classes (three seeds; unchanged by the two 50k fixes) that match no §6 finding or §7 item when the class signatures §7 names are read literally — 623 instances, 43 classes: B1 ahead (old true, new false; creator running true; creator memo:value / children / computed, effect) ×406, 4 classes (the previous triage filed these under F-ahead-posture; §7's text names only old false → new true); D5 E vs NR for an errored node in flight (u f · f ip · u f ip · one f d vlane; D and V postures; 4 classes under oldVlane) ×101, 17 classes; L8 E vs NR, the same shape at a leaf ×34, 6 classes; V1 C vs P (s h, oldNoLane, t1 w0) ×57, 2 classes; V4 C vs NR (f ip; ×8 in 3 classes under oldVlane, ×4 in 2 classes oldNoLane) ×12, 5 classes; V1'' P vs E (s h u ip — the D-41 staged-error shape on a row the L4/L2/V1' fix did not touch; V1'' still serves P for an uninitialized held node) ×2, 2 classes; V1' P vs C (s h, w2) ×2, 2 classes and Vl1 P vs C (s h, w0, oldNoLane) ×1, 1 class; world after the read old 2 → new 1 with equal slots (Vl1 P ×4, Vp1 C ×2, Vl5 Dv ×1) ×7, 3 classes; B2 born held (old true, new false; world 1, ahead false, mount false, rows L8) ×1, 1 class untraced — listed in design doc §4.2, not classified

Design doc §7 has both readings and the screen each produces for every F-item.

Size

pnpm build after the change; scripts/size head vs the next base dists (diff -rq of packages/{signals,solid,web}/dist against the pre-change snapshot shows solid and web byte-identical; signals differs only in the prettified dev/observe/prod sources where if (x) return y; became if (x) { return y; } around a dropped __TEST__ statement, plus the new types/core/seat.d.ts). Minified and brotli bytes are identical on every scenario that bundles:

signals: core floor (createSignal/Memo/Effect/Root/flush)  brotli   7413 minified  19971 = base
signals: + createStore                                     brotli  14924 minified  43916 = base
signals: + isPending/latest                                brotli  10379 minified  28678 = base
signals: + until/refresh (truth readers, no optimism)      brotli   8037 minified  21776 = base
app: render + one signal (the simple-app floor)            brotli   9769 minified  26803 = base
app: hydrating (no stores) with Show/For/Loading/Errored   brotli  18606 minified  53357 = base
app: hydrating + every store primitive family              brotli  31758 minified  96961 = base
app: CSR with Show/For/Loading/Errored/lazy                brotli  13380 minified  36932 = base
app: CSR, observe tier                                     brotli  15105 minified  41880 = base
app: CSR, observe tier + attribution engine enabled        brotli  29713 minified  88219 = base
frames: eager client consumer                              brotli  11754 minified  34886 = base
page: base server components                               brotli  33533 minified 101358 = base
page: live server components                               brotli  37954 minified 115118 = base
server: floor (getRequestEvent + isServer)                 brotli   1275 minified   3255 = base
server: renderToString (the server-render floor)           brotli  22786 minified  77473 = base

The seven scenarios that compile JSX (five compiled-template, two router) fail to bundle on both head and base in this worktree (@solidjs/compiler-wasm32-wasi binary not installed) — an environment gap, not a size result; CI measures them.

Public API changes

None. No export, prop, option, parameter, default, documented behaviour or diagnostic code changes. SIGNALS_TEST_DEFINE is a build-time environment switch for the rollup config, SEAT_SHADOW_REPORT / SEAT_SHADOW_TRACE / SEAT_SHADOW_STRICT are test-harness environment variables; none reach a published artifact.


Co-authored-by: Claude via Cursor

ryansolid and others added 2 commits October 28, 2026 15:01
… under __TEST__, differential, then swap

Co-authored-by: Cursor <cursoragent@cursor.com>
…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 <noreply@cursor.com>
@changeset-bot

changeset-bot Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 697217f

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

ryansolid and others added 4 commits October 28, 2026 17:06
…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 <noreply@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…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 <noreply@cursor.com>
…d / chosen 2026-10-28

Co-authored-by: Cursor <cursoragent@cursor.com>
…is the hold's own work, not a persisted posture; swap pins corrected

Co-authored-by: Cursor <cursoragent@cursor.com>
This was referenced Oct 29, 2026
@ryansolid

Copy link
Copy Markdown
Member Author

Superseded by #4438, which lands this S0 work together with the S1 M1 re-baseline (4c413e1ea: the shadow against next @ 5e68925, 0 mismatch classes on every suite and on 2k × 3 seeds, shadowKnown for #4426 #4427 #4428 #4429, dist byte-identical).

The swap that was to follow (#4430) is stopped; the record is the design doc's §8 on the seat/spec branch. Closing this draft; the branch seat/rebuild-beside stays.

— Claude via Cursor

@ryansolid ryansolid closed this Oct 30, 2026
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.

1 participant