fix(signals): the lane-membership ruling — F8 and adopted staging (F6 stopped on A18 (c)) - #3806
Merged
Merged
Conversation
…aging) The 2026-10-05 paragraph under the L2 section, ruled by the maintainer (reading A for all three): a render effect off a lane that reads a lane node directly is a stale reader of the lane whether or not the lane is held (F8); a leaf reading through a boundary showing content is the lane's frame reader through it (F6, A33); an on-screen reader whose removal an adopted write stages follows the lane's work (adopted staging). Derivations between the lane node and the leaf stay born held under A29 (#3761). RULES-INDEX regenerated (line shifts only). Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…r of the lane, held or not (fuzzer F8) A mainline mount over a lane derivation whose re-ask nobody had observed (the seam left the lane unheld) published its mount control while the child entered the lane and waited on the flight: control and content disagreed (fuzzer S2 "mount control"). `laneRead`'s stale-reader arm was gated on `l._held`; it now also takes a render effect off the lane reading a member whose own flight is up. The child shows the screen (the lane's revealed derivation) beside its control, and re-derives at the reveal. Rule: A15 lanes corollary (#3460) and the lane-membership ruling (2026-10-05, reading A): "a stale reader of the lane whether or not the lane is held at that moment". Through a memo the mount stays born held (A29, #3761) — pinned beside it. Removing the gate outright livelocks the verdict lanes (F7b, F12: an unheld, unblocked verdict lane re-queues its stale readers at every reveal); the arm is widened only to a member with a flight up. Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…lows the lane's work (fuzzer adopted staging) An action writes `source = 1`; the same tick withdraws the mount around a render effect `E` that reads `m = [latest(source)]` (adopted, O1). `E` is a zombie of the action's frame, still on screen. The lane recomputes `m` and dirties `E`, but `E` was not a lane member, so `laneDirty` left it unmarked; it became a zombie later in the same flush, `_update` deferred it, and the park cancelled its pass. `E` kept `[0]` while a sibling reader of `m` showed `[1]` — the direct `latest()` twin already re-ran by #3444's verdict-reader exception. `laneDirty` now also marks a render effect on no lane. The mark is inert for its pass (`recompute` seats a lane only from the node's own `_x`), and makes its zombie pass the lane's live write, which `_update` runs now — the existing rule for the lane's own dirty members (#3463). Memos stay unmarked: a lane frame's child memo reading the guess keeps the #3662 cancellation (#3698 pins). Rule: the lane-membership ruling (2026-10-05, reading A): an on-screen render effect whose removal an adopted write has staged follows the lane's re-derivation of a memo it reads; A15 #3463 (a zombie is live for every hold until the commit that disposes it); A28 (1). New pin (none existed): `fuzz-findings-l2.test.ts` "adopted staging". Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
… A18 (c) Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
🦋 Changeset detectedLatest commit: 34157ea The changes in this PR will be included in the next version bump. This PR includes changesets to release 12 packages
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 |
Coverage Report for CI Build 37374274558Warning No base build found for commit Coverage: 75.991%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsRequires a base build to compare against. How to fix this → Coverage Stats
💛 - Coveralls |
Size (brotli, eager entry chunk)
Bundled with Rolldown (what Vite ships), brotli q11, decimal KB. Caps in |
… scenarios (Size-Exception) Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
ryansolid
added a commit
that referenced
this pull request
Oct 5, 2026
The check job measured the merge commit onto next @ a8c98bd, where #3806 had added 4 B minified to the signals scheduler since the local measurement at b07fed5; the compiled floor landed 3 B over. The caps follow the repo's rule — CI head + 10 B, rounded up to 0.01 KB: floor 10,033 -> 10.05 KB, CSR 25,070 -> 25.08 KB, hydrating 30,899 -> 30.91 KB (unchanged). The compiled app's own bytes are identical on CI and locally (app=229 / 2,575 / 3,058); the delta is the runtime's. Co-authored-by: Claude via Cursor <noreply@cursor.com>
ryansolid
added a commit
that referenced
this pull request
Oct 6, 2026
…do app CSR + hydrating) (#3810) * chore(size): bundle.mjs compiles JSX scenarios with the measured checkout's @solidjs/compiler A scenario with `compile` is JSX under scripts/size/fixtures/, compiled at measure time by the native compiler of the checkout being measured (packagesRoot/compiler/index.js — the compare job's base builds its own, so a compiler-only change moves the compiled scenarios on the comparison) in client DOM mode with the production posture the Vite plugin uses (`generate: "dom"`, `dev: false`, `hydratable` from the scenario). Every `.jsx` module the entry reaches is compiled and handed to Rolldown as plain JS; the compiler sees only each file's basename, so the output is the same on every host. Modules under fixtures/ report as the `app` package, so the compiled app's own bytes are separable from the runtime's. No scenario uses it yet; existing scenarios are byte-identical. Co-authored-by: Claude via Cursor <noreply@cursor.com> * chore(size): compiled-template scenarios — a compiled floor and a JSX todo app, CSR and hydrating Three scenarios the hand-written `app:` fixtures could not stand in for (solid-web-size-audit §1.3, §2.4): none of them compiles a template, so the DOM attribute runtime and the hydratable walk helpers were never on the gate. fixtures/compiled/ holds a one-button floor (one template, one text hole, one delegated click) and a todo app with an element spread, merge/omit, a component spread (the compiler's mergeProps), delegated and direct events, class/style objects, a keyed <For>, <Show>, <Loading> around a lazy() child and a store, entered through render() and through hydrate(). Measured on next @ b07fed5 (identical on Node 24 and Node 26): compiled floor 28,184 min / 10,019 br cap 10.03 KB compiled CSR 78,803 min / 25,128 br cap 25.14 KB (lazy stats.js 182 br) compiled hydrating 99,064 min / 30,898 br cap 30.91 KB (lazy stats.js 195 br) Caps at measured + 10 B rounded up to 0.01 KB, inline (not frozen floors yet). The props machinery — spread + mergeProps + merge/omit — retains 14,751 B min / 3,918 B br of the CSR scenario; the same app on signals alone (no store) measures 54,467 / 18,097 against the audit's 51,898 / 17,498 for its Babel-compiled fixture. Co-authored-by: Claude via Cursor <noreply@cursor.com> * chore(size): size.mjs and attribute.mjs keep the first positional filter Without `--json` (or `--min`), the flag's index is -1 and `i !== jsonAt + 1` dropped the first positional argument, so `node size.mjs compiled` ran every scenario and `node attribute.mjs name` attributed all of them. Only exclude the value argument when the flag is present. CI always passes `--json`, so the gate was unaffected. Co-authored-by: Claude via Cursor <noreply@cursor.com> * chore(size): compiled-scenario caps at CI-measured + 10 B The check job measured the merge commit onto next @ a8c98bd, where #3806 had added 4 B minified to the signals scheduler since the local measurement at b07fed5; the compiled floor landed 3 B over. The caps follow the repo's rule — CI head + 10 B, rounded up to 0.01 KB: floor 10,033 -> 10.05 KB, CSR 25,070 -> 25.08 KB, hydrating 30,899 -> 30.91 KB (unchanged). The compiled app's own bytes are identical on CI and locally (app=229 / 2,575 / 3,058); the delta is the runtime's. Co-authored-by: Claude via Cursor <noreply@cursor.com> --------- Co-authored-by: Claude via Cursor <noreply@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the lane-membership amendment (2026-10-05) as ruled. Reading A was chosen for all three cases. Two of the three fixes landed. F6 stopped: its fix flips Tier-A A18 pins, so it needs a ruling (details below). It stays
it.fails.The spec paragraph from
spec/lane-membership(572ea7c) is cherry-picked with "proposed" replaced by the ruling, plus a "Pinned:" line and a note on F6.F8: a direct leaf read of a lane node in flight is a stale reader, held or not (c4b4383)
latestS2 "mount control"). An action re-asks a lane derivation that nobody had observed, so the seam left the lane unheld. A mainline mount whose child reads the derivation directly published its control. The child entered the lane and waited on the flight, so control and content disagreed.laneRead's stale-reader arm (core/lanes.ts) was gated onl._held.l._held || (!guess && status & STATUS_PENDING)). Removing the gate outright livelocks F7b and F12; the narrowed form does not.it. The child shows the lane's screen ([true, 1]), then re-derives at the reveal ([true, 2]).[false, "absent"](A29, feat(signals): let mainline mounts publish before foreign holds #3761).Adopted staging: an on-screen reader whose removal an action adopted follows the lane's work (acefaa5)
latest, ex-"R-oracle"). An action writessource = 1. The same tick withdraws the mount around a render effectEthat readsm = [latest(source)](adopted, O1).mand dirtiesE.Ewas not a lane member, solaneDirtyleft it unmarked.Ebecame a zombie later in the same flush,_updatedeferred it, and the park cancelled its pass.Ekept[0], while a sibling reader ofmshowed[1].laneDirty(core/scheduler.ts) also marks a render effect on no lane.recomputeseats a lane only from the node's own_x._updateruns now. That is the existing rule for the lane's own dirty members.F6: STOPPED (needs a ruling on A18 (c))
The preferred one-flag shape works for the F6 case:
forward(b, tree)inboundaries.tssetsREACTIVE_FRAME_READon the boundary's output when it forwards content.[0,0], then[1,1]at the landing.It is not committed, for two reasons:
spec-async-semantics.test.tsA18 "differing arrival…" and "sync wrapper…", and thestore/lane-authority-twins2.0.0-rc.7 async downstream of optimistic delays reveal of final result #3331 twin.next, the no-boundary analogue already gives the untracked read0where A18 (c) expects3. The cause issupersede'sif (l._shown) n._value = n._x!._lane.Per the instruction, I did not fall back to a
blockedBywalk. The patch is kept locally to apply once A18 (c) is ruled.Verification
latestcase 827 is not fixed. It is unchanged before and after this PR, and it still fails with the F6 patch applied, so F6's fix does not cover it.[latest(-2), node0, latest(-2)]mounts with no boundary.[1, 0, 1](thelatest()reads are ahead of the committednode0) on bothnextand this branch.nextand on this branch: with a derived store plus a local write, the optimistic view showsOld / saving: falsewhile the action is pending. NoFixes.[mounted: true, child: "absent"]after the action completes, on bothnextand this branch.latestcases 416 and 936 (seed 3289). Not touched here.Interaction with F5, #3766 and #3794
The adopted-staging mark doesn't make render effects lane members:
passLanestays null, and only the zombie-deferral decision in_updatereads the flag. F5's settle walk (asyncWrite → settlePendingSource → enqueueSub) is untouched, so this neither fixes nor widens F5. Theoptimistic-readinesscohort is unchanged (0 and 2 findings, the same as before), and the F5 pin is stillit.failswith no crash.Fuzzer gate (1000 cases per run,
--shrink)[4]where the oracle expects[3]), while its flight for the newer input is up.latest()-published input, until the landing.Suites
@solidjs/signals: 263 files, 4937 passed, 7 expected fail.solid-js: 819 passed.@solidjs/web: 1139 passed, 1 expected fail (the same asnext).rules-index --check: clean.Public API changes
None.
Size
Size-Exception: the lane-membership ruling (F8 + adopted staging, +13 B minified) puts two scenarios over their caps by brotli layout —
app: hydrating + every store primitive family28.84 -> 28.87 KB andapp: CSR, observe tier + attribution engine enabled28.62 -> 28.66 KB (CI-measured + 10 B). Accepted by the maintainer (2026-10-05).Measured locally against
origin/next(b07fed5), as brotli / minified bytes. Local brotli varies by ±30 to 60 B between builds. The CIcheckjob reproduced these numbers exactly, including both overages and the 9846 B br hello world.Per fix: F8 is +9 B minified and adopted staging is +4 B minified, both within the ≤ 20 B budget. CI reported the same two brotli overages (17 B and 22 B). The maintainer approved raising both caps to CI-measured + 10 B (dated notes in
scripts/size/scenarios.js).Re-pins
None in the committed fixes; no existing pin changed expectation. The F6 Tier-A flips listed above are the reason F6 stopped, and were not applied.