Skip to content

fix(signals): one unflushed — a staging adopted before any flush answers the committed value on every channel (spec O4) - #3510

Merged
ryansolid merged 2 commits into
nextfrom
fix/adopted-unflushed
Sep 17, 2026
Merged

ryansolid merged 2 commits into
nextfrom
fix/adopted-unflushed

Conversation

@ryansolid

Copy link
Copy Markdown
Member

Stacked on #3505 (its commit is included; merges cleanly after it). Closes spec O4 — the last pinned violation; the signals suite has no expected fails for the first time.

Bug (posture matrix S1)

Same-tick adoption is by design (O1): set(x, 1); action(...) in one tick makes the action own the write. A28 still governs what is visible before the flush: nothing. Inside the adopting action's body:

latest(x) isPending(x)
store leaf 0 false
signal (before) 1 true

The store answered correctly by coincidence (its foreign-hold rule), the signal wrongly: adoption stamped it with the transaction, and unflushedValue read a stamped node with no stash as "flushed, held". One rule, two implementations, two answers — the first concrete target of the consolidation's "one unflushed".

Fix

One definition: a node staged outside a flush and adopted before any flush has carried it is unflushed whatever its stamp.

  • initTransition's adoption loop sets CONFIG_ADOPTED_UNFLUSHED when it runs outside a flush (one masked |= in the existing loop).
  • unflushedValue serves the committed value for it → latest() answers the pre-write value; flushedStaged answers NOT_PENDING → the verdict sees nothing staged.
  • reassignPendingTransition — the carrying flush re-stamping the transaction's pending nodes — clears the bit (one &= in the existing loop); the hold takes over.

Signal and store leaves now answer through the same arm. +56 B minified (25,193 → 25,249).

Two earlier shapes were rejected by the gate: keying on the transaction's _time === clock (re-entry re-stamps _time, so a genuinely held value read from another action read as unflushed — four pins caught it), and a sentinel in the stash plus a helper (+179 B).

Gate

  • Matrix: the only changed cells are the staged, ambient state's under the adopting postures — latest/isPending now 0/false, and a memo/effect created there publishes the committed 0 and holds its 1 with the action instead of publishing the held write. 0 invariant violations across 621 cells.
  • Fuzzer (test(signals): add experimental semantic fuzzer #3446, same campaign): 994 / 0 fail / 6 policy, unchanged.
  • signals 2,742 (no expected fails) / solid 623 / web 825. Four brotli caps +18…+50 B.

@changeset-bot

changeset-bot Bot commented Sep 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e739c0b

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

This PR includes changesets to release 11 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

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 2 commits September 16, 2026 23:45
…swers the committed value on every channel (spec O4)

Same-tick adoption is by design (O1): an action started after an ambient
write owns it. A28 still governs what is VISIBLE before the flush: nothing.
The store's leaves answered latest 0 / isPending false inside the adopting
action's body — through the foreign-hold rule, by coincidence — while the
signal answered 1 / true: adoption had stamped it and `unflushedValue` read
a stamped node with no stash as "flushed, held". One rule, two
implementations, two answers (posture matrix S1).

One definition now: a node staged outside a flush and adopted before any
flush has carried it is unflushed whatever its stamp. initTransition's
adoption loop sets CONFIG_ADOPTED_UNFLUSHED when it runs outside a flush;
unflushedValue serves the committed value for it and flushedStaged answers
NOT_PENDING; reassignPendingTransition — the carrying flush re-stamping the
transaction's pending nodes — clears the bit. Signal and store leaves take
the same arm.

(A first cut keyed on the transaction's `_time === clock` — re-entry
re-stamps `_time`, so a genuinely held value read from another action read
as unflushed; four pins caught it. A second used a sentinel in the stash and
a helper: +179 B; the config bit is +56 B.)

S1 pin flipped. The matrix's only changed cells are the `staged, ambient`
state's: latest/isPending under the adopting posture now 0/false, and a
memo/effect created there publishes the committed 0 and holds its 1 with
the action instead of publishing the held write. 2,742 signals tests, no
expected fails remaining.

Co-authored-by: Claude via Cursor <noreply@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…(+18…+50 B)

Co-authored-by: Cursor <cursoragent@cursor.com>
@ryansolid
ryansolid force-pushed the fix/adopted-unflushed branch from c74b86b to e739c0b Compare September 17, 2026 06:52
@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 35191800471

Warning

No base build found for commit 3da9f43 on next.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 71.46%

Details

  • Patch coverage: No coverable lines changed in this PR.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 1018
Covered Lines: 772
Line Coverage: 75.83%
Relevant Branches: 790
Covered Branches: 520
Branch Coverage: 65.82%
Branches in Coverage %: Yes
Coverage Strength: 14.94 hits per line

💛 - Coveralls

@codspeed

codspeed Bot commented Sep 17, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 175 untouched benchmarks


Comparing fix/adopted-unflushed (e739c0b) with next (3da9f43)

Open in CodSpeed

@ryansolid
ryansolid merged commit a1a3bc6 into next Sep 17, 2026
7 of 8 checks passed
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