Skip to content

test(signals): visibility oracle — node state × reader kind; latest() of an uninitialized source throws; A7/A16/A17/A32 rulings - #3449

Merged
ryansolid merged 3 commits into
nextfrom
test/visibility-oracle
Sep 15, 2026
Merged

ryansolid merged 3 commits into
nextfrom
test/visibility-oracle

Conversation

@ryansolid

@ryansolid ryansolid commented Sep 15, 2026

Copy link
Copy Markdown
Member

Follow-up to #3432. Three commits.

Visibility oracle (tests/visibility-oracle.test.ts)

The runtime decides what a read serves at eleven sites (read, readNodeFast, supersededRead, latestRead, gatedRead, laneReadsCommitted, heldFromStale, store nodeValue / serveDataKey / optimisticView); every new visibility rule is threaded through them by hand. The oracle pins the observable outcome per cell: 10 node states (committed, staged ambient, held by an action, override active, override ambient, superseded with an initialized / an uninitialized downstream, pending own async, uninitialized, loading window) × 9 reader kinds (untracked, derives-from, published, pre-existing, stale foreign, children-forbidden, latest, isPending, authoritative) = 90 cells.

Each expected cell cites the rule that fixes it. Cells the spec does not fix are pinned as observed and listed by the report (VISIBILITY_ORACLE_REPORT=<file>); cells where the runtime disagrees with the rule are pinned at current behavior as violation — the suite stays green, the disagreement stays visible, and fixing the runtime fails the cell so it gets flipped to rule.

Harness lessons recorded in the file: open-ended promises are released between cells (a never-settling action left its transaction live across the next cell); pre-flush states are built synchronously (an async build crosses the scheduler's microtask flush); the stale reader lets NotReady propagate so a suspended pass reads HELD.

Three violations, left red for a follow-up (root cause found, fix needs an A29 ruling — see below):

  1. held signal × fresh memo + render effect → publishes the held value into the mainline frame (A29)
  2. superseded node × fresh memo + render effect → publishes the truth while a fresh direct effect beside it shows the override (A18 c)
  3. superseded node with an uninitialized downstream → isPending false while the arrival differs (A18 d)

1 and 2 share a root cause: recompute's creation-time pass always direct-commits (create ||) and is never held, so a memo born mainline during a hold commits the staged value it read even though enterStagedRead entered the transaction. A prototype fix exposed a second question — enterStagedRead at creation time (outside a flush) leaves activeTransition set for the rest of the synchronous block, so sibling nodes created afterwards become transaction work too. Ruling pending; the fix lands separately.

latest() of an uninitialized source throws in every scope

latestRead's NotReady fallback served the visible value when the caller was unowned or the source initialized, so an event handler reading latest(x) before x's first landing got undefined — a value latest<T> excludes. Now the fallback is the initialized case only. isPending() unchanged: unowned probes answer false, which boolean admits. Nothing in signals / solid / web relied on the undefined. Changeset included.

Rulings (2026-09-14, from the oracle's first run)

  • A7 amended: before the first landing latest() throws, never undefined.
  • A16 wording corrected: the boundary is ownership (context === null), not tracking — untrack() inside an owner propagates NotReady. The old text said "untracked contexts"; the runtime never did that.
  • A17 authoritative-reader carve-out: until()'s predicate reads the landed world (staged values included, before they are visible), never the caller's optimism.
  • A32 (new): children-forbidden readers (createTrackedEffect, onSettled) see the frame — committed values and a displayed override — never a held write; they cannot enter a hold on their own.
  • Observer dependence of a fresh derivation over a pending node recorded as inherent (the frame is identical either way).

Oracle: 18 unspecified cells → 10 after the rulings; 7 of the 10 are pre-flush cells parked on A28 (#3337).

Rules-index staleness gate

scripts/rules-index.mjs --check now regenerates in memory and compares with the committed RULES-INDEX.md (normalized for prettier's reflow), so the index cannot go stale silently.

Verification

  • @solidjs/signals 171 files / 1887 tests · solid-js 613 · @solidjs/web 768 — all green (compiler and dists rebuilt in the worktree)
  • node scripts/rules-index.mjs --check: every src citation resolves (80 ids), every live A-rule cited by a test, index current

Co-authored-by: Cursor cursoragent@cursor.com

Made with Cursor

@changeset-bot

changeset-bot Bot commented Sep 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1ea6374

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 11 packages
Name Type
@solidjs/signals Patch
test-integration Patch
@solidjs/web Patch
@solidjs/babel-plugin Patch
@solidjs/compiler Patch
@solidjs/diagnostics Patch
@solidjs/element Patch
@solidjs/h Patch
@solidjs/html Patch
solid-js Patch
@solidjs/universal Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

ryansolid and others added 3 commits September 14, 2026 22:54
…mitted index with a regeneration

The gate verified citations and pins but not that docs/RULES-INDEX.md
itself was regenerated after the docs changed; it could go stale silently.
--check now regenerates in memory and compares with the committed file,
normalized for what prettier does on commit (cell padding, escaped
underscores, blank lines around headings, dash runs).

Co-authored-by: Cursor <cursoragent@cursor.com>
… served

Ten node states (committed, staged ambient, held by an action, override
active, override ambient, superseded with an initialized / an uninitialized
downstream, pending own async, uninitialized, loading window) × nine reader
kinds (untracked, derives-from, published, pre-existing, stale foreign,
children-forbidden, latest, isPending, authoritative). 90 cells. Each
expected value cites the rule that fixes it; cells the spec does not fix
are pinned as `observed` and listed by the report
(VISIBILITY_ORACLE_REPORT=<file>); cells where the runtime disagrees with
the rule are pinned at current behavior as `violation` so the suite stays
green and the disagreement stays visible — fixing the runtime fails the
cell, which is then flipped to `rule`.

Harness lessons recorded in the file: every open-ended promise is released
between cells (a never-settling action left its transaction live across
the next cell); pre-flush states are built synchronously (an async build
crosses the scheduler's microtask flush); the stale reader lets NotReady
propagate so a suspended pass reads HELD.

Three violations (A18 c/d, A29) and eighteen unspecified cells in the
first run — reported separately.

Co-authored-by: Cursor <cursoragent@cursor.com>
…pe; rule the oracle's open cells

latestRead's NotReady fallback served the visible value when the caller was
unowned OR the source was initialized; an uninitialized source read from an
event handler therefore returned undefined — a value latest<T>'s type
excludes. The condition is now the initialized case only. isPending is
unchanged (an unowned probe answers false, which boolean admits).

Rulings recorded in SPEC-ASYNC-SEMANTICS.md (2026-09-14, from the
visibility oracle's first run):
- A7 amended: before the first landing latest() throws, never undefined.
- A16 wording corrected: the boundary is ownership (context === null), not
  tracking — untrack() inside an owner propagates NotReady.
- A17 authoritative-reader carve-out: until()'s predicate reads the landed
  world (staged values included, before they are visible), never the
  caller's optimism.
- A32 (new): children-forbidden readers (createTrackedEffect, onSettled)
  see the frame — committed values and a displayed override — never a
  held write; they cannot enter a hold on their own.
- Observer dependence of a fresh derivation over a pending node recorded
  as inherent (the frame is identical either way).

Oracle: 18 unspecified cells → 10, of which 7 are pre-flush cells parked on
A28 (#3337). Three violation cells stand.

Co-authored-by: Cursor <cursoragent@cursor.com>
@ryansolid
ryansolid force-pushed the test/visibility-oracle branch from 3601623 to 1ea6374 Compare September 15, 2026 05:56
@ryansolid ryansolid changed the title test(signals): visibility oracle — node state × reader kind; latest() of an uninitialized source throws; A7/A16/A17/A31 rulings test(signals): visibility oracle — node state × reader kind; latest() of an uninitialized source throws; A7/A16/A17/A32 rulings Sep 15, 2026
@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 34934759364

Warning

No base build found for commit a5d8eae on next.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 71.842%

Details

  • Patch coverage: No coverable lines changed in this PR.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 1007
Covered Lines: 772
Line Coverage: 76.66%
Relevant Branches: 790
Covered Branches: 519
Branch Coverage: 65.7%
Branches in Coverage %: Yes
Coverage Strength: 15.09 hits per line

💛 - Coveralls

@codspeed

codspeed Bot commented Sep 15, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 12.02%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 1 improved benchmark
✅ 159 untouched benchmarks

Performance Changes

Benchmark BASE HEAD Efficiency
projection derive: write one NESTED field (reference) 2.8 ms 2.5 ms +12.02%

Tip

Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.


Comparing test/visibility-oracle (1ea6374) with next (a5d8eae)

Open in CodSpeed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants