diff --git a/.changeset/shared-read-predicates.md b/.changeset/shared-read-predicates.md new file mode 100644 index 000000000..f79f8b9d7 --- /dev/null +++ b/.changeset/shared-read-predicates.md @@ -0,0 +1,5 @@ +--- +"@solidjs/signals": patch +--- + +Consolidate value-selection predicates: `readerSeesCommitted` (the full committed-vs-staged arm read()'s slow tail used to inline) and `visibleOverride` / `hasActiveOverride` (one definition each, previously duplicated between the core, lanes, verdict channels and the store) — a zero-semantic-change refactor toward one implementation per rule (DESIGN-CONSOLIDATION, move 3b). diff --git a/packages/signals/docs/RULES-INDEX.md b/packages/signals/docs/RULES-INDEX.md index eef447a37..40583b464 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 | 33 | 17 | 33 | 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 | 33 | 18 | 33 | 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,371 +43,360 @@ 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:251` | — | 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:259` | — | 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:267` | — | 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:275` | — | 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:283` | — | 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:291` | — | 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×1 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×3 core.ts×4 lanes.ts×2 scheduler.ts×2 | 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 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×5 visibility-oracle.states.ts×6 visibility-oracle.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×9 invariants.ts×3 optimistic.ts×6 scheduler.ts×2 verdict.ts×2 signals.ts×2 optimistic.ts×1 store.ts×3 | optimistic-undefined-override.test.ts×1 refresh-await.test.ts×1 reveal-gating-contract.test.ts×3 spec-async-semantics.test.ts×10 createOptimisticStore.test.ts×1 treeshake.test.ts×1 until.test.ts×1 visibility-oracle-store.states.ts×11 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×6 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 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×1 optimistic.ts×1 verdict.ts×1 | spec-async-semantics.test.ts×3 superseded-before-first-commit.test.ts×1 uninitialized-visibility.test.ts×1 visibility-oracle-store.states.ts×5 visibility-oracle-store.test.ts×1 visibility-oracle.states.ts×11 visibility-oracle.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:340` | 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:347` | — | 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×2 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:241` | 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` | scheduler.ts×1 | action-await-contract.test.ts×2 visibility-oracle-store.states.ts×1 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:233` | — | 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×18 optimistic.ts×1 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×3 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 core.ts×5 effect.ts×1 optimistic.ts×1 signals.ts×1 | 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×1 treeshake.test.ts×1 visibility-oracle-store.states.ts×3 visibility-oracle-store.test.ts×1 visibility-oracle.states.ts×5 visibility-oracle.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×1 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×1 | [ruled 2026-09-13 (#3410)] A memo's dependencies are the committed frame's until the frame is replaced — A pass that _staged_ its value has not replaced the committed frame, so the committed value sti… | -| A31 | live | `docs/SPEC-ASYNC-SEMANTICS.md: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` | — | visibility-oracle-store.states.ts×5 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:221` | 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… | - +| id | status | defined | cited in src | cited in tests | statement (at definition) | +|---|---|---|---|---|---| +| A1 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:251` | — | 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:259` | — | 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:267` | — | 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:275` | — | 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:283` | — | 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:291` | — | 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×1 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×3 core.ts×4 lanes.ts×2 scheduler.ts×2 | 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 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×5 visibility-oracle.states.ts×6 visibility-oracle.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×9 invariants.ts×3 optimistic.ts×6 scheduler.ts×2 verdict.ts×2 signals.ts×2 optimistic.ts×1 store.ts×3 | optimistic-undefined-override.test.ts×1 refresh-await.test.ts×1 reveal-gating-contract.test.ts×3 spec-async-semantics.test.ts×10 createOptimisticStore.test.ts×1 treeshake.test.ts×1 until.test.ts×1 visibility-oracle-store.states.ts×11 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×6 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 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×1 optimistic.ts×1 verdict.ts×1 | spec-async-semantics.test.ts×3 superseded-before-first-commit.test.ts×1 uninitialized-visibility.test.ts×1 visibility-oracle-store.states.ts×5 visibility-oracle-store.test.ts×1 visibility-oracle.states.ts×11 visibility-oracle.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:340` | 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:347` | — | 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×2 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:241` | 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` | scheduler.ts×1 | action-await-contract.test.ts×2 visibility-oracle-store.states.ts×1 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:233` | — | 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×19 optimistic.ts×1 scheduler.ts×3 types.ts×1 verdict.ts×6 optimistic.ts×3 | createOptimistic.test.ts×5 latest-held-till-flush.test.ts×1 optimistic-store-layer-scope.test.ts×1 posture-store-parity.test.ts×3 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 core.ts×6 effect.ts×1 optimistic.ts×1 signals.ts×1 | 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×1 treeshake.test.ts×1 visibility-oracle-store.states.ts×3 visibility-oracle-store.test.ts×1 visibility-oracle.states.ts×5 visibility-oracle.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×1 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×1 | [ruled 2026-09-13 (#3410)] A memo's dependencies are the committed frame's until the frame is replaced — A pass that _staged_ its value has not replaced the committed frame, so the committed value sti… | +| A31 | live | `docs/SPEC-ASYNC-SEMANTICS.md: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 | visibility-oracle-store.states.ts×5 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:221` | 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… | ## V — fixed violations -| id | status | defined | cited in src | cited in tests | statement (at definition) | -| --- | ------ | ---------------------------------- | ------------ | ------------------------------ | ------------------------------------------------------------------------------ | -| V1 | fixed | `docs/SPEC-ASYNC-SEMANTICS.md:410` | 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:420` | 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:426` | — | spec-async-semantics.test.ts×2 | - **V3 (violated A19) — FIXED.** After a reporter-less transition completed, | -| V4 | fixed | `docs/SPEC-ASYNC-SEMANTICS.md:433` | — | 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:445` | — | 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:410` | 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:420` | 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:426` | — | spec-async-semantics.test.ts×2 | - **V3 (violated A19) — FIXED.** After a reporter-less transition completed, | +| V4 | fixed | `docs/SPEC-ASYNC-SEMANTICS.md:433` | — | 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:445` | — | 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:376` | — | 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:386` | — | — | - [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:376` | — | 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:386` | — | — | - [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:147` | invariants.ts×2 | — | - **INV-1 (high)** `pendingProbe` is non-null only inside an `isPending()` call | -| INV-2 | live | `docs/INTERNALS-ASYNC-STATE.md:149` | invariants.ts×2 | — | - **INV-2 (high)** A node with an _active_ override (`hasActiveOverride`) is | -| INV-3 | live | `docs/INTERNALS-ASYNC-STATE.md:153` | 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:160` | 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:164` | invariants.ts×2 lanes.ts×1 | — | - **INV-5 (medium)** A lane in `activeLanes` has `_mergedInto === null` | -| INV-6 | live | `docs/INTERNALS-ASYNC-STATE.md:170` | 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:173` | 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:176` | 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:187` | 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:192` | 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:197` | 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:147` | invariants.ts×2 | — | - **INV-1 (high)** `pendingProbe` is non-null only inside an `isPending()` call | +| INV-2 | live | `docs/INTERNALS-ASYNC-STATE.md:149` | invariants.ts×2 | — | - **INV-2 (high)** A node with an _active_ override (`hasActiveOverride`) is | +| INV-3 | live | `docs/INTERNALS-ASYNC-STATE.md:153` | 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:160` | 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:164` | invariants.ts×2 lanes.ts×1 | — | - **INV-5 (medium)** A lane in `activeLanes` has `_mergedInto === null` | +| INV-6 | live | `docs/INTERNALS-ASYNC-STATE.md:170` | 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:173` | 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:176` | 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:187` | 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:192` | 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:197` | 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×1 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×2 | 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×1 | 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×1 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×2 | 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×1 | 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` | invariants.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` | invariants.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/core/core.ts b/packages/signals/src/core/core.ts index 9e5f90e62..cdb6fbbb6 100644 --- a/packages/signals/src/core/core.ts +++ b/packages/signals/src/core/core.ts @@ -1631,6 +1631,50 @@ export function enterStagedRead( globalQueue.initTransition(t); } +/** + * Rule 1 (value selection), the full arm: does this reader see a STAGED + * node's COMMITTED value? One implementation of the rule the fast paths + * (readNodeFast, read's fast block) carry as their trivial ternary and that + * every slow site — read's tail, the store's backing selection, the lane and + * verdict arms — used to restate by hand (DESIGN-CONSOLIDATION, move 3b). In order: + * - no reader at all (an untracked read) — the committed frame; + * - a reader under an optimistic lane the engine says reads committed + * (laneReadsCommitted: another lane's hold, #3460); + * - nothing staged; + * - a children-forbidden reader (createTrackedEffect / onSettled: the frame, + * never the graph — A32); + * - a stale reader (render effect) of a FOREIGN transaction's staged write — + * committed, no entanglement (heldFromStale registers the replay; a node + * born held has no committed frame to fall back to, `noCommitted`); + * - A17 for HELD truth (#3164, CONFIG_HELD_TRUTH): staged confirming truth — + * fold-staged onto an armed family, or entangle-stolen by an awaited + * until() — is masked from ordinary readers until its transaction's + * reveal, the retaining transaction's own speculative recomputes included + * (partial override coverage would otherwise compose override + staged + * truth into a state no timeline contains). Authoritative readers + * (until()'s predicate) and latest() see the staged truth — the tunnel that + * keeps the hold deadlock-free. + * False means the reader derives from the staged value and enters its + * transaction (enterStagedRead, A29). + */ +export function readerSeesCommitted( + el: Signal | Computed, + c: Computed | null, + owner: Signal | Computed, + noCommitted: boolean +): boolean { + return !!( + !c || + (currentOptimisticLane !== null && GlobalQueue._laneReadsCommitted!(el, owner, c)) || + el._pendingValue === NOT_PENDING || + c._config & CONFIG_CHILDREN_FORBIDDEN || + (stale && !noCommitted && heldFromStale(el, c)) || + (el._config & CONFIG_HELD_TRUTH && + !latestReadActive && + !(c._config & CONFIG_AUTHORITATIVE_READ)) + ); +} + /** A28 — set when a node is staged (queuePendingNode) or a held node rewritten * (stashHeldRewrite) OUTSIDE a flush; cleared when the next flush begins. The * read sites test this one module boolean instead of `globalQueue._running`: @@ -1685,6 +1729,22 @@ export function unflushedOverride(el: Signal | Computed): boolean { // Companions are optimistic signals written by the engine (see unflushed). return !globalQueue._running && el._x?._overrideTime === clock && !el._x?._parentSource; } +/** Active optimistic override on an armed node (an armed slot idles at + * NOT_PENDING; undefined = unarmed plain node). The writer's own channels — + * the draft, `in`/keys inside the setter — compose on this regardless of + * flush state. */ +export function hasActiveOverride(el: Signal | Computed): boolean { + const x = el._x; + return x !== null && x._overrideValue !== undefined && x._overrideValue !== NOT_PENDING; +} +/** The override a READER sees: installed, and carried by a flush (A28 (5) — + * an optimistic write is a write; until its flush no reader sees it). One + * implementation for read()'s override arm, the verdict channels + * (latestRead, computePendingState) and the store's selection + * (DESIGN-CONSOLIDATION, move 3b). */ +export function visibleOverride(el: Signal | Computed): boolean { + return hasActiveOverride(el) && !unflushedOverride(el); +} /** A derivation served the committed value because of an unflushed write * (A28) must run again in the flush that carries it — the late-linker case * (#3337's reason to defer the walk): it linked after the write walked. */ @@ -1933,7 +1993,7 @@ export function read(el: Signal | Computed): T { nodeName: (owner as any)?._name }); - if (el._x?._overrideValue !== undefined && el._x?._overrideValue !== NOT_PENDING) { + if (hasActiveOverride(el)) { // A17: the override IS the value for every reader — except an authoritative // reader (until()'s predicate carries CONFIG_AUTHORITATIVE_READ): it must // observe independently-arriving truth, and serving it the caller's own @@ -1997,26 +2057,9 @@ export function read(el: Signal | Computed): T { if (pendingCheckActive) GlobalQueue._recordFresh!(el, u); return u as T; } - const value = - !c || - (currentOptimisticLane !== null && - GlobalQueue._laneReadsCommitted!(el, owner, c as Computed)) || - el._pendingValue === NOT_PENDING || - c._config & CONFIG_CHILDREN_FORBIDDEN || - (stale && !noCommitted && heldFromStale(el, c as Computed)) || - // A17 for HELD truth (#3164, see CONFIG_HELD_TRUTH): staged confirming - // truth — fold-staged onto an armed family, or entangle-stolen by an - // awaited until() — is masked from ordinary readers until its - // transaction's reveal; the retaining transaction's own speculative - // recomputes included (partial override coverage would otherwise - // compose override + staged truth into a state no timeline contains). - // Authoritative readers (until()'s predicate) and latest() see the - // staged truth — the tunnel that keeps the hold deadlock-free. - (el._config & CONFIG_HELD_TRUTH && - !latestReadActive && - !((c as Computed)._config & CONFIG_AUTHORITATIVE_READ)) - ? el._value - : (enterStagedRead(el), el._pendingValue as T); + const value = readerSeesCommitted(el, c as Computed | null, owner, noCommitted) + ? el._value + : (enterStagedRead(el), el._pendingValue as T); // Record that this isPending() probe observed the fresh pending value, so // the probe doesn't pair "pending" with the new value (#2831). if (pendingCheckActive) GlobalQueue._recordFresh!(el, value); diff --git a/packages/signals/src/core/lanes.ts b/packages/signals/src/core/lanes.ts index 3dd12e3c3..75a723321 100644 --- a/packages/signals/src/core/lanes.ts +++ b/packages/signals/src/core/lanes.ts @@ -4,7 +4,8 @@ import { NOT_PENDING, REACTIVE_DISPOSED } from "./constants.js"; -import { currentOptimisticLane, ext } from "./core.js"; +import { currentOptimisticLane, ext, hasActiveOverride } from "./core.js"; +export { hasActiveOverride }; import { enqueueSub } from "./heap.js"; import { activeTransition, @@ -201,14 +202,6 @@ export function resolveTransition(el: Signal | Computed): Transition | return resolveLane(el)?._transition ?? el._transition; } -/** - * Check if a node has an active optimistic override. - */ -export function hasActiveOverride(el: Signal | Computed): boolean { - const x = el._x; - return x !== null && x._overrideValue !== undefined && x._overrideValue !== NOT_PENDING; -} - /** * Assign or merge a lane onto a node. At convergence points (node already has * a different active lane), merge unless the node has an active override. diff --git a/packages/signals/src/core/verdict.ts b/packages/signals/src/core/verdict.ts index ace5b0ef5..62e39d2fe 100644 --- a/packages/signals/src/core/verdict.ts +++ b/packages/signals/src/core/verdict.ts @@ -37,7 +37,7 @@ import { setSignal, unflushed, unflushedCompanions, - unflushedOverride, + visibleOverride, unflushedValue, setStrictRead, stale, @@ -292,8 +292,7 @@ function computePendingState(el: Signal | Computed): boolean { if ( el._config & CONFIG_OVERRIDE_SUPERSEDED && el._pendingValue === NOT_PENDING && - hasActiveOverride(el) && - !unflushedOverride(el) + visibleOverride(el) ) return !el._equals || !el._equals(el._value as any, unwrapOverride(el._x?._overrideValue)); // A28 (2): an unflushed write is not yet observable — the verdict answers @@ -306,7 +305,7 @@ function computePendingState(el: Signal | Computed): boolean { // non-final"; an override is one (a node whose first landing was held // by a reveal it never got to commit, then superseded under its // override, read false here). - if (hasActiveOverride(el) && !unflushedOverride(el)) + if (visibleOverride(el)) return !el._equals || !el._equals(staged as any, unwrapOverride(el._x?._overrideValue)); // A quiet re-ask's held landing still answers the same question: the // classification survives the landing (asyncWrite) and dies with the @@ -503,9 +502,7 @@ function latestRead(el: Signal | Computed): T { const prevPending = latestReadActive; setLatestReadActive(false); const visibleValue = ( - hasActiveOverride(el) && !unflushedOverride(el) - ? unwrapOverride(el._x?._overrideValue) - : el._value + visibleOverride(el) ? unwrapOverride(el._x?._overrideValue) : el._value ) as T; // A28: an unflushed write is not the staged value latest() serves. The // shadow was written at the source's write to mirror it (A8) — consult it diff --git a/packages/signals/src/store/next/store.ts b/packages/signals/src/store/next/store.ts index 804014715..ef324fccc 100644 --- a/packages/signals/src/store/next/store.ts +++ b/packages/signals/src/store/next/store.ts @@ -36,7 +36,8 @@ import { isEqual, latestReadActive, stale, - unflushedOverride, + hasActiveOverride, + visibleOverride, prepareComputed, read as readNode, READ_SLOW, @@ -1757,18 +1758,7 @@ export function runAuthoritative(fn: () => T): T { } } -/** Active optimistic override on an armed node (armed slot idles at - * NOT_PENDING; undefined = unarmed plain node). */ -export function hasActiveOverride(node: Signal): boolean { - return node._x?._overrideValue !== undefined && node._x?._overrideValue !== NOT_PENDING; -} -/** The override a READER sees: installed, and carried by a flush (A28 (5) — - * an optimistic write is a write; until its flush no reader sees it). The - * writer's own channels (the draft, `in`/keys inside the setter) compose on - * the installed override regardless — they use hasActiveOverride. */ -export function visibleOverride(node: Signal): boolean { - return hasActiveOverride(node) && !unflushedOverride(node); -} +export { hasActiveOverride, visibleOverride }; /** The reading computation is until()'s authoritative-view predicate — same * source of truth as core read()'s A17 carve-out (`context`, which persists @@ -1806,7 +1796,7 @@ function nodeValue(node: Signal, backing: any): any { // only: staged pending values are authoritative, overrides are the // caller's optimism. const v = - !authoritativeServe() && hasActiveOverride(node) && !unflushedOverride(node) + !authoritativeServe() && visibleOverride(node) ? unwrapOverride(node._x?._overrideValue) : node._pendingValue !== NOT_PENDING && (latestReadActive || diff --git a/scripts/size/.size-limit.js b/scripts/size/.size-limit.js index bdab03972..6c7b563ce 100644 --- a/scripts/size/.size-limit.js +++ b/scripts/size/.size-limit.js @@ -539,6 +539,11 @@ module.exports = [ // Hydration claim-path trim (#3513, 2026-09-17): measured at 16,517 B against // current `next`'s 16,495 (+22). The fixed-shape `_snapshotValue` cleanup // replaces `delete` with assignment; the pure core floor shrinks by 1 B. + // Shared read predicates (DESIGN-CONSOLIDATION move 3b step 1, 2026-09-17): + // readerSeesCommitted / visibleOverride / one hasActiveOverride. Minified + // signals: core +13 B, +createStore -38 B, full bundle -72 B; brotli on the + // pure-signals fixtures -4 / -29 / -5 B. This scenario's esbuild bundle + // measured at 16,509 B rebased over #3507, against `next`'s 16,517 (-8 B). limit: "16.55 KB", modifyEsbuildConfig }, @@ -925,7 +930,12 @@ module.exports = [ // hydrating body that marks the snapshot root; a resume window records its // boundary owner and `sharedConfig.isClaiming` walks `_parent` to it; // @solidjs/web's isHydrating consults it. 0 B in the signals floor. - limit: "20.05 KB", + // Shared read predicates (DESIGN-CONSOLIDATION move 3b step 1, 2026-09-17): + // readerSeesCommitted / visibleOverride / one hasActiveOverride. Minified + // signals: core +13 B, +createStore -38 B, full bundle -72 B; brotli on the + // pure-signals fixtures -4 / -29 / -5 B. This scenario's esbuild bundle + // measured at 20,054 B rebased over #3507, against `next`'s 20,044 (+10 B). + limit: "20.10 KB", modifyEsbuildConfig }, { @@ -1229,7 +1239,12 @@ module.exports = [ // 15,170 B before the #3496 rebase (+30 over its base); 0 B minified in // the signals floor (24,578 flat). Combined with `_parent` mangling: // 15,250 B; cap ratcheted to the measured output. - limit: "15.25 KB", + // Shared read predicates (DESIGN-CONSOLIDATION move 3b step 1, 2026-09-17): + // readerSeesCommitted / visibleOverride / one hasActiveOverride. Minified + // signals: core +13 B, +createStore -38 B, full bundle -72 B; brotli on the + // pure-signals fixtures -4 / -29 / -5 B. This scenario's esbuild bundle + // measured at 15,251 B rebased over #3507, against `next`'s 15,238 (+13 B). + limit: "15.30 KB", modifyEsbuildConfig }, { @@ -1472,7 +1487,11 @@ module.exports = [ // consumer (enable, subscribe, formatRerun) and ships none of them. The // formatters stay in the engine because the `log` option prints through // them. Ratcheted down to pin the reduction. - limit: "27.25 KB", + // Shared read predicates (DESIGN-CONSOLIDATION move 3b step 1, 2026-09-17): + // measured at 27,256 B rebased over #3507, against `next`'s 27,196 (+60 B). + // The source change is in the signals core; this scenario's attribution + // modules only alter the compressor layout. + limit: "27.30 KB", modifyEsbuildConfig: observeEsbuildConfig }, {