diff --git a/.changeset/born-held-effect-lane-run-restages.md b/.changeset/born-held-effect-lane-run-restages.md new file mode 100644 index 000000000..72085d0f4 --- /dev/null +++ b/.changeset/born-held-effect-lane-run-restages.md @@ -0,0 +1,5 @@ +--- +"@solidjs/signals": patch +--- + +A born-held render effect re-run as lane work (a `latest()` reader remounted during an action's hold) re-stages its value, so the commit applies the latest pass instead of the stale born-held one (fuzzer F13). diff --git a/.changeset/errored-pass-tail-still-observes.md b/.changeset/errored-pass-tail-still-observes.md new file mode 100644 index 000000000..677b0967e --- /dev/null +++ b/.changeset/errored-pass-tail-still-observes.md @@ -0,0 +1,5 @@ +--- +"@solidjs/signals": patch +--- + +A render reader whose pass errored (NotReady included) before reaching a flight it read last time still holds that flight's transaction: a write no longer shows beside a reader still derived from the previous value while the flight is in the air (fuzzer F4). diff --git a/.changeset/landing-rejudges-parked-transactions.md b/.changeset/landing-rejudges-parked-transactions.md new file mode 100644 index 000000000..b2a325d98 --- /dev/null +++ b/.changeset/landing-rejudges-parked-transactions.md @@ -0,0 +1,5 @@ +--- +"@solidjs/signals": patch +--- + +A landing whose commits dispose a zombie re-judges the transactions parked at the same seam: a write held only because that zombie observed its flight now lands with the commit that disposes it instead of staying unpublished until an unrelated flush (fuzzer F3). diff --git a/.changeset/verdict-pull-outside-window.md b/.changeset/verdict-pull-outside-window.md new file mode 100644 index 000000000..97af6bba2 --- /dev/null +++ b/.changeset/verdict-pull-outside-window.md @@ -0,0 +1,5 @@ +--- +"@solidjs/signals": patch +--- + +A memo pulled by an `isPending`/`latest` probe computes as its own pass, outside the probe's window: it no longer caches the committed input in place of a flushed write (fuzzer F10), or reads an uninitialized input as `undefined` instead of suspending (F11). diff --git a/.changeset/verdict-reader-silent-landing.md b/.changeset/verdict-reader-silent-landing.md new file mode 100644 index 000000000..e80e92ce3 --- /dev/null +++ b/.changeset/verdict-reader-silent-landing.md @@ -0,0 +1,5 @@ +--- +"@solidjs/signals": patch +--- + +An `isPending` reader of an async memo settles when the memo's flight lands equal to its committed value: the settle walk re-derives the verdict reader instead of leaving it `true` (fuzzer F9). diff --git a/packages/signals/docs/RULES-INDEX.md b/packages/signals/docs/RULES-INDEX.md index 2c8fb6fad..8cee032de 100644 --- a/packages/signals/docs/RULES-INDEX.md +++ b/packages/signals/docs/RULES-INDEX.md @@ -63,7 +63,7 @@ Status legend: **live** stated and standing · **ruled** carries an explicit rul | A16 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:193` | scheduler.ts×1 | spec-async-semantics.test.ts×1 strict-read-pending-store.test.ts×2 uninitialized-visibility.test.ts×1 visibility-oracle-store.states.ts×2 visibility-oracle.states.ts×2 visibility-oracle.test.ts×1 | [ruled, amended in place 2026-07-06 (promoted from B5)] `isPending` never throws in untracked contexts — (was B5) `isPending` never throws in untracked contexts — thunks that throw real errors or read… | | A17 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:51` | async.ts×3 constants.ts×1 lanes.ts×5 scheduler.ts×2 verdict.ts×1 map.ts×1 store.ts×3 | fuzz-findings-l2.test.ts×4 lane-uninitialized-landing-3648.test.ts×5 optimistic-read-lane-not-transaction-3698.test.ts×2 optimistic-undefined-override.test.ts×1 posture-store-parity.test.ts×1 refresh-await.test.ts×1 reveal-gating-contract.test.ts×3 spec-async-semantics.test.ts×10 createOptimisticStore.test.ts×2 kanban-a17-fixture.test.ts×3 optimistic-list-mutation-matrix.test.ts×1 optimistic-maparray-index-frame-f1.test.ts×1 optimistic-untracked-reads-f3-f5.test.ts×1 signal-store-twins-qd.test.ts×1 treeshake.test.ts×1 until.test.ts×1 visibility-oracle-store.states.ts×24 visibility-oracle-store.test.ts×1 visibility-oracle.states.ts×20 visibility-oracle.test.ts×1 | [ruled, amended in place 2026-07-06 (promoted from C4)] An active override is the displayed value until its transaction commits, and the graph's value until its own source answers — **Statement (curre… | | A18 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:61` | action.ts×1 async.ts×2 core.ts×3 lanes.ts×8 scheduler.ts×2 types.ts×1 verdict.ts×1 map.ts×1 optimistic.ts×1 projection.ts×1 | body-end-supersession-visibility.test.ts×4 createOptimistic.test.ts×3 lane-contract.test.ts×1 lane-frame-deferred-run-3662.test.ts×1 lane-outside-view.test.ts×1 lane-uninitialized-landing-3648.test.ts×5 optimistic-move-duplicate-3548.test.ts×2 optimistic-read-lane-not-transaction-3698.test.ts×4 posture-store-parity.test.ts×5 spec-async-semantics.test.ts×3 flight-owned-transaction.test.ts×1 optimistic-list-mutation-matrix.test.ts×1 optimistic-untracked-reads-f3-f5.test.ts×1 signal-store-twins-qd.test.ts×1 unchanged-presence-no-hold-3743.test.ts×2 superseded-before-first-commit.test.ts×4 visibility-oracle-store.states.ts×8 visibility-oracle-store.test.ts×1 visibility-oracle.states.ts×19 visibility-oracle.test.ts×1 | [ruled, amended in place 2026-07-07 (promoted from B4)] An override lives exactly as long as its own transaction; a newer truth from the source supersedes it in the graph immediately, on screen at com… | -| A19 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:121` | constants.ts×1 core.ts×6 scheduler.ts×2 types.ts×1 verdict.ts×4 store.ts×1 | fuzz-findings-l2.test.ts×4 lane-uninitialized-landing-3648.test.ts×2 mount-over-foreign-hold-3761.test.ts×2 spec-async-semantics.test.ts×3 derived-presence-async-3726.test.ts×2 superseded-before-first-commit.test.ts×3 uninitialized-visibility.test.ts×1 visibility-oracle-store.states.ts×8 visibility-oracle-store.test.ts×1 visibility-oracle.states.ts×14 visibility-oracle.test.ts×1 write-proposals-3494.test.ts×1 | [ruled, amended in place 2026-07-07 (promoted from C1)] `isPending(x)` ≡ the observable value is not final (three causes) — (was C1 — **partially reverses an earlier decision**) **Definition: `isPendi… | +| A19 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:121` | async.ts×1 constants.ts×1 core.ts×6 scheduler.ts×2 types.ts×1 verdict.ts×4 store.ts×1 | fuzz-findings-l2.test.ts×4 lane-uninitialized-landing-3648.test.ts×2 mount-over-foreign-hold-3761.test.ts×2 spec-async-semantics.test.ts×3 derived-presence-async-3726.test.ts×2 superseded-before-first-commit.test.ts×3 uninitialized-visibility.test.ts×1 visibility-oracle-store.states.ts×8 visibility-oracle-store.test.ts×1 visibility-oracle.states.ts×14 visibility-oracle.test.ts×1 write-proposals-3494.test.ts×1 | [ruled, amended in place 2026-07-07 (promoted from C1)] `isPending(x)` ≡ the observable value is not final (three causes) — (was C1 — **partially reverses an earlier decision**) **Definition: `isPendi… | | A20 | superseded | `docs/SPEC-ASYNC-SEMANTICS.md:380` | — | question-scoped-pending.test.ts×2 spec-async-semantics.test.ts×3 createOptimisticStore.test.ts×1 | [superseded 2026-07-13 by A24] (superseded) Optimistic writes announce a store-wide pending — (**SUPERSEDED 2026-07-13 by A24** — the mask is deleted; optimistic writes are verdict-inert. Kept for the… | | A21 | superseded | `docs/SPEC-ASYNC-SEMANTICS.md:387` | — | question-scoped-pending.test.ts×3 spec-async-semantics.test.ts×3 | [superseded 2026-07-13 by A24] (superseded) The store-wide mask — (**SUPERSEDED 2026-07-13 by A24** — the store-wide mask is deleted with the mask model; nothing silences a new question. The effective… | | A22 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:201` | store.ts×1 | createProjection.draft-lifetime-3585.test.ts×1 spec-async-semantics.test.ts×1 visibility-oracle-store.states.ts×1 | [ruled, amended in place 2026-07-08] Pending is per-node; store-wide only for the firewall's own work — **Pending is per-node: store-wide verdicts exist only as the firewall's own in-flight work (A9) … | @@ -72,13 +72,13 @@ Status legend: **live** stated and standing · **ruled** carries an explicit rul | A25 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:281` | projection.ts×1 | derived-presence-async-3726.test.ts×2 uninitialized-visibility.test.ts×3 visibility-oracle-store.states.ts×7 visibility-oracle-store.test.ts×1 | [ruled 2026-07-16] A derived store's seed is a draft, never an observable value — (**ruled 2026-07-16**, #2897) **A derived store's seed is a draft, never an observable value.** The seed exists for th… | | A26 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:87` | — | action-await-contract.test.ts×2 fuzz-findings-l2.test.ts×1 posture-store-parity.test.ts×2 visibility-oracle-store.states.ts×2 visibility-oracle.states.ts×1 visibility-oracle.test.ts×1 | [ruled 2026-07-17] An ambient transaction window is one flush; parking is flush-driven — (**ruled 2026-07-17**, #2913; **enforcement hardened 2026-08-31**, #3141 — parking is flush-driven, and a trans… | | A27 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:273` | — | loading-value.test.ts×2 visibility-oracle.states.ts×18 visibility-oracle.test.ts×1 | [ruled 2026-08-10] The commit-#0 loading window is loading-class and verdict-quiet — (**ruled 2026-08-10**) **The commit-#0 loading window is loading-class and verdict-quiet.** A node born committed v… | -| A28 | ruled, mechanism landed | `docs/SPEC-ASYNC-SEMANTICS.md:71` | constants.ts×1 core.ts×12 lanes.ts×2 scheduler.ts×3 types.ts×1 verdict.ts×2 store.ts×4 | createOptimistic.test.ts×5 fuzz-findings-l2.test.ts×2 held-derivation-not-a-proposal-3612.test.ts×1 latest-held-till-flush.test.ts×1 posture-store-parity.test.ts×5 question-scoped-pending.test.ts×3 snapshot-derived-store-rows.test.ts×1 createOptimisticStore.test.ts×10 optimistic-draft-visibility-3665.test.ts×5 optimistic-list-mutation-matrix.harness.ts×1 optimistic-list-mutation-matrix.test.ts×2 shallow.test.ts×1 woken-transaction-adopts-staged-bump.test.ts×1 treeshake.test.ts×2 verdict-contract.test.ts×1 visibility-oracle-store.states.ts×8 visibility-oracle.states.ts×8 | [ruled, mechanism landed 2026-09-15] A write becomes visible at flush — to every channel — (**ruled 2026-09-08**; supersedes the #2922 mid-tick pull) **A write becomes visible at flush — to every chan… | -| A29 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:95` | boundaries.ts×1 action.ts×1 constants.ts×1 core.ts×8 effect.ts×1 scheduler.ts×3 signals.ts×1 store.ts×1 | adoption-unchanged-key-read-3706.test.ts×9 body-end-supersession-visibility.test.ts×1 born-held.test.ts×3 boundary-not-born-held-3540.test.ts×4 createProjection.draft-lifetime-3585.test.ts×1 direct-commit-readers-posture.test.ts×1 fuzz-findings-l2.test.ts×2 held-conditional-memo.test.ts×1 held-frame-dependencies.test.ts×2 held-truth-lane-only.test.ts×3 l2-contract.test.ts×1 latest-held-till-flush.test.ts×2 mount-over-foreign-hold-3761.test.ts×1 optimistic-read-lane-not-transaction-3698.test.ts×3 posture-born-held-and-observation.test.ts×1 posture-store-parity.test.ts×6 derived-presence-async-3726.test.ts×3 optimistic-untracked-reads-f3-f5.test.ts×1 store-unchanged-read-independent-write-3688.test.ts×1 tick-scoped-pass-transaction.test.ts×2 treeshake.test.ts×3 visibility-oracle-store.states.ts×5 visibility-oracle-store.test.ts×1 visibility-oracle.states.ts×9 visibility-oracle.test.ts×1 write-proposals-3494.test.ts×1 | [ruled, amended in place 2026-09-13 (#3408)] A tracked read served a live transaction's staged value enters that transaction — A tracked computation served a node's staged `_pendingValue` — a value a … | -| A30 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:229` | async.ts×3 attribution.ts×1 constants.ts×1 core.ts×1 effect.ts×1 scheduler.ts×4 | async-landing-deps-3461.test.ts×3 fuzz-findings-l2.test.ts×2 held-conditional-effect.test.ts×1 held-conditional-memo.test.ts×1 held-frame-dependencies.test.ts×2 ispending-in-boundary-on-3528.test.ts×1 lane-frame-deferred-run-3662.test.ts×1 lane-frame-held-lane-3662.test.ts×1 posture-born-held-and-observation.test.ts×1 treeshake.test.ts×2 write-proposals-3494.test.ts×2 zombie-rerun-after-commit-3546.test.ts×2 | [ruled 2026-09-13 (#3410)] A memo's dependencies are the committed frame's until the frame is replaced — A pass that _staged_ its value has not replaced the committed frame, so the committed value sti… | -| A31 | live | `docs/SPEC-ASYNC-SEMANTICS.md:103` | boundaries.ts×1 core.ts×1 verdict.ts×1 | fuzz-findings-l2.test.ts×2 ispending-combined-atomic-3442.test.ts×1 | [live 2026-09-14 (#3442)] A memo computes under its own lane posture, never its puller's — A memo's value is one shared slot every reader sees, so its pass runs under the lane posture the memo itself … | +| A28 | ruled, mechanism landed | `docs/SPEC-ASYNC-SEMANTICS.md:71` | constants.ts×1 core.ts×12 lanes.ts×2 scheduler.ts×3 types.ts×1 verdict.ts×2 store.ts×4 | createOptimistic.test.ts×5 fuzz-findings-l2.test.ts×4 held-derivation-not-a-proposal-3612.test.ts×1 latest-held-till-flush.test.ts×1 posture-store-parity.test.ts×5 question-scoped-pending.test.ts×3 snapshot-derived-store-rows.test.ts×1 createOptimisticStore.test.ts×10 optimistic-draft-visibility-3665.test.ts×5 optimistic-list-mutation-matrix.harness.ts×1 optimistic-list-mutation-matrix.test.ts×2 shallow.test.ts×1 woken-transaction-adopts-staged-bump.test.ts×1 treeshake.test.ts×2 verdict-contract.test.ts×1 visibility-oracle-store.states.ts×8 visibility-oracle.states.ts×8 | [ruled, mechanism landed 2026-09-15] A write becomes visible at flush — to every channel — (**ruled 2026-09-08**; supersedes the #2922 mid-tick pull) **A write becomes visible at flush — to every chan… | +| A29 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:95` | boundaries.ts×1 action.ts×1 constants.ts×1 core.ts×9 effect.ts×1 scheduler.ts×3 signals.ts×1 store.ts×1 | adoption-unchanged-key-read-3706.test.ts×9 body-end-supersession-visibility.test.ts×1 born-held.test.ts×3 boundary-not-born-held-3540.test.ts×4 createProjection.draft-lifetime-3585.test.ts×1 direct-commit-readers-posture.test.ts×1 fuzz-findings-l2.test.ts×4 held-conditional-memo.test.ts×1 held-frame-dependencies.test.ts×2 held-truth-lane-only.test.ts×3 l2-contract.test.ts×1 latest-held-till-flush.test.ts×2 mount-over-foreign-hold-3761.test.ts×1 optimistic-read-lane-not-transaction-3698.test.ts×3 posture-born-held-and-observation.test.ts×1 posture-store-parity.test.ts×6 derived-presence-async-3726.test.ts×3 optimistic-untracked-reads-f3-f5.test.ts×1 store-unchanged-read-independent-write-3688.test.ts×1 tick-scoped-pass-transaction.test.ts×2 treeshake.test.ts×3 visibility-oracle-store.states.ts×5 visibility-oracle-store.test.ts×1 visibility-oracle.states.ts×9 visibility-oracle.test.ts×1 write-proposals-3494.test.ts×1 | [ruled, amended in place 2026-09-13 (#3408)] A tracked read served a live transaction's staged value enters that transaction — A tracked computation served a node's staged `_pendingValue` — a value a … | +| A30 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:229` | async.ts×3 attribution.ts×1 constants.ts×1 core.ts×1 effect.ts×1 scheduler.ts×5 | async-landing-deps-3461.test.ts×3 fuzz-findings-l2.test.ts×2 held-conditional-effect.test.ts×1 held-conditional-memo.test.ts×1 held-frame-dependencies.test.ts×2 ispending-in-boundary-on-3528.test.ts×1 lane-frame-deferred-run-3662.test.ts×1 lane-frame-held-lane-3662.test.ts×1 posture-born-held-and-observation.test.ts×1 treeshake.test.ts×2 write-proposals-3494.test.ts×2 zombie-rerun-after-commit-3546.test.ts×2 | [ruled 2026-09-13 (#3410)] A memo's dependencies are the committed frame's until the frame is replaced — A pass that _staged_ its value has not replaced the committed frame, so the committed value sti… | +| A31 | live | `docs/SPEC-ASYNC-SEMANTICS.md:103` | boundaries.ts×1 core.ts×1 verdict.ts×2 | fuzz-findings-l2.test.ts×5 ispending-combined-atomic-3442.test.ts×1 | [live 2026-09-14 (#3442)] A memo computes under its own lane posture, never its puller's — A memo's value is one shared slot every reader sees, so its pass runs under the lane posture the memo itself … | | A32 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:111` | core.ts×4 lanes.ts×1 store.ts×1 | visibility-oracle-store.states.ts×8 visibility-oracle-store.test.ts×1 visibility-oracle.states.ts×9 visibility-oracle.test.ts×1 | [ruled 2026-09-14] Children-forbidden readers see the frame, not the graph — `createTrackedEffect` and `onSettled` callbacks are effect-phase code that runs after the frame is decided. They read the f… | | A33 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:245` | boundaries.ts×1 scheduler.ts×1 | async-chain-supersession.test.ts×2 boundary-not-born-held-3540.test.ts×2 fuzz-findings-l2.test.ts×2 ispending-in-boundary-on-3528.test.ts×2 loading-reset-collects-forwarded-3459.test.ts×3 | [ruled 2026-09-12 (#3375)] A fallback-caught flight holds no transaction; a Loading reset moves the hold onto the boundary — A `` boundary showing its fallback is the display of everything un… | -| A34 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:255` | constants.ts×1 core.ts×6 lanes.ts×2 scheduler.ts×2 store.ts×4 | a34-writes-then-derivations.test.ts×2 createMemo.test.ts×1 derived-write-then-derivation-3733.test.ts×1 finalize-reentry.test.ts×2 fuzz-findings-l2.test.ts×3 held-derivation-not-a-proposal-3612.test.ts×6 optimistic-list-mutation-matrix.test.ts×1 unchanged-presence-no-hold-3743.test.ts×3 woken-transaction-adopts-staged-bump.test.ts×1 transition-corpse-revival.test.ts×1 treeshake.test.ts×2 visibility-oracle.states.ts×2 write-proposals-3494.test.ts×5 | [ruled 2026-09-16 (#3494)] A write is a proposal: one on a held node entangles its tick; one that nets to the committed value is none — A write proposes a value for a node. **Held, both are suggestion… | +| A34 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:255` | constants.ts×1 core.ts×6 lanes.ts×2 scheduler.ts×2 store.ts×4 | a34-writes-then-derivations.test.ts×2 createMemo.test.ts×1 derived-write-then-derivation-3733.test.ts×1 finalize-reentry.test.ts×2 fuzz-findings-l2.test.ts×5 held-derivation-not-a-proposal-3612.test.ts×6 optimistic-list-mutation-matrix.test.ts×1 unchanged-presence-no-hold-3743.test.ts×3 woken-transaction-adopts-staged-bump.test.ts×1 transition-corpse-revival.test.ts×1 treeshake.test.ts×2 visibility-oracle.states.ts×2 write-proposals-3494.test.ts×5 | [ruled 2026-09-16 (#3494)] A write is a proposal: one on a held node entangles its tick; one that nets to the committed value is none — A write proposes a value for a node. **Held, both are suggestion… | ## V — fixed violations | id | status | defined | cited in src | cited in tests | statement (at definition) | diff --git a/packages/signals/src/core/async.ts b/packages/signals/src/core/async.ts index 81b5eeb49..e9556d66b 100644 --- a/packages/signals/src/core/async.ts +++ b/packages/signals/src/core/async.ts @@ -249,15 +249,22 @@ export function settlePendingSource(el: Computed, source: Computed = e let released: Computed[] | undefined; const visited = new Set>(); const settle = (node: Computed) => { - if (visited.has(node)) return; - // A conditional dropped this source, but another dependency can still - // carry it. Only retire pending state inherited through the recovered - // branch. Deliberately NOT marked visited on this early return: the - // carrying dependency may itself be a later branch of this same walk - // (two unchanged memos converging), and its visit must be free to - // re-examine this node once that branch has retired the source. - if (source !== el && retryReaches(node, source)) return; - if (!removePendingSource(node, source)) return; + if ( + visited.has(node) || + // A conditional dropped this source, but another dependency can still + // carry it. Only retire pending state inherited through the recovered + // branch. Deliberately NOT marked visited on this early return: the + // carrying dependency may itself be a later branch of this same walk + // (two unchanged memos converging), and its visit must be free to + // re-examine this node once that branch has retired the source. + (source !== el && retryReaches(node, source)) || + !removePendingSource(node, source) + ) + // A19: a verdict reader holds no pending of its own (it was re-derived + // when the source went pending, `propagateStatus`), and a landing + // equal to the committed value notifies nobody — the source settling + // is its verdict changing: it runs again. + return node._config & CONFIG_VERDICT && enqueueSub(node); visited.add(node); node._time = clock; const remaining = node._x?._pendingSources?.values().next().value; diff --git a/packages/signals/src/core/core.ts b/packages/signals/src/core/core.ts index 7a33a0d74..80f163985 100644 --- a/packages/signals/src/core/core.ts +++ b/packages/signals/src/core/core.ts @@ -620,9 +620,10 @@ export function recompute(el: Computed, create: boolean = false): void { // revealed (its reveal shows it), as its staging once it has (the // next reveal promotes it; the screen keeps the revealed value). An // effect's value slot is private: the run the lane holds is what - // shows it. - if (isEffect) el._value = value; - else if (lane._shown) el._pendingValue = value; + // shows it — unless the effect was born held, whose first run is the + // commit's (A29): it re-stages, as below. + if (isEffect && el._pendingValue === NOT_PENDING) el._value = value; + else if (isEffect || lane._shown) el._pendingValue = value; else el._x!._lane = value; } else if ( create diff --git a/packages/signals/src/core/scheduler.ts b/packages/signals/src/core/scheduler.ts index bd687779b..2fd417b7b 100644 --- a/packages/signals/src/core/scheduler.ts +++ b/packages/signals/src/core/scheduler.ts @@ -229,7 +229,9 @@ export function merge(t: Transaction, f: Transaction): void { * one hop of `_subs` is the chain. Reads of the reader's LAST pass only (the * link's generation is the pass's): a tail kept for A30 — the committed * frame's dependency, awaiting the run that retires it — is not a read of - * the flight, and a reader that stopped reading releases it (O3, #3494). A + * the flight, and a reader that stopped reading releases it (O3, #3494). + * An errored pass (NotReady included) did not stop: it never got there, and + * its full list stands (A30) — the reads it did not reach still observe. A * flight nobody renders holds nothing (A29). Only the nodes `t` still owns * count: one a lane took over since is the lane's to wait on. A guess whose * own truth is in flight blocks the lane's parent, not the lane — it stands @@ -314,7 +316,7 @@ function blockedBy(nodes: Signal[], owner: Transaction, own = false): boole // over a held window, V5/A17). A probe alone does not hold. if ( ((r as any)._type === EFFECT_RENDER || r._flags & REACTIVE_FRAME_READ) && - s._gen === r._depGen && + (s._gen === r._depGen || r._x?._error != null) && !(r._flags & REACTIVE_DISPOSED) && onScreen(r, judge ?? owner) ) @@ -834,6 +836,10 @@ export class GlobalQueue implements IQueue { transactions.splice(k, 1); GlobalQueue._endLanes?.(u); land(u); + // A landing's commits can dispose a zombie a transaction judged above + // was blocked on (#3463: live "until the commit that disposes it") — + // judge them again. + k = transactions.length; } // The store folds the pending backings whose container nodes committed // — this flush's, or a landing's (store/store.ts installs it). diff --git a/packages/signals/src/core/verdict.ts b/packages/signals/src/core/verdict.ts index b21fa3222..516df817a 100644 --- a/packages/signals/src/core/verdict.ts +++ b/packages/signals/src/core/verdict.ts @@ -36,7 +36,8 @@ import { spectating, strictRead, tracking, - unflushedValue + unflushedValue, + verdict } from "./core.js"; import { warnStrictReadUntracked } from "./dev.js"; import { NotReadyError } from "./error.js"; @@ -113,10 +114,6 @@ function observeFlight(c: Computed, el: Computed): void { let latestActive = false; let probing = false; let probeFound = false; -/** `read` dispatches here while a window is open. */ -function setWindows(): void { - setVerdict(latestActive || probing ? verdictValue : null); -} /** A verdict read of a node a transaction (or lane) holds: the reading pass * is the holder's verdict lane's and re-derives at the holder's landing @@ -228,17 +225,20 @@ function markVerdictReader(window: number): void { } } -/** The windows. */ +/** The windows. `read` dispatches to `verdictValue` while one is open; each + * restores the dispatch it found — none inside a memo's pass a window + * pulled (`verdictValue`). */ export function latest(fn: () => T): T { markVerdictReader(2); - const prev = latestActive; + const prev = latestActive, + prevVerdict = verdict; latestActive = true; - setWindows(); + setVerdict(verdictValue); try { return fn(); } finally { latestActive = prev; - setWindows(); + setVerdict(prevVerdict); } } @@ -246,9 +246,10 @@ export function isPending(fn: () => any): boolean { markVerdictReader(1); const prevProbing = probing; const prevFound = probeFound; + const prevVerdict = verdict; probing = true; probeFound = false; - setWindows(); + setVerdict(verdictValue); try { fn(); return probeFound; @@ -264,7 +265,7 @@ export function isPending(fn: () => any): boolean { } finally { probing = prevProbing; probeFound = prevFound; - setWindows(); + setVerdict(prevVerdict); } } @@ -303,7 +304,14 @@ function verdictValue(el: Signal | Computed, c: Computed | null): const tracked = c !== null && tracking && !(c._config & CONFIG_CHILDREN_FORBIDDEN); if (c !== null && tracking) { link(el, c); - if (owner._fn !== undefined) pullComputed(owner, c); + if (owner._fn !== undefined) { + // A31: the pulled memo's pass is its own, not the probe's — it reads + // outside the window, and the probe's read below answers for what + // the pass produced. + setVerdict(null); + pullComputed(owner, c); + setVerdict(verdictValue); + } } // A live `affects()` mark on the node or a dependency (affects.ts): not // final by declaration — the value itself is read as below. The verdict is diff --git a/packages/signals/tests/fuzz-findings-l2.test.ts b/packages/signals/tests/fuzz-findings-l2.test.ts index bd1f85a4a..412a9772c 100644 --- a/packages/signals/tests/fuzz-findings-l2.test.ts +++ b/packages/signals/tests/fuzz-findings-l2.test.ts @@ -18,6 +18,7 @@ * F4 nested case 112 (S1 tear) F10 readiness cases 1320, 594 (L1, click) * F5 optimistic case 1590 (S1 tear) F11 readiness cases 1366, 10 (L1, S1) * F6 latest cases 330, 1216 (S1) F12 derived-readiness case 1793 (error) + * F13 latest cases 788, 930 (L1; the 2026-10-05 1000-case re-run) */ import { describe, expect, it, afterEach } from "vitest"; import { @@ -195,127 +196,124 @@ describe("fuzz findings on L2 — holds and boundaries", () => { // is "live for every hold until the commit that disposes it". A reader // mounted while a flight is up is born held (its mount control stays // unpublished); unmounting it in the same hold stages its removal; that - // hold lands (mount nets to committed) — and the reader survives it as a - // zombie no longer owned by any transaction, keeping an unrelated write - // (`src=0`, held only because the zombie observes its flight) unpublished - // although nothing on screen waits on anything (fuzzer P1). - it.fails( - "F3: a born-held reader dies with the commit that withdraws its mount, releasing an unrelated write (A29 ruling A, A15 #3463)", - async () => { - const gates = new Map void>(); - const [src, setSrc] = createSignal(0); - const [mounted, setMounted] = createSignal(false); - let shownSrc = -1, - shownMounted: boolean | undefined, - panel: unknown = "absent"; - let dispose!: () => void; - createRoot(d => { - dispose = d; - const a = createMemo(() => gated(gates, `a:${src()}`, src())); - const b = createMemo(() => { - const v = a(); - return gated(gates, `b:${v}`, v); - }); - createRenderEffect(src, v => { - shownSrc = v; - }); - createRenderEffect(mounted, v => { - shownMounted = v; - }); - createRenderEffect( - () => - mounted() - ? createRoot(d2 => { - createRenderEffect(b, v => { - panel = v; - }); - onCleanup(() => { - panel = "absent"; - }); - return d2; - }) - : undefined, - d2 => { - if (d2) onCleanup(d2); - } - ); + // hold lands (mount nets to committed) and its commits dispose the zombie. + // The seam judged the newer transaction (`src=0`, held only because the + // zombie observes its flight) first, blocked, and never again: a + // `schedule()` from the disposal inside the landing is overwritten by the + // flush's own `scheduled` recompute. The seam now re-judges the parked + // transactions after every landing (fuzzer P1). + it("F3: a write held only by a zombie lands with the commit that disposes it (A29 ruling A, A15 #3463)", async () => { + const gates = new Map void>(); + const [src, setSrc] = createSignal(0); + const [mounted, setMounted] = createSignal(false); + let shownSrc = -1, + shownMounted: boolean | undefined, + panel: unknown = "absent"; + let dispose!: () => void; + createRoot(d => { + dispose = d; + const a = createMemo(() => gated(gates, `a:${src()}`, src())); + const b = createMemo(() => { + const v = a(); + return gated(gates, `b:${v}`, v); }); - flush(); - gates.get("a:0")!(); - await drain(); - gates.get("b:0")!(); - await drain(); - expect([shownSrc, shownMounted, panel]).toEqual([0, false, "absent"]); - setSrc(1); // a:1 flies unobserved → commits - await drain(); - expect(shownSrc).toBe(1); - gates.get("a:1")!(); // b:1 starts, unobserved - await drain(); - setMounted(true); // the reader is born held on b:1; the mount is held with it - await drain(); - expect([shownMounted, panel]).toEqual([false, "absent"]); - setSrc(0); // a:0 — held while the (unshown) reader waits - await drain(); - expect(shownSrc).toBe(1); - setMounted(false); // the mount request is withdrawn: nets to committed, lands - await drain(); - expect([shownMounted, panel]).toEqual([false, "absent"]); - // Nothing visible observes any flight: the write publishes. - expect(shownSrc).toBe(0); - dispose(); - for (const g of gates.values()) g(); - await drain(); - } - ); + createRenderEffect(src, v => { + shownSrc = v; + }); + createRenderEffect(mounted, v => { + shownMounted = v; + }); + createRenderEffect( + () => + mounted() + ? createRoot(d2 => { + createRenderEffect(b, v => { + panel = v; + }); + onCleanup(() => { + panel = "absent"; + }); + return d2; + }) + : undefined, + d2 => { + if (d2) onCleanup(d2); + } + ); + }); + flush(); + gates.get("a:0")!(); + await drain(); + gates.get("b:0")!(); + await drain(); + expect([shownSrc, shownMounted, panel]).toEqual([0, false, "absent"]); + setSrc(1); // a:1 flies unobserved → commits + await drain(); + expect(shownSrc).toBe(1); + gates.get("a:1")!(); // b:1 starts, unobserved + await drain(); + setMounted(true); // the reader is born held on b:1; the mount is held with it + await drain(); + expect([shownMounted, panel]).toEqual([false, "absent"]); + setSrc(0); // a:0 — held while the (unshown) reader waits + await drain(); + expect(shownSrc).toBe(1); + setMounted(false); // the mount request is withdrawn: nets to committed, lands + await drain(); + expect([shownMounted, panel]).toEqual([false, "absent"]); + // Nothing visible observes any flight: the write publishes. + expect(shownSrc).toBe(0); + dispose(); + for (const g of gates.values()) g(); + await drain(); + }); // F4. A15: "writes whose async work is observed by a shared reader settle // as one unit (no tearing — nothing commits until all entangled async // resolves)"; A30: "an errored pass (a throw, NotReady included) keeps its // full list". The tuple reader observed m0's flight for `a=1`; a later // `b=1` re-runs it and the pass throws NotReady at `m1` before reaching - // `m0`. `blockedBy` reads only the pass's reads up to `_depsTail` (the O3 - // "stopped reading" rule), so the unreached `m0` counts as dropped, the - // hold on `a=1` lands, and `A=1` shows beside a tuple still derived from - // `a=0` while m0's flight is in the air. - it.fails( - "F4: a NotReady-interrupted pass still observes the flights it did not reach (A15, A30)", - async () => { - const gates = new Map void>(); - const [a, setA] = createSignal(0); - const [b, setB] = createSignal(0); - let shownA = -1; - let tuple: number[] = []; - let dispose!: () => void; - createRoot(d => { - dispose = d; - const m0 = createMemo(() => gated(gates, `a:${a()}`, a())); - const m1 = createMemo(() => Promise.resolve(b())); - createRenderEffect(a, v => { - shownA = v; - }); - createRenderEffect( - () => [b(), m1(), m0()], - t => { - tuple = t; - } - ); + // `m0`. `blockedBy` read only the pass's reads up to `_depsTail` (the O3 + // "stopped reading" rule), so the unreached `m0` counted as dropped, the + // hold on `a=1` landed, and `A=1` showed beside a tuple still derived + // from `a=0` while m0's flight was in the air. An errored pass did not + // stop reading: its whole list now observes. + it("F4: a NotReady-interrupted pass still observes the flights it did not reach (A15, A30)", async () => { + const gates = new Map void>(); + const [a, setA] = createSignal(0); + const [b, setB] = createSignal(0); + let shownA = -1; + let tuple: number[] = []; + let dispose!: () => void; + createRoot(d => { + dispose = d; + const m0 = createMemo(() => gated(gates, `a:${a()}`, a())); + const m1 = createMemo(() => Promise.resolve(b())); + createRenderEffect(a, v => { + shownA = v; }); - flush(); - gates.get("a:0")!(); - await drain(); - expect([shownA, tuple]).toEqual([0, [0, 0, 0]]); - setA(1); // m0 flies for a=1, observed by the tuple reader → a=1 held - await drain(); - expect([shownA, tuple]).toEqual([0, [0, 0, 0]]); - setB(1); // the reader re-runs and throws at m1, before reading m0 - await drain(4); - // a=1 must not show beside a tuple whose m0 still answers a=0. - expect(shownA === 1 ? tuple[2] : 0).toBe(shownA === 1 ? 1 : 0); - dispose(); - gates.get("a:1")?.(); - await drain(); - } - ); + createRenderEffect( + () => [b(), m1(), m0()], + t => { + tuple = t; + } + ); + }); + flush(); + gates.get("a:0")!(); + await drain(); + expect([shownA, tuple]).toEqual([0, [0, 0, 0]]); + setA(1); // m0 flies for a=1, observed by the tuple reader → a=1 held + await drain(); + expect([shownA, tuple]).toEqual([0, [0, 0, 0]]); + setB(1); // the reader re-runs and throws at m1, before reading m0 + await drain(4); + // a=1 must not show beside a tuple whose m0 still answers a=0. + expect(shownA === 1 ? tuple[2] : 0).toBe(shownA === 1 ? 1 : 0); + dispose(); + gates.get("a:1")?.(); + await drain(); + }); }); describe("fuzz findings on L2 — lanes", () => { @@ -520,128 +518,229 @@ describe("fuzz findings on L2 — lanes", () => { dispose(); } ); + + // F13. A29 (creation-time form): a reader born held is "staged into [the + // transaction], committed with it" — `recompute`'s own note: "An effect + // still carrying an uncommitted staged value re-stages: the commit applies + // the latest pass, not the born-held one"; A28: a write is visible at flush + // to every channel. A `latest(source)` reader whose mount is withdrawn in + // the action's tick (adopted, O1) and restored in the next (A34 (1)) is + // born held with its creation value (`_pendingValue` 0); the action's + // final write re-runs it as the verdict lane's work. The lane arm wrote an + // effect's run into its private `_value` (1) beside the born-held staging, + // and the lane's seam then committed the node (`commitPendingNode`), + // applying the stale staging (0) and queuing the run with it: the + // committed truth never showed. A lane pass of an effect still carrying a + // staging now re-stages it, as the frame's own pass does. + it("F13: a born-held latest() reader re-mounted during the hold shows the action's final write (A29, A28)", async () => { + const [source, setSource] = createSignal(0); + const [mounted, setMounted] = createSignal(true); + let resume!: () => void; + let run!: () => Promise; + let child: unknown = "absent"; + let dispose!: () => void; + createRoot(d => { + dispose = d; + createRenderEffect(mounted, () => {}); + createRenderEffect( + () => + mounted() + ? createRoot(dd => { + createRenderEffect( + () => latest(source), + v => { + child = v; + } + ); + onCleanup(() => { + child = "absent"; + }); + return dd; + }) + : undefined, + dd => { + if (dd) onCleanup(dd); + } + ); + run = action(function* () { + yield new Promise(r => { + resume = r; + }); + setSource(1); + }); + }); + flush(); + expect(child).toBe(0); + const p = run(); + setMounted(false); // the action's tick: the unmount rides with it (O1) + await drain(); + setMounted(true); // a write to a held node: joins the hold (A34 (1)) + await drain(); + resume(); // the body ends with the truth: source = 1 + await p; + await drain(4); + expect(child).toBe(1); + dispose(); + }); }); describe("fuzz findings on L2 — verdicts", () => { // F9. A19: "`isPending(x)` ≡ the observable value is not final … final the // moment [no cause] holds it"; A34 (2): a tick whose writes net to the // committed value "pends nothing: `isPending` stays false". `setSrc(1); - // setSrc(0)` re-asks the async memo for the committed input; the quiet - // re-ask lands and nothing is held — yet a probe-only reader of the memo - // reads `true` forever. (With a plain data reader beside it the verdict - // settles; the probe alone is stranded.) - it.fails( - "F9: a probe-only isPending settles after a coalesced toggle's re-ask lands (A19, A34 (2))", - async () => { - const [src, setSrc] = createSignal(0); - let verdict: unknown = "unpublished"; - let dispose!: () => void; - createRoot(d => { - dispose = d; - const m = createMemo(() => Promise.resolve(src())); - createRenderEffect( - () => isPending(m), - v => { - verdict = v; - } - ); + // setSrc(0)` re-asks the async memo for the committed input; the probe + // reader was re-derived when the memo went pending (`propagateStatus`'s + // verdict arm) and read `true`. The re-ask landed equal to the committed + // value: `setSignal` notified nobody, and the settle walk skipped the + // reader — a verdict reader holds no pending source of its own — so it + // read `true` forever. (A plain data reader beside it re-ran on its own + // settle and took the probe with it.) The settle walk now re-derives a + // verdict reader it reaches: the source settling is its verdict changing. + it("F9: a probe-only isPending settles when a coalesced toggle's re-ask lands silently (A19, A34 (2))", async () => { + const [src, setSrc] = createSignal(0); + let verdict: unknown = "unpublished"; + let dispose!: () => void; + createRoot(d => { + dispose = d; + const m = createMemo(() => Promise.resolve(src())); + createRenderEffect( + () => isPending(m), + v => { + verdict = v; + } + ); + }); + await drain(); + expect(verdict).toBe(false); + setSrc(1); + setSrc(0); // nets to the committed value: no proposal + await drain(5); + expect(verdict).toBe(false); + dispose(); + }); + + // F10. A31: "A memo computes under its own lane posture, never its + // puller's"; A28: "a write becomes visible at flush — to every channel". A + // gated `isPending` reader revealed in the same tick as a write to the + // probed memo's source (`setShow(true); setSrc(1)`, memo between) pulled + // the memo from inside its window: `verdictValue` → `pullComputed(m)` → + // `m`'s pass read `src` with the window's dispatch still installed, and + // the unheld-staged arm served it the committed `0`. `m` cached the + // committed input, its plain reader stayed on the old value for good, and + // the write never published. The pulled pass now reads outside the window + // (the probe's own read of `m` answers the verdict afterwards), and a + // window restores the dispatch it found, so a window inside that pass + // closes back to none. + it("F10: a memo an isPending probe pulls computes from the flushed write, which publishes (A31, A28)", async () => { + const [src, setSrc] = createSignal(0); + const [show, setShow] = createSignal(false); + let data: unknown = "unpublished"; + let verdict: unknown = "unpublished"; + let dispose!: () => void; + createRoot(d => { + dispose = d; + const m = createMemo(() => src()); + createRenderEffect(m, v => { + data = v; }); - await drain(); - expect(verdict).toBe(false); - setSrc(1); - setSrc(0); // nets to the committed value: no proposal - await drain(5); - expect(verdict).toBe(false); - dispose(); - } - ); + createRenderEffect( + () => (show() ? isPending(() => m()) : "hidden"), + v => { + verdict = v; + } + ); + }); + flush(); + expect([data, verdict]).toEqual([0, "hidden"]); + setShow(true); + setSrc(1); + await drain(); + expect([data, verdict]).toEqual([1, false]); + dispose(); + }); - // F10. A28: "a write becomes visible at flush — to every channel". A gated - // `isPending` reader revealed in the same tick as a write to the probed - // memo's source (`setShow(true); setSrc(1)`, memo between) leaves the - // memo's plain reader on the old value for good: no flight exists, the - // verdict reads `false`, and the write is never published. (Writing first, - // or probing the signal directly, is fine.) - it.fails( - "F10: a sync write beside a same-tick reveal of a gated isPending reader publishes (A28)", - async () => { - const [src, setSrc] = createSignal(0); - const [show, setShow] = createSignal(false); - let data: unknown = "unpublished"; - let verdict: unknown = "unpublished"; - let dispose!: () => void; - createRoot(d => { - dispose = d; - const m = createMemo(() => src()); - createRenderEffect(m, v => { - data = v; - }); - createRenderEffect( - () => (show() ? isPending(() => m()) : "hidden"), - v => { - verdict = v; - } - ); + // F10, nested: the pulled memo's own window (an `isPending` inside its + // body) closes back to no window, not to the puller's — the rest of the + // memo's pass is still its own. + it("F10 (nested window): a memo an isPending probe pulls keeps its own reads outside the probe after its own window closes (A31)", async () => { + const [src, setSrc] = createSignal(0); + const [show, setShow] = createSignal(false); + let data: unknown = "unpublished"; + let verdict: unknown = "unpublished"; + let dispose!: () => void; + createRoot(d => { + dispose = d; + const m = createMemo(() => { + isPending(() => 0); + return src(); }); - flush(); - expect([data, verdict]).toEqual([0, "hidden"]); - setShow(true); - setSrc(1); - await drain(); - expect([data, verdict]).toEqual([1, false]); - dispose(); - } - ); + createRenderEffect(m, v => { + data = v; + }); + createRenderEffect( + () => (show() ? isPending(() => m()) : "hidden"), + v => { + verdict = v; + } + ); + }); + flush(); + setShow(true); + setSrc(1); + await drain(); + expect([data, verdict]).toEqual([1, false]); + dispose(); + }); // F11. A31: "A memo computes under its own lane posture, never its // puller's" (#3442: the probe's pull of `copy` made it read the in-flight // `slow` as its committed value); A19 exc. 1 / A7: an uninitialized source // throws, a value is never fabricated. A sibling `isPending(() => [a(), // c()])` probe pulls `c` (async over a sync memo over async `a`) during - // the initial load; `c`'s pass reads `b` as `undefined` instead of - // suspending, and the plain data reader never publishes at all. - it.fails( - "F11: a memo pulled by an isPending probe suspends on its uninitialized input (A31, A19 exc. 1)", - async () => { - const [src] = createSignal(0); - const gates = new Map void>(); - const inputsSeen: unknown[] = []; - let data: unknown = "unpublished"; - let dispose!: () => void; - createRoot(d => { - dispose = d; - const a = createMemo(() => gated(gates, `a:${src()}`, src())); - const b = createMemo(() => a() + 1); - const c = createMemo(() => { - const v = b(); - inputsSeen.push(v); - return gated(gates, `c:${v}`, v * 10); - }); - createRenderEffect( - () => isPending(() => [a(), c()]), - () => {} - ); - createRenderEffect( - () => [a(), c()], - v => { - data = v; - } - ); + // the initial load; `c`'s pass — run inside the probe's window — read the + // uninitialized `b`'s staging through the unheld-staged arm as + // `undefined` instead of suspending, and the plain data reader never + // published at all. F10's change: the pulled pass reads outside the + // window, as a plain pass, and suspends. + it("F11: a memo an isPending probe pulls suspends on its uninitialized input (A31, A19 exc. 1)", async () => { + const [src] = createSignal(0); + const gates = new Map void>(); + const inputsSeen: unknown[] = []; + let data: unknown = "unpublished"; + let dispose!: () => void; + createRoot(d => { + dispose = d; + const a = createMemo(() => gated(gates, `a:${src()}`, src())); + const b = createMemo(() => a() + 1); + const c = createMemo(() => { + const v = b(); + inputsSeen.push(v); + return gated(gates, `c:${v}`, v * 10); }); + createRenderEffect( + () => isPending(() => [a(), c()]), + () => {} + ); + createRenderEffect( + () => [a(), c()], + v => { + data = v; + } + ); + }); + await drain(); + for (const g of [...gates.values()]) { + g(); await drain(); - for (const g of [...gates.values()]) { - g(); - await drain(); - } - for (const g of [...gates.values()]) { - g(); - await drain(); - } - expect(inputsSeen.every(v => Number.isFinite(v))).toBe(true); - expect(data).toEqual([0, 10]); - dispose(); } - ); + for (const g of [...gates.values()]) { + g(); + await drain(); + } + expect(inputsSeen.every(v => Number.isFinite(v))).toBe(true); + expect(data).toEqual([0, 10]); + dispose(); + }); }); describe("fuzz findings on L2 — crashes", () => { diff --git a/scripts/size/floor-caps.json b/scripts/size/floor-caps.json index 438c4451e..0fb36068d 100644 --- a/scripts/size/floor-caps.json +++ b/scripts/size/floor-caps.json @@ -1,9 +1,9 @@ { - "signals: core floor (createSignal/Memo/Effect/Root/flush)": "7.33 KB", - "app: render + one signal (the simple-app floor)": "9.83 KB", - "app: hydrating (no stores) with Show/For/Loading/Errored/lazy": "17.66 KB", + "signals: core floor (createSignal/Memo/Effect/Root/flush)": "7.35 KB", + "app: render + one signal (the simple-app floor)": "9.86 KB", + "app: hydrating (no stores) with Show/For/Loading/Errored/lazy": "17.71 KB", "page: base server components (hydrating + dynamic + frames + sf reference)": "44.84 KB", - "page: live server components (base + live/GET + action + isPending/latest)": "48.47 KB", + "page: live server components (base + live/GET + action + isPending/latest)": "48.51 KB", "server: floor (getRequestEvent + isServer)": "1.34 KB", "server: renderToString (the server-render floor)": "20.42 KB" } diff --git a/scripts/size/scenarios.js b/scripts/size/scenarios.js index d1f01237d..0782bcbab 100644 --- a/scripts/size/scenarios.js +++ b/scripts/size/scenarios.js @@ -461,6 +461,14 @@ module.exports = [ // above is gone (no heap refusal, no `_manualWriteTime`, no // `deleteFromHeap` in the setter). Reverts the #3740 raise. The cap is // frozen again at 9.51 KB. + // Size-Exception (fuzz Batch A, #3801, 2026-10-05): 7.33 -> 7.35 KB, + // measured at 7,338 B by CI against `next` @ b0bad0267's 7,319 + // (+19 B; 8 B over the cap; +56 B minified) — fuzz Batch A + // F3/F4/F9/F10-11/F13 (+56 B minified batch on the core floor: landing + // re-judge +11, blockedBy errored tail +16, verdict settle +16, born-held + // lane re-stage +13, verdict pull +1). Cap set at measured + 10 B rounded + // up to 0.01 KB. Accepted by the maintainer (2026-10-05). The cap is + // frozen again at 7.35 KB. limit: floorCaps["signals: core floor (createSignal/Memo/Effect/Root/flush)"], alias }, @@ -871,7 +879,15 @@ module.exports = [ // Lowered (#3774, 2026-10-04): 16.85 -> 14.53 KB, measured at 14.52 KB by CI // at 22c3d3e14 — the hold model (L2): the signals core rebuilt from its // rulings. Lowering is the ratchet; raise only with a Size-Exception. - limit: "14.53 KB", + // Size-Exception (fuzz Batch A, #3801, 2026-10-05): 14.53 -> 14.56 KB, + // measured at 14,546 B by CI against `next` @ b0bad0267's 14,504 + // (+42 B; 16 B over the cap; +56 B minified) — fuzz Batch A + // F3/F4/F9/F10-11/F13 (+56 B minified batch on the core floor: landing + // re-judge +11, blockedBy errored tail +16, verdict settle +16, born-held + // lane re-stage +13, verdict pull +1). Cap set at measured + 10 B rounded + // up to 0.01 KB. Accepted by the maintainer (2026-10-05). The cap is + // frozen again at 14.56 KB. + limit: "14.56 KB", alias }, { @@ -1104,7 +1120,15 @@ module.exports = [ // brotli layout. Cap set at measured + 10 B rounded up to 0.01 KB. // Accepted by the maintainer (2026-10-05). The cap is frozen again at // 9.47 KB. - limit: "9.47 KB", + // Size-Exception (fuzz Batch A, #3801, 2026-10-05): 9.47 -> 9.49 KB, + // measured at 9,476 B by CI against `next` @ b0bad0267's 9,456 + // (+20 B; 6 B over the cap; +57 B minified) — fuzz Batch A + // F3/F4/F9/F10-11/F13 (+56 B minified batch on the core floor: landing + // re-judge +11, blockedBy errored tail +16, verdict settle +16, born-held + // lane re-stage +13, verdict pull +1). Cap set at measured + 10 B rounded + // up to 0.01 KB. Accepted by the maintainer (2026-10-05). The cap is + // frozen again at 9.49 KB. + limit: "9.49 KB", alias }, { @@ -1286,6 +1310,14 @@ module.exports = [ // cap at once, this one the smallest minified. Accepted by the // maintainer (2026-10-04). The cap is frozen again at 9.83 KB // (measured + 10 B). + // Size-Exception (fuzz Batch A, #3801, 2026-10-05): 9.83 -> 9.86 KB, + // measured at 9,848 B by CI against `next` @ b0bad0267's 9,811 + // (+37 B; 18 B over the cap; +56 B minified) — fuzz Batch A + // F3/F4/F9/F10-11/F13 (+56 B minified batch on the core floor: landing + // re-judge +11, blockedBy errored tail +16, verdict settle +16, born-held + // lane re-stage +13, verdict pull +1). Cap set at measured + 10 B rounded + // up to 0.01 KB. Accepted by the maintainer (2026-10-05). The cap is + // frozen again at 9.86 KB. limit: floorCaps["app: render + one signal (the simple-app floor)"], alias }, @@ -1599,6 +1631,14 @@ module.exports = [ // floor note; brotli layout over the larger bundle. Accepted by the // maintainer (2026-10-04). The cap is frozen again at 17.66 KB // (measured + 10 B). + // Size-Exception (fuzz Batch A, #3801, 2026-10-05): 17.66 -> 17.71 KB, + // measured at 17,695 B by CI against `next` @ b0bad0267's 17,648 + // (+47 B; 35 B over the cap; +56 B minified) — fuzz Batch A + // F3/F4/F9/F10-11/F13 (+56 B minified batch on the core floor: landing + // re-judge +11, blockedBy errored tail +16, verdict settle +16, born-held + // lane re-stage +13, verdict pull +1). Cap set at measured + 10 B rounded + // up to 0.01 KB. Accepted by the maintainer (2026-10-05). The cap is + // frozen again at 17.71 KB. limit: floorCaps["app: hydrating (no stores) with Show/For/Loading/Errored/lazy"], alias }, @@ -2008,7 +2048,15 @@ module.exports = [ // bundle (before #3791 the same +5 B measured +2 B here). Accepted by // the maintainer (2026-10-04). The cap is frozen again at 28.80 KB // (measured + 10 B). - limit: "28.80 KB", + // Size-Exception (fuzz Batch A, #3801, 2026-10-05): 28.80 -> 28.84 KB, + // measured at 28,829 B by CI against `next` @ b0bad0267's 28,736 + // (+93 B; 29 B over the cap; +56 B minified) — fuzz Batch A + // F3/F4/F9/F10-11/F13 (+56 B minified batch on the core floor: landing + // re-judge +11, blockedBy errored tail +16, verdict settle +16, born-held + // lane re-stage +13, verdict pull +1). Cap set at measured + 10 B rounded + // up to 0.01 KB. Accepted by the maintainer (2026-10-05). The cap is + // frozen again at 28.84 KB. + limit: "28.84 KB", alias }, { @@ -2222,7 +2270,15 @@ module.exports = [ // Lowered (#3774, 2026-10-04): 14.94 -> 12.82 KB, measured at 12.81 KB by CI // at 22c3d3e14 — the hold model (L2): the signals core rebuilt from its // rulings. Lowering is the ratchet; raise only with a Size-Exception. - limit: "12.82 KB", + // Size-Exception (fuzz Batch A, #3801, 2026-10-05): 12.82 -> 12.86 KB, + // measured at 12,847 B by CI against `next` @ b0bad0267's 12,815 + // (+32 B; 27 B over the cap; +56 B minified) — fuzz Batch A + // F3/F4/F9/F10-11/F13 (+56 B minified batch on the core floor: landing + // re-judge +11, blockedBy errored tail +16, verdict settle +16, born-held + // lane re-stage +13, verdict pull +1). Cap set at measured + 10 B rounded + // up to 0.01 KB. Accepted by the maintainer (2026-10-05). The cap is + // frozen again at 12.86 KB. + limit: "12.86 KB", alias }, { @@ -2486,7 +2542,15 @@ module.exports = [ // Lowered (#3774, 2026-10-04): 16.48 -> 14.39 KB, measured at 14.38 KB by CI // at 22c3d3e14 — the hold model (L2): the signals core rebuilt from its // rulings. Lowering is the ratchet; raise only with a Size-Exception. - limit: "14.39 KB", + // Size-Exception (fuzz Batch A, #3801, 2026-10-05): 14.39 -> 14.46 KB, + // measured at 14,446 B by CI against `next` @ b0bad0267's 14,388 + // (+58 B; 56 B over the cap; +56 B minified) — fuzz Batch A + // F3/F4/F9/F10-11/F13 (+56 B minified batch on the core floor: landing + // re-judge +11, blockedBy errored tail +16, verdict settle +16, born-held + // lane re-stage +13, verdict pull +1). Cap set at measured + 10 B rounded + // up to 0.01 KB. Accepted by the maintainer (2026-10-05). The cap is + // frozen again at 14.46 KB. + limit: "14.46 KB", alias: observeAlias }, { @@ -3336,6 +3400,14 @@ module.exports = [ // observeFlight first-observer hold +17); brotli layout. Cap set at // measured + 10 B rounded up to 0.01 KB. Accepted by the maintainer // (2026-10-05). The cap is frozen again at 48.47 KB. + // Size-Exception (fuzz Batch A, #3801, 2026-10-05): 48.47 -> 48.51 KB, + // measured at 48,495 B by CI against `next` @ b0bad0267's 48,454 + // (+41 B; 25 B over the cap; +59 B minified) — fuzz Batch A + // F3/F4/F9/F10-11/F13 (+56 B minified batch on the core floor: landing + // re-judge +11, blockedBy errored tail +16, verdict settle +16, born-held + // lane re-stage +13, verdict pull +1). Cap set at measured + 10 B rounded + // up to 0.01 KB. Accepted by the maintainer (2026-10-05). The cap is + // frozen again at 48.51 KB. limit: floorCaps["page: live server components (base + live/GET + action + isPending/latest)"], alias: pageAlias },