test(signals): visibility oracle — node state × reader kind; latest() of an uninitialized source throws; A7/A16/A17/A32 rulings - #3449
Conversation
🦋 Changeset detectedLatest commit: 1ea6374 The changes in this PR will be included in the next version bump. This PR includes changesets to release 11 packages
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 |
…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>
3601623 to
1ea6374
Compare
Coverage Report for CI Build 34934759364Warning No base build found for commit Coverage: 71.842%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsRequires a base build to compare against. How to fix this → Coverage Stats
💛 - Coveralls |
Merging this PR will improve performance by 12.02%
|
| 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)
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, storenodeValue/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
observedand listed by the report (VISIBILITY_ORACLE_REPORT=<file>); cells where the runtime disagrees with the rule are pinned at current behavior asviolation— the suite stays green, the disagreement stays visible, and fixing the runtime fails the cell so it gets flipped torule.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
asyncbuild crosses the scheduler's microtask flush); the stale reader letsNotReadypropagate so a suspended pass readsHELD.Three violations, left red for a follow-up (root cause found, fix needs an A29 ruling — see below):
isPendingfalse 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 thoughenterStagedReadentered the transaction. A prototype fix exposed a second question —enterStagedReadat creation time (outside a flush) leavesactiveTransitionset 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 scopelatestRead'sNotReadyfallback served the visible value when the caller was unowned or the source initialized, so an event handler readinglatest(x)beforex's first landing gotundefined— a valuelatest<T>excludes. Now the fallback is the initialized case only.isPending()unchanged: unowned probes answerfalse, whichbooleanadmits. Nothing in signals / solid / web relied on theundefined. Changeset included.Rulings (2026-09-14, from the oracle's first run)
latest()throws, neverundefined.context === null), not tracking —untrack()inside an owner propagatesNotReady. The old text said "untracked contexts"; the runtime never did that.until()'s predicate reads the landed world (staged values included, before they are visible), never the caller's optimism.createTrackedEffect,onSettled) see the frame — committed values and a displayed override — never a held write; they cannot enter a hold on their own.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 --checknow regenerates in memory and compares with the committedRULES-INDEX.md(normalized for prettier's reflow), so the index cannot go stale silently.Verification
@solidjs/signals171 files / 1887 tests ·solid-js613 ·@solidjs/web768 — 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 currentCo-authored-by: Cursor cursoragent@cursor.com
Made with Cursor