diff --git a/.changeset/fix-loading-reset-collects-forwarded.md b/.changeset/fix-loading-reset-collects-forwarded.md
new file mode 100644
index 000000000..489010b12
--- /dev/null
+++ b/.changeset/fix-loading-reset-collects-forwarded.md
@@ -0,0 +1,7 @@
+---
+"@solidjs/signals": patch
+---
+
+A `Loading` boundary whose `on` resets keeps its fallback until every reader under it has settled, not only the ones that notified after the reset (#3459).
+
+- The reset clears the boundary's collected sources and re-collects from the pending notifications that follow. A reader already pending from an earlier write never re-notifies (status propagation dedupes on its `_pendingSources`), so a sibling reader's fresh flight was the only source collected, and its landing revealed the still-flying one stale: `B: 1 | Fast: 1 | Slow: 0` for two seconds. The reset now also harvests what its forwarded readers still wait on from the live transactions' `_asyncReporters` (INV-3, the one record of a forwarded reader), so the hold the #3375 ruling takes off the lane lands on the boundary instead: `B: 1 | Loading`, then `Fast: 1 | Slow: 1` together.
diff --git a/packages/signals/docs/INTERNALS-ASYNC-STATE.md b/packages/signals/docs/INTERNALS-ASYNC-STATE.md
index 59f42f9c8..57b74f0b6 100644
--- a/packages/signals/docs/INTERNALS-ASYNC-STATE.md
+++ b/packages/signals/docs/INTERNALS-ASYNC-STATE.md
@@ -105,7 +105,7 @@ Semantics of the `(_pendingValue, _overrideValue)` pair for an optimistic node
- `onSettled` and the revert re-derive (#3411): the settle drops the overrides in the commit pass and only _enqueues_ their subscribers (the revert's `insertSubs`, the contested and gated replays, the store clears); the pass after re-derives them, and reads do not pull (`prepareComputed(el, false)`). An unowned `onSettled` callback is a one-shot in the commit pass's user phase, so it read the optimistic source already reverted next to a sync memo of it still holding the optimistic value. The fire waits for the heap to drain instead — while `dirtyQueue` has work it re-enqueues itself (`run` swaps the queue, so the re-enqueue lands in the next pass; `enqueue` keeps the drain alive) — which is what settled means: no derivation outstanding. Forcing the re-derive into the commit pass is not an option: an optimistic write completing in its own flush shows its lane frame (applied by `cleanupCompletedLanes`) before the truth, and the store clears compose a torn shape when their readers re-derive inside finalize (#3164).
- Finalize re-entry (#3319): `finalizePureQueue` can _enter_ a held transaction partway through — a store commit hook (`bumpDeep` on a node the transaction owns), a boundary `_checkSources` write, or a stamped recompute in its heap — and `initTransition` then adopts the batch being finalized. Two rules keep that consistent. **State:** finalize captures the batch it started with and, if `currentBatch` changed, commits/reverts nothing batch-derived (the entered transaction owns it now); a _completing_ transaction whose ambient batch was separate (the #2916 shape) still settles its own containers, since adoption never touched them. **Effects:** ownership. A run applies with the commit of the transaction that computed its value (`Effect._valueTransition`). The ordinary effect phase runs with `activeTransition` set only in a flush whose finalize entered one, so `runEffect` leaves runs owned by a still-held transaction queued — `_modified` stays set — for the next gate to stash with the owner, while everything computed mainline (the write that caused the flush) applies now. Lanes are exempt by construction: they apply their own effects ahead of their transaction (the optimistic view) and their runner ORs `LANE_RUN` into the `type` it passes; the creation-time immediate run in `effect()` passes it too. The exemption is keyed on the _effect_ still having a lane, not on the runner: after a supersession demotes the cascade (#3331, §1), a `LANE_RUN` runner reaching a now lane-less effect whose value was computed under a still-held transaction leaves it queued like any owned run — otherwise the lane would apply the corrected derivation ahead of the commit that is supposed to reveal it. Known residue: writes staged by a hook _before_ the entry are adopted (held) and, because finalize's heap runs after its hooks, their dependents recompute owner-stamped and park with them; an entry that happens _inside_ that heap can leave an earlier mainline-computed effect applied over an adopted source — narrow, and inherited from adoption rather than from this rule.
- `transitionComplete`: prunes dead reporters (`reporterBlocksSource`), transition is done when no live reporter still blocks a source that is **still pending** (a non-empty `_pendingSources`) and no active-override node is blocked on someone else's async. Judged by the set, not `_error.source` (#3375: a later-pending input overwrites it on propagation while the flight is still in the air) and not the self entry alone (#3462: an upstream re-ask that supersedes the source's own flight retires that entry and leaves the source pending on the re-ask — its reader still cannot render, and the landing folds the transaction in; judged complete instead, a re-entry before the landing, such as a repeated write to the held signal, committed the held writes beside the reader's stale frame).
-- Fallback-caught async holds nothing — in both orders (ruled 2026-09-12, #3375). A collecting boundary consumes the notification, so a reader under a fallback never registers. A reader registered while its boundary showed content (forwarded) stays registered when the boundary's `on` changes and it flips to the fallback; `reporterBlocksSource` therefore walks the reporter's `_queue._parent` chain and treats a reporter behind a collecting pending-type boundary (`_collectionType & STATUS_PENDING && !_initialized`) as not live. If nothing outside the boundary consumes the flight, the hold is over; a reader outside it still holds. The reset itself calls `wakeParked()` so the re-judgement happens in the same drain.
+- Fallback-caught async holds nothing — in both orders (ruled 2026-09-12, #3375). A collecting boundary consumes the notification, so a reader under a fallback never registers. A reader registered while its boundary showed content (forwarded) stays registered when the boundary's `on` changes and it flips to the fallback; `reporterBlocksSource` therefore walks the reporter's `_queue._parent` chain and treats a reporter behind a collecting pending-type boundary (`_collectionType & STATUS_PENDING && !_initialized`) as not live. If nothing outside the boundary consumes the flight, the hold is over; a reader outside it still holds. The reset itself calls `wakeParked()` so the re-judgement happens in the same drain. The hold moves onto the boundary, not off the screen (#3459): the reset also collects, from every live transaction's `_asyncReporters` (INV-3, the one record of a forwarded reader), the sources of each reporter it routes — `_holds`: under this queue with no collecting pending-type boundary between — plus that reporter's `_pendingSources`, and flips to the fallback if it found any. A forwarded reader already pending never re-notifies (status propagation dedupes on its `_pendingSources`), so without this a sibling reader's fresh flight was the only source collected, and its landing revealed the still-flying one stale (`B: 1 | Fast: 1 | Slow: 0`). Pinned: `tests/loading-reset-collects-forwarded-3459.test.ts`.
- Wake of parked transactions (`wokenTransitions`): the flush judges only the _active_ transaction; a parked one is re-entered by a stamped node's landing (`settleTransition`), a stamped recompute, or an action resuming. A reporter that stops counting for another reason — its boundary reset (above), or its disposal by ambient work (#3372: `disposeChildren(self)` on a node with `_transition` and `STATUS_PENDING`; a pending reader is always queued as a pending node, so the stamp is reliable) — is none of those: `reporterBlocksSource` would prune it at the next check, but no check comes, and the writes held with it stay staged. Such sites record the transaction (deduped) and schedule; the flush re-enters a woken transaction from the `finally` of a full pass — reached from the park exit and the normal exit alike — and only when idle: no `activeTransition` and `!scheduled`, which at that point means an empty dirty heap, no write since the heap ran (every write re-arms it) and, the finalize having reverted them, no optimistic ambient nodes. Entering adopts the ambient batch, and ambient work present at that instant would be held behind flights it never read; a wake in a pass with work just falls to the next. Entries are popped in a loop until one enters: a wake whose transaction completed by other means is a bare return (`initTransition` on `_done`) and must not strand the ones behind it. The fast drain defers to the full path while a wake is outstanding so such dead entries are still consumed. A wake with other live reporters re-parks; the idle pass is its only cost. Known shape: the ambient write that triggered the reset commits in its own pass and the released hold in the idle pass after it — two effect runs in one synchronous drain (`Sum: 1`, `Sum: 2` at the same clock time in the #3375 pin), never a visible tear.
- Staged reads enter (A29, #3408): `read()` calls `enterStagedRead` on every selection that returns `_pendingValue` — the fast paths and the slow path — and it enters `el._transition` unless that is null (ambient batch), already active, or the read is a probe (`pendingCheckActive`). The third entry beside `setSignal` on a stamped node and `recompute` of a stamped node; rule text and the `Panel: 1` beside `Count: 0` shape live in the spec. A stale (render) reader never reaches it: the carve-out below serves it the committed value.
- Dependencies are the committed frame's (A30, #3410): `recompute`'s tail trims the previous pass's dependency tail only for a pass that published or changed nothing (`_pendingValue === NOT_PENDING` and no `_error`); a staged pass leaves it linked and `commitPendingNode` trims after a clean pass (`_error == null` — a set `_error` means the last pass threw, kept its full list, and `_depsTail` marks where it stopped); an effect pass that direct-committed but still owes a run (`_modified`, #3438 — the flush may stash that run into a transaction it opens later) leaves it for `runEffect` to trim once the run applies. `__OBSERVE__` fan-in counting walks the validated prefix only, so a held tail does not inflate distinct-source counts. Why commit-time and not the pass, and the `Selected: 0` beside `Count: 1` shape: the spec.
diff --git a/packages/signals/docs/RULES-INDEX.md b/packages/signals/docs/RULES-INDEX.md
index 8e35aee70..0beb6f608 100644
--- a/packages/signals/docs/RULES-INDEX.md
+++ b/packages/signals/docs/RULES-INDEX.md
@@ -43,39 +43,39 @@ Status legend: **live** stated and standing · **ruled** carries an explicit rul
## A — spec propositions
-| id | status | defined | cited in src | cited in tests | statement (at definition) |
-| --- | ---------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| A1 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:231` | — | onCleanup.test.ts×2 transitionEntanglement.test.ts×4 | [ruled 2026-07-06] Effect error interception is compute-phase only — `EffectBundle.error` intercepts compute-phase errors only; effect-phase throws escalate to the nearest error boundary (halt if none… |
-| A2 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:239` | — | onCleanup.test.ts×2 | [ruled] Unhandled compute-phase errors in user effects are logged and skipped — Compute-phase errors in _user_ effects without a handler are logged and the run is skipped; the system keeps running. |
-| A3 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:247` | — | equals-comparator-errors.test.ts×1 | [ruled] Comparator throws are compute-phase errors — Errors thrown by a user `equals` comparator behave exactly like compute-phase errors (boundary-containable; loud halt without a boundary). |
-| A4 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:255` | — | equals-comparator-errors.test.ts×1 | [ruled] A custom `equals` never sees `undefined` prev on first commit — A custom `equals` is never invoked with `undefined` previous value on a node's first commit. |
-| A5 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:263` | — | errorHalt.test.ts×1 | [ruled] An error escaping every boundary halts the system — An error escaping every boundary permanently halts the system with `REACTIVITY_HALTED`; later writes log "Update ignored". |
-| A6 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:271` | — | enforceLoadingBoundary.test.ts×1 | [ruled] `ASYNC_OUTSIDE_LOADING_BOUNDARY` is warn-only — `ASYNC_OUTSIDE_LOADING_BOUNDARY` is a warn-only diagnostic; an `Errored` above must not swallow it and must not show its fallback for a pending. |
-| A7 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:109` | verdict.ts×2 | spec-async-semantics.test.ts×2 visibility-oracle-store.test.ts×1 visibility-oracle.test.ts×2 | [ruled, amended in place] Resolved async never reads `[false, undefined]` — After an async memo resolves, `[isPending(x), latest(x)]` is `[false, resolvedValue]` — never `[false, undefined]`. \*\*Amende… |
-| A8 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:117` | — | createMemo.test.ts×1 visibility-oracle-store.test.ts×1 visibility-oracle.test.ts×2 | [ruled, amended in place 2026-07-07] `isPending(() => latest(x))` follows `x`'s own async only — verdicts are per-channel — (**re-ruled 2026-07-07c** — was "tracks the transition the same as `isPendin… |
-| A9 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:125` | — | spec-async-semantics.test.ts×3 visibility-oracle-store.test.ts×5 | [ruled, amended in place 2026-07-07] Store leaves behind a firewall report the firewall's new-question refetch — `isPending` on a store leaf behind a firewall reports the firewall's refetch like any a… |
-| A10 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:133` | invariants.ts×1 verdict.ts×1 | createMemo.test.ts×1 ispending-memo-unstamped-hold-3457.test.ts×2 latest-isPending-consistency.test.ts×1 | [ruled] `[isPending(x), x()]` is atomic within one scope — `[isPending(x), x()]` read in one scope is atomic: a reader that observed the fresh value must not see `pending === true` for it. |
-| A11 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:51` | — | latest-isPending-consistency.test.ts×1 visibility-oracle-store.test.ts×1 visibility-oracle.test.ts×1 | [ruled] Sync derivations of held sources are visible through `latest()`/`isPending()` — Sync derivations of transition-held sources are visible through `latest()`/`isPending()` (held sync recompute is… |
-| A12 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:141` | — | createOptimistic.test.ts×2 spec-async-semantics.test.ts×1 | [ruled, amended in place] Resting optimistic nodes report pending like a plain memo — A resting optimistic node reports pending via exactly the causes a plain async memo does (A19) — a reverting optim… |
-| A13 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:149` | async.ts×1 | spec-async-semantics.test.ts×7 | [ruled 2026-07-06 (promoted from B1)] Resting optimistic ≡ plain async memo at every checkpoint — (was B1) A resting optimistic node (no active override) is observationally identical to a plain async … |
-| A14 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:157` | — | spec-async-semantics.test.ts×2 | [ruled, amended in place 2026-07-06 (promoted from B2)] Companion nodes get child lanes that do not merge with the owner — (was B2) `isPending`/`latest` companion nodes get child lanes that do not mer… |
-| A15 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:191` | async.ts×3 core.ts×3 lanes.ts×1 scheduler.ts×2 | async-chain-supersession.test.ts×1 lane-hold-on-observation.test.ts×1 overlapping-flights.test.ts×3 reveal-carve-out.test.ts×2 shared-effect-no-entangle.test.ts×1 spec-async-semantics.test.ts×2 stale-read-uninitialized-cross-transition.test.ts×1 superseded-source-blocks-3462.test.ts×2 treeshake.test.ts×4 visibility-oracle-store.test.ts×5 visibility-oracle.test.ts×7 | [ruled, amended in place 2026-07-06 (promoted from B3)] Transition entanglement is graph-driven; lanes settle as one reveal — (was B3) Transition entanglement is graph-driven: writes whose async work … |
-| A16 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:165` | verdict.ts×1 | spec-async-semantics.test.ts×1 strict-read-pending-store.test.ts×2 uninitialized-visibility.test.ts×1 visibility-oracle-store.test.ts×2 visibility-oracle.test.ts×3 | [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:31` | async.ts×3 constants.ts×2 core.ts×7 invariants.ts×3 optimistic.ts×5 scheduler.ts×2 verdict.ts×2 signals.ts×2 optimistic.ts×1 store.ts×3 | optimistic-undefined-override.test.ts×1 refresh-await.test.ts×1 reveal-gating-contract.test.ts×3 spec-async-semantics.test.ts×10 createOptimisticStore.test.ts×1 treeshake.test.ts×1 until.test.ts×1 visibility-oracle-store.test.ts×12 visibility-oracle.test.ts×26 | [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:41` | async.ts×2 constants.ts×1 core.ts×4 optimistic.ts×4 scheduler.ts×2 types.ts×2 verdict.ts×2 optimistic.ts×1 | body-end-supersession-visibility.test.ts×4 createOptimistic.test.ts×1 spec-async-semantics.test.ts×3 flight-owned-transaction.test.ts×1 superseded-before-first-commit.test.ts×5 visibility-oracle-store.test.ts×10 visibility-oracle.test.ts×25 | [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 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:93` | async.ts×1 core.ts×1 optimistic.ts×1 verdict.ts×1 | spec-async-semantics.test.ts×3 superseded-before-first-commit.test.ts×1 uninitialized-visibility.test.ts×1 visibility-oracle-store.test.ts×6 visibility-oracle.test.ts×12 | [ruled 2026-07-07 (promoted from C1)] `isPending(x)` ≡ the observable value is not final (three causes) — (was C1 — **partially reverses an earlier decision**) \*\*Definition: `isPending(x)` ≡ the value… |
-| A20 | superseded | `docs/SPEC-ASYNC-SEMANTICS.md:283` | invariants.ts×1 | 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:290` | — | 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 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:173` | — | spec-async-semantics.test.ts×1 visibility-oracle-store.test.ts×1 | [ruled 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) and the decree tha… |
-| A23 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:181` | — | spec-async-semantics.test.ts×1 | [ruled 2026-07-08] The `isPending` probe is reads-only — **The `isPending` probe is reads-only — the thunk's return value is never inspected.** `isPending(() => store)` reads nothing and reports `fals… |
-| A24 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:101` | — | optimistic-undefined-override.test.ts×1 reveal-gating-contract.test.ts×1 spec-async-semantics.test.ts×2 visibility-oracle-store.test.ts×2 visibility-oracle.test.ts×4 | [ruled 2026-07-13] Question-scoped pending: pending iff a value change is in flight or an `affects()` mark is live — (**ruled 2026-07-13** — supersedes A20/A21; the converged model from the #2844/#272… |
-| A25 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:221` | verdict.ts×1 | uninitialized-visibility.test.ts×3 visibility-oracle-store.test.ts×8 | [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:59` | scheduler.ts×1 | action-await-contract.test.ts×2 visibility-oracle-store.test.ts×1 visibility-oracle.test.ts×2 | [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:213` | — | loading-value.test.ts×2 visibility-oracle.test.ts×19 | [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… |
-| A29 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:67` | core.ts×4 effect.ts×1 optimistic.ts×1 | body-end-supersession-visibility.test.ts×1 born-held.test.ts×3 held-conditional-memo.test.ts×1 treeshake.test.ts×1 visibility-oracle-store.test.ts×4 visibility-oracle.test.ts×6 | [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:199` | async.ts×1 attribution.ts×1 core.ts×1 effect.ts×1 scheduler.ts×1 | async-landing-deps-3461.test.ts×3 held-conditional-effect.test.ts×1 held-conditional-memo.test.ts×1 treeshake.test.ts×1 | [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:75` | core.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 … |
-| A32 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:83` | — | visibility-oracle-store.test.ts×6 visibility-oracle.test.ts×10 | [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… |
+| id | status | defined | cited in src | cited in tests | statement (at definition) |
+| --- | ---------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| A1 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:231` | — | onCleanup.test.ts×2 transitionEntanglement.test.ts×4 | [ruled 2026-07-06] Effect error interception is compute-phase only — `EffectBundle.error` intercepts compute-phase errors only; effect-phase throws escalate to the nearest error boundary (halt if none… |
+| A2 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:239` | — | onCleanup.test.ts×2 | [ruled] Unhandled compute-phase errors in user effects are logged and skipped — Compute-phase errors in _user_ effects without a handler are logged and the run is skipped; the system keeps running. |
+| A3 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:247` | — | equals-comparator-errors.test.ts×1 | [ruled] Comparator throws are compute-phase errors — Errors thrown by a user `equals` comparator behave exactly like compute-phase errors (boundary-containable; loud halt without a boundary). |
+| A4 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:255` | — | equals-comparator-errors.test.ts×1 | [ruled] A custom `equals` never sees `undefined` prev on first commit — A custom `equals` is never invoked with `undefined` previous value on a node's first commit. |
+| A5 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:263` | — | errorHalt.test.ts×1 | [ruled] An error escaping every boundary halts the system — An error escaping every boundary permanently halts the system with `REACTIVITY_HALTED`; later writes log "Update ignored". |
+| A6 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:271` | — | enforceLoadingBoundary.test.ts×1 | [ruled] `ASYNC_OUTSIDE_LOADING_BOUNDARY` is warn-only — `ASYNC_OUTSIDE_LOADING_BOUNDARY` is a warn-only diagnostic; an `Errored` above must not swallow it and must not show its fallback for a pending. |
+| A7 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:109` | verdict.ts×2 | spec-async-semantics.test.ts×2 visibility-oracle-store.test.ts×1 visibility-oracle.test.ts×2 | [ruled, amended in place] Resolved async never reads `[false, undefined]` — After an async memo resolves, `[isPending(x), latest(x)]` is `[false, resolvedValue]` — never `[false, undefined]`. \*\*Amende… |
+| A8 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:117` | — | createMemo.test.ts×1 visibility-oracle-store.test.ts×1 visibility-oracle.test.ts×2 | [ruled, amended in place 2026-07-07] `isPending(() => latest(x))` follows `x`'s own async only — verdicts are per-channel — (**re-ruled 2026-07-07c** — was "tracks the transition the same as `isPendin… |
+| A9 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:125` | — | spec-async-semantics.test.ts×3 visibility-oracle-store.test.ts×5 | [ruled, amended in place 2026-07-07] Store leaves behind a firewall report the firewall's new-question refetch — `isPending` on a store leaf behind a firewall reports the firewall's refetch like any a… |
+| A10 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:133` | invariants.ts×1 verdict.ts×1 | createMemo.test.ts×1 ispending-memo-unstamped-hold-3457.test.ts×2 latest-isPending-consistency.test.ts×1 | [ruled] `[isPending(x), x()]` is atomic within one scope — `[isPending(x), x()]` read in one scope is atomic: a reader that observed the fresh value must not see `pending === true` for it. |
+| A11 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:51` | — | latest-isPending-consistency.test.ts×1 visibility-oracle-store.test.ts×1 visibility-oracle.test.ts×1 | [ruled] Sync derivations of held sources are visible through `latest()`/`isPending()` — Sync derivations of transition-held sources are visible through `latest()`/`isPending()` (held sync recompute is… |
+| A12 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:141` | — | createOptimistic.test.ts×2 spec-async-semantics.test.ts×1 | [ruled, amended in place] Resting optimistic nodes report pending like a plain memo — A resting optimistic node reports pending via exactly the causes a plain async memo does (A19) — a reverting optim… |
+| A13 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:149` | async.ts×1 | spec-async-semantics.test.ts×7 | [ruled 2026-07-06 (promoted from B1)] Resting optimistic ≡ plain async memo at every checkpoint — (was B1) A resting optimistic node (no active override) is observationally identical to a plain async … |
+| A14 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:157` | — | spec-async-semantics.test.ts×2 | [ruled, amended in place 2026-07-06 (promoted from B2)] Companion nodes get child lanes that do not merge with the owner — (was B2) `isPending`/`latest` companion nodes get child lanes that do not mer… |
+| A15 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:191` | async.ts×3 core.ts×3 lanes.ts×1 scheduler.ts×2 | async-chain-supersession.test.ts×1 lane-hold-on-observation.test.ts×1 loading-reset-collects-forwarded-3459.test.ts×3 overlapping-flights.test.ts×3 reveal-carve-out.test.ts×2 shared-effect-no-entangle.test.ts×1 spec-async-semantics.test.ts×2 stale-read-uninitialized-cross-transition.test.ts×1 superseded-source-blocks-3462.test.ts×2 treeshake.test.ts×4 visibility-oracle-store.test.ts×5 visibility-oracle.test.ts×7 | [ruled, amended in place 2026-07-06 (promoted from B3)] Transition entanglement is graph-driven; lanes settle as one reveal — (was B3) Transition entanglement is graph-driven: writes whose async work … |
+| A16 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:165` | verdict.ts×1 | spec-async-semantics.test.ts×1 strict-read-pending-store.test.ts×2 uninitialized-visibility.test.ts×1 visibility-oracle-store.test.ts×2 visibility-oracle.test.ts×3 | [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:31` | async.ts×3 constants.ts×2 core.ts×7 invariants.ts×3 optimistic.ts×5 scheduler.ts×2 verdict.ts×2 signals.ts×2 optimistic.ts×1 store.ts×3 | optimistic-undefined-override.test.ts×1 refresh-await.test.ts×1 reveal-gating-contract.test.ts×3 spec-async-semantics.test.ts×10 createOptimisticStore.test.ts×1 treeshake.test.ts×1 until.test.ts×1 visibility-oracle-store.test.ts×12 visibility-oracle.test.ts×26 | [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:41` | async.ts×2 constants.ts×1 core.ts×4 optimistic.ts×4 scheduler.ts×2 types.ts×2 verdict.ts×2 optimistic.ts×1 | body-end-supersession-visibility.test.ts×4 createOptimistic.test.ts×1 spec-async-semantics.test.ts×3 flight-owned-transaction.test.ts×1 superseded-before-first-commit.test.ts×5 visibility-oracle-store.test.ts×10 visibility-oracle.test.ts×25 | [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 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:93` | async.ts×1 core.ts×1 optimistic.ts×1 verdict.ts×1 | spec-async-semantics.test.ts×3 superseded-before-first-commit.test.ts×1 uninitialized-visibility.test.ts×1 visibility-oracle-store.test.ts×6 visibility-oracle.test.ts×12 | [ruled 2026-07-07 (promoted from C1)] `isPending(x)` ≡ the observable value is not final (three causes) — (was C1 — **partially reverses an earlier decision**) \*\*Definition: `isPending(x)` ≡ the value… |
+| A20 | superseded | `docs/SPEC-ASYNC-SEMANTICS.md:283` | invariants.ts×1 | 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:290` | — | 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 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:173` | — | spec-async-semantics.test.ts×1 visibility-oracle-store.test.ts×1 | [ruled 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) and the decree tha… |
+| A23 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:181` | — | spec-async-semantics.test.ts×1 | [ruled 2026-07-08] The `isPending` probe is reads-only — **The `isPending` probe is reads-only — the thunk's return value is never inspected.** `isPending(() => store)` reads nothing and reports `fals… |
+| A24 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:101` | — | optimistic-undefined-override.test.ts×1 reveal-gating-contract.test.ts×1 spec-async-semantics.test.ts×2 visibility-oracle-store.test.ts×2 visibility-oracle.test.ts×4 | [ruled 2026-07-13] Question-scoped pending: pending iff a value change is in flight or an `affects()` mark is live — (**ruled 2026-07-13** — supersedes A20/A21; the converged model from the #2844/#272… |
+| A25 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:221` | verdict.ts×1 | uninitialized-visibility.test.ts×3 visibility-oracle-store.test.ts×8 | [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:59` | scheduler.ts×1 | action-await-contract.test.ts×2 visibility-oracle-store.test.ts×1 visibility-oracle.test.ts×2 | [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:213` | — | loading-value.test.ts×2 visibility-oracle.test.ts×19 | [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… |
+| A29 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:67` | core.ts×4 effect.ts×1 optimistic.ts×1 | body-end-supersession-visibility.test.ts×1 born-held.test.ts×3 held-conditional-memo.test.ts×1 treeshake.test.ts×1 visibility-oracle-store.test.ts×4 visibility-oracle.test.ts×6 | [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:199` | async.ts×1 attribution.ts×1 core.ts×1 effect.ts×1 scheduler.ts×1 | async-landing-deps-3461.test.ts×3 held-conditional-effect.test.ts×1 held-conditional-memo.test.ts×1 treeshake.test.ts×1 | [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:75` | core.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 … |
+| A32 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:83` | — | visibility-oracle-store.test.ts×6 visibility-oracle.test.ts×10 | [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… |
## V — fixed violations
@@ -108,19 +108,19 @@ Status legend: **live** stated and standing · **ruled** carries an explicit rul
## INV — invariants
-| id | status | defined | cited in src | cited in tests | statement (at definition) |
-| ------ | ------- | ----------------------------------- | --------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------- |
-| INV-1 | live | `docs/INTERNALS-ASYNC-STATE.md:145` | invariants.ts×2 | — | - **INV-1 (high)** `pendingProbe` is non-null only inside an `isPending()` call |
-| INV-2 | live | `docs/INTERNALS-ASYNC-STATE.md:147` | invariants.ts×2 | — | - **INV-2 (high)** A node with an _active_ override (`hasActiveOverride`) is |
-| INV-3 | live | `docs/INTERNALS-ASYNC-STATE.md:151` | core.ts×1 invariants.ts×2 lanes.ts×1 scheduler.ts×2 | lane-hold-on-observation.test.ts×1 | - **INV-3 (high)** `_asyncReporters` gains entries only inside |
-| INV-4 | live | `docs/INTERNALS-ASYNC-STATE.md:158` | invariants.ts×3 | — | - **INV-4 (medium)** After any of the three write paths completes for node `el` |
-| INV-5 | live | `docs/INTERNALS-ASYNC-STATE.md:162` | invariants.ts×2 lanes.ts×1 | — | - **INV-5 (medium)** A lane in `activeLanes` has `_mergedInto === null` |
-| INV-6 | live | `docs/INTERNALS-ASYNC-STATE.md:168` | invariants.ts×2 | — | - **INV-6 (medium)** At the end of a completing-transition flush: every node in |
-| INV-7 | live | `docs/INTERNALS-ASYNC-STATE.md:171` | core.ts×1 invariants.ts×2 | action-completion-race.test.ts×2 | - **INV-7 (medium)** `_pendingValue !== NOT_PENDING` on a non-optimistic node |
-| INV-8 | retired | `docs/INTERNALS-ASYNC-STATE.md:174` | invariants.ts×1 | rules-index.test.ts×1 | - **INV-8 (RETIRED 2026-07-07b, §5e)** Hold-provenance: a `_pendingValue` on an |
-| INV-9 | live | `docs/INTERNALS-ASYNC-STATE.md:185` | invariants.ts×1 owner.ts×1 | — | - **INV-9 (high)** An `isPending` companion of a DISPOSED owner reads `false` |
-| INV-10 | live | `docs/INTERNALS-ASYNC-STATE.md:190` | invariants.ts×2 | action-done-window.test.ts×1 | - **INV-10 (high)** Affects-count balance (question-scoped model, 2026-07-13; |
-| INV-11 | live | `docs/INTERNALS-ASYNC-STATE.md:195` | core.ts×1 optimistic.ts×1 | spec-async-semantics.test.ts×1 treeshake.test.ts×1 | - **INV-11 (high, structural — pinned, not asserted)** A recompute's equality |
+| id | status | defined | cited in src | cited in tests | statement (at definition) |
+| ------ | ------- | ----------------------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
+| INV-1 | live | `docs/INTERNALS-ASYNC-STATE.md:145` | invariants.ts×2 | — | - **INV-1 (high)** `pendingProbe` is non-null only inside an `isPending()` call |
+| INV-2 | live | `docs/INTERNALS-ASYNC-STATE.md:147` | invariants.ts×2 | — | - **INV-2 (high)** A node with an _active_ override (`hasActiveOverride`) is |
+| INV-3 | live | `docs/INTERNALS-ASYNC-STATE.md:151` | boundaries.ts×1 core.ts×1 invariants.ts×2 lanes.ts×1 scheduler.ts×2 | lane-hold-on-observation.test.ts×1 loading-reset-collects-forwarded-3459.test.ts×1 | - **INV-3 (high)** `_asyncReporters` gains entries only inside |
+| INV-4 | live | `docs/INTERNALS-ASYNC-STATE.md:158` | invariants.ts×3 | — | - **INV-4 (medium)** After any of the three write paths completes for node `el` |
+| INV-5 | live | `docs/INTERNALS-ASYNC-STATE.md:162` | invariants.ts×2 lanes.ts×1 | — | - **INV-5 (medium)** A lane in `activeLanes` has `_mergedInto === null` |
+| INV-6 | live | `docs/INTERNALS-ASYNC-STATE.md:168` | invariants.ts×2 | — | - **INV-6 (medium)** At the end of a completing-transition flush: every node in |
+| INV-7 | live | `docs/INTERNALS-ASYNC-STATE.md:171` | core.ts×1 invariants.ts×2 | action-completion-race.test.ts×2 | - **INV-7 (medium)** `_pendingValue !== NOT_PENDING` on a non-optimistic node |
+| INV-8 | retired | `docs/INTERNALS-ASYNC-STATE.md:174` | invariants.ts×1 | rules-index.test.ts×1 | - **INV-8 (RETIRED 2026-07-07b, §5e)** Hold-provenance: a `_pendingValue` on an |
+| INV-9 | live | `docs/INTERNALS-ASYNC-STATE.md:185` | invariants.ts×1 owner.ts×1 | — | - **INV-9 (high)** An `isPending` companion of a DISPOSED owner reads `false` |
+| INV-10 | live | `docs/INTERNALS-ASYNC-STATE.md:190` | invariants.ts×2 | action-done-window.test.ts×1 | - **INV-10 (high)** Affects-count balance (question-scoped model, 2026-07-13; |
+| INV-11 | live | `docs/INTERNALS-ASYNC-STATE.md:195` | core.ts×1 optimistic.ts×1 | spec-async-semantics.test.ts×1 treeshake.test.ts×1 | - **INV-11 (high, structural — pinned, not asserted)** A recompute's equality |
## RUL — store rulings
diff --git a/packages/signals/src/boundaries.ts b/packages/signals/src/boundaries.ts
index aaf2452b8..466393c0e 100644
--- a/packages/signals/src/boundaries.ts
+++ b/packages/signals/src/boundaries.ts
@@ -12,6 +12,7 @@ import {
Queue,
read,
REACTIVE_DISPOSED,
+ REACTIVE_ZOMBIE,
setContext,
runWithOwner,
setSignal,
@@ -26,7 +27,7 @@ import {
import type { IQueue, Signal } from "./core/index.js";
import { emitDiagnostic, reportDiagnostic } from "./core/dev.js";
import { attrHooks } from "./core/attribution-hooks.js";
-import { haltReactivity, schedule, wakeParked } from "./core/scheduler.js";
+import { haltReactivity, schedule, transitions, wakeParked } from "./core/scheduler.js";
import { accessor, type Accessor } from "./signals.js";
export interface BoundaryComputed B: {b()}
Fast: {fast()}
+Slow: {slow()}
+ + > + ); +} + +describe("A15 / #3459 a Loading `on` reset keeps the fallback until every reader under it settles", () => { + test("A15 / #3459 resetting `on` while slow is in flight never reveals Fast: 1 beside Slow: 0", async () => { + const div = document.createElement("div"); + document.body.appendChild(div); + const dispose = render(() =>