From f49dee6da713f7b07b2262644a00dba97b52bd7f Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Thu, 17 Sep 2026 13:33:27 -0700 Subject: [PATCH 1/3] =?UTF-8?q?fix(signals):=20A28=20at=20the=20store=20ba?= =?UTF-8?q?cking=20=E2=80=94=20an=20unflushed=20write=20is=20invisible=20t?= =?UTF-8?q?o=20owner-context=20readers=20until=20the=20carrying=20flush=20?= =?UTF-8?q?(S6;=20store=20follows=20signal)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ruled 2026-09-17: store rules follow signal rules. Core serve() serves an unflushed node's committed value and re-runs the reader in the carrying flush; the store's backing selection served the pending backing to every owner-context reader at once. A derivation created inside a foreign action that adopted a mainline setStore published the unflushed 1; the signal published 0. Now: ensurePB stamps the target with clock when the draft opens outside a flush from outside any owner (A28 (4) — a write inside a computation stays visible to its block, core's notePromotedWrite twin); pendingBackingVisible's owner-context branch serves committed for a backing stamped this tick and marks the reader late linker; getNode gains the third born kind — a node born in the unflushed window stages the pending backing's value — with stageHeldKey generalized to one stageKey(node, nv, txn | null) (a hold's staging in its transaction's list; the unflushed window's through queuePendingNode, adopted-unflushed under a transaction body). deep() / snapshot() keep their speculative-peek contract; nodeValue already has the arm through serve(). Pinned: posture-store-parity S6 flips from a recorded divergence to a parity pin (three shapes, with the commit follow-through). Matrix: exactly the 12 S6 cells move, to the signal's values; all six paired states 0 diffs. +283 B minified store (down from +402 at the old twin); one brotli cap ratcheted. Co-authored-by: Claude via Cursor --- .changeset/store-a28-backing.md | 5 + packages/signals/docs/RULES-INDEX.md | 727 +++++++++--------- packages/signals/src/store/next/store.ts | 66 +- .../tests/posture-store-parity.test.ts | 38 +- 4 files changed, 451 insertions(+), 385 deletions(-) create mode 100644 .changeset/store-a28-backing.md diff --git a/.changeset/store-a28-backing.md b/.changeset/store-a28-backing.md new file mode 100644 index 000000000..d25118f9e --- /dev/null +++ b/.changeset/store-a28-backing.md @@ -0,0 +1,5 @@ +--- +"@solidjs/signals": patch +--- + +A store write made outside a flush by imperative code is no longer visible to computations until the flush that carries it, as a signal write is not (A28): a memo created inside an action that adopted such a write published the pending value where the same memo over a signal published the committed one. A property node born in that window stages the write so the carrying flush delivers it through the node. diff --git a/packages/signals/docs/RULES-INDEX.md b/packages/signals/docs/RULES-INDEX.md index 28a08dc29..79785bf0d 100644 --- a/packages/signals/docs/RULES-INDEX.md +++ b/packages/signals/docs/RULES-INDEX.md @@ -6,34 +6,34 @@ IDs are never renumbered or deleted — source comments cite them. A superseded, ## Vocabularies -| prefix | defined in | meaning | -|---|---|---| -| `A` | `SPEC-ASYNC-SEMANTICS.md` Tier A | ruled, test-pinned propositions about isPending / latest / transitions / optimistic lanes | -| `B` `C` | `SPEC-ASYNC-SEMANTICS.md` Tier B/C | inferred / open items, all since ruled, closed or promoted into an A-rule (`(was B1)` — the alias row points at it) | -| `V` | `SPEC-ASYNC-SEMANTICS.md` Known violations | violations of A-rules found and fixed by the #2838 redesign; pinned in `spec-async-semantics.test.ts` | -| `INV-` | `INTERNALS-ASYNC-STATE.md` §5 | `__TEST__` invariants (asserted in `invariants.ts`) | -| `RUL-` | `INTERNALS-STORE-STATE.md` §8b, `rules-mining/FINDINGS.md` | store rulings mined from the suites (2026-08-16) | -| `-R` | `rules-mining/.md` | mined behavioral rules. **Each file numbers from R1**, so an R-id is only meaningful with its namespace: `CS` core-store · `OL` optimistic-lanes · `OS` optimistic-store · `PJ` projections · `RS` reconcile-snapshot. Comments qualify with `core`/`opt`/`proj`/`snap`; a bare `R` refers to the citing module's own file. | -| `§` | `INTERNALS-STORE-STATE.md` sections; `NODE-SHAPE.md` §11b, §12–§12e | design sections cited as rules. §11–§12 are the stage-3 node-shape decisions recovered from the deleted `DESIGN-PATCH-CHANNEL.md` (see NODE-SHAPE.md for provenance) | +| prefix | defined in | meaning | +| ------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `A` | `SPEC-ASYNC-SEMANTICS.md` Tier A | ruled, test-pinned propositions about isPending / latest / transitions / optimistic lanes | +| `B` `C` | `SPEC-ASYNC-SEMANTICS.md` Tier B/C | inferred / open items, all since ruled, closed or promoted into an A-rule (`(was B1)` — the alias row points at it) | +| `V` | `SPEC-ASYNC-SEMANTICS.md` Known violations | violations of A-rules found and fixed by the #2838 redesign; pinned in `spec-async-semantics.test.ts` | +| `INV-` | `INTERNALS-ASYNC-STATE.md` §5 | `__TEST__` invariants (asserted in `invariants.ts`) | +| `RUL-` | `INTERNALS-STORE-STATE.md` §8b, `rules-mining/FINDINGS.md` | store rulings mined from the suites (2026-08-16) | +| `-R` | `rules-mining/.md` | mined behavioral rules. **Each file numbers from R1**, so an R-id is only meaningful with its namespace: `CS` core-store · `OL` optimistic-lanes · `OS` optimistic-store · `PJ` projections · `RS` reconcile-snapshot. Comments qualify with `core`/`opt`/`proj`/`snap`; a bare `R` refers to the citing module's own file. | +| `§` | `INTERNALS-STORE-STATE.md` sections; `NODE-SHAPE.md` §11b, §12–§12e | design sections cited as rules. §11–§12 are the stage-3 node-shape decisions recovered from the deleted `DESIGN-PATCH-CHANNEL.md` (see NODE-SHAPE.md for provenance) | Status legend: **live** stated and standing · **ruled** carries an explicit ruling date · **amended** re-ruled or re-scoped in place (the row text says how) · **superseded** replaced by a later rule (the text names it) · **retired** mechanism removed, ID kept for citations · **fixed / resolved / closed** a violation or open item with its outcome · **ruled out** a design that was tried and rejected. ## Summary | vocabulary | rules | cited in src | cited in tests | cited nowhere | -|---|---|---|---|---| -| A | 34 | 19 | 34 | 0 | -| V | 5 | 2 | 5 | 0 | -| B | 5 | 0 | 5 | 0 | -| C | 4 | 0 | 3 | 1 | -| INV | 11 | 11 | 7 | 0 | -| RUL | 13 | 6 | 6 | 5 | -| R (CS) | 59 | 18 | 16 | 31 | -| R (OL) | 37 | 0 | 0 | 37 | -| R (OS) | 46 | 2 | 0 | 44 | -| R (PJ) | 36 | 6 | 1 | 30 | -| R (RS) | 38 | 9 | 2 | 27 | -| § | 24 | 14 | 8 | 8 | +| ---------- | ----- | ------------ | -------------- | ------------- | +| A | 34 | 19 | 34 | 0 | +| V | 5 | 2 | 5 | 0 | +| B | 5 | 0 | 5 | 0 | +| C | 4 | 0 | 3 | 1 | +| INV | 11 | 11 | 7 | 0 | +| RUL | 13 | 6 | 6 | 5 | +| R (CS) | 59 | 18 | 16 | 31 | +| R (OL) | 37 | 0 | 0 | 37 | +| R (OS) | 46 | 2 | 0 | 44 | +| R (PJ) | 36 | 6 | 1 | 30 | +| R (RS) | 38 | 9 | 2 | 27 | +| § | 24 | 14 | 8 | 8 | ## Unresolved citations @@ -43,361 +43,372 @@ 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:261` | — | 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:269` | — | 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:277` | — | 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:285` | — | 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:293` | — | 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:301` | — | 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:117` | verdict.ts×2 | spec-async-semantics.test.ts×2 visibility-oracle-store.states.ts×1 visibility-oracle.states.ts×1 visibility-oracle.test.ts×1 | [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:125` | core.ts×1 verdict.ts×2 | createMemo.test.ts×1 visibility-oracle-store.states.ts×1 visibility-oracle.states.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:133` | — | spec-async-semantics.test.ts×3 visibility-oracle-store.states.ts×4 visibility-oracle-store.test.ts×1 | [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:141` | 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:59` | — | latest-isPending-consistency.test.ts×1 visibility-oracle-store.states.ts×2 visibility-oracle.states.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:149` | — | 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:157` | 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:165` | — | 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:199` | async.ts×4 core.ts×5 lanes.ts×2 scheduler.ts×2 store.ts×1 | async-chain-supersession.test.ts×1 first-observer-stale-reader.test.ts×1 lane-hold-on-observation.test.ts×1 lane-outside-view.test.ts×1 overlapping-flights.test.ts×3 posture-born-held-and-observation.test.ts×4 posture-store-parity.test.ts×2 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.states.ts×6 visibility-oracle.states.ts×6 visibility-oracle.test.ts×1 write-proposals-3494.test.ts×1 | [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:173` | verdict.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:31` | async.ts×4 constants.ts×2 core.ts×10 invariants.ts×3 optimistic.ts×6 scheduler.ts×2 verdict.ts×2 signals.ts×2 optimistic.ts×1 store.ts×1 | 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×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×25 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:41` | async.ts×3 constants.ts×1 core.ts×7 optimistic.ts×6 scheduler.ts×3 types.ts×2 verdict.ts×2 optimistic.ts×1 | body-end-supersession-visibility.test.ts×4 createOptimistic.test.ts×1 lane-outside-view.test.ts×1 posture-store-parity.test.ts×5 spec-async-semantics.test.ts×3 flight-owned-transaction.test.ts×1 superseded-before-first-commit.test.ts×5 visibility-oracle-store.states.ts×9 visibility-oracle-store.test.ts×1 visibility-oracle.states.ts×24 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 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:101` | async.ts×1 core.ts×2 optimistic.ts×1 verdict.ts×2 | spec-async-semantics.test.ts×3 superseded-before-first-commit.test.ts×1 uninitialized-visibility.test.ts×1 visibility-oracle-store.states.ts×8 visibility-oracle-store.test.ts×1 visibility-oracle.states.ts×11 visibility-oracle.test.ts×1 write-proposals-3494.test.ts×1 | [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:350` | 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:357` | — | 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:181` | — | spec-async-semantics.test.ts×1 visibility-oracle-store.states.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:189` | — | 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:109` | — | optimistic-undefined-override.test.ts×1 reveal-gating-contract.test.ts×1 spec-async-semantics.test.ts×2 visibility-oracle-store.states.ts×4 visibility-oracle.states.ts×3 visibility-oracle.test.ts×1 | [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:251` | verdict.ts×1 | 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:67` | core.ts×1 scheduler.ts×1 store.ts×1 | action-await-contract.test.ts×2 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:243` | — | 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:51` | constants.ts×2 core.ts×20 optimistic.ts×2 scheduler.ts×3 types.ts×1 verdict.ts×6 optimistic.ts×3 store.ts×1 | createOptimistic.test.ts×5 latest-held-till-flush.test.ts×1 optimistic-store-layer-scope.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 shallow.test.ts×1 treeshake.test.ts×2 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:75` | action.ts×1 async.ts×1 core.ts×7 effect.ts×1 optimistic.ts×1 scheduler.ts×1 signals.ts×1 store.ts×3 | body-end-supersession-visibility.test.ts×1 born-held.test.ts×3 direct-commit-readers-posture.test.ts×1 held-conditional-memo.test.ts×1 held-frame-dependencies.test.ts×2 latest-held-till-flush.test.ts×2 posture-store-parity.test.ts×4 treeshake.test.ts×1 visibility-oracle-store.states.ts×5 visibility-oracle-store.test.ts×1 visibility-oracle.states.ts×5 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:207` | async.ts×3 attribution.ts×1 core.ts×2 effect.ts×1 scheduler.ts×4 | async-landing-deps-3461.test.ts×3 held-conditional-effect.test.ts×1 held-conditional-memo.test.ts×1 held-frame-dependencies.test.ts×2 posture-born-held-and-observation.test.ts×1 treeshake.test.ts×2 write-proposals-3494.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:83` | 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:91` | core.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:223` | boundaries.ts×2 scheduler.ts×1 | async-chain-supersession.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:233` | async.ts×1 core.ts×1 scheduler.ts×2 verdict.ts×1 | treeshake.test.ts×1 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… | +| id | status | defined | cited in src | cited in tests | statement (at definition) | +| --- | ----------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| A1 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:261` | — | 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:269` | — | 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:277` | — | 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:285` | — | 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:293` | — | 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:301` | — | 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:117` | verdict.ts×2 | spec-async-semantics.test.ts×2 visibility-oracle-store.states.ts×1 visibility-oracle.states.ts×1 visibility-oracle.test.ts×1 | [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:125` | core.ts×1 verdict.ts×2 | createMemo.test.ts×1 visibility-oracle-store.states.ts×1 visibility-oracle.states.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:133` | — | spec-async-semantics.test.ts×3 visibility-oracle-store.states.ts×4 visibility-oracle-store.test.ts×1 | [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:141` | 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:59` | — | latest-isPending-consistency.test.ts×1 visibility-oracle-store.states.ts×2 visibility-oracle.states.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:149` | — | 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:157` | 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:165` | — | 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:199` | async.ts×4 core.ts×5 lanes.ts×2 scheduler.ts×2 store.ts×1 | async-chain-supersession.test.ts×1 first-observer-stale-reader.test.ts×1 lane-hold-on-observation.test.ts×1 lane-outside-view.test.ts×1 overlapping-flights.test.ts×3 posture-born-held-and-observation.test.ts×4 posture-store-parity.test.ts×2 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.states.ts×6 visibility-oracle.states.ts×6 visibility-oracle.test.ts×1 write-proposals-3494.test.ts×1 | [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:173` | verdict.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:31` | async.ts×4 constants.ts×2 core.ts×10 invariants.ts×3 optimistic.ts×6 scheduler.ts×2 verdict.ts×2 signals.ts×2 optimistic.ts×1 store.ts×1 | 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×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×25 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:41` | async.ts×3 constants.ts×1 core.ts×7 optimistic.ts×6 scheduler.ts×3 types.ts×2 verdict.ts×2 optimistic.ts×1 | body-end-supersession-visibility.test.ts×4 createOptimistic.test.ts×1 lane-outside-view.test.ts×1 posture-store-parity.test.ts×5 spec-async-semantics.test.ts×3 flight-owned-transaction.test.ts×1 superseded-before-first-commit.test.ts×5 visibility-oracle-store.states.ts×9 visibility-oracle-store.test.ts×1 visibility-oracle.states.ts×24 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 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:101` | async.ts×1 core.ts×2 optimistic.ts×1 verdict.ts×2 | spec-async-semantics.test.ts×3 superseded-before-first-commit.test.ts×1 uninitialized-visibility.test.ts×1 visibility-oracle-store.states.ts×8 visibility-oracle-store.test.ts×1 visibility-oracle.states.ts×11 visibility-oracle.test.ts×1 write-proposals-3494.test.ts×1 | [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:350` | 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:357` | — | 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:181` | — | spec-async-semantics.test.ts×1 visibility-oracle-store.states.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:189` | — | 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:109` | — | optimistic-undefined-override.test.ts×1 reveal-gating-contract.test.ts×1 spec-async-semantics.test.ts×2 visibility-oracle-store.states.ts×4 visibility-oracle.states.ts×3 visibility-oracle.test.ts×1 | [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:251` | verdict.ts×1 | 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:67` | core.ts×1 scheduler.ts×1 store.ts×1 | action-await-contract.test.ts×2 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:243` | — | 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:51` | constants.ts×2 core.ts×20 optimistic.ts×2 scheduler.ts×3 types.ts×1 verdict.ts×6 optimistic.ts×3 store.ts×6 | createOptimistic.test.ts×5 latest-held-till-flush.test.ts×1 optimistic-store-layer-scope.test.ts×1 posture-store-parity.test.ts×7 question-scoped-pending.test.ts×3 snapshot-derived-store-rows.test.ts×1 createOptimisticStore.test.ts×10 shallow.test.ts×1 treeshake.test.ts×2 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:75` | action.ts×1 async.ts×1 core.ts×7 effect.ts×1 optimistic.ts×1 scheduler.ts×1 signals.ts×1 store.ts×3 | body-end-supersession-visibility.test.ts×1 born-held.test.ts×3 direct-commit-readers-posture.test.ts×1 held-conditional-memo.test.ts×1 held-frame-dependencies.test.ts×2 latest-held-till-flush.test.ts×2 posture-store-parity.test.ts×4 treeshake.test.ts×1 visibility-oracle-store.states.ts×5 visibility-oracle-store.test.ts×1 visibility-oracle.states.ts×5 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:207` | async.ts×3 attribution.ts×1 core.ts×2 effect.ts×1 scheduler.ts×4 | async-landing-deps-3461.test.ts×3 held-conditional-effect.test.ts×1 held-conditional-memo.test.ts×1 held-frame-dependencies.test.ts×2 posture-born-held-and-observation.test.ts×1 treeshake.test.ts×2 write-proposals-3494.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:83` | 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:91` | core.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:223` | boundaries.ts×2 scheduler.ts×1 | async-chain-supersession.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:233` | async.ts×1 core.ts×1 scheduler.ts×2 verdict.ts×1 | treeshake.test.ts×1 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) | -|---|---|---|---|---|---| -| V1 | fixed | `docs/SPEC-ASYNC-SEMANTICS.md:420` | async.ts×1 | spec-async-semantics.test.ts×7 | - **V1 (violated A13) — FIXED.** A _resting_ optimistic node reported | -| V2 | fixed | `docs/SPEC-ASYNC-SEMANTICS.md:430` | async.ts×1 | spec-async-semantics.test.ts×2 | - **V2 (violated A7/A13) — FIXED.** `latest()`'s verdict in the window was | -| V3 | fixed | `docs/SPEC-ASYNC-SEMANTICS.md:436` | — | spec-async-semantics.test.ts×2 | - **V3 (violated A19) — FIXED.** After a reporter-less transition completed, | -| V4 | fixed | `docs/SPEC-ASYNC-SEMANTICS.md:443` | — | spec-async-semantics.test.ts×5 | - **V4 (violated the old A20's three-form algebra) — FIXED, then the rule it | -| V5 | live | `docs/SPEC-ASYNC-SEMANTICS.md:455` | — | spec-async-semantics.test.ts×3 | - **V5 (A17 corollary — found and fixed with the revert-target elimination, | +| id | status | defined | cited in src | cited in tests | statement (at definition) | +| --- | ------ | ---------------------------------- | ------------ | ------------------------------ | ------------------------------------------------------------------------------ | +| V1 | fixed | `docs/SPEC-ASYNC-SEMANTICS.md:420` | async.ts×1 | spec-async-semantics.test.ts×7 | - **V1 (violated A13) — FIXED.** A _resting_ optimistic node reported | +| V2 | fixed | `docs/SPEC-ASYNC-SEMANTICS.md:430` | async.ts×1 | spec-async-semantics.test.ts×2 | - **V2 (violated A7/A13) — FIXED.** `latest()`'s verdict in the window was | +| V3 | fixed | `docs/SPEC-ASYNC-SEMANTICS.md:436` | — | spec-async-semantics.test.ts×2 | - **V3 (violated A19) — FIXED.** After a reporter-less transition completed, | +| V4 | fixed | `docs/SPEC-ASYNC-SEMANTICS.md:443` | — | spec-async-semantics.test.ts×5 | - \*\*V4 (violated the old A20's three-form algebra) — FIXED, then the rule it | +| V5 | live | `docs/SPEC-ASYNC-SEMANTICS.md:455` | — | spec-async-semantics.test.ts×3 | - \*\*V5 (A17 corollary — found and fixed with the revert-target elimination, | + ## B — tier B -| id | status | defined | cited in src | cited in tests | statement (at definition) | -|---|---|---|---|---|---| -| B1 | live | `docs/SPEC-ASYNC-SEMANTICS.md:157` | — | createRevealOrder.test.ts×16 onCleanup.test.ts×2 spec-async-semantics.test.ts×2 | PROMOTED → A13 (A13's section carries the ruling). | -| B2 | live | `docs/SPEC-ASYNC-SEMANTICS.md:165` | — | createRevealOrder.test.ts×16 onCleanup.test.ts×2 spec-async-semantics.test.ts×2 | PROMOTED → A14 (A14's section carries the ruling). | -| B3 | live | `docs/SPEC-ASYNC-SEMANTICS.md:199` | — | spec-async-semantics.test.ts×2 | PROMOTED → A15 (A15's section carries the ruling). | -| B4 | live | `docs/SPEC-ASYNC-SEMANTICS.md:41` | — | spec-async-semantics.test.ts×2 | PROMOTED → A18 (A18's section carries the ruling). | -| B5 | live | `docs/SPEC-ASYNC-SEMANTICS.md:173` | — | spec-async-semantics.test.ts×2 | PROMOTED → A16 (A16's section carries the ruling). | +| id | status | defined | cited in src | cited in tests | statement (at definition) | +| --- | ------ | ---------------------------------- | ------------ | ------------------------------------------------------------------------------- | -------------------------------------------------- | +| B1 | live | `docs/SPEC-ASYNC-SEMANTICS.md:157` | — | createRevealOrder.test.ts×16 onCleanup.test.ts×2 spec-async-semantics.test.ts×2 | PROMOTED → A13 (A13's section carries the ruling). | +| B2 | live | `docs/SPEC-ASYNC-SEMANTICS.md:165` | — | createRevealOrder.test.ts×16 onCleanup.test.ts×2 spec-async-semantics.test.ts×2 | PROMOTED → A14 (A14's section carries the ruling). | +| B3 | live | `docs/SPEC-ASYNC-SEMANTICS.md:199` | — | spec-async-semantics.test.ts×2 | PROMOTED → A15 (A15's section carries the ruling). | +| B4 | live | `docs/SPEC-ASYNC-SEMANTICS.md:41` | — | spec-async-semantics.test.ts×2 | PROMOTED → A18 (A18's section carries the ruling). | +| B5 | live | `docs/SPEC-ASYNC-SEMANTICS.md:173` | — | spec-async-semantics.test.ts×2 | PROMOTED → A16 (A16's section carries the ruling). | + ## C — tier C -| id | status | defined | cited in src | cited in tests | statement (at definition) | -|---|---|---|---|---|---| -| C1 | live | `docs/SPEC-ASYNC-SEMANTICS.md:101` | — | onCleanup.test.ts×2 spec-async-semantics.test.ts×1 | PROMOTED → A19 (A19's section carries the ruling). | -| C2 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:386` | — | onCleanup.test.ts×2 | - [x] **C2 — RULED (2026-07-07): reverts do not trump other live lanes.** A | -| C3 | closed | `docs/SPEC-ASYNC-SEMANTICS.md:396` | — | — | - [x] **C3 — CLOSED by A19 (2026-07-07): early completion is by design.** | -| C4 | live | `docs/SPEC-ASYNC-SEMANTICS.md:31` | — | spec-async-semantics.test.ts×1 | PROMOTED → A17 (A17's section carries the ruling). | +| id | status | defined | cited in src | cited in tests | statement (at definition) | +| --- | ------ | ---------------------------------- | ------------ | -------------------------------------------------- | --------------------------------------------------------------------------- | +| C1 | live | `docs/SPEC-ASYNC-SEMANTICS.md:101` | — | onCleanup.test.ts×2 spec-async-semantics.test.ts×1 | PROMOTED → A19 (A19's section carries the ruling). | +| C2 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:386` | — | onCleanup.test.ts×2 | - [x] **C2 — RULED (2026-07-07): reverts do not trump other live lanes.** A | +| C3 | closed | `docs/SPEC-ASYNC-SEMANTICS.md:396` | — | — | - [x] **C3 — CLOSED by A19 (2026-07-07): early completion is by design.** | +| C4 | live | `docs/SPEC-ASYNC-SEMANTICS.md:31` | — | spec-async-semantics.test.ts×1 | PROMOTED → A17 (A17's section carries the ruling). | + ## INV — invariants -| id | status | defined | cited in src | cited in tests | statement (at definition) | -|---|---|---|---|---|---| -| INV-1 | live | `docs/INTERNALS-ASYNC-STATE.md:150` | invariants.ts×2 | — | - **INV-1 (high)** `pendingProbe` is non-null only inside an `isPending()` call | -| INV-2 | live | `docs/INTERNALS-ASYNC-STATE.md:152` | invariants.ts×2 | — | - **INV-2 (high)** A node with an _active_ override (`hasActiveOverride`) is | -| INV-3 | live | `docs/INTERNALS-ASYNC-STATE.md:156` | boundaries.ts×1 core.ts×2 invariants.ts×2 lanes.ts×1 scheduler.ts×2 | first-observer-stale-reader.test.ts×1 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:163` | invariants.ts×3 verdict.ts×1 | inv4-projection-dispose-shadow.test.ts×3 posture-store-parity.test.ts×1 | - **INV-4 (medium)** After any of the three write paths completes for node `el` | -| INV-5 | live | `docs/INTERNALS-ASYNC-STATE.md:167` | invariants.ts×2 lanes.ts×1 | — | - **INV-5 (medium)** A lane in `activeLanes` has `_mergedInto === null` | -| INV-6 | live | `docs/INTERNALS-ASYNC-STATE.md:173` | 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:176` | 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:179` | 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:190` | invariants.ts×1 owner.ts×2 | inv4-projection-dispose-shadow.test.ts×1 | - **INV-9 (high)** An `isPending` companion of a DISPOSED owner reads `false` | -| INV-10 | live | `docs/INTERNALS-ASYNC-STATE.md:195` | 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:200` | core.ts×1 optimistic.ts×2 | 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:150` | invariants.ts×2 | — | - **INV-1 (high)** `pendingProbe` is non-null only inside an `isPending()` call | +| INV-2 | live | `docs/INTERNALS-ASYNC-STATE.md:152` | invariants.ts×2 | — | - **INV-2 (high)** A node with an _active_ override (`hasActiveOverride`) is | +| INV-3 | live | `docs/INTERNALS-ASYNC-STATE.md:156` | boundaries.ts×1 core.ts×2 invariants.ts×2 lanes.ts×1 scheduler.ts×2 | first-observer-stale-reader.test.ts×1 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:163` | invariants.ts×3 verdict.ts×1 | inv4-projection-dispose-shadow.test.ts×3 posture-store-parity.test.ts×1 | - **INV-4 (medium)** After any of the three write paths completes for node `el` | +| INV-5 | live | `docs/INTERNALS-ASYNC-STATE.md:167` | invariants.ts×2 lanes.ts×1 | — | - **INV-5 (medium)** A lane in `activeLanes` has `_mergedInto === null` | +| INV-6 | live | `docs/INTERNALS-ASYNC-STATE.md:173` | 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:176` | 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:179` | 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:190` | invariants.ts×1 owner.ts×2 | inv4-projection-dispose-shadow.test.ts×1 | - **INV-9 (high)** An `isPending` companion of a DISPOSED owner reads `false` | +| INV-10 | live | `docs/INTERNALS-ASYNC-STATE.md:195` | 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:200` | core.ts×1 optimistic.ts×2 | spec-async-semantics.test.ts×1 treeshake.test.ts×1 | - **INV-11 (high, structural — pinned, not asserted)** A recompute's equality | + ## RUL — store rulings -| id | status | defined | cited in src | cited in tests | statement (at definition) | -|---|---|---|---|---|---| -| RUL-1 | resolved | `docs/INTERNALS-STORE-STATE.md:512` | store.ts×2 target.ts×1 | next-smoke.test.ts×1 | - **RUL-1 — RESOLVED (2026-08-16, verified per "mirror signals if verified" — | -| RUL-2 | ruled | `docs/INTERNALS-STORE-STATE.md:521` | optimistic.ts×1 target.ts×1 | createOptimisticStore.test.ts×1 | - **RUL-2 — RULED (Ryan, 2026-08-17): no landing matrix. Two orthogonal | -| RUL-3 | resolved | `docs/INTERNALS-STORE-STATE.md:620` | optimistic.ts×1 target.ts×1 | — | - **RUL-3 — RESOLVED (verified 2026-08-16).** Ownership already lives | -| RUL-4 | resolved | `docs/INTERNALS-STORE-STATE.md:627` | — | optimistic-signal-refetch-hold.test.ts×1 | - **RUL-4 — RESOLVED (2026-08-17, signal parity — verified empirically).** | -| RUL-5 | live | `docs/INTERNALS-STORE-STATE.md:640` | reconcile.ts×1 | adoption-lane-rollback.test.ts×1 | - **RUL-5 — SPEC'D (2026-08-17): §6b.** Lane backing + lane-view/committed- | -| RUL-6 | live | `docs/INTERNALS-STORE-STATE.md:644` | — | — | - **RUL-6 — reclassified: SPEC WORK, not a ruling.** Live chaining (#2941 — | -| RUL-7 | live | `docs/INTERNALS-STORE-STATE.md:653` | — | — | - **RUL-7 — SPEC'D (2026-08-17): §6c.** One status field on the root target, | -| RUL-8 | live | `docs/INTERNALS-STORE-STATE.md:656` | — | adoption-lane-rollback.test.ts×1 | - **RUL-8 — SPEC'D (2026-08-17): §6 rewrite, resolves O2.** Per-transaction | -| RUL-9 | resolved | `docs/INTERNALS-STORE-STATE.md:659` | — | — | - **RUL-9 — RESOLVED (2026-08-17, parity by construction).** Every piece of | -| RUL-10 | live | `docs/INTERNALS-STORE-STATE.md:664` | optimistic.ts×1 | — | - **RUL-10 — The equality trio.** One precise rule needed spanning: no-op | -| RUL-11 | live | `docs/INTERNALS-STORE-STATE.md:669` | — | — | - **RUL-11 — SPEC'D (2026-08-17): §6d.** Sticky descendants flag ported from | -| RUL-12 | live | `docs/INTERNALS-STORE-STATE.md:671` | optimistic.ts×1 reconcile.ts×1 store.ts×2 | createProjection.async.test.ts×1 shared-child-multiparent.test.ts×1 | - **RUL-12 — Smaller rulings, each with a proposed default** (proceeding on | -| RUL-13 | resolved | `docs/INTERNALS-STORE-STATE.md:717` | — | — | - **RUL-13 — RESOLVED (verified 2026-08-16)**: `optimistic-lane-transaction- | +| id | status | defined | cited in src | cited in tests | statement (at definition) | +| ------ | -------- | ----------------------------------- | ----------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------- | +| RUL-1 | resolved | `docs/INTERNALS-STORE-STATE.md:512` | store.ts×2 target.ts×1 | next-smoke.test.ts×1 | - \*\*RUL-1 — RESOLVED (2026-08-16, verified per "mirror signals if verified" — | +| RUL-2 | ruled | `docs/INTERNALS-STORE-STATE.md:521` | optimistic.ts×1 target.ts×1 | createOptimisticStore.test.ts×1 | - \*\*RUL-2 — RULED (Ryan, 2026-08-17): no landing matrix. Two orthogonal | +| RUL-3 | resolved | `docs/INTERNALS-STORE-STATE.md:620` | optimistic.ts×1 target.ts×1 | — | - **RUL-3 — RESOLVED (verified 2026-08-16).** Ownership already lives | +| RUL-4 | resolved | `docs/INTERNALS-STORE-STATE.md:627` | — | optimistic-signal-refetch-hold.test.ts×1 | - **RUL-4 — RESOLVED (2026-08-17, signal parity — verified empirically).** | +| RUL-5 | live | `docs/INTERNALS-STORE-STATE.md:640` | reconcile.ts×1 | adoption-lane-rollback.test.ts×1 | - **RUL-5 — SPEC'D (2026-08-17): §6b.** Lane backing + lane-view/committed- | +| RUL-6 | live | `docs/INTERNALS-STORE-STATE.md:644` | — | — | - **RUL-6 — reclassified: SPEC WORK, not a ruling.** Live chaining (#2941 — | +| RUL-7 | live | `docs/INTERNALS-STORE-STATE.md:653` | — | — | - **RUL-7 — SPEC'D (2026-08-17): §6c.** One status field on the root target, | +| RUL-8 | live | `docs/INTERNALS-STORE-STATE.md:656` | — | adoption-lane-rollback.test.ts×1 | - **RUL-8 — SPEC'D (2026-08-17): §6 rewrite, resolves O2.** Per-transaction | +| RUL-9 | resolved | `docs/INTERNALS-STORE-STATE.md:659` | — | — | - **RUL-9 — RESOLVED (2026-08-17, parity by construction).** Every piece of | +| RUL-10 | live | `docs/INTERNALS-STORE-STATE.md:664` | optimistic.ts×1 | — | - **RUL-10 — The equality trio.** One precise rule needed spanning: no-op | +| RUL-11 | live | `docs/INTERNALS-STORE-STATE.md:669` | — | — | - **RUL-11 — SPEC'D (2026-08-17): §6d.** Sticky descendants flag ported from | +| RUL-12 | live | `docs/INTERNALS-STORE-STATE.md:671` | optimistic.ts×1 reconcile.ts×1 store.ts×2 | createProjection.async.test.ts×1 shared-child-multiparent.test.ts×1 | - **RUL-12 — Smaller rulings, each with a proposed default** (proceeding on | +| RUL-13 | resolved | `docs/INTERNALS-STORE-STATE.md:717` | — | — | - **RUL-13 — RESOLVED (verified 2026-08-16)**: `optimistic-lane-transaction- | + ## R — core-store (`CS-R`) -| id | status | defined | cited in src | cited in tests | statement (at definition) | -|---|---|---|---|---|---| -| CS-R1 | live | `docs/rules-mining/core-store.md:9` | — | flatten-async-iterable.test.ts×5 syncThenable.test.ts×14 visibility-oracle-store.states.ts×1 | Wrappable values are wrapped: reading a plain object/array child never returns the raw source (`state.data !== data`).** | -| CS-R2 | live | `docs/rules-mining/core-store.md:13` | — | syncThenable.test.ts×12 visibility-oracle.states.ts×1 | Raw→proxy resolution is global and deduplicating: wrapping the same raw through two different stores yields the same proxy (`outer.list === inner`).** | -| CS-R2a | live | `docs/INTERNALS-STORE-STATE.md:107` | reconcile.ts×1 store.ts×1 | — | Corollary R2a (Ryan, 2026-08-17): **take no responsibility for mutation | -| CS-R3 | live | `docs/rules-mining/core-store.md:18` | — | — | A store proxy ingested into another store (deep or shallow) is re-wrapped in the ingesting store's own proxy family — never identity-passed, never raw-marked.** | -| CS-R4 | live | `docs/rules-mining/core-store.md:22` | — | — | Write isolation across a store chain: writing through the last store in a derived chain is visible only there; upstream stores and base objects untouched (shallow or deep middle).** | -| CS-R5 | live | `docs/rules-mining/core-store.md:26` | — | visibility-oracle-store.states.ts×4 visibility-oracle.states.ts×4 | Upstream writes propagate downstream through the chain without re-running structural machinery.** | -| CS-R6 | live | `docs/rules-mining/core-store.md:30` | — | — | No store write path ever mutates a user-provided source object.** Aligned with 2026-08-16b. | -| CS-R7 | live | `docs/rules-mining/core-store.md:34` | — | — | Circular references wrap without infinite recursion; cycle consistent through proxy (`state.b.a === state.a`).** | -| CS-R8 | live | `docs/rules-mining/core-store.md:38` | — | — | `snapshot` returns fully unwrapped values (no proxy anywhere, `$TARGET` undefined), incl. frozen objects/arrays; reflects committed written values incl. writes over inherited prototype props.** | -| CS-R9 | live | `docs/rules-mining/core-store.md:42` | store.ts×2 target.ts×1 | — | Proxy identity per logical slot is stable across writes and reconciles** (mapArray keyed flows reuse rows across refetch/reconcile). | -| CS-R10 | live | `docs/rules-mining/core-store.md:48` | — | next-smoke.test.ts×1 | Per-property tracking; same-value writes (direct or functional path setter returning prev) do not re-trigger.** | -| CS-R11 | live | `docs/rules-mining/core-store.md:52` | — | visibility-oracle-store.states.ts×3 visibility-oracle.states.ts×2 | Per-path tracking: reading `state.user.firstName` subscribes to that leaf; reading the reference `store[0]` does not subscribe to `store[0].i`.** | -| CS-R12 | live | `docs/rules-mining/core-store.md:56` | store.ts×1 | — | Reading an absent key subscribes to that key: other-key changes don't trigger; defining it later (assignment or defineProperty) does.** | -| CS-R13 | live | `docs/rules-mining/core-store.md:60` | target.ts×1 | — | `in` tracks presence, not value: undefined-write doesn't retrigger; delete does; adding absent key does. `in`/`has` never invokes source getters.** | -| CS-R14 | live | `docs/rules-mining/core-store.md:64` | — | — | `Object.keys` / `for…in` subscribe to key-set membership (root and nested) — distinct from property nodes.** Aligned: key-set node. | -| CS-R15 | live | `docs/rules-mining/core-store.md:68` | store.ts×1 | — | Array structural tracking is uniform across idioms: indexed length loop, `for…of`, mapArray ($TRACK) all re-run exactly once per flush on add/update/removal.** | -| CS-R16 | live | `docs/rules-mining/core-store.md:72` | — | — | `length` independently trackable; index write extending the array notifies length subscribers.** | -| CS-R17 | live | `docs/rules-mining/core-store.md:76` | — | — | Truncating via `length = N` notifies tracked index reads of removed slots (re-run, observe undefined) and clears has/index/keys for removed indices.** | -| CS-R18 | live | `docs/rules-mining/core-store.md:80` | — | — | `snapshot` is non-tracking.** Aligned with read table. | -| CS-R19 | live | `docs/rules-mining/core-store.md:84` | — | — | `untrack` scopes only the wrapped read; property access on the escaped value afterwards tracks normally.** | -| CS-R20 | live | `docs/rules-mining/core-store.md:88` | store.ts×1 | — | Source getters (own, prototype, merge-installed) execute with the proxy as receiver, so their internal reads track — incl. through projections.** | -| CS-R21 | live | `docs/rules-mining/core-store.md:92` | store.ts×3 | deep-chained-view.test.ts×1 | Structural subscriptions through a wrapper view (store-in-store) chain to the wrapped source: $TRACK/mapArray, ownKeys, snapshot/trackSelf through an outer derived store re-run when the inner store re… | -| CS-R22 | live | `docs/rules-mining/core-store.md:97` | — | — | Slots holding non-wrappable values (markRaw, Map/Date, function) track by reference: reassignment notifies; internal mutation doesn't.** | -| CS-R23 | live | `docs/rules-mining/core-store.md:103` | store.ts×1 | — | The proxy is immutable from outside the setter: direct assignment and delete are silently ignored (no change, no notify, no TypeError — traps report success while discarding).** | -| CS-R24 | live | `docs/rules-mining/core-store.md:107` | — | next-smoke.test.ts×1 | Writes batch like signals: inside the setter draft, reads are read-your-writes (values, length, `in` sync); outside the setter, ALL reads — value, `in`, length — return pre-write state until flush(). … | -| CS-R25 | live | `docs/rules-mining/core-store.md:112` | — | next-smoke.test.ts×1 | Writes to properties with ZERO observers still batch (no effects anywhere; pre-write value visible between setState and flush).** | -| CS-R26 | live | `docs/rules-mining/core-store.md:117` | — | — | Setting a key to undefined is not deletion: key stays present (`in` true, no key-set notify); only delete / storePath.DELETE removes.** | -| CS-R27 | live | `docs/rules-mining/core-store.md:121` | store.ts×1 | — | The setter may return a replacement value that swaps the root wholesale; symbol keys on the replacement preserved.** | -| CS-R28 | live | `docs/rules-mining/core-store.md:125` | — | — | `storePath` addressing: string keys, numeric indices, index arrays, predicate filters ((value, index)), ranges, trailing functional setters address and update intended paths + trigger per-path subscri… | -| CS-R29 | live | `docs/rules-mining/core-store.md:129` | store.ts×2 | overlay.test.ts×1 | Merge/replacement preserve accessor descriptors and keep getters LIVE (re-evaluated per read, reactive reads track), for pre-existing and new keys.** | -| CS-R30 | live | `docs/rules-mining/core-store.md:134` | store.ts×2 | — | Prototype pollution fully guarded: `__proto__` assignment inert; reading `constructor` on the draft returns undefined; storePath refuses `__proto__`/`constructor`/`prototype` segments; skips unsafe ow… | -| CS-R31 | live | `docs/rules-mining/core-store.md:138` | projection.ts×1 | reconcile-resend-identity.test.ts×1 | Derived-store manual writes win over the recompute for the tick: manual setStore beats a queued recompute in the same flush; a SAME-VALUE manual write still holds against the recompute for that tick; … | -| CS-R32 | live | `docs/rules-mining/core-store.md:143` | store.ts×1 | visibility-oracle-store.states.ts×1 | A setter-staged replacement followed by reconcile lands the reconciled value — staged writes fold into the diff.** Aligned: O7's resolution (a test already exists). | -| CS-R33 | live | `docs/rules-mining/core-store.md:147` | — | visibility-oracle-store.states.ts×4 | Action/async lane semantics on store properties: a write held by an action makes isPending true for that property (per-property, not whole-store) while showing the committed value; applies on settle.*… | -| CS-R34 | live | `docs/rules-mining/core-store.md:151` | store.ts×1 | visibility-oracle-store.states.ts×1 | ~~Optimistic writes visible immediately at write time (before flush)~~, never touch base raw; ambient (non-action) optimistic writes auto-revert at flush end.** _Visibility superseded 2026-09-10 by A2… | -| CS-R35 | live | `docs/rules-mining/core-store.md:156` | — | — | Mid-refetch optimistic overlays are consumed when data lands — identical via direct reads, mapArray, wrapper views, Object.keys, snapshot.** | -| CS-R36 | live | `docs/rules-mining/core-store.md:160` | — | — | An active optimistic hold on a wrapper view masks inner-store changes for the view's subscribers: mid-hold inner refresh landing causes ZERO re-runs of the view's structural subscribers; the reveal re… | -| CS-R37 | live | `docs/rules-mining/core-store.md:165` | — | visibility-oracle-store.states.ts×3 | Setting store state from effect callbacks and promise resolutions works, applying next flush.** | -| CS-R38 | live | `docs/rules-mining/core-store.md:171` | — | — | Shallow stores: root keys reactive (per-key nodes, membership, length), values served raw by identity at every depth, arrays and objects.** | -| CS-R39 | live | `docs/rules-mining/core-store.md:175` | — | visibility-oracle-store.states.ts×1 | Shallow setter-scope reads serve raws; in-place mutation of a served raw is reactively inert — records replaced, never edited.** | -| CS-R40 | live | `docs/rules-mining/core-store.md:179` | — | — | Shallow reconcile is positional: per-index effects only where the reference changed; reference-identical rows skip entirely; length propagates; `key` option moot.** Aligned: unowned-reference skip rul… | -| CS-R41 | live | `docs/rules-mining/core-store.md:183` | reconcile.ts×1 store.ts×2 | — | A plain record replaced into a shallow store is STICKY raw-marked: presents raw in this store AND in any deep store that later ingests it.** | -| CS-R42 | live | `docs/rules-mining/core-store.md:188` | reconcile.ts×1 store.ts×1 | — | markRaw values never wrap through ANY store (deep included); leaves for reconcile (reference replacement, no recursion).** | -| CS-R43 | live | `docs/rules-mining/core-store.md:192` | — | — | Store proxies are exempt from shallow raw treatment: shallow store ingesting another store's proxy passes it through unmarked and serves a live wrapped view (upstream visible, downstream isolated), se… | -| CS-R44 | live | `docs/rules-mining/core-store.md:196` | store.ts×1 | — | Ingesting an already-deep-tracked raw into a shallow store throws in dev.** | -| CS-R45 | live | `docs/rules-mining/core-store.md:201` | — | — | A shallow store nested in a deep store participates in the parent's reconcile (raw replacement, per-index notify).** | -| CS-R46 | live | `docs/rules-mining/core-store.md:205` | — | — | Shallow projections work end-to-end (derive re-runs, output reconciles at boundary, rows stay raw).** | -| CS-R47 | live | `docs/rules-mining/core-store.md:211` | — | — | Platform objects (Map, Set, Date, Node instances, subclasses) are structurally non-wrappable: served raw by identity; internal-slot methods work on read and draft paths; draft mutations land on the ra… | -| CS-R48 | live | `docs/rules-mining/core-store.md:216` | — | — | User class instances (custom prototypes) DO wrap: prototype getters track; methods on the draft receive the proxy as `this` (reactive writes).** | -| CS-R49 | live | `docs/rules-mining/core-store.md:220` | — | — | Null-prototype objects wrap and track; function-valued props callable through the proxy.** | -| CS-R50 | live | `docs/rules-mining/core-store.md:224` | — | — | Frozen sources fully supported (read/snapshot; getters returning frozen don't throw).** | -| CS-R51 | live | `docs/rules-mining/core-store.md:229` | store.ts×3 | overlay.test.ts×1 write-floor.test.ts×2 | Proxy-invariant compliance via target indirection: keys/spread/descriptor reads never throw regardless of source rigidity; source-non-configurable prop readable, writable through the store, reported `… | -| CS-R52 | live | `docs/rules-mining/core-store.md:233` | — | — | Symbol-keyed properties first-class: read/write/descriptors/preserved through root replacement + storePath root merge; on arrays symbol writes are metadata (never affect length).** | -| CS-R53 | live | `docs/rules-mining/core-store.md:237` | — | — | Array key hygiene: non-index string keys never affect length; `s[len] = undefined` grows length AND creates a present key.** | -| CS-R54 | live | `docs/rules-mining/core-store.md:241` | — | — | Array natives work through the proxy on read (filter/reduce/map/iterate) and draft (push/pop/shift) paths.** | -| CS-R55 | live | `docs/rules-mining/core-store.md:243` | — | — | Functions stored as values served raw, replaceable, slot-tracked.** | -| CS-R56 | live | `docs/rules-mining/core-store.md:247` | — | — | Multiple setter calls before one flush coalesce: even a deep-reading (structural clone) effect re-runs exactly once per flush.** | -| CS-R57 | live | `docs/rules-mining/core-store.md:251` | — | — | Effect ordering: parent effects before child effects created inside them, incl. shared deps through memos.** | -| CS-R58 | live | `docs/rules-mining/core-store.md:255` | — | — | Mid-flush read coherence: untracked store reads inside internal machinery running WITHIN a flush (mapArray keyed:false under a Root owner) must observe the value being written in that flush, not stale… | +| id | status | defined | cited in src | cited in tests | statement (at definition) | +| ------ | ------ | ------------------------------------- | ------------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| CS-R1 | live | `docs/rules-mining/core-store.md:9` | — | flatten-async-iterable.test.ts×5 syncThenable.test.ts×14 visibility-oracle-store.states.ts×1 | Wrappable values are wrapped: reading a plain object/array child never returns the raw source (`state.data !== data`).\*\* | +| CS-R2 | live | `docs/rules-mining/core-store.md:13` | — | syncThenable.test.ts×12 visibility-oracle.states.ts×1 | Raw→proxy resolution is global and deduplicating: wrapping the same raw through two different stores yields the same proxy (`outer.list === inner`).\*\* | +| CS-R2a | live | `docs/INTERNALS-STORE-STATE.md:107` | reconcile.ts×1 store.ts×1 | — | Corollary R2a (Ryan, 2026-08-17): \*\*take no responsibility for mutation | +| CS-R3 | live | `docs/rules-mining/core-store.md:18` | — | — | A store proxy ingested into another store (deep or shallow) is re-wrapped in the ingesting store's own proxy family — never identity-passed, never raw-marked.\*\* | +| CS-R4 | live | `docs/rules-mining/core-store.md:22` | — | — | Write isolation across a store chain: writing through the last store in a derived chain is visible only there; upstream stores and base objects untouched (shallow or deep middle).\*\* | +| CS-R5 | live | `docs/rules-mining/core-store.md:26` | — | visibility-oracle-store.states.ts×4 visibility-oracle.states.ts×4 | Upstream writes propagate downstream through the chain without re-running structural machinery.\*\* | +| CS-R6 | live | `docs/rules-mining/core-store.md:30` | — | — | No store write path ever mutates a user-provided source object.\*\* Aligned with 2026-08-16b. | +| CS-R7 | live | `docs/rules-mining/core-store.md:34` | — | — | Circular references wrap without infinite recursion; cycle consistent through proxy (`state.b.a === state.a`).\*\* | +| CS-R8 | live | `docs/rules-mining/core-store.md:38` | — | — | `snapshot` returns fully unwrapped values (no proxy anywhere, `$TARGET` undefined), incl. frozen objects/arrays; reflects committed written values incl. writes over inherited prototype props.\*\* | +| CS-R9 | live | `docs/rules-mining/core-store.md:42` | store.ts×2 target.ts×1 | — | Proxy identity per logical slot is stable across writes and reconciles\*\* (mapArray keyed flows reuse rows across refetch/reconcile). | +| CS-R10 | live | `docs/rules-mining/core-store.md:48` | — | next-smoke.test.ts×1 | Per-property tracking; same-value writes (direct or functional path setter returning prev) do not re-trigger.\*\* | +| CS-R11 | live | `docs/rules-mining/core-store.md:52` | — | visibility-oracle-store.states.ts×3 visibility-oracle.states.ts×2 | Per-path tracking: reading `state.user.firstName` subscribes to that leaf; reading the reference `store[0]` does not subscribe to `store[0].i`.\*\* | +| CS-R12 | live | `docs/rules-mining/core-store.md:56` | store.ts×1 | — | Reading an absent key subscribes to that key: other-key changes don't trigger; defining it later (assignment or defineProperty) does.\*\* | +| CS-R13 | live | `docs/rules-mining/core-store.md:60` | target.ts×1 | — | `in` tracks presence, not value: undefined-write doesn't retrigger; delete does; adding absent key does. `in`/`has` never invokes source getters.\*\* | +| CS-R14 | live | `docs/rules-mining/core-store.md:64` | — | — | `Object.keys` / `for…in` subscribe to key-set membership (root and nested) — distinct from property nodes.\*\* Aligned: key-set node. | +| CS-R15 | live | `docs/rules-mining/core-store.md:68` | store.ts×1 | — | Array structural tracking is uniform across idioms: indexed length loop, `for…of`, mapArray ($TRACK) all re-run exactly once per flush on add/update/removal.\*\* | +| CS-R16 | live | `docs/rules-mining/core-store.md:72` | — | — | `length` independently trackable; index write extending the array notifies length subscribers.\*\* | +| CS-R17 | live | `docs/rules-mining/core-store.md:76` | — | — | Truncating via `length = N` notifies tracked index reads of removed slots (re-run, observe undefined) and clears has/index/keys for removed indices.\*\* | +| CS-R18 | live | `docs/rules-mining/core-store.md:80` | — | — | `snapshot` is non-tracking.\*\* Aligned with read table. | +| CS-R19 | live | `docs/rules-mining/core-store.md:84` | — | — | `untrack` scopes only the wrapped read; property access on the escaped value afterwards tracks normally.\*\* | +| CS-R20 | live | `docs/rules-mining/core-store.md:88` | store.ts×1 | — | Source getters (own, prototype, merge-installed) execute with the proxy as receiver, so their internal reads track — incl. through projections.\*\* | +| CS-R21 | live | `docs/rules-mining/core-store.md:92` | store.ts×3 | deep-chained-view.test.ts×1 | Structural subscriptions through a wrapper view (store-in-store) chain to the wrapped source: $TRACK/mapArray, ownKeys, snapshot/trackSelf through an outer derived store re-run when the inner store re… | +| CS-R22 | live | `docs/rules-mining/core-store.md:97` | — | — | Slots holding non-wrappable values (markRaw, Map/Date, function) track by reference: reassignment notifies; internal mutation doesn't.\*\* | +| CS-R23 | live | `docs/rules-mining/core-store.md:103` | store.ts×1 | — | The proxy is immutable from outside the setter: direct assignment and delete are silently ignored (no change, no notify, no TypeError — traps report success while discarding).\*\* | +| CS-R24 | live | `docs/rules-mining/core-store.md:107` | — | next-smoke.test.ts×1 | Writes batch like signals: inside the setter draft, reads are read-your-writes (values, length, `in` sync); outside the setter, ALL reads — value, `in`, length — return pre-write state until flush(). … | +| CS-R25 | live | `docs/rules-mining/core-store.md:112` | — | next-smoke.test.ts×1 | Writes to properties with ZERO observers still batch (no effects anywhere; pre-write value visible between setState and flush).\*\* | +| CS-R26 | live | `docs/rules-mining/core-store.md:117` | — | — | Setting a key to undefined is not deletion: key stays present (`in` true, no key-set notify); only delete / storePath.DELETE removes.\*\* | +| CS-R27 | live | `docs/rules-mining/core-store.md:121` | store.ts×1 | — | The setter may return a replacement value that swaps the root wholesale; symbol keys on the replacement preserved.\*\* | +| CS-R28 | live | `docs/rules-mining/core-store.md:125` | — | — | `storePath` addressing: string keys, numeric indices, index arrays, predicate filters ((value, index)), ranges, trailing functional setters address and update intended paths + trigger per-path subscri… | +| CS-R29 | live | `docs/rules-mining/core-store.md:129` | store.ts×2 | overlay.test.ts×1 | Merge/replacement preserve accessor descriptors and keep getters LIVE (re-evaluated per read, reactive reads track), for pre-existing and new keys.\*\* | +| CS-R30 | live | `docs/rules-mining/core-store.md:134` | store.ts×2 | — | Prototype pollution fully guarded: `__proto__` assignment inert; reading `constructor` on the draft returns undefined; storePath refuses `__proto__`/`constructor`/`prototype` segments; skips unsafe ow… | +| CS-R31 | live | `docs/rules-mining/core-store.md:138` | projection.ts×1 | reconcile-resend-identity.test.ts×1 | Derived-store manual writes win over the recompute for the tick: manual setStore beats a queued recompute in the same flush; a SAME-VALUE manual write still holds against the recompute for that tick; … | +| CS-R32 | live | `docs/rules-mining/core-store.md:143` | store.ts×1 | visibility-oracle-store.states.ts×1 | A setter-staged replacement followed by reconcile lands the reconciled value — staged writes fold into the diff.\*\* Aligned: O7's resolution (a test already exists). | +| CS-R33 | live | `docs/rules-mining/core-store.md:147` | — | visibility-oracle-store.states.ts×4 | Action/async lane semantics on store properties: a write held by an action makes isPending true for that property (per-property, not whole-store) while showing the committed value; applies on settle.\*… | +| CS-R34 | live | `docs/rules-mining/core-store.md:151` | store.ts×1 | visibility-oracle-store.states.ts×1 | ~~Optimistic writes visible immediately at write time (before flush)~~, never touch base raw; ambient (non-action) optimistic writes auto-revert at flush end.\*\* \_Visibility superseded 2026-09-10 by A2… | +| CS-R35 | live | `docs/rules-mining/core-store.md:156` | — | — | Mid-refetch optimistic overlays are consumed when data lands — identical via direct reads, mapArray, wrapper views, Object.keys, snapshot.\*\* | +| CS-R36 | live | `docs/rules-mining/core-store.md:160` | — | — | An active optimistic hold on a wrapper view masks inner-store changes for the view's subscribers: mid-hold inner refresh landing causes ZERO re-runs of the view's structural subscribers; the reveal re… | +| CS-R37 | live | `docs/rules-mining/core-store.md:165` | — | visibility-oracle-store.states.ts×3 | Setting store state from effect callbacks and promise resolutions works, applying next flush.\*\* | +| CS-R38 | live | `docs/rules-mining/core-store.md:171` | — | — | Shallow stores: root keys reactive (per-key nodes, membership, length), values served raw by identity at every depth, arrays and objects.\*\* | +| CS-R39 | live | `docs/rules-mining/core-store.md:175` | — | visibility-oracle-store.states.ts×1 | Shallow setter-scope reads serve raws; in-place mutation of a served raw is reactively inert — records replaced, never edited.\*\* | +| CS-R40 | live | `docs/rules-mining/core-store.md:179` | — | — | Shallow reconcile is positional: per-index effects only where the reference changed; reference-identical rows skip entirely; length propagates; `key` option moot.\*\* Aligned: unowned-reference skip rul… | +| CS-R41 | live | `docs/rules-mining/core-store.md:183` | reconcile.ts×1 store.ts×2 | — | A plain record replaced into a shallow store is STICKY raw-marked: presents raw in this store AND in any deep store that later ingests it.\*\* | +| CS-R42 | live | `docs/rules-mining/core-store.md:188` | reconcile.ts×1 store.ts×1 | — | markRaw values never wrap through ANY store (deep included); leaves for reconcile (reference replacement, no recursion).\*\* | +| CS-R43 | live | `docs/rules-mining/core-store.md:192` | — | — | Store proxies are exempt from shallow raw treatment: shallow store ingesting another store's proxy passes it through unmarked and serves a live wrapped view (upstream visible, downstream isolated), se… | +| CS-R44 | live | `docs/rules-mining/core-store.md:196` | store.ts×1 | — | Ingesting an already-deep-tracked raw into a shallow store throws in dev.\*\* | +| CS-R45 | live | `docs/rules-mining/core-store.md:201` | — | — | A shallow store nested in a deep store participates in the parent's reconcile (raw replacement, per-index notify).\*\* | +| CS-R46 | live | `docs/rules-mining/core-store.md:205` | — | — | Shallow projections work end-to-end (derive re-runs, output reconciles at boundary, rows stay raw).\*\* | +| CS-R47 | live | `docs/rules-mining/core-store.md:211` | — | — | Platform objects (Map, Set, Date, Node instances, subclasses) are structurally non-wrappable: served raw by identity; internal-slot methods work on read and draft paths; draft mutations land on the ra… | +| CS-R48 | live | `docs/rules-mining/core-store.md:216` | — | — | User class instances (custom prototypes) DO wrap: prototype getters track; methods on the draft receive the proxy as `this` (reactive writes).\*\* | +| CS-R49 | live | `docs/rules-mining/core-store.md:220` | — | — | Null-prototype objects wrap and track; function-valued props callable through the proxy.\*\* | +| CS-R50 | live | `docs/rules-mining/core-store.md:224` | — | — | Frozen sources fully supported (read/snapshot; getters returning frozen don't throw).\*\* | +| CS-R51 | live | `docs/rules-mining/core-store.md:229` | store.ts×3 | overlay.test.ts×1 write-floor.test.ts×2 | Proxy-invariant compliance via target indirection: keys/spread/descriptor reads never throw regardless of source rigidity; source-non-configurable prop readable, writable through the store, reported `… | +| CS-R52 | live | `docs/rules-mining/core-store.md:233` | — | — | Symbol-keyed properties first-class: read/write/descriptors/preserved through root replacement + storePath root merge; on arrays symbol writes are metadata (never affect length).\*\* | +| CS-R53 | live | `docs/rules-mining/core-store.md:237` | — | — | Array key hygiene: non-index string keys never affect length; `s[len] = undefined` grows length AND creates a present key.\*\* | +| CS-R54 | live | `docs/rules-mining/core-store.md:241` | — | — | Array natives work through the proxy on read (filter/reduce/map/iterate) and draft (push/pop/shift) paths.\*\* | +| CS-R55 | live | `docs/rules-mining/core-store.md:243` | — | — | Functions stored as values served raw, replaceable, slot-tracked.\*\* | +| CS-R56 | live | `docs/rules-mining/core-store.md:247` | — | — | Multiple setter calls before one flush coalesce: even a deep-reading (structural clone) effect re-runs exactly once per flush.\*\* | +| CS-R57 | live | `docs/rules-mining/core-store.md:251` | — | — | Effect ordering: parent effects before child effects created inside them, incl. shared deps through memos.\*\* | +| CS-R58 | live | `docs/rules-mining/core-store.md:255` | — | — | Mid-flush read coherence: untracked store reads inside internal machinery running WITHIN a flush (mapArray keyed:false under a Root owner) must observe the value being written in that flush, not stale… | + ## R — optimistic-lanes (`OL-R`) -| id | status | defined | cited in src | cited in tests | statement (at definition) | -|---|---|---|---|---|---| -| OL-R1 | live | `docs/rules-mining/optimistic-lanes.md:11` | — | — | `createOptimistic(value \| fn)` returns `[accessor, setter]`; the accessor returns the initial or computed value; the setter accepts a value or an updater function. | -| OL-R2 | superseded | `docs/rules-mining/optimistic-lanes.md:15` | — | — | ~~An optimistic write is synchronously visible to direct reads before any flush — inside the action body, outside it, and outside any reactive context.~~ **Superseded 2026-09-10 by A28(5)** (SPEC-ASYN… | -| OL-R3 | live | `docs/rules-mining/optimistic-lanes.md:19` | — | — | The setter's updater receives the current _visible_ (optimistic-if-overridden) value, never the committed value; a plain setter on the underlying source during a transition composes on the transition'… | -| OL-R4 | live | `docs/rules-mining/optimistic-lanes.md:23` | — | — | Multiple optimistic writes before settle compose sequentially (each updater sees the prior override; last write wins). | -| OL-R5 | live | `docs/rules-mining/optimistic-lanes.md:27` | — | — | An optimistic write **outside any action** reverts at the next flush; subscribers observe the optimistic value and then the reverted value within that single flush (effect log `[1, 2, 1]` after one `f… | -| OL-R6 | live | `docs/rules-mining/optimistic-lanes.md:32` | — | — | An optimistic write inside an `action` holds for the entire action window and reverts when the action's transition completes; each intermediate write during a multi-yield action is observable in order… | -| OL-R7 | live | `docs/rules-mining/optimistic-lanes.md:36` | — | — | Computed-form `createOptimistic(fn)` with no overrides is a transparent passthrough of its (possibly async) source: promise resolutions, re-fired promises, and async-iterable yields all propagate; ove… | -| OL-R8 | live | `docs/rules-mining/optimistic-lanes.md:40` | — | — | Reset-on-settle targets the source's **newly computed value at settle time**, not the pre-write value: a wrong optimistic guess is auto-corrected to the real result; a correct guess settles silently (… | -| OL-R9 | live | `docs/rules-mining/optimistic-lanes.md:44` | — | — | Regular signals written in the same action are held (transition semantics) while optimistic writes display immediately; downstream memos and chained optimistic computeds see optimistic values and reve… | -| OL-R10 | live | `docs/rules-mining/optimistic-lanes.md:48` | — | — | `refresh()` of an optimistic accessor inside an action clears the override when the refetch settles; calling `refresh()` while the upstream source is still pending must not throw. | -| OL-R11 | live | `docs/rules-mining/optimistic-lanes.md:52` | — | — | Verdict channels: an optimistic override **is the value** on every channel — plain read and `latest()` both return it (including literal `undefined`); the override itself is **verdict-inert** — it nev… | -| OL-R12 | live | `docs/rules-mining/optimistic-lanes.md:56` | — | — | A bare `refresh()` is a quiet re-ask — never pending; a **declared** reload (`affects(x)` + `refresh(x)` inside an action) pends the slot for the whole reload window, even when the sole consumer is a … | -| OL-R13 | live | `docs/rules-mining/optimistic-lanes.md:60` | — | — | During the pending window, a source recompute that reveals a value **different** from the current override corrects the override in place (before the action settles), triggering downstream refetch; a … | -| OL-R14 | live | `docs/rules-mining/optimistic-lanes.md:67` | — | — | Independent optimistic writes to unrelated signals form independent lanes: notifications scoped to each signal's own subscribers; each action's overrides revert when _that_ action settles, regardless … | -| OL-R15 | live | `docs/rules-mining/optimistic-lanes.md:71` | — | — | A shared subscriber reading multiple optimistic sources merges lanes **for scheduling only**; it must not transfer transaction ownership of overrides. Disjoint-key work settles with its owning action … | -| OL-R16 | live | `docs/rules-mining/optimistic-lanes.md:76` | — | — | Same-key writes from multiple actions **entangle** those actions: the override (and transitively every override of the entangled actions) reverts only when the **last** entangled action settles. | -| OL-R17 | live | `docs/rules-mining/optimistic-lanes.md:81` | — | — | An equal-value write still registers ownership and still performs lane bookkeeping: a second action writing the same value keeps the override alive after the first settles; an override write whose val… | -| OL-R18 | live | `docs/rules-mining/optimistic-lanes.md:86` | — | — | All optimistic writes in one action share one transaction and revert together atomically; lanes/transactions clean up fully between cycles — the Nth cycle behaves exactly like the first, including aft… | -| OL-R19 | live | `docs/rules-mining/optimistic-lanes.md:90` | — | — | A shared **upstream** async resolving must not merge distinct downstream optimistic lanes — independent paths keep updating independently; genuine merge happens only at convergence points (a memo read… | -| OL-R20 | live | `docs/rules-mining/optimistic-lanes.md:94` | — | — | A later action's override wins over an earlier action's background settle: when action 1's refresh resolves _under_ action 2's live override, the visible value is unchanged, downstream must not recomp… | -| OL-R21 | live | `docs/rules-mining/optimistic-lanes.md:100` | — | — | An optimistic write of literal `undefined` is a full-fledged override: visible on plain read and `latest()`, verdict-inert on `isPending`, and it reverts at settle exactly like any other value. | -| OL-R22 | live | `docs/rules-mining/optimistic-lanes.md:105` | — | — | A follow-up optimistic write after an `undefined` override still rides the optimistic path and reverts at settle — `undefined` in the slot must never erase the node's optimistic identity or route late… | -| OL-R23 | live | `docs/rules-mining/optimistic-lanes.md:109` | — | — | Store form distinguishes "override to undefined" from "delete": optimistic set-to-undefined reads `undefined` with the key still present; optimistic `delete` reads `undefined` **and** `"key" in store … | -| OL-R24 | live | `docs/rules-mining/optimistic-lanes.md:116` | — | — | A transition completes only when **all** reachable asyncs (upstream source and downstream lane asyncs) resolve; held source values must never leak to subscribers before completion, even when the upstr… | -| OL-R25 | live | `docs/rules-mining/optimistic-lanes.md:120` | — | — | Lane readiness gating: subscribers reached _through a downstream async memo_ fire with optimistic values only once that async resolves; direct reads show the override immediately. The lane may flush *… | -| OL-R26 | live | `docs/rules-mining/optimistic-lanes.md:124` | — | — | At settle, the commit of held transition writes and the revert of optimistic overrides are delivered **atomically**: one subscriber run observing both, never a torn intermediate. | -| OL-R27 | live | `docs/rules-mining/optimistic-lanes.md:128` | — | — | Rapid successive user writes replay correctly: the latest override wins; earlier lane flushes deliver the values current at their readiness time; final settled state reflects the last action's confirm… | -| OL-R28 | live | `docs/rules-mining/optimistic-lanes.md:134` | — | — | No-op settles are silent: if the optimistic write equals the current value, neither the write nor the revert notifies; if the settle-time computed value equals the override, no extra notification fire… | -| OL-R29 | live | `docs/rules-mining/optimistic-lanes.md:139` | — | — | Pre-flush writes coalesce: subscribers see only the latest override per flush (`[0, 2, 0]`, never intermediate `1`). | -| OL-R30 | live | `docs/rules-mining/optimistic-lanes.md:143` | — | — | Render-tier and user-tier effects must observe **identical value sequences** at every flush, including the mid-transition moment where an action finished but async reporters are still in flight. | -| OL-R31 | live | `docs/rules-mining/optimistic-lanes.md:147` | — | — | Optimistic lane notifications run even while an unrelated transition is stashed/pending; pending async in one lane never blocks another lane's write/revert notifications. | -| OL-R32 | live | `docs/rules-mining/optimistic-lanes.md:151` | — | — | `isPending` granularity: each async path's pending slot clears when its **own** async resolves; merged downstream nodes stay pending — emitting **no intermediate half-state values** — until all inputs… | -| OL-R33 | live | `docs/rules-mining/optimistic-lanes.md:155` | — | — | No pending flicker when the visible value is unchanged: background refresh phases with an unchanged visible override must not re-pend downstream; a genuinely new in-flight question must fire `isPendin… | -| OL-R34 | live | `docs/rules-mining/optimistic-lanes.md:159` | — | — | `latest()` readers opt into progressive per-path display while plain readers of merged memos wait for full resolution. | -| OL-R35 | live | `docs/rules-mining/optimistic-lanes.md:165` | — | — | `createOptimisticStore` returns `[proxy, setter]`; draft-style mutations inside an action are optimistic: immediately visible through the proxy, wholly reverted at settle. | -| OL-R36 | live | `docs/rules-mining/optimistic-lanes.md:169` | — | — | Array structural edits (e.g. filter-removal) are visible during the window through `length`, index reads, and iteration, and fully revert at settle. | -| OL-R37 | live | `docs/rules-mining/optimistic-lanes.md:174` | — | — | Store optimism is per-key: different keys written by different actions settle independently — same ownership rules as signals (R15/R16) at store-key granularity. | +| id | status | defined | cited in src | cited in tests | statement (at definition) | +| ------ | ---------- | ------------------------------------------- | ------------ | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| OL-R1 | live | `docs/rules-mining/optimistic-lanes.md:11` | — | — | `createOptimistic(value \| fn)` returns `[accessor, setter]`; the accessor returns the initial or computed value; the setter accepts a value or an updater function. | +| OL-R2 | superseded | `docs/rules-mining/optimistic-lanes.md:15` | — | — | ~~An optimistic write is synchronously visible to direct reads before any flush — inside the action body, outside it, and outside any reactive context.~~ **Superseded 2026-09-10 by A28(5)** (SPEC-ASYN… | +| OL-R3 | live | `docs/rules-mining/optimistic-lanes.md:19` | — | — | The setter's updater receives the current _visible_ (optimistic-if-overridden) value, never the committed value; a plain setter on the underlying source during a transition composes on the transition'… | +| OL-R4 | live | `docs/rules-mining/optimistic-lanes.md:23` | — | — | Multiple optimistic writes before settle compose sequentially (each updater sees the prior override; last write wins). | +| OL-R5 | live | `docs/rules-mining/optimistic-lanes.md:27` | — | — | An optimistic write **outside any action** reverts at the next flush; subscribers observe the optimistic value and then the reverted value within that single flush (effect log `[1, 2, 1]` after one `f… | +| OL-R6 | live | `docs/rules-mining/optimistic-lanes.md:32` | — | — | An optimistic write inside an `action` holds for the entire action window and reverts when the action's transition completes; each intermediate write during a multi-yield action is observable in order… | +| OL-R7 | live | `docs/rules-mining/optimistic-lanes.md:36` | — | — | Computed-form `createOptimistic(fn)` with no overrides is a transparent passthrough of its (possibly async) source: promise resolutions, re-fired promises, and async-iterable yields all propagate; ove… | +| OL-R8 | live | `docs/rules-mining/optimistic-lanes.md:40` | — | — | Reset-on-settle targets the source's **newly computed value at settle time**, not the pre-write value: a wrong optimistic guess is auto-corrected to the real result; a correct guess settles silently (… | +| OL-R9 | live | `docs/rules-mining/optimistic-lanes.md:44` | — | — | Regular signals written in the same action are held (transition semantics) while optimistic writes display immediately; downstream memos and chained optimistic computeds see optimistic values and reve… | +| OL-R10 | live | `docs/rules-mining/optimistic-lanes.md:48` | — | — | `refresh()` of an optimistic accessor inside an action clears the override when the refetch settles; calling `refresh()` while the upstream source is still pending must not throw. | +| OL-R11 | live | `docs/rules-mining/optimistic-lanes.md:52` | — | — | Verdict channels: an optimistic override **is the value** on every channel — plain read and `latest()` both return it (including literal `undefined`); the override itself is **verdict-inert** — it nev… | +| OL-R12 | live | `docs/rules-mining/optimistic-lanes.md:56` | — | — | A bare `refresh()` is a quiet re-ask — never pending; a **declared** reload (`affects(x)` + `refresh(x)` inside an action) pends the slot for the whole reload window, even when the sole consumer is a … | +| OL-R13 | live | `docs/rules-mining/optimistic-lanes.md:60` | — | — | During the pending window, a source recompute that reveals a value **different** from the current override corrects the override in place (before the action settles), triggering downstream refetch; a … | +| OL-R14 | live | `docs/rules-mining/optimistic-lanes.md:67` | — | — | Independent optimistic writes to unrelated signals form independent lanes: notifications scoped to each signal's own subscribers; each action's overrides revert when _that_ action settles, regardless … | +| OL-R15 | live | `docs/rules-mining/optimistic-lanes.md:71` | — | — | A shared subscriber reading multiple optimistic sources merges lanes **for scheduling only**; it must not transfer transaction ownership of overrides. Disjoint-key work settles with its owning action … | +| OL-R16 | live | `docs/rules-mining/optimistic-lanes.md:76` | — | — | Same-key writes from multiple actions **entangle** those actions: the override (and transitively every override of the entangled actions) reverts only when the **last** entangled action settles. | +| OL-R17 | live | `docs/rules-mining/optimistic-lanes.md:81` | — | — | An equal-value write still registers ownership and still performs lane bookkeeping: a second action writing the same value keeps the override alive after the first settles; an override write whose val… | +| OL-R18 | live | `docs/rules-mining/optimistic-lanes.md:86` | — | — | All optimistic writes in one action share one transaction and revert together atomically; lanes/transactions clean up fully between cycles — the Nth cycle behaves exactly like the first, including aft… | +| OL-R19 | live | `docs/rules-mining/optimistic-lanes.md:90` | — | — | A shared **upstream** async resolving must not merge distinct downstream optimistic lanes — independent paths keep updating independently; genuine merge happens only at convergence points (a memo read… | +| OL-R20 | live | `docs/rules-mining/optimistic-lanes.md:94` | — | — | A later action's override wins over an earlier action's background settle: when action 1's refresh resolves _under_ action 2's live override, the visible value is unchanged, downstream must not recomp… | +| OL-R21 | live | `docs/rules-mining/optimistic-lanes.md:100` | — | — | An optimistic write of literal `undefined` is a full-fledged override: visible on plain read and `latest()`, verdict-inert on `isPending`, and it reverts at settle exactly like any other value. | +| OL-R22 | live | `docs/rules-mining/optimistic-lanes.md:105` | — | — | A follow-up optimistic write after an `undefined` override still rides the optimistic path and reverts at settle — `undefined` in the slot must never erase the node's optimistic identity or route late… | +| OL-R23 | live | `docs/rules-mining/optimistic-lanes.md:109` | — | — | Store form distinguishes "override to undefined" from "delete": optimistic set-to-undefined reads `undefined` with the key still present; optimistic `delete` reads `undefined` **and** `"key" in store … | +| OL-R24 | live | `docs/rules-mining/optimistic-lanes.md:116` | — | — | A transition completes only when **all** reachable asyncs (upstream source and downstream lane asyncs) resolve; held source values must never leak to subscribers before completion, even when the upstr… | +| OL-R25 | live | `docs/rules-mining/optimistic-lanes.md:120` | — | — | Lane readiness gating: subscribers reached _through a downstream async memo_ fire with optimistic values only once that async resolves; direct reads show the override immediately. The lane may flush \*… | +| OL-R26 | live | `docs/rules-mining/optimistic-lanes.md:124` | — | — | At settle, the commit of held transition writes and the revert of optimistic overrides are delivered **atomically**: one subscriber run observing both, never a torn intermediate. | +| OL-R27 | live | `docs/rules-mining/optimistic-lanes.md:128` | — | — | Rapid successive user writes replay correctly: the latest override wins; earlier lane flushes deliver the values current at their readiness time; final settled state reflects the last action's confirm… | +| OL-R28 | live | `docs/rules-mining/optimistic-lanes.md:134` | — | — | No-op settles are silent: if the optimistic write equals the current value, neither the write nor the revert notifies; if the settle-time computed value equals the override, no extra notification fire… | +| OL-R29 | live | `docs/rules-mining/optimistic-lanes.md:139` | — | — | Pre-flush writes coalesce: subscribers see only the latest override per flush (`[0, 2, 0]`, never intermediate `1`). | +| OL-R30 | live | `docs/rules-mining/optimistic-lanes.md:143` | — | — | Render-tier and user-tier effects must observe **identical value sequences** at every flush, including the mid-transition moment where an action finished but async reporters are still in flight. | +| OL-R31 | live | `docs/rules-mining/optimistic-lanes.md:147` | — | — | Optimistic lane notifications run even while an unrelated transition is stashed/pending; pending async in one lane never blocks another lane's write/revert notifications. | +| OL-R32 | live | `docs/rules-mining/optimistic-lanes.md:151` | — | — | `isPending` granularity: each async path's pending slot clears when its **own** async resolves; merged downstream nodes stay pending — emitting **no intermediate half-state values** — until all inputs… | +| OL-R33 | live | `docs/rules-mining/optimistic-lanes.md:155` | — | — | No pending flicker when the visible value is unchanged: background refresh phases with an unchanged visible override must not re-pend downstream; a genuinely new in-flight question must fire `isPendin… | +| OL-R34 | live | `docs/rules-mining/optimistic-lanes.md:159` | — | — | `latest()` readers opt into progressive per-path display while plain readers of merged memos wait for full resolution. | +| OL-R35 | live | `docs/rules-mining/optimistic-lanes.md:165` | — | — | `createOptimisticStore` returns `[proxy, setter]`; draft-style mutations inside an action are optimistic: immediately visible through the proxy, wholly reverted at settle. | +| OL-R36 | live | `docs/rules-mining/optimistic-lanes.md:169` | — | — | Array structural edits (e.g. filter-removal) are visible during the window through `length`, index reads, and iteration, and fully revert at settle. | +| OL-R37 | live | `docs/rules-mining/optimistic-lanes.md:174` | — | — | Store optimism is per-key: different keys written by different actions settle independently — same ownership rules as signals (R15/R16) at store-key granularity. | + ## R — optimistic-store (`OS-R`) -| id | status | defined | cited in src | cited in tests | statement (at definition) | -|---|---|---|---|---|---| -| OS-R1 | superseded | `docs/rules-mining/optimistic-store.md:9` | — | — | ~~Synchronous universal visibility.~~** ~~An optimistic write is visible to every reader immediately at write time, before any flush.~~ **Superseded 2026-09-10 by A28(5)**: visible at the flush that c… | -| OS-R2 | live | `docs/rules-mining/optimistic-store.md:13` | — | — | Drafts compose on the live optimistic view.** Each setter draft reads through all prior optimistic state (same tick, across ticks, across separate actions/refetches). | -| OS-R3 | live | `docs/rules-mining/optimistic-store.md:18` | — | — | Per-change notification.** One notification per distinct optimistic value change; sequences like `[0, 1, 2, 0]` are contract. | -| OS-R4 | live | `docs/rules-mining/optimistic-store.md:22` | — | — | Equality cut.** An optimistic write equal to current committed value: no notification on write or settle. | -| OS-R5 | live | `docs/rules-mining/optimistic-store.md:26` | — | — | Snapshot/deep read the optimistic view (resolves O1).** snapshot()/deep() agree with every other reader: overlays, nested writes, optimistic deletes (key absent), array mutations; after settle show co… | -| OS-R6 | live | `docs/rules-mining/optimistic-store.md:31` | — | — | Snapshot allocates fresh objects while an overlay is live** (not identity-stable across calls); settled returns raw identity. | -| OS-R7 | live | `docs/rules-mining/optimistic-store.md:36` | — | — | Propagation through derived graphs** (memo chains, mapArray) like committed values. | -| OS-R8 | live | `docs/rules-mining/optimistic-store.md:38` | — | — | `latest()` returns the optimistic value** during a pending refetch window. | -| OS-R9 | live | `docs/rules-mining/optimistic-store.md:40` | — | — | Cross-lane atomic flip.** Regular store written in the same action holds old value while optimistic store shows overlay; at settle both land in ONE notification pass (mixed intermediates never observe… | -| OS-R10 | live | `docs/rules-mining/optimistic-store.md:46` | — | — | Settle reverts to base with one notification** (`[0,1,0]`). | -| OS-R11 | live | `docs/rules-mining/optimistic-store.md:48` | — | — | Deep-state restoration.** Revert restores complete pre-overlay state at every depth: nested writes, wholesale replacement, array length/indices/order, deletions (value + key membership). | -| OS-R12 | live | `docs/rules-mining/optimistic-store.md:50` | — | — | Revert target is the CURRENT derived base, not a stale snapshot** (dependency changed mid-overlay → revert to recomputed value). | -| OS-R13 | live | `docs/rules-mining/optimistic-store.md:55` | — | — | Base data is not overlay data.** Async-fetched/derived data commits to base and persists; only setter-originated optimistic state discards. | -| OS-R14 | live | `docs/rules-mining/optimistic-store.md:57` | — | — | No-flicker across the settle/refresh seam.** From action-body return until refresh fetch lands, subscribers never observe the previously-committed value of an overridden property. | -| OS-R15 | live | `docs/rules-mining/optimistic-store.md:62` | — | — | Unaffected subscribers do not rerun on another action's settle.** | -| OS-R16 | live | `docs/rules-mining/optimistic-store.md:64` | — | — | Cycles are independent** (no residue between sequential write/settle cycles). | -| OS-R17 | live | `docs/rules-mining/optimistic-store.md:66` | — | — | Optimistic writes never pend.** A plain optimistic store is never pending; an optimistic write alone never makes isPending true on any read (shallow, deep(), root or nested proxy, value or length, sam… | -| OS-R18 | live | `docs/rules-mining/optimistic-store.md:70` | — | — | Overlay lifetime is transaction-bound, per key** (never a timer, never a mere flush boundary — under an action). | -| OS-R19 | live | `docs/rules-mining/optimistic-store.md:72` | — | — | Disjoint-key concurrent actions revert independently** (incl. different rows, deletes) (#2899 ×3). | -| OS-R20 | live | `docs/rules-mining/optimistic-store.md:76` | — | — | Same-key writes entangle whole transactions:** latest write displays; NOTHING in the merged transaction settles until the last member completes — including keys written by only one of them. | -| OS-R21 | live | `docs/rules-mining/optimistic-store.md:81` | — | — | Optimistic delete is per-transaction scoped** (a concurrent action's settle must not resurrect another action's delete). | -| OS-R22 | live | `docs/rules-mining/optimistic-store.md:85` | — | — | Ambient (transaction-less) writes flash:** visible until end of flush, then revert — without touching in-flight actions' keys. | -| OS-R23 | live | `docs/rules-mining/optimistic-store.md:89` | — | — | Actions scope globally (a transaction, not a store handle):** writes made under action A belong to A regardless of which store; separate stores under separate actions settle independently. | -| OS-R24 | live | `docs/rules-mining/optimistic-store.md:91` | — | — | Re-override of a still-overridden key notifies and wins;** the earlier action's completion never resurfaces its value. | -| OS-R25 | live | `docs/rules-mining/optimistic-store.md:95` | — | — | Array mutation overlays:** push, splice, whole-array replacement, top-level array stores — length, index reads, holes, spread/iteration, .map all coherent mid-pending and restore exactly on revert. | -| OS-R26 | live | `docs/rules-mining/optimistic-store.md:97` | — | — | Length reactively consistent with contents;** a consumer reading length then indices in one computation never observes a torn state. | -| OS-R27 | live | `docs/rules-mining/optimistic-store.md:101` | — | — | Key enumeration and `has` are lane-reactive** (Object.keys / `in` reflect optimistic adds/deletes, notify, revert). | -| OS-R28 | live | `docs/rules-mining/optimistic-store.md:103` | — | — | Proxy identity survives truth adoption of optimistic rows:** server data key-matching an optimistically pushed row recycles the proxy (identity preserved) and adopts server values. Single and multiple… | -| OS-R29 | live | `docs/rules-mining/optimistic-store.md:107` | — | — | Entity-swap key probes read committed base, not overlay** (an optimistic `s.id = 99` must not confuse the swap); `key: null` → positional identity. | -| OS-R30 | live | `docs/rules-mining/optimistic-store.md:113` | store.ts×1 | — | Seed invisibility.** Derived store's seed is a draft, never observable: before first resolution every read — get, `in`, keys, spread — throws NotReadyError untracked. Applies to createStore(fn, seed) … | -| OS-R31 | live | `docs/rules-mining/optimistic-store.md:117` | — | — | Dev strictRead scopes escalate:** uninitialized read in a component body throws the `[PENDING_ASYNC_UNTRACKED_READ]` dev error (exact tag is contract), precedence over plain NotReadyError. | -| OS-R32 | live | `docs/rules-mining/optimistic-store.md:119` | — | — | Post-init untracked reads flow committed values,** including during a later refetch window. | -| OS-R33 | live | `docs/rules-mining/optimistic-store.md:121` | — | — | Refetch window keeps the dev safeguard** (committed value untracked; component-body read still dev-throws). | -| OS-R34 | live | `docs/rules-mining/optimistic-store.md:123` | — | — | isPending probes take the prod path in both builds:** dev safeguard must not fire inside a probe; uninitialized + surrounding context ⇒ NotReadyError propagates out of isPending identically dev/prod; … | -| OS-R35 | live | `docs/rules-mining/optimistic-store.md:125` | — | — | Plain stores unaffected** (read normally in every context incl. component bodies). | -| OS-R36 | live | `docs/rules-mining/optimistic-store.md:129` | — | — | Dependency-driven refetch pends the leaf and holds the committed view** until the fetch lands. | -| OS-R37 | live | `docs/rules-mining/optimistic-store.md:131` | — | — | Optimistic writes are verdict-inert:** a mid-refetch write displays but neither clears nor causes pending; the honest mixed state {value: 999, pending: true} is observable. (Re-ruled 2026-07-13, super… | -| OS-R38 | live | `docs/rules-mining/optimistic-store.md:133` | optimistic.ts×1 | — | No-op setters are fully inert:** trap-firing no-ops (s => s, s => ({...s}), same-value write, delete of absent prop) mid-refetch display nothing, don't silence pending, don't entangle with the surroun… | -| OS-R39 | live | `docs/rules-mining/optimistic-store.md:137` | — | — | Landing truth wins over the override:** fetch resolves → server/computed value displays, override consumed, pending clears — even if written mid-flight. | -| OS-R40 | live | `docs/rules-mining/optimistic-store.md:139` | — | — | Bare refresh is a quiet re-ask; affects + refresh is a declared reload.** refresh(store) alone never pends reads; affects(store) + refresh pends them, clearing when data lands. Sync-back refresh insid… | -| OS-R41 | live | `docs/rules-mining/optimistic-store.md:141` | — | — | Streaming continuations are not pending windows.** A generator-based derive (or wrapped createProjection) that yielded once reads settled while awaiting its next chunk, incl. with an override displaye… | -| OS-R42 | live | `docs/rules-mining/optimistic-store.md:143` | — | — | Bare writes ride an in-flight refetch (#2951).** A transaction-less optimistic write while the store's own truth is in flight does NOT revert at flush end; holds until truth lands. Order-independent w… | -| OS-R43 | live | `docs/rules-mining/optimistic-store.md:147` | — | — | Refresh-in-action landings preserve still-pending overlays** (same key ⇒ merged transaction: landing does not consume the pending action's optimistic value). | -| OS-R44 | live | `docs/rules-mining/optimistic-store.md:149` | — | — | Bare-refresh landings consume key-matched overlay content** (optimistic "Optimistic" → server "Saved"); the action's later settle does not revert it. | -| OS-R45 | live | `docs/rules-mining/optimistic-store.md:151` | — | — | Separate-transition landings clear foreign optimistic rows (#2719):** a different source transition resolving fresh data clears optimistic rows of a still-pending unrelated action immediately; later s… | -| OS-R46 | live | `docs/rules-mining/optimistic-store.md:155` | — | — | Refetch persistence across multi-action windows:** overlay survives arbitrary interleaved refresh landings while any overlapping action is pending. | +| id | status | defined | cited in src | cited in tests | statement (at definition) | +| ------ | ---------- | ------------------------------------------- | --------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| OS-R1 | superseded | `docs/rules-mining/optimistic-store.md:9` | — | — | ~~Synchronous universal visibility.~~** ~~An optimistic write is visible to every reader immediately at write time, before any flush.~~ **Superseded 2026-09-10 by A28(5)\*\*: visible at the flush that c… | +| OS-R2 | live | `docs/rules-mining/optimistic-store.md:13` | — | — | Drafts compose on the live optimistic view.\*\* Each setter draft reads through all prior optimistic state (same tick, across ticks, across separate actions/refetches). | +| OS-R3 | live | `docs/rules-mining/optimistic-store.md:18` | — | — | Per-change notification.\*\* One notification per distinct optimistic value change; sequences like `[0, 1, 2, 0]` are contract. | +| OS-R4 | live | `docs/rules-mining/optimistic-store.md:22` | — | — | Equality cut.\*\* An optimistic write equal to current committed value: no notification on write or settle. | +| OS-R5 | live | `docs/rules-mining/optimistic-store.md:26` | — | — | Snapshot/deep read the optimistic view (resolves O1).\*\* snapshot()/deep() agree with every other reader: overlays, nested writes, optimistic deletes (key absent), array mutations; after settle show co… | +| OS-R6 | live | `docs/rules-mining/optimistic-store.md:31` | — | — | Snapshot allocates fresh objects while an overlay is live\*\* (not identity-stable across calls); settled returns raw identity. | +| OS-R7 | live | `docs/rules-mining/optimistic-store.md:36` | — | — | Propagation through derived graphs\*\* (memo chains, mapArray) like committed values. | +| OS-R8 | live | `docs/rules-mining/optimistic-store.md:38` | — | — | `latest()` returns the optimistic value\*\* during a pending refetch window. | +| OS-R9 | live | `docs/rules-mining/optimistic-store.md:40` | — | — | Cross-lane atomic flip.\*\* Regular store written in the same action holds old value while optimistic store shows overlay; at settle both land in ONE notification pass (mixed intermediates never observe… | +| OS-R10 | live | `docs/rules-mining/optimistic-store.md:46` | — | — | Settle reverts to base with one notification\*\* (`[0,1,0]`). | +| OS-R11 | live | `docs/rules-mining/optimistic-store.md:48` | — | — | Deep-state restoration.\*\* Revert restores complete pre-overlay state at every depth: nested writes, wholesale replacement, array length/indices/order, deletions (value + key membership). | +| OS-R12 | live | `docs/rules-mining/optimistic-store.md:50` | — | — | Revert target is the CURRENT derived base, not a stale snapshot\*\* (dependency changed mid-overlay → revert to recomputed value). | +| OS-R13 | live | `docs/rules-mining/optimistic-store.md:55` | — | — | Base data is not overlay data.\*\* Async-fetched/derived data commits to base and persists; only setter-originated optimistic state discards. | +| OS-R14 | live | `docs/rules-mining/optimistic-store.md:57` | — | — | No-flicker across the settle/refresh seam.\*\* From action-body return until refresh fetch lands, subscribers never observe the previously-committed value of an overridden property. | +| OS-R15 | live | `docs/rules-mining/optimistic-store.md:62` | — | — | Unaffected subscribers do not rerun on another action's settle.\*\* | +| OS-R16 | live | `docs/rules-mining/optimistic-store.md:64` | — | — | Cycles are independent\*\* (no residue between sequential write/settle cycles). | +| OS-R17 | live | `docs/rules-mining/optimistic-store.md:66` | — | — | Optimistic writes never pend.\*\* A plain optimistic store is never pending; an optimistic write alone never makes isPending true on any read (shallow, deep(), root or nested proxy, value or length, sam… | +| OS-R18 | live | `docs/rules-mining/optimistic-store.md:70` | — | — | Overlay lifetime is transaction-bound, per key\*\* (never a timer, never a mere flush boundary — under an action). | +| OS-R19 | live | `docs/rules-mining/optimistic-store.md:72` | — | — | Disjoint-key concurrent actions revert independently\*\* (incl. different rows, deletes) (#2899 ×3). | +| OS-R20 | live | `docs/rules-mining/optimistic-store.md:76` | — | — | Same-key writes entangle whole transactions:\*\* latest write displays; NOTHING in the merged transaction settles until the last member completes — including keys written by only one of them. | +| OS-R21 | live | `docs/rules-mining/optimistic-store.md:81` | — | — | Optimistic delete is per-transaction scoped\*\* (a concurrent action's settle must not resurrect another action's delete). | +| OS-R22 | live | `docs/rules-mining/optimistic-store.md:85` | — | — | Ambient (transaction-less) writes flash:\*\* visible until end of flush, then revert — without touching in-flight actions' keys. | +| OS-R23 | live | `docs/rules-mining/optimistic-store.md:89` | — | — | Actions scope globally (a transaction, not a store handle):\*\* writes made under action A belong to A regardless of which store; separate stores under separate actions settle independently. | +| OS-R24 | live | `docs/rules-mining/optimistic-store.md:91` | — | — | Re-override of a still-overridden key notifies and wins;\*\* the earlier action's completion never resurfaces its value. | +| OS-R25 | live | `docs/rules-mining/optimistic-store.md:95` | — | — | Array mutation overlays:\*\* push, splice, whole-array replacement, top-level array stores — length, index reads, holes, spread/iteration, .map all coherent mid-pending and restore exactly on revert. | +| OS-R26 | live | `docs/rules-mining/optimistic-store.md:97` | — | — | Length reactively consistent with contents;\*\* a consumer reading length then indices in one computation never observes a torn state. | +| OS-R27 | live | `docs/rules-mining/optimistic-store.md:101` | — | — | Key enumeration and `has` are lane-reactive\*\* (Object.keys / `in` reflect optimistic adds/deletes, notify, revert). | +| OS-R28 | live | `docs/rules-mining/optimistic-store.md:103` | — | — | Proxy identity survives truth adoption of optimistic rows:\*\* server data key-matching an optimistically pushed row recycles the proxy (identity preserved) and adopts server values. Single and multiple… | +| OS-R29 | live | `docs/rules-mining/optimistic-store.md:107` | — | — | Entity-swap key probes read committed base, not overlay\*\* (an optimistic `s.id = 99` must not confuse the swap); `key: null` → positional identity. | +| OS-R30 | live | `docs/rules-mining/optimistic-store.md:113` | store.ts×1 | — | Seed invisibility.\*\* Derived store's seed is a draft, never observable: before first resolution every read — get, `in`, keys, spread — throws NotReadyError untracked. Applies to createStore(fn, seed) … | +| OS-R31 | live | `docs/rules-mining/optimistic-store.md:117` | — | — | Dev strictRead scopes escalate:\*\* uninitialized read in a component body throws the `[PENDING_ASYNC_UNTRACKED_READ]` dev error (exact tag is contract), precedence over plain NotReadyError. | +| OS-R32 | live | `docs/rules-mining/optimistic-store.md:119` | — | — | Post-init untracked reads flow committed values,\*\* including during a later refetch window. | +| OS-R33 | live | `docs/rules-mining/optimistic-store.md:121` | — | — | Refetch window keeps the dev safeguard\*\* (committed value untracked; component-body read still dev-throws). | +| OS-R34 | live | `docs/rules-mining/optimistic-store.md:123` | — | — | isPending probes take the prod path in both builds:\*\* dev safeguard must not fire inside a probe; uninitialized + surrounding context ⇒ NotReadyError propagates out of isPending identically dev/prod; … | +| OS-R35 | live | `docs/rules-mining/optimistic-store.md:125` | — | — | Plain stores unaffected\*\* (read normally in every context incl. component bodies). | +| OS-R36 | live | `docs/rules-mining/optimistic-store.md:129` | — | — | Dependency-driven refetch pends the leaf and holds the committed view\*\* until the fetch lands. | +| OS-R37 | live | `docs/rules-mining/optimistic-store.md:131` | — | — | Optimistic writes are verdict-inert:\*\* a mid-refetch write displays but neither clears nor causes pending; the honest mixed state {value: 999, pending: true} is observable. (Re-ruled 2026-07-13, super… | +| OS-R38 | live | `docs/rules-mining/optimistic-store.md:133` | optimistic.ts×1 | — | No-op setters are fully inert:\*\* trap-firing no-ops (s => s, s => ({...s}), same-value write, delete of absent prop) mid-refetch display nothing, don't silence pending, don't entangle with the surroun… | +| OS-R39 | live | `docs/rules-mining/optimistic-store.md:137` | — | — | Landing truth wins over the override:\*\* fetch resolves → server/computed value displays, override consumed, pending clears — even if written mid-flight. | +| OS-R40 | live | `docs/rules-mining/optimistic-store.md:139` | — | — | Bare refresh is a quiet re-ask; affects + refresh is a declared reload.\*\* refresh(store) alone never pends reads; affects(store) + refresh pends them, clearing when data lands. Sync-back refresh insid… | +| OS-R41 | live | `docs/rules-mining/optimistic-store.md:141` | — | — | Streaming continuations are not pending windows.\*\* A generator-based derive (or wrapped createProjection) that yielded once reads settled while awaiting its next chunk, incl. with an override displaye… | +| OS-R42 | live | `docs/rules-mining/optimistic-store.md:143` | — | — | Bare writes ride an in-flight refetch (#2951).\*\* A transaction-less optimistic write while the store's own truth is in flight does NOT revert at flush end; holds until truth lands. Order-independent w… | +| OS-R43 | live | `docs/rules-mining/optimistic-store.md:147` | — | — | Refresh-in-action landings preserve still-pending overlays\*\* (same key ⇒ merged transaction: landing does not consume the pending action's optimistic value). | +| OS-R44 | live | `docs/rules-mining/optimistic-store.md:149` | — | — | Bare-refresh landings consume key-matched overlay content\*\* (optimistic "Optimistic" → server "Saved"); the action's later settle does not revert it. | +| OS-R45 | live | `docs/rules-mining/optimistic-store.md:151` | — | — | Separate-transition landings clear foreign optimistic rows (#2719):\*\* a different source transition resolving fresh data clears optimistic rows of a still-pending unrelated action immediately; later s… | +| OS-R46 | live | `docs/rules-mining/optimistic-store.md:155` | — | — | Refetch persistence across multi-action windows:\*\* overlay survives arbitrary interleaved refresh landings while any overlapping action is pending. | + ## R — projections (`PJ-R`) -| id | status | defined | cited in src | cited in tests | statement (at definition) | -|---|---|---|---|---|---| -| PJ-R1 | live | `docs/rules-mining/projections.md:9` | — | — | The derive receives the projection's current state as a mutable draft that persists across runs**; prior runs' writes are visible and editable later. | -| PJ-R2 | live | `docs/rules-mining/projections.md:12` | store.ts×1 | projection-absent-key-tracking.test.ts×1 | Draft reads inside the derive never register dependencies (no self-tracking)**, including `has`/index probes from array methods (`findIndex`, `splice`) and inspection traps from `console.log`. | -| PJ-R3 | live | `docs/rules-mining/projections.md:15` | — | — | The derive runs eagerly at creation (before any read/subscriber) and re-runs on flush when tracked sources change, even with zero subscribers.** | -| PJ-R4 | live | `docs/rules-mining/projections.md:19` | — | — | A returned value merges reconcile-style**: changed paths notify, absent keys delete, unchanged paths keep value and identity. | -| PJ-R5 | live | `docs/rules-mining/projections.md:22` | reconcile.ts×2 | — | The projection's root proxy identity is stable for its lifetime**: across entity swaps, shape changes, and root key mismatches (root key change merges in place, no throw). | -| PJ-R6 | live | `docs/rules-mining/projections.md:25` | reconcile.ts×1 | — | Keyed diff (default key `"id"`)**: key-matched subtrees merge in place preserving child proxy identity, skipping notification for unchanged slots; key mismatch replaces the subtree with a fresh proxy. | -| PJ-R7 | live | `docs/rules-mining/projections.md:28` | reconcile.ts×2 | — | Key matching is hierarchically scoped**: when the root entity's key changes, children are NOT merged across the entity change even if their own keys match. | -| PJ-R8 | live | `docs/rules-mining/projections.md:32` | — | — | `{ key: null }` merges positionally** (proxy identity preserved regardless of key-field changes). | -| PJ-R9 | live | `docs/rules-mining/projections.md:34` | — | — | A proxy detached by an entity swap remains a coherent read view of its own (old) data** — never dead, never reflecting the new entity. | -| PJ-R10 | live | `docs/rules-mining/projections.md:37` | reconcile.ts×1 | — | After a root swap, the outgoing raw stops resolving to the projection root**; re-handed as nested data it wraps as a distinct proxy with its own values. | -| PJ-R11 | live | `docs/rules-mining/projections.md:41` | — | — | `reconcile()` on a plain store still throws on root key mismatch**; the projection root's merge-in-place (R5) is a projection-specific relaxation. | -| PJ-R12 | live | `docs/rules-mining/projections.md:46` | — | — | Only subscribers of actually-changed properties rerun**; equal-value rewrites and writes to unobserved keys notify nobody. | -| PJ-R13 | live | `docs/rules-mining/projections.md:49` | — | — | Deleting a key notifies its subscribers; subscribers of absent keys track and are notified on later creation.** | -| PJ-R14 | live | `docs/rules-mining/projections.md:53` | — | — | Every subscriber of a changed property is notified exactly once per change.** | -| PJ-R15 | live | `docs/rules-mining/projections.md:55` | — | — | Projections compose** (projection reading another projection; downstream effects run once per upstream change with correct previous values). | -| PJ-R16 | live | `docs/rules-mining/projections.md:57` | — | — | `Object.keys` of a projection is tracked and notifies on key-set changes, including through a chained store backing.** | -| PJ-R17 | live | `docs/rules-mining/projections.md:62` | — | — | A derive returning a live store proxy adopts it live**: subsequent source-store writes flow through the projection without re-running the derive. | -| PJ-R18 | live | `docs/rules-mining/projections.md:66` | — | — | Fine-grained isolation preserved through the chain**: a nested source-store write notifies only the projection subscribers of that nested path. | -| PJ-R19 | live | `docs/rules-mining/projections.md:68` | — | — | When the derive's return switches (store → plain → other store), subscribers see each new value and the previous chain is fully severed.** | -| PJ-R20 | live | `docs/rules-mining/projections.md:70` | — | — | Chained backing works for array roots** (structural + row-level edits flow). | -| PJ-R21 | live | `docs/rules-mining/projections.md:72` | — | — | `createStore(fn, seed)` is the same projection mechanism and chains identically.** | -| PJ-R22 | live | `docs/rules-mining/projections.md:74` | — | — | `snapshot()` of a chained projection returns plain data equal to the current view and detached from future source writes.** | -| PJ-R23 | live | `docs/rules-mining/projections.md:80` | store.ts×2 | — | The seed is a draft for the derive, never observable (#2897)**: until first settle/yield, every read — tracked, untracked, enumeration/spread — throws NotReadyError. | -| PJ-R24 | live | `docs/rules-mining/projections.md:83` | — | — | Draft writes during an in-flight async run are invisible until that run settles** (per-run atomic visibility). | -| PJ-R25 | live | `docs/rules-mining/projections.md:85` | — | — | Async generators publish one snapshot per yield**: bare `yield` publishes accumulated draft mutations; `yield value` replaces the entire state (no merge); each yield transforms again. | -| PJ-R26 | live | `docs/rules-mining/projections.md:87` | — | — | Latest-run-wins supersession**: superseded runs' later yields and pending draft writes are discarded entirely; if no run ever landed, stays NotReady. | -| PJ-R27 | live | `docs/rules-mining/projections.md:89` | — | — | Async recompute does not coarsen granularity**: after settle, only changed-path subscribers rerun. | -| PJ-R28 | live | `docs/rules-mining/projections.md:91` | — | — | `refresh(proj)` forces a new derive run; bare refresh is quiet** (no pending published; silent reveal). | -| PJ-R29 | live | `docs/rules-mining/projections.md:93` | — | — | `affects(proj)` + `refresh(proj)` is a declared reload**: subscribed effects see isPending true + stale value for the window, then settle. | -| PJ-R30 | live | `docs/rules-mining/projections.md:95` | — | — | With no effect subscribed, async work creates no transition** (isPending false throughout initial load). | -| PJ-R31 | live | `docs/rules-mining/projections.md:97` | — | — | With a subscribed effect, source-triggered async reruns are transitions** (pending true + stale during window); initial no-stale-data load is never pending. | -| PJ-R32 | live | `docs/rules-mining/projections.md:99` | — | — | Reading a pending async source inside the derive propagates NotReady to consumers** (Loading boundaries fall back); settle fires downstream effects exactly once with the settled value, never the seed … | -| PJ-R33 | live | `docs/rules-mining/projections.md:101` | — | — | Settlement is a status change, not a value diff**: boundaries and blocked effects release even when the settled value equals the seed. | -| PJ-R34 | live | `docs/rules-mining/projections.md:103` | — | — | Errored derives follow async memo rules**: after rejection ALL readers (settle-time, late tracked, untracked) throw the error (StatusError-wrapped; boundaries unwrap). Seed never served uninitialized;… | -| PJ-R35 | live | `docs/rules-mining/projections.md:106` | — | — | A genuine tracked read on a later cycle retries an errored derive** (memo parity: never untracked, never inside isPending probe, at most once per cycle); successful retry serves fresh value. | -| PJ-R36 | live | `docs/rules-mining/projections.md:110` | — | — | Disposing the owning root stops the projection** (no recomputes, no notifications afterward). | +| id | status | defined | cited in src | cited in tests | statement (at definition) | +| ------ | ------ | -------------------------------------- | -------------- | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| PJ-R1 | live | `docs/rules-mining/projections.md:9` | — | — | The derive receives the projection's current state as a mutable draft that persists across runs\*\*; prior runs' writes are visible and editable later. | +| PJ-R2 | live | `docs/rules-mining/projections.md:12` | store.ts×1 | projection-absent-key-tracking.test.ts×1 | Draft reads inside the derive never register dependencies (no self-tracking)\*\*, including `has`/index probes from array methods (`findIndex`, `splice`) and inspection traps from `console.log`. | +| PJ-R3 | live | `docs/rules-mining/projections.md:15` | — | — | The derive runs eagerly at creation (before any read/subscriber) and re-runs on flush when tracked sources change, even with zero subscribers.\*\* | +| PJ-R4 | live | `docs/rules-mining/projections.md:19` | — | — | A returned value merges reconcile-style\*\*: changed paths notify, absent keys delete, unchanged paths keep value and identity. | +| PJ-R5 | live | `docs/rules-mining/projections.md:22` | reconcile.ts×2 | — | The projection's root proxy identity is stable for its lifetime\*\*: across entity swaps, shape changes, and root key mismatches (root key change merges in place, no throw). | +| PJ-R6 | live | `docs/rules-mining/projections.md:25` | reconcile.ts×1 | — | Keyed diff (default key `"id"`)\*\*: key-matched subtrees merge in place preserving child proxy identity, skipping notification for unchanged slots; key mismatch replaces the subtree with a fresh proxy. | +| PJ-R7 | live | `docs/rules-mining/projections.md:28` | reconcile.ts×2 | — | Key matching is hierarchically scoped\*\*: when the root entity's key changes, children are NOT merged across the entity change even if their own keys match. | +| PJ-R8 | live | `docs/rules-mining/projections.md:32` | — | — | `{ key: null }` merges positionally\*\* (proxy identity preserved regardless of key-field changes). | +| PJ-R9 | live | `docs/rules-mining/projections.md:34` | — | — | A proxy detached by an entity swap remains a coherent read view of its own (old) data\*\* — never dead, never reflecting the new entity. | +| PJ-R10 | live | `docs/rules-mining/projections.md:37` | reconcile.ts×1 | — | After a root swap, the outgoing raw stops resolving to the projection root\*\*; re-handed as nested data it wraps as a distinct proxy with its own values. | +| PJ-R11 | live | `docs/rules-mining/projections.md:41` | — | — | `reconcile()` on a plain store still throws on root key mismatch\*\*; the projection root's merge-in-place (R5) is a projection-specific relaxation. | +| PJ-R12 | live | `docs/rules-mining/projections.md:46` | — | — | Only subscribers of actually-changed properties rerun\*\*; equal-value rewrites and writes to unobserved keys notify nobody. | +| PJ-R13 | live | `docs/rules-mining/projections.md:49` | — | — | Deleting a key notifies its subscribers; subscribers of absent keys track and are notified on later creation.\*\* | +| PJ-R14 | live | `docs/rules-mining/projections.md:53` | — | — | Every subscriber of a changed property is notified exactly once per change.\*\* | +| PJ-R15 | live | `docs/rules-mining/projections.md:55` | — | — | Projections compose\*\* (projection reading another projection; downstream effects run once per upstream change with correct previous values). | +| PJ-R16 | live | `docs/rules-mining/projections.md:57` | — | — | `Object.keys` of a projection is tracked and notifies on key-set changes, including through a chained store backing.\*\* | +| PJ-R17 | live | `docs/rules-mining/projections.md:62` | — | — | A derive returning a live store proxy adopts it live\*\*: subsequent source-store writes flow through the projection without re-running the derive. | +| PJ-R18 | live | `docs/rules-mining/projections.md:66` | — | — | Fine-grained isolation preserved through the chain\*\*: a nested source-store write notifies only the projection subscribers of that nested path. | +| PJ-R19 | live | `docs/rules-mining/projections.md:68` | — | — | When the derive's return switches (store → plain → other store), subscribers see each new value and the previous chain is fully severed.\*\* | +| PJ-R20 | live | `docs/rules-mining/projections.md:70` | — | — | Chained backing works for array roots\*\* (structural + row-level edits flow). | +| PJ-R21 | live | `docs/rules-mining/projections.md:72` | — | — | `createStore(fn, seed)` is the same projection mechanism and chains identically.\*\* | +| PJ-R22 | live | `docs/rules-mining/projections.md:74` | — | — | `snapshot()` of a chained projection returns plain data equal to the current view and detached from future source writes.\*\* | +| PJ-R23 | live | `docs/rules-mining/projections.md:80` | store.ts×2 | — | The seed is a draft for the derive, never observable (#2897)\*\*: until first settle/yield, every read — tracked, untracked, enumeration/spread — throws NotReadyError. | +| PJ-R24 | live | `docs/rules-mining/projections.md:83` | — | — | Draft writes during an in-flight async run are invisible until that run settles\*\* (per-run atomic visibility). | +| PJ-R25 | live | `docs/rules-mining/projections.md:85` | — | — | Async generators publish one snapshot per yield\*\*: bare `yield` publishes accumulated draft mutations; `yield value` replaces the entire state (no merge); each yield transforms again. | +| PJ-R26 | live | `docs/rules-mining/projections.md:87` | — | — | Latest-run-wins supersession\*\*: superseded runs' later yields and pending draft writes are discarded entirely; if no run ever landed, stays NotReady. | +| PJ-R27 | live | `docs/rules-mining/projections.md:89` | — | — | Async recompute does not coarsen granularity\*\*: after settle, only changed-path subscribers rerun. | +| PJ-R28 | live | `docs/rules-mining/projections.md:91` | — | — | `refresh(proj)` forces a new derive run; bare refresh is quiet\*\* (no pending published; silent reveal). | +| PJ-R29 | live | `docs/rules-mining/projections.md:93` | — | — | `affects(proj)` + `refresh(proj)` is a declared reload\*\*: subscribed effects see isPending true + stale value for the window, then settle. | +| PJ-R30 | live | `docs/rules-mining/projections.md:95` | — | — | With no effect subscribed, async work creates no transition\*\* (isPending false throughout initial load). | +| PJ-R31 | live | `docs/rules-mining/projections.md:97` | — | — | With a subscribed effect, source-triggered async reruns are transitions\*\* (pending true + stale during window); initial no-stale-data load is never pending. | +| PJ-R32 | live | `docs/rules-mining/projections.md:99` | — | — | Reading a pending async source inside the derive propagates NotReady to consumers\*\* (Loading boundaries fall back); settle fires downstream effects exactly once with the settled value, never the seed … | +| PJ-R33 | live | `docs/rules-mining/projections.md:101` | — | — | Settlement is a status change, not a value diff\*\*: boundaries and blocked effects release even when the settled value equals the seed. | +| PJ-R34 | live | `docs/rules-mining/projections.md:103` | — | — | Errored derives follow async memo rules\*\*: after rejection ALL readers (settle-time, late tracked, untracked) throw the error (StatusError-wrapped; boundaries unwrap). Seed never served uninitialized;… | +| PJ-R35 | live | `docs/rules-mining/projections.md:106` | — | — | A genuine tracked read on a later cycle retries an errored derive\*\* (memo parity: never untracked, never inside isPending probe, at most once per cycle); successful retry serves fresh value. | +| PJ-R36 | live | `docs/rules-mining/projections.md:110` | — | — | Disposing the owning root stops the projection\*\* (no recomputes, no notifications afterward). | + ## R — reconcile-snapshot (`RS-R`) -| id | status | defined | cited in src | cited in tests | statement (at definition) | -|---|---|---|---|---|---| -| RS-R1 | live | `docs/rules-mining/reconcile-snapshot.md:9` | — | — | Keyed object merge deletes absent keys.** Properties present in `next` update; properties absent from `next` are deleted (read `undefined`, removed from `in`/keys). | -| RS-R2 | live | `docs/rules-mining/reconcile-snapshot.md:12` | — | — | Reconcile applies to any nested proxy, not just the root**, with identical semantics. | -| RS-R3 | live | `docs/rules-mining/reconcile-snapshot.md:15` | — | — | Keyed identity mismatch at the target throws** (key differs, or key present on target but missing from `next`). Post-throw state is deliberately unasserted (original expectations commented out) — the … | -| RS-R4 | live | `docs/rules-mining/reconcile-snapshot.md:18` | — | — | `key: null` / `key: ""` disables key matching**: positional merge, no root identity check. | -| RS-R5 | live | `docs/rules-mining/reconcile-snapshot.md:21` | — | — | Key modes: string key, key function, none.** KeyFn's call set is observable (see R17). | -| RS-R6 | live | `docs/rules-mining/reconcile-snapshot.md:24` | — | — | Key-matched items preserve logical (proxy) identity across reorder, insert, delete.** | -| RS-R7 | live | `docs/rules-mining/reconcile-snapshot.md:27` | — | — | Re-sent identical objects preserve raw identity: `snapshot(state.arr[i])` is `Object.is`-equal to the original.** CONFLICT (benign, verify): adoption satisfies this since raw becomes the incoming obje… | -| RS-R8 | live | `docs/rules-mining/reconcile-snapshot.md:30` | reconcile.ts×1 | — | Positional merge preserves slot proxy identity even when identifying fields change** (fixed-shape dashboard pattern). | -| RS-R9 | live | `docs/rules-mining/reconcile-snapshot.md:33` | reconcile.ts×2 | — | Only changed leaves notify** (changed `a` reruns its subscriber exactly once; `b` subscriber zero times). | -| RS-R10 | live | `docs/rules-mining/reconcile-snapshot.md:36` | reconcile.ts×2 | — | Kind changes (object↔array) at any position replace wholesale, never merge, and notify the property node.** | -| RS-R11 | live | `docs/rules-mining/reconcile-snapshot.md:39` | reconcile.ts×2 | — | Null entries and primitives are legal keyed-array members** (#2772). | -| RS-R12 | live | `docs/rules-mining/reconcile-snapshot.md:42` | — | — | Array resize notification matrix.** Shrink: tracked removed indices notify `undefined`; tracked `in` flips false; untracked reads agree with new length (no stale node values). Growth: tracked missing … | -| RS-R13 | live | `docs/rules-mining/reconcile-snapshot.md:45` | — | — | Numeric-coercible non-index string props on arrays (`"1e3"`, `"1.5"`) survive resize**; node sync must be membership-based, not length-range-based. | -| RS-R14 | live | `docs/rules-mining/reconcile-snapshot.md:48` | — | — | Symbol keys have full parity with string keys under reconcile** (update/remove/add/nested/mixed). | -| RS-R15 | live | `docs/rules-mining/reconcile-snapshot.md:51` | — | — | Reconcile can assign, swap, and reorder values that are other stores' proxies.** CONFLICT (attention): adoption must handle `next` values that are live proxies of other stores — `storeLookup` resoluti… | -| RS-R16 | live | `docs/rules-mining/reconcile-snapshot.md:54` | reconcile.ts×1 | — | Captured proxies with a live subscriber anywhere below are diffed in place through never-tracked intermediate levels.** CONFLICT (design obligation): a node exists deep below an un-noded path; adoptio… | -| RS-R17 | live | `docs/rules-mining/reconcile-snapshot.md:57` | reconcile.ts×1 | — | Never-subscribed subtrees are pruned: the diff does not walk below their top-level pair** (observable via keyFn call set). | -| RS-R18 | live | `docs/rules-mining/reconcile-snapshot.md:60` | reconcile.ts×3 | — | Captured-but-unobserved proxies may detach and go stale after reconcile** (pinned pruning contract); a key mismatch detaches even an observed captured proxy. | -| RS-R19 | live | `docs/rules-mining/reconcile-snapshot.md:63` | — | — | `deep()` observes a reconcile as a single notification carrying the final plain data.** | -| RS-R20 | live | `docs/rules-mining/reconcile-snapshot.md:66` | — | — | (type-level) — `reconcile(next)` requires the complete store type.** | -| RS-R21 | live | `docs/rules-mining/reconcile-snapshot.md:70` | — | — | A reconcile in the same batch after an unflushed setter write behaves identically to a clean reconcile.** CONFLICT (framing only): tests motivate via `STORE_OVERRIDE`/`applyStateSlow` routing (deleted… | -| RS-R22 | live | `docs/rules-mining/reconcile-snapshot.md:73` | — | adoption-lane-rollback.test.ts×1 | Reconcile inside an optimistic action window is tentatively visible; captured-proxy readers see exactly what tracked readers see, during and after settle.** CONFLICT (load-bearing): adoption must ride… | -| RS-R23 | live | `docs/rules-mining/reconcile-snapshot.md:78` | — | — | `snapshot()`/`deep()` always return plain non-proxy data** — including rows through derived stores, nested objects in them, chained views. | -| RS-R24 | live | `docs/rules-mining/reconcile-snapshot.md:81` | — | — | CoW identity preservation:** never-written store snapshots as the original source object (`===`); after a write, changed object + ancestors are new copies, unchanged siblings keep prior snapshot ident… | -| RS-R25 | live | `docs/rules-mining/reconcile-snapshot.md:84` | — | — | Snapshot through a derived-store view returns the same raw object as through the base store** when nothing overridden. CONFLICT (attention): requires unwrapping chained proxy backings to base raw; the… | -| RS-R26 | live | `docs/rules-mining/reconcile-snapshot.md:87` | — | — | Snapshot reflects in-flight optimistic overrides while the base stays untouched.** Confirms O1's "snapshot = current view, lane values included". | -| RS-R27 | live | `docs/rules-mining/reconcile-snapshot.md:90` | — | — | Snapshot sees pending (unflushed) setter writes synchronously, while untracked proxy reads return the previous value until flush.** CONFLICT (MAJOR): §3's "urgent writes are synchronous commits" would… | -| RS-R28 | live | `docs/rules-mining/reconcile-snapshot.md:93` | reconcile.ts×1 | — | Array holes and length survive snapshot/deep** (trailing delete keeps length; holes stay holes; explicit length truncation round-trips; overridden length 0 snapshots as `[]`). | -| RS-R29 | live | `docs/rules-mining/reconcile-snapshot.md:96` | store.ts×1 | — | Symbol-keyed data round-trips through snapshot**: enumerable symbols preserved in copies; writes inside symbol subtrees captured; added-after-snapshot appear; deleted dropped; NON-enumerable symbols e… | -| RS-R30 | live | `docs/rules-mining/reconcile-snapshot.md:99` | — | — | Snapshot-scope machinery (setSnapshotCapture / markSnapshotScope / releaseSnapshotScope / clearSnapshots):** signals/memos created during capture freeze creation-time value for scoped readers; writes … | -| RS-R31 | live | `docs/rules-mining/reconcile-snapshot.md:102` | — | — | Store properties written during capture preserve pre-write value for scoped readers; unwritten use current.** CONFLICT: current mechanism (`STORE_SNAPSHOT_PROPS` in set trap) is layer-adjacent; the wr… | -| RS-R32 | live | `docs/rules-mining/reconcile-snapshot.md:105` | — | — | A pending async projection suppresses snapshot capture**; after resolve + release, readers see resolved value. CONFLICT (mild): guard must move to lane-scoped adoption writes. | -| RS-R33 | live | `docs/rules-mining/reconcile-snapshot.md:110` | — | — | `merge` core contract:** lazy getters (`this` = source); later sources win incl. explicit `undefined`; key union via `in`/keys; value props copied by value; non-enumerable → enumerable on result; firs… | -| RS-R34 | live | `docs/rules-mining/reconcile-snapshot.md:113` | — | — | `merge` reference-return optimization:** same reference for single arg, trailing falsy args, and when last source's own keys cover the union; new proxy otherwise; holds for store proxies. | -| RS-R35 | live | `docs/rules-mining/reconcile-snapshot.md:115` | — | — | `merge` over signal-of-object source is reactive with minimal notifications.** | -| RS-R36 | live | `docs/rules-mining/reconcile-snapshot.md:117` | — | — | `omit` contract:** removed keys disappear from get/`in`/keys incl. store-proxy sources; kept value props copied; descriptors cloned faithfully; pollution-safe; composes with merge. | -| RS-R37 | live | `docs/rules-mining/reconcile-snapshot.md:119` | — | shared-child-multiparent.test.ts×1 | `deep()` contract:** plain data; tracks entire reachable tree (leaf writes, push, branch replacement, symbol subtree writes, symbol add/delete, shared-object writes through other paths); one notificat… | -| RS-R38 | live | `docs/rules-mining/reconcile-snapshot.md:121` | — | — | Untracked read-through (via merge clone) shows pre-write values until flush.** CONFLICT (same as R27, MAJOR): contradicts write-through-immediately unless plain setter writes stay staged until flush. … | +| id | status | defined | cited in src | cited in tests | statement (at definition) | +| ------ | ------ | --------------------------------------------- | -------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| RS-R1 | live | `docs/rules-mining/reconcile-snapshot.md:9` | — | — | Keyed object merge deletes absent keys.\*\* Properties present in `next` update; properties absent from `next` are deleted (read `undefined`, removed from `in`/keys). | +| RS-R2 | live | `docs/rules-mining/reconcile-snapshot.md:12` | — | — | Reconcile applies to any nested proxy, not just the root\*\*, with identical semantics. | +| RS-R3 | live | `docs/rules-mining/reconcile-snapshot.md:15` | — | — | Keyed identity mismatch at the target throws\*\* (key differs, or key present on target but missing from `next`). Post-throw state is deliberately unasserted (original expectations commented out) — the … | +| RS-R4 | live | `docs/rules-mining/reconcile-snapshot.md:18` | — | — | `key: null` / `key: ""` disables key matching\*\*: positional merge, no root identity check. | +| RS-R5 | live | `docs/rules-mining/reconcile-snapshot.md:21` | — | — | Key modes: string key, key function, none.\*\* KeyFn's call set is observable (see R17). | +| RS-R6 | live | `docs/rules-mining/reconcile-snapshot.md:24` | — | — | Key-matched items preserve logical (proxy) identity across reorder, insert, delete.\*\* | +| RS-R7 | live | `docs/rules-mining/reconcile-snapshot.md:27` | — | — | Re-sent identical objects preserve raw identity: `snapshot(state.arr[i])` is `Object.is`-equal to the original.\*\* CONFLICT (benign, verify): adoption satisfies this since raw becomes the incoming obje… | +| RS-R8 | live | `docs/rules-mining/reconcile-snapshot.md:30` | reconcile.ts×1 | — | Positional merge preserves slot proxy identity even when identifying fields change\*\* (fixed-shape dashboard pattern). | +| RS-R9 | live | `docs/rules-mining/reconcile-snapshot.md:33` | reconcile.ts×2 | — | Only changed leaves notify\*\* (changed `a` reruns its subscriber exactly once; `b` subscriber zero times). | +| RS-R10 | live | `docs/rules-mining/reconcile-snapshot.md:36` | reconcile.ts×2 | — | Kind changes (object↔array) at any position replace wholesale, never merge, and notify the property node.\*\* | +| RS-R11 | live | `docs/rules-mining/reconcile-snapshot.md:39` | reconcile.ts×2 | — | Null entries and primitives are legal keyed-array members\*\* (#2772). | +| RS-R12 | live | `docs/rules-mining/reconcile-snapshot.md:42` | — | — | Array resize notification matrix.\*\* Shrink: tracked removed indices notify `undefined`; tracked `in` flips false; untracked reads agree with new length (no stale node values). Growth: tracked missing … | +| RS-R13 | live | `docs/rules-mining/reconcile-snapshot.md:45` | — | — | Numeric-coercible non-index string props on arrays (`"1e3"`, `"1.5"`) survive resize\*\*; node sync must be membership-based, not length-range-based. | +| RS-R14 | live | `docs/rules-mining/reconcile-snapshot.md:48` | — | — | Symbol keys have full parity with string keys under reconcile\*\* (update/remove/add/nested/mixed). | +| RS-R15 | live | `docs/rules-mining/reconcile-snapshot.md:51` | — | — | Reconcile can assign, swap, and reorder values that are other stores' proxies.\*\* CONFLICT (attention): adoption must handle `next` values that are live proxies of other stores — `storeLookup` resoluti… | +| RS-R16 | live | `docs/rules-mining/reconcile-snapshot.md:54` | reconcile.ts×1 | — | Captured proxies with a live subscriber anywhere below are diffed in place through never-tracked intermediate levels.\*\* CONFLICT (design obligation): a node exists deep below an un-noded path; adoptio… | +| RS-R17 | live | `docs/rules-mining/reconcile-snapshot.md:57` | reconcile.ts×1 | — | Never-subscribed subtrees are pruned: the diff does not walk below their top-level pair\*\* (observable via keyFn call set). | +| RS-R18 | live | `docs/rules-mining/reconcile-snapshot.md:60` | reconcile.ts×3 | — | Captured-but-unobserved proxies may detach and go stale after reconcile\*\* (pinned pruning contract); a key mismatch detaches even an observed captured proxy. | +| RS-R19 | live | `docs/rules-mining/reconcile-snapshot.md:63` | — | — | `deep()` observes a reconcile as a single notification carrying the final plain data.\*\* | +| RS-R20 | live | `docs/rules-mining/reconcile-snapshot.md:66` | — | — | (type-level) — `reconcile(next)` requires the complete store type.\*\* | +| RS-R21 | live | `docs/rules-mining/reconcile-snapshot.md:70` | — | — | A reconcile in the same batch after an unflushed setter write behaves identically to a clean reconcile.\*\* CONFLICT (framing only): tests motivate via `STORE_OVERRIDE`/`applyStateSlow` routing (deleted… | +| RS-R22 | live | `docs/rules-mining/reconcile-snapshot.md:73` | — | adoption-lane-rollback.test.ts×1 | Reconcile inside an optimistic action window is tentatively visible; captured-proxy readers see exactly what tracked readers see, during and after settle.\*\* CONFLICT (load-bearing): adoption must ride… | +| RS-R23 | live | `docs/rules-mining/reconcile-snapshot.md:78` | — | — | `snapshot()`/`deep()` always return plain non-proxy data\*\* — including rows through derived stores, nested objects in them, chained views. | +| RS-R24 | live | `docs/rules-mining/reconcile-snapshot.md:81` | — | — | CoW identity preservation:\*\* never-written store snapshots as the original source object (`===`); after a write, changed object + ancestors are new copies, unchanged siblings keep prior snapshot ident… | +| RS-R25 | live | `docs/rules-mining/reconcile-snapshot.md:84` | — | — | Snapshot through a derived-store view returns the same raw object as through the base store\*\* when nothing overridden. CONFLICT (attention): requires unwrapping chained proxy backings to base raw; the… | +| RS-R26 | live | `docs/rules-mining/reconcile-snapshot.md:87` | — | — | Snapshot reflects in-flight optimistic overrides while the base stays untouched.\*\* Confirms O1's "snapshot = current view, lane values included". | +| RS-R27 | live | `docs/rules-mining/reconcile-snapshot.md:90` | — | — | Snapshot sees pending (unflushed) setter writes synchronously, while untracked proxy reads return the previous value until flush.\*\* CONFLICT (MAJOR): §3's "urgent writes are synchronous commits" would… | +| RS-R28 | live | `docs/rules-mining/reconcile-snapshot.md:93` | reconcile.ts×1 | — | Array holes and length survive snapshot/deep\*\* (trailing delete keeps length; holes stay holes; explicit length truncation round-trips; overridden length 0 snapshots as `[]`). | +| RS-R29 | live | `docs/rules-mining/reconcile-snapshot.md:96` | store.ts×1 | — | Symbol-keyed data round-trips through snapshot\*\*: enumerable symbols preserved in copies; writes inside symbol subtrees captured; added-after-snapshot appear; deleted dropped; NON-enumerable symbols e… | +| RS-R30 | live | `docs/rules-mining/reconcile-snapshot.md:99` | — | — | Snapshot-scope machinery (setSnapshotCapture / markSnapshotScope / releaseSnapshotScope / clearSnapshots):\*\* signals/memos created during capture freeze creation-time value for scoped readers; writes … | +| RS-R31 | live | `docs/rules-mining/reconcile-snapshot.md:102` | — | — | Store properties written during capture preserve pre-write value for scoped readers; unwritten use current.\*\* CONFLICT: current mechanism (`STORE_SNAPSHOT_PROPS` in set trap) is layer-adjacent; the wr… | +| RS-R32 | live | `docs/rules-mining/reconcile-snapshot.md:105` | — | — | A pending async projection suppresses snapshot capture\*\*; after resolve + release, readers see resolved value. CONFLICT (mild): guard must move to lane-scoped adoption writes. | +| RS-R33 | live | `docs/rules-mining/reconcile-snapshot.md:110` | — | — | `merge` core contract:\*\* lazy getters (`this` = source); later sources win incl. explicit `undefined`; key union via `in`/keys; value props copied by value; non-enumerable → enumerable on result; firs… | +| RS-R34 | live | `docs/rules-mining/reconcile-snapshot.md:113` | — | — | `merge` reference-return optimization:\*\* same reference for single arg, trailing falsy args, and when last source's own keys cover the union; new proxy otherwise; holds for store proxies. | +| RS-R35 | live | `docs/rules-mining/reconcile-snapshot.md:115` | — | — | `merge` over signal-of-object source is reactive with minimal notifications.\*\* | +| RS-R36 | live | `docs/rules-mining/reconcile-snapshot.md:117` | — | — | `omit` contract:\*\* removed keys disappear from get/`in`/keys incl. store-proxy sources; kept value props copied; descriptors cloned faithfully; pollution-safe; composes with merge. | +| RS-R37 | live | `docs/rules-mining/reconcile-snapshot.md:119` | — | shared-child-multiparent.test.ts×1 | `deep()` contract:\*\* plain data; tracks entire reachable tree (leaf writes, push, branch replacement, symbol subtree writes, symbol add/delete, shared-object writes through other paths); one notificat… | +| RS-R38 | live | `docs/rules-mining/reconcile-snapshot.md:121` | — | — | Untracked read-through (via merge clone) shows pre-write values until flush.\*\* CONFLICT (same as R27, MAJOR): contradicts write-through-immediately unless plain setter writes stay staged until flush. … | + ## § — design sections -| id | status | defined | cited in src | cited in tests | statement (at definition) | -|---|---|---|---|---|---| -| §1 | live | `docs/INTERNALS-STORE-STATE.md:27` | target.ts×1 | reveal-gating-contract.test.ts×1 | Storage model (the single-home rule) | -| §2 | live | `docs/INTERNALS-STORE-STATE.md:81` | — | — | Read paths | -| §3 | live | `docs/INTERNALS-STORE-STATE.md:116` | scheduler.ts×1 optimistic.ts×1 reconcile.ts×1 store.ts×1 target.ts×1 | — | Write paths (all must stay equivalent) | -| §4 | live | `docs/INTERNALS-STORE-STATE.md:178` | — | — | Identity rules | -| §5 | live | `docs/INTERNALS-STORE-STATE.md:190` | — | — | Laziness invariants (candidates for `__TEST__` assertions) | -| §5b | live | `docs/INTERNALS-STORE-STATE.md:208` | target.ts×2 | — | Creation budget (phase-1 fitness) | -| §5c | live | `docs/INTERNALS-STORE-STATE.md:227` | — | — | Comparison method (shipped vs rewrite) | -| §6 | live | `docs/INTERNALS-STORE-STATE.md:278` | core.ts×1 invariants.ts×1 lanes.ts×1 optimistic.ts×1 store.ts×2 target.ts×1 | — | Structural edits — the key-set node (resolves O2, RUL-8) | -| §6b | live | `docs/INTERNALS-STORE-STATE.md:306` | reconcile.ts×2 | adoption-lane-rollback.test.ts×1 | Lane-aware adoption (RUL-5) | -| §6c | live | `docs/INTERNALS-STORE-STATE.md:325` | projection.ts×2 store.ts×1 | createProjection.async.test.ts×1 flight-owned-transaction.test.ts×1 | Store-wide status gating (RUL-7) | -| §6d | live | `docs/INTERNALS-STORE-STATE.md:338` | reconcile.ts×1 target.ts×2 | — | Diff reachability (RUL-11) | -| §7 | live | `docs/INTERNALS-STORE-STATE.md:350` | optimistic.ts×1 projection.ts×1 | — | Projections & optimism layering | -| §7b | live | `docs/INTERNALS-STORE-STATE.md:360` | projection.ts×1 reconcile.ts×1 store.ts×11 target.ts×3 store.ts×1 | — | Chained backing (cross-store) — spec | -| §8 | live | `docs/INTERNALS-STORE-STATE.md:431` | — | reconcile-resend-identity.test.ts×1 | Assumptions / open questions | -| §8b | live | `docs/INTERNALS-STORE-STATE.md:487` | — | — | Suite-mined rules (2026-08-16) — index & rulings needed | -| §9 | live | `docs/INTERNALS-STORE-STATE.md:722` | — | — | Decision log | -| §11 | live | `docs/NODE-SHAPE.md:29` | — | — | Stage 3 opener: the core tax map (2026-08-21) | -| §11b | live | `docs/NODE-SHAPE.md:53` | constants.ts×1 | rules-index.test.ts×1 treeshake.test.ts×1 | Presence bits — hot-path monomorphism | -| §11c | live | `docs/NODE-SHAPE.md:72` | — | — | Stage-3 increment log | -| §12 | live | `docs/NODE-SHAPE.md:84` | constants.ts×1 core.ts×1 types.ts×1 | dist-artifacts.test.ts×1 rules-index.test.ts×1 treeshake.test.ts×2 | Cold-field extension (`_x`, `ext()`) | -| §12b | live | `docs/NODE-SHAPE.md:107` | — | treeshake.test.ts×1 | Zombie pair in the extension; plain-commit fast drain | -| §12c | live | `docs/NODE-SHAPE.md:119` | types.ts×1 | — | What stays IN the core literal | -| §12d | live | `docs/NODE-SHAPE.md:129` | core.ts×2 graph.ts×1 scheduler.ts×2 types.ts×1 | — | Staged-rewrite fast path (notify epoch) — _reconstructed_ | -| §12e | live | `docs/NODE-SHAPE.md:149` | core.ts×2 optimistic.ts×1 | rules-index.test.ts×1 | Signal-literal diet: `_time`, `_fn`, `_statusFlags` are computed-only — _reconstructed_ | +| id | status | defined | cited in src | cited in tests | statement (at definition) | +| ---- | ------ | ----------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | +| §1 | live | `docs/INTERNALS-STORE-STATE.md:27` | target.ts×1 | reveal-gating-contract.test.ts×1 | Storage model (the single-home rule) | +| §2 | live | `docs/INTERNALS-STORE-STATE.md:81` | — | — | Read paths | +| §3 | live | `docs/INTERNALS-STORE-STATE.md:116` | scheduler.ts×1 optimistic.ts×1 reconcile.ts×1 store.ts×1 target.ts×1 | — | Write paths (all must stay equivalent) | +| §4 | live | `docs/INTERNALS-STORE-STATE.md:178` | — | — | Identity rules | +| §5 | live | `docs/INTERNALS-STORE-STATE.md:190` | — | — | Laziness invariants (candidates for `__TEST__` assertions) | +| §5b | live | `docs/INTERNALS-STORE-STATE.md:208` | target.ts×2 | — | Creation budget (phase-1 fitness) | +| §5c | live | `docs/INTERNALS-STORE-STATE.md:227` | — | — | Comparison method (shipped vs rewrite) | +| §6 | live | `docs/INTERNALS-STORE-STATE.md:278` | core.ts×1 invariants.ts×1 lanes.ts×1 optimistic.ts×1 store.ts×2 target.ts×1 | — | Structural edits — the key-set node (resolves O2, RUL-8) | +| §6b | live | `docs/INTERNALS-STORE-STATE.md:306` | reconcile.ts×2 | adoption-lane-rollback.test.ts×1 | Lane-aware adoption (RUL-5) | +| §6c | live | `docs/INTERNALS-STORE-STATE.md:325` | projection.ts×2 store.ts×1 | createProjection.async.test.ts×1 flight-owned-transaction.test.ts×1 | Store-wide status gating (RUL-7) | +| §6d | live | `docs/INTERNALS-STORE-STATE.md:338` | reconcile.ts×1 target.ts×2 | — | Diff reachability (RUL-11) | +| §7 | live | `docs/INTERNALS-STORE-STATE.md:350` | optimistic.ts×1 projection.ts×1 | — | Projections & optimism layering | +| §7b | live | `docs/INTERNALS-STORE-STATE.md:360` | projection.ts×1 reconcile.ts×1 store.ts×11 target.ts×3 store.ts×1 | — | Chained backing (cross-store) — spec | +| §8 | live | `docs/INTERNALS-STORE-STATE.md:431` | — | reconcile-resend-identity.test.ts×1 | Assumptions / open questions | +| §8b | live | `docs/INTERNALS-STORE-STATE.md:487` | — | — | Suite-mined rules (2026-08-16) — index & rulings needed | +| §9 | live | `docs/INTERNALS-STORE-STATE.md:722` | — | — | Decision log | +| §11 | live | `docs/NODE-SHAPE.md:29` | — | — | Stage 3 opener: the core tax map (2026-08-21) | +| §11b | live | `docs/NODE-SHAPE.md:53` | constants.ts×1 | rules-index.test.ts×1 treeshake.test.ts×1 | Presence bits — hot-path monomorphism | +| §11c | live | `docs/NODE-SHAPE.md:72` | — | — | Stage-3 increment log | +| §12 | live | `docs/NODE-SHAPE.md:84` | constants.ts×1 core.ts×1 types.ts×1 | dist-artifacts.test.ts×1 rules-index.test.ts×1 treeshake.test.ts×2 | Cold-field extension (`_x`, `ext()`) | +| §12b | live | `docs/NODE-SHAPE.md:107` | — | treeshake.test.ts×1 | Zombie pair in the extension; plain-commit fast drain | +| §12c | live | `docs/NODE-SHAPE.md:119` | types.ts×1 | — | What stays IN the core literal | +| §12d | live | `docs/NODE-SHAPE.md:129` | core.ts×2 graph.ts×1 scheduler.ts×2 types.ts×1 | — | Staged-rewrite fast path (notify epoch) — _reconstructed_ | +| §12e | live | `docs/NODE-SHAPE.md:149` | core.ts×2 optimistic.ts×1 | rules-index.test.ts×1 | Signal-literal diet: `_time`, `_fn`, `_statusFlags` are computed-only — _reconstructed_ | diff --git a/packages/signals/src/store/next/store.ts b/packages/signals/src/store/next/store.ts index d105ea72d..ca24d5a95 100644 --- a/packages/signals/src/store/next/store.ts +++ b/packages/signals/src/store/next/store.ts @@ -28,7 +28,8 @@ import { unwrapOverride, CONFIG_AUTHORITATIVE_READ, CONFIG_HELD_TRUTH, - CONFIG_OPTIMISTIC + CONFIG_OPTIMISTIC, + CONFIG_ADOPTED_UNFLUSHED } from "../../core/constants.js"; import { context, @@ -41,6 +42,7 @@ import { visibleOverride, recordStaleReplay, enterStagedRead, + markLateLinker, ownsHold, serve, prepareComputed, @@ -58,10 +60,12 @@ import { } from "../../core/core.js"; import { activeTransition, + clock, currentTransition, deferSlotRelease, globalQueue, insertSubs, + queuePendingNode, type Transition } from "../../core/scheduler.js"; import { devTrackHeldPending } from "../../core/invariants.js"; @@ -343,7 +347,7 @@ export function getNode( // (the declaration walk could only cover nodes existing then). if (key !== $AFFECTS && affectsScopesLive()) inheritAffectsMarks(created, target.v, key); if (held !== null) - stageHeldKey( + stageKey( created, fold !== null ? target.del !== null && target.del.has(key) @@ -352,6 +356,19 @@ export function getNode( : (target.v as any)[key], held ); + // Third kind (A28 at the backing): born in the UNFLUSHED window of an + // ambient fold — a setter ran outside a flush, no flush has carried it, + // and the key had no node to take the write at setter exit. `current` is + // the committed value (readSource served it under unflushedBacking); + // stage the pending backing's value so the carrying flush commits it + // through the node and the reader served committed (and marked late + // linker) finds the write there. + else if (target.pb !== null && unflushedBacking(target) && plainFold(target)) + stageKey( + created, + target.del !== null && target.del.has(key) ? undefined : (target.pb as any)[key], + null + ); nodes[key] = node; target.nc++; markDescendants(target); @@ -399,8 +416,11 @@ function liveFoldTransition(target: StoreNextTarget): Transition | null { * heldTruthMasked — neither is a plain staged write to mirror. Chained * backings serve the inner store's live value, never a node value. */ function heldFoldTransition(target: StoreNextTarget): Transition | null { - if (target.ch || target.fam?.opt === true || inDraft(target)) return null; - return liveFoldTransition(target); + return plainFold(target) ? liveFoldTransition(target) : null; +} +/** A backing whose staged writes a node can mirror (see heldFoldTransition). */ +function plainFold(target: StoreNextTarget): boolean { + return !target.ch && target.fam?.opt !== true && !inDraft(target); } /** @@ -426,11 +446,25 @@ function holdVisible(txn: Transition | null, c: Computed): boolean { return true; } -function stageHeldKey(node: Signal, nv: any, txn: Transition): void { +function stageKey(node: Signal, nv: any, txn: Transition | null): void { if (slotNodeEquals.call(node, node._value, nv)) return; node._pendingValue = nv; - node._transition = txn; - txn._pendingNodes.push(node); + if (txn !== null) { + // A hold's staging: transition-stamped, in its pending list — no + // parked-flush pass will stamp it (runFolded's shape). + node._transition = txn; + txn._pendingNodes.push(node); + } else { + // The unflushed window of an ambient fold (A28): core's ambient staging + // — queued for the carrying flush to commit through the node, adopted + // before any flush if a transaction body is running (initTransition's + // mark for a node that existed at the write). + if (activeTransition !== null) { + node._transition = activeTransition; + node._config |= CONFIG_ADOPTED_UNFLUSHED; + } + queuePendingNode(node); + } if (__DEV__) devTrackHeldPending(node); } @@ -693,6 +727,7 @@ function ensurePB(target: StoreNextTarget): Record { pb = target.pb = null; } if (activeTransition !== null) foldBatches.set(target, activeTransition); + if (!globalQueue._running && getOwner() === null) unflushedPBs.set(target, clock); if (pb === null) { // Prototype-chain overlay (#3044): plain-data non-array containers open // drafts in O(1) — own keys are the writes, reads fall through to @@ -886,6 +921,17 @@ function queueFold(target: StoreNextTarget): void { * Refreshed on every write; resolved through currentTransition at drain * (transitions merge — same rule as heldMaskView). */ const foldBatches = new WeakMap(); +/** A28 for backings — the tick (`clock`) a target's pending backing was opened + * OUTSIDE a flush by a write from outside any owner (core's promoted-write + * exemption, A28 (4): a write inside a computation is visible to the rest of + * its block). `clock` advances after every flush, so "stamped this tick, no + * flush running" is a write no flush has carried: owner-context readers see + * the committed container and re-run in the carrying flush (markLateLinker), + * as core serve() treats an unflushed node (unflushedValue). */ +const unflushedPBs = new WeakMap(); +function unflushedBacking(target: StoreNextTarget): boolean { + return !globalQueue._running && unflushedPBs.get(target) === clock; +} /** Parked truth-staged pending backings (#3164 fold): a tentative draft that * opens while a folded landing's backing is live moves the staged container @@ -1711,7 +1757,11 @@ function pendingBackingVisible(target: StoreNextTarget, speculative: boolean): b if (speculative) return txn === null || ownsHold(txn); return target.fam !== null && c === null && !foldHeld(target) && txn === null; } - return holdVisible(liveFoldTransition(target), c); + const txn = liveFoldTransition(target); + // A28: an ambient staging no flush has carried is not yet visible — the + // committed container, and the pass runs again in the carrying flush. + if (txn === null && unflushedBacking(target)) return (markLateLinker(c), false); + return holdVisible(txn, c); } /** #3164 fold: HELD truth on an optimistic family — a pending backing diff --git a/packages/signals/tests/posture-store-parity.test.ts b/packages/signals/tests/posture-store-parity.test.ts index 82b486ade..a6f6ef2f1 100644 --- a/packages/signals/tests/posture-store-parity.test.ts +++ b/packages/signals/tests/posture-store-parity.test.ts @@ -25,12 +25,10 @@ * S5 — (fixed, 3b step 4) a mainline derivation's UNTRACKED read of a held * store key is born held (A29) as the signal's is — the store's untracked * paths served the pending value without entering the transaction. - * S6 — RULED (2026-09-17, "store rules follow signal rules"), fix DEFERRED: - * A28 at the backing. Pinned at the store's CURRENT value below so the - * divergence stays visible; the fix is the store half of `serve` - * (DESIGN-CONSOLIDATION move 3b step 6) — done at the twin it costs - * +400 B minified (a node born in the unflushed window must stage the - * write; #3521 first cut). + * S6 — (fixed) A28 at the backing: a write staged outside a flush by + * imperative code is invisible to owner-context readers until the flush + * that carries it, as a signal's is; a node born in that window stages + * the write (one stageKey for holds and the unflushed window). * S7 — (fixed) an optimistic override survives its key becoming unobserved * (the slot release defers to the flush that resolves the override). * S8 — (fixed, 3b step 6c) a derivation's untracked read of a SUPERSEDED @@ -380,17 +378,19 @@ describe("S5 — a mainline derivation's UNTRACKED read of a held value is born } }); -/** S6 — DIVERGENCE, ruled, fix deferred. Staged, ambient (a write before - * any flush), reader created INSIDE a foreign action (which adopts the - * write, spec O1): the signal's memo → render effect publishes the committed - * 0 (A28 / #3510: adopted before any flush = unflushed, served committed); - * the store's publishes the pending 1 (pendingBackingVisible: owner context - * → pending backing). The verdict channels already agree (S1); the - * derivation reads do not. Ruling: the store follows the signal (0). The - * store side is pinned at its CURRENT value so the divergence stays visible - * until the store's value selection shares core's (`serve`, move 3b step 6); - * flip it to `[0]` then. */ -describe("S6 — DIVERGENCE (ruled: store follows signal; fix deferred to `serve`): staged-ambient write read by a derivation created inside a foreign action", () => { +/** S6 (fixed): A28 at the backing. A write staged outside a flush is not + * visible to any reader until the flush that carries it (core serve() serves + * an unflushed node's committed value and re-runs the reader in the carrying + * flush — unflushedValue / markLateLinker). The store's backing selection + * served the pending backing to every owner-context reader at once; a + * derivation created inside a foreign action (which adopts the write, O1) + * published the unflushed 1 where the signal published 0. The backing now + * carries the rule (unflushedBacking: the target stamped `clock` when a + * draft opens outside a flush from outside any owner — A28 (4)'s promoted- + * write exemption), and a node born in that window stages the pending value + * (stageKey with no transaction) so the carrying flush commits it through + * the node and the late-linked reader finds it there. */ +describe("S6 — a write before any flush, read by a derivation created inside a foreign action (A28) — signal vs store", () => { function publishedInsideForeignAction(read: () => number) { const log: number[] = []; action(function* () { @@ -410,12 +410,12 @@ describe("S6 — DIVERGENCE (ruled: store follows signal; fix deferred to `serve setX(1); expect(publishedInsideForeignAction(x)).toEqual([0]); }); - it("store: publishes the pending 1 (CURRENT; rule says 0)", () => { + it("store: publishes the committed 0, as the signal does", () => { const [s, setS] = createStore({ n: 0 }); setS(d => { d.n = 1; }); - expect(publishedInsideForeignAction(() => s.n)).toEqual([1]); + expect(publishedInsideForeignAction(() => s.n)).toEqual([0]); }); }); From fef7c97af50b8a21354dd6681bd0652d4e5696e7 Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Thu, 17 Sep 2026 15:31:27 -0700 Subject: [PATCH 2/3] perf(signals): A28 stamp once per draft open, in a Map (was: per trap write, in a WeakMap) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodSpeed on #3526: -7…-19% on the store write floor. ensurePB runs on every trap write of a draft, and the stamp sat before its pb === null branch — a WeakMap.set per WRITE (batched: 1000 sets per commit), and a WeakMap.set on a fresh target pays the identity hash plus the ephemeron. The stamp now sits inside the branch, once per draft open beside queueFold, in a Map whose entry leaves at the drain. Co-authored-by: Claude via Cursor --- packages/signals/docs/RULES-INDEX.md | 2 +- packages/signals/src/store/next/store.ts | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/signals/docs/RULES-INDEX.md b/packages/signals/docs/RULES-INDEX.md index 79785bf0d..6c73c5e73 100644 --- a/packages/signals/docs/RULES-INDEX.md +++ b/packages/signals/docs/RULES-INDEX.md @@ -72,7 +72,7 @@ Status legend: **live** stated and standing · **ruled** carries an explicit rul | A25 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:251` | verdict.ts×1 | 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:67` | core.ts×1 scheduler.ts×1 store.ts×1 | action-await-contract.test.ts×2 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:243` | — | 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:51` | constants.ts×2 core.ts×20 optimistic.ts×2 scheduler.ts×3 types.ts×1 verdict.ts×6 optimistic.ts×3 store.ts×6 | createOptimistic.test.ts×5 latest-held-till-flush.test.ts×1 optimistic-store-layer-scope.test.ts×1 posture-store-parity.test.ts×7 question-scoped-pending.test.ts×3 snapshot-derived-store-rows.test.ts×1 createOptimisticStore.test.ts×10 shallow.test.ts×1 treeshake.test.ts×2 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… | +| A28 | ruled, mechanism landed | `docs/SPEC-ASYNC-SEMANTICS.md:51` | constants.ts×2 core.ts×20 optimistic.ts×2 scheduler.ts×3 types.ts×1 verdict.ts×6 optimistic.ts×3 store.ts×7 | createOptimistic.test.ts×5 latest-held-till-flush.test.ts×1 optimistic-store-layer-scope.test.ts×1 posture-store-parity.test.ts×7 question-scoped-pending.test.ts×3 snapshot-derived-store-rows.test.ts×1 createOptimisticStore.test.ts×10 shallow.test.ts×1 treeshake.test.ts×2 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:75` | action.ts×1 async.ts×1 core.ts×7 effect.ts×1 optimistic.ts×1 scheduler.ts×1 signals.ts×1 store.ts×3 | body-end-supersession-visibility.test.ts×1 born-held.test.ts×3 direct-commit-readers-posture.test.ts×1 held-conditional-memo.test.ts×1 held-frame-dependencies.test.ts×2 latest-held-till-flush.test.ts×2 posture-store-parity.test.ts×4 treeshake.test.ts×1 visibility-oracle-store.states.ts×5 visibility-oracle-store.test.ts×1 visibility-oracle.states.ts×5 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:207` | async.ts×3 attribution.ts×1 core.ts×2 effect.ts×1 scheduler.ts×4 | async-landing-deps-3461.test.ts×3 held-conditional-effect.test.ts×1 held-conditional-memo.test.ts×1 held-frame-dependencies.test.ts×2 posture-born-held-and-observation.test.ts×1 treeshake.test.ts×2 write-proposals-3494.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:83` | 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 … | diff --git a/packages/signals/src/store/next/store.ts b/packages/signals/src/store/next/store.ts index ca24d5a95..8e1b52ddb 100644 --- a/packages/signals/src/store/next/store.ts +++ b/packages/signals/src/store/next/store.ts @@ -727,7 +727,6 @@ function ensurePB(target: StoreNextTarget): Record { pb = target.pb = null; } if (activeTransition !== null) foldBatches.set(target, activeTransition); - if (!globalQueue._running && getOwner() === null) unflushedPBs.set(target, clock); if (pb === null) { // Prototype-chain overlay (#3044): plain-data non-array containers open // drafts in O(1) — own keys are the writes, reads fall through to @@ -787,6 +786,9 @@ function ensurePB(target: StoreNextTarget): Record { } } queueFold(target); + // A28 stamp, once per draft open (not per write — ensurePB runs on every + // trap write of the draft). + if (!globalQueue._running && getOwner() === null) unflushedPBs.set(target, clock); } return pb; } @@ -928,7 +930,9 @@ const foldBatches = new WeakMap(); * flush running" is a write no flush has carried: owner-context readers see * the committed container and re-run in the carrying flush (markLateLinker), * as core serve() treats an unflushed node (unflushedValue). */ -const unflushedPBs = new WeakMap(); +// A Map, not a WeakMap: a WeakMap.set per fresh target (identity hash + +// ephemeron) cost 15–30% on the write floor; entries leave at the drain. +const unflushedPBs = new Map(); function unflushedBacking(target: StoreNextTarget): boolean { return !globalQueue._running && unflushedPBs.get(target) === clock; } @@ -1043,6 +1047,7 @@ function drainFolds(): void { } foldBatches.delete(t); } + unflushedPBs.delete(t); // Setter path: nodes were setSignal'd at setter exit (write-time // notification — transitions/holds ride core machinery). Commit the // backing only for keys whose nodes have committed; a still-pending From 4be36d0008d396fe0a4ce679de3c12d74f974423 Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Thu, 17 Sep 2026 16:03:17 -0700 Subject: [PATCH 3/3] perf(signals): the A28 backing stamp is a target field, not a side table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodSpeed on #3526 after the per-draft Map: still -7…-9% on the store write floor (style/class store objects mutated in place x500, fresh stores) — a Map with 500–2000 set/delete cycles per commit churns its backing store. The stamp is now `target.uf`, a plain field initialized in the target literal (TargetShape / createTarget). The array shape rule in target.ts was re-measured on Node 26: array and object targets stay fast-properties at 25–28 named+symbol props; CodSpeed's array benches guard it. Local interleaved A/B against next on the write-floor and commit-boundary benches is within noise (-3.5…+3.8%, base spread up to 10%). Co-authored-by: Claude via Cursor --- packages/signals/docs/RULES-INDEX.md | 2 +- packages/signals/src/store/next/store.ts | 27 ++++++++++++----------- packages/signals/src/store/next/target.ts | 7 +++++- scripts/size/.size-limit.js | 16 ++++++++++++-- 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/packages/signals/docs/RULES-INDEX.md b/packages/signals/docs/RULES-INDEX.md index 6c73c5e73..bb0939938 100644 --- a/packages/signals/docs/RULES-INDEX.md +++ b/packages/signals/docs/RULES-INDEX.md @@ -72,7 +72,7 @@ Status legend: **live** stated and standing · **ruled** carries an explicit rul | A25 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:251` | verdict.ts×1 | 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:67` | core.ts×1 scheduler.ts×1 store.ts×1 | action-await-contract.test.ts×2 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:243` | — | 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:51` | constants.ts×2 core.ts×20 optimistic.ts×2 scheduler.ts×3 types.ts×1 verdict.ts×6 optimistic.ts×3 store.ts×7 | createOptimistic.test.ts×5 latest-held-till-flush.test.ts×1 optimistic-store-layer-scope.test.ts×1 posture-store-parity.test.ts×7 question-scoped-pending.test.ts×3 snapshot-derived-store-rows.test.ts×1 createOptimisticStore.test.ts×10 shallow.test.ts×1 treeshake.test.ts×2 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… | +| A28 | ruled, mechanism landed | `docs/SPEC-ASYNC-SEMANTICS.md:51` | constants.ts×2 core.ts×20 optimistic.ts×2 scheduler.ts×3 types.ts×1 verdict.ts×6 optimistic.ts×3 store.ts×7 target.ts×1 | createOptimistic.test.ts×5 latest-held-till-flush.test.ts×1 optimistic-store-layer-scope.test.ts×1 posture-store-parity.test.ts×7 question-scoped-pending.test.ts×3 snapshot-derived-store-rows.test.ts×1 createOptimisticStore.test.ts×10 shallow.test.ts×1 treeshake.test.ts×2 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:75` | action.ts×1 async.ts×1 core.ts×7 effect.ts×1 optimistic.ts×1 scheduler.ts×1 signals.ts×1 store.ts×3 | body-end-supersession-visibility.test.ts×1 born-held.test.ts×3 direct-commit-readers-posture.test.ts×1 held-conditional-memo.test.ts×1 held-frame-dependencies.test.ts×2 latest-held-till-flush.test.ts×2 posture-store-parity.test.ts×4 treeshake.test.ts×1 visibility-oracle-store.states.ts×5 visibility-oracle-store.test.ts×1 visibility-oracle.states.ts×5 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:207` | async.ts×3 attribution.ts×1 core.ts×2 effect.ts×1 scheduler.ts×4 | async-landing-deps-3461.test.ts×3 held-conditional-effect.test.ts×1 held-conditional-memo.test.ts×1 held-frame-dependencies.test.ts×2 posture-born-held-and-observation.test.ts×1 treeshake.test.ts×2 write-proposals-3494.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:83` | 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 … | diff --git a/packages/signals/src/store/next/store.ts b/packages/signals/src/store/next/store.ts index 8e1b52ddb..5ba054f56 100644 --- a/packages/signals/src/store/next/store.ts +++ b/packages/signals/src/store/next/store.ts @@ -156,6 +156,7 @@ function TargetShape(this: any) { this.wk = undefined; this.hv = undefined; this.ht = undefined; + this.uf = undefined; } TargetShape.prototype = Object.prototype; @@ -196,6 +197,7 @@ function createTarget( t.del = null; t.hv = null; t.ht = null; + t.uf = -1; t.px = new Proxy(t, traps); // Legacy interop: shared machinery (affects walks, wrap dedupe) reads the // proxy off looked-up targets as a field. @@ -788,7 +790,7 @@ function ensurePB(target: StoreNextTarget): Record { queueFold(target); // A28 stamp, once per draft open (not per write — ensurePB runs on every // trap write of the draft). - if (!globalQueue._running && getOwner() === null) unflushedPBs.set(target, clock); + if (!globalQueue._running && getOwner() === null) target.uf = clock; } return pb; } @@ -923,18 +925,18 @@ function queueFold(target: StoreNextTarget): void { * Refreshed on every write; resolved through currentTransition at drain * (transitions merge — same rule as heldMaskView). */ const foldBatches = new WeakMap(); -/** A28 for backings — the tick (`clock`) a target's pending backing was opened - * OUTSIDE a flush by a write from outside any owner (core's promoted-write - * exemption, A28 (4): a write inside a computation is visible to the rest of - * its block). `clock` advances after every flush, so "stamped this tick, no - * flush running" is a write no flush has carried: owner-context readers see - * the committed container and re-run in the carrying flush (markLateLinker), - * as core serve() treats an unflushed node (unflushedValue). */ -// A Map, not a WeakMap: a WeakMap.set per fresh target (identity hash + -// ephemeron) cost 15–30% on the write floor; entries leave at the drain. -const unflushedPBs = new Map(); +/** A28 for backings — `target.uf` is the tick (`clock`) the pending backing + * was opened OUTSIDE a flush by a write from outside any owner (core's + * promoted-write exemption, A28 (4): a write inside a computation is visible + * to the rest of its block). `clock` advances after every flush, so "stamped + * this tick, no flush running" is a write no flush has carried: owner-context + * readers see the committed container and re-run in the carrying flush + * (markLateLinker), as core serve() treats an unflushed node (unflushedValue). + * A field, not a side table: a WeakMap.set per draft open cost 7–19% on the + * store write floor (identity hash + ephemeron) and a Map with set/delete + * churn per commit 7–9% (CodSpeed on #3526). */ function unflushedBacking(target: StoreNextTarget): boolean { - return !globalQueue._running && unflushedPBs.get(target) === clock; + return !globalQueue._running && target.uf === clock; } /** Parked truth-staged pending backings (#3164 fold): a tentative draft that @@ -1047,7 +1049,6 @@ function drainFolds(): void { } foldBatches.delete(t); } - unflushedPBs.delete(t); // Setter path: nodes were setSignal'd at setter exit (write-time // notification — transitions/holds ride core machinery). Commit the // backing only for keys whose nodes have committed; a still-pending diff --git a/packages/signals/src/store/next/target.ts b/packages/signals/src/store/next/target.ts index 29f32bd94..631608822 100644 --- a/packages/signals/src/store/next/target.ts +++ b/packages/signals/src/store/next/target.ts @@ -81,8 +81,13 @@ export interface StoreNextTarget { * (empirically at counts ≡ 0 mod 3 from 18 up on V8 13.x) — every trap * field read then becomes a hash lookup (~15% uibench, tree suites * worst). Future write-side state MUST ride an extension object, not new - * named fields. */ + * named fields. (Re-measured 2026-09-17 on Node 26 / V8 14: array and + * object targets stay fast-properties at 25–28 named+symbol props, so `uf` + * below was added as a field; CodSpeed's array benches are the guard.) */ wk: Set | null; + /** A28 at the backing: the `clock` tick the pending backing was opened + * outside a flush by imperative code (-1 = never). See unflushedBacking. */ + uf: number; /** Lazy deep-witness node: `deep()` subscribes ONE node per record instead * of one per path; write paths bump it only when it exists. Separate from * `k` so $TRACK/mapArray never rerun on leaf value changes (R9). */ diff --git a/scripts/size/.size-limit.js b/scripts/size/.size-limit.js index bbd55746a..36d01ccc7 100644 --- a/scripts/size/.size-limit.js +++ b/scripts/size/.size-limit.js @@ -580,7 +580,13 @@ module.exports = [ // drop (signals and writable memos, through commitPendingNode), a // kept-tail pending mark re-deriving its subscriber (A30), and // reporterBlocksSource following `_pendingSources` one hop. - limit: "16.70 KB", + // A28 at the store backing (S6, store follows signal, #3526, 2026-09-17, + // rebased over #3519): a write staged outside a flush by imperative code is + // invisible to owner-context readers until the carrying flush; a node born + // in that window stages it (one stageKey); the stamp is a target field + // (`uf`) after a WeakMap and a Map both cost 7–19% on the write floor. + // Measured at 16,766 B. + limit: "16.80 KB", modifyEsbuildConfig }, { @@ -1242,7 +1248,13 @@ module.exports = [ // drop (signals and writable memos, through commitPendingNode), a // kept-tail pending mark re-deriving its subscriber (A30), and // reporterBlocksSource following `_pendingSources` one hop. - limit: "30.35 KB", + // A28 at the store backing (S6, store follows signal, #3526, 2026-09-17, + // rebased over #3519): a write staged outside a flush by imperative code is + // invisible to owner-context readers until the carrying flush; a node born + // in that window stages it (one stageKey); the stamp is a target field + // (`uf`) after a WeakMap and a Map both cost 7–19% on the write floor. + // Measured at 30,374 B. + limit: "30.40 KB", modifyEsbuildConfig }, {