fix(signals): a render effect that stops reading a pending memo releases the memo's source (fuzzer #3446 P1) - #3488
Merged
Merged
Conversation
…er, the fuzzer's P1 as a cell and a pinned violation Adds to the posture matrix: - posture `gatedAway`: the reader is built behind a `show()` gate, then the gate closes — alive, no longer deriving from `x`. Records `x`, `isPending(x)` and the state's held SOURCE write after the gate closes, before any release. - reader `effect`: a render effect reading `x` DIRECTLY (the reporter shape the fuzzer's reduction used; the matrix only had memo + effect-over-memo). - state contract: `source` (the mainline signal whose write the flight holds) and `perturb` (a write made after the matrix reader exists, so the reader observes the flight it opens). - a matrix-only state, "pending own async, observed only by the matrix reader (fuzzer P1)". Result: fuzzer #3446 law P1 (21/1000 cases, one symptom group, case 854) reproduces as exactly one cell — `effect × gatedAway` on that state: the source's write stays held after the only reader gated away; with a memo between the effect and the flight (`memo × gatedAway`) it releases. Pinned `it.fails` in posture-born-held-and-observation.test.ts; recorded as spec O3 (violation, open): A15 / #3426 live reporter — "re-ran and no longer derives from the source" is the fifth liveness case, to be fixed in one readerLive() predicate rather than a fifth arm of reporterBlocksSource. Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…ses the memo's source (fuzzer #3446 P1) A pending REPORTER that recovers without its flight landing — its pass no longer reads the source (a show() gate closed) — stops counting for the transaction it reported to (A15 / #3426: the hold lasts while a live reporter observes the flight). `reporterBlocksSource` already judged the re-run effect dead; nothing RE-JUDGED the parked transaction, since the flush that re-ran the effect had no active transaction, and the writes held with it stayed staged for as long as the flight stayed up — forever, for one that never lands. recompute's tail now treats that recovery as the completion event it is and wakes the transaction (wokenTransitions — the third site after disposal #3372 and boundary reset #3375), skipped under the transaction's own flush, which judges the landing itself. Reporters are stamped, so no unstamped fallback is needed. Found by the semantic fuzzer (#3446, law P1, 21/1000 cases in one symptom group, case 854); reproduced as the posture matrix's `effect × gatedAway` cell on the "observed only by the matrix reader" state — the memo-between variant (`memo × gatedAway`) already released, which is what narrowed it to the direct-effect reporter. Pin flipped from it.fails; spec O3 fixed. +100 B minified (24,478 -> 24,578). Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
🦋 Changeset detectedLatest commit: 62b0a22 The changes in this PR will be included in the next version bump. This PR includes changesets to release 11 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 35067907246Coverage remained the same at 71.46%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
4 tasks
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.
Stacked on #3485 (the posture matrix cell this fixes); merges cleanly after it.
Bug
A render effect that directly observed a pending async memo, then re-ran without reading it (a
show()gate closed), kept the memo's source held — forever when the flight never lands:Found by the semantic fuzzer (#3446, law P1: "ordinary writes publish after a drain when no visible reader still needs an unresolved answer") — 21 of 1,000 cases in one symptom group, case 854. Reproduced as the posture matrix's
effect × gatedAwaycell; the memo-between variant (memo × gatedAway) already released, which narrowed it to the direct-effect reporter.Cause
Not the liveness predicate:
reporterBlocksSourcealready judged the re-run effect dead (no pending status, no pending sources, deps without the memo). Nobody re-asked: the flush that re-ran the effect had no active transaction, andtransitionCompleteonly judges the active one.wokenTransitions' own doc comment names the class — "a reporter that stops counting for another reason … no check comes" — with two sites: disposal (#3372) and boundary reset (#3375). This is the third.Fix
recompute's tail: a pending reporter (render effect) that recovers without its flight landing —wasPending && !(STATUS_PENDING)after the pass — wakes the transaction it reported to (wokenTransitions.push(t); schedule()), skipped under that transaction's own flush (the landing that recovers a reader there is judged by that flush). Reporters are stamped, so no unstamped fallback. +100 B minified (24,478 → 24,578);+createStorebrotli cap +1 B.Fuzzer, same campaign against the fix: 21 → 4 failures, policy unchanged (12; those translate to plain Solid that publishes synchronously — the fuzzer's wait-budget model).
Remaining, pinned
it.fails(spec O3, remaining form)The 4 are one shape: gate and write in the same flush (case 21; case 79 multi-step). The effect is notified pending by the write (registering as reporter) and dirtied by the gate in the same flush; the verdict runs after the pure phase, before effects, so the effect still looks live, the transaction parks, and the effect's run — the one that would prove it dead — is stashed with the transaction. The hold keeps the run that would release it. That's the effect-phase parking question (#3407), not a fifth predicate arm; left for the consolidation.
Tests
posture-born-held-and-observation.test.ts: P1 pin flipped fromit.failstoit; same-flush form added asit.fails. signals 2,495 + 1 expected fail; solid 620; web 823.