Skip to content

feat(signals,diagnostics): serializable re-run records, artifact timeOrigin (v7), observe idle-cost cap - #3493

Merged
ryansolid merged 2 commits into
nextfrom
attribution-records
Sep 16, 2026
Merged

ryansolid merged 2 commits into
nextfrom
attribution-records

Conversation

@ryansolid

Copy link
Copy Markdown
Member

The remainder of observe-tier-plan.md PR B: the attribution engine's records become wire-shaped before the RC stabilises, and the observe tier's idle cost becomes a cap. origin — the third leg of PR B — already landed through the Sentry workstream (withOrigin, CallEvent.origin, currentOrigin()).

RerunEvent is serializable as emitted

  • node (a live Computed) is gone. nodeId names the scope: the engine's existing per-node id — the one ChangeOrigin.run and the relay/cycle checks already key on — stable across a scope's runs in the process and distinct between scopes, so runs of unnamed effects still fold to one scope offline.
  • OBSERVE.subjectOf(record), which diagnostics already had, now answers for re-run records too. Keyed by the record object, so the node lives exactly as long as some consumer holds the record — the lifetime it had when the record carried it. A copy that left the process has no subject.
  • attribution.why(target) / subscriptions(target) unchanged. Only the engine's own recordCosts/why() and two tests read .node; @solidjs/diagnostics was stripping it at three sites and now stores re-runs verbatim (RerunRecord is an alias of RerunEvent).

Clocks: the anchor travels once, not a ts per record

Every at the engine and the runtimes emit is on one clock, performance.now(), and exporters (the spike) already anchor with performance.timeOrigin. A second clock per record would cost bytes on every record and drift against the first. Instead DiagnosticsArtifact.timeOrigin (format v7; also on the JSONL meta line and the browser bridge payload) makes every at in an artifact absolute after the fact — re-runs, holds, records, diagnostic data — and lines a server capture up with the browser session it served. RFC 08 documents the contract.

Idle wiring is a cap

packages/signals/tests/observe-idle-cost.test.ts runs one graph-heavy workload (1000 signal→memo→memo→effect chains, 10 write passes) against the built prod and observe artifacts in one process, interleaved best-of-5, and caps observe/prod at 1.25 — measured 1.03–1.09 with no hooks installed. A round over the cap re-measures (up to three): worker-thread contention is intermittent, a regression is not. Held across four full-suite runs with the web and solid suites running in parallel.

Engine diet — measured, deferred

Ranking every engine function by minified weight: the console face (formatRerun/formatCause/logRerun) is ~0.45 KB gz of the engine's 11.7 KB — 4% — and formatOrigin another 0.15 KB, which the spike calls for span names. The plan's premise is true but small. What a record-consuming production adapter never calls is the in-process query/fold surface (costs, feedback, why, history, ~1.5 KB gz); shedding it means splitting attribution into tree-shakable named exports — an API change for ~2 KB (17%). Written up in the plan's PR B section as a decision rather than taken here.

Verification

  • signals 2736 (incl. the new tripwire and a serializability test: no node key, JSON round-trip equal, nodeId stable/distinct, subjectOf returns the node, why() by accessor and by node), diagnostics 34, solid 620 + 825 types, web 986 + 185 types.
  • Size caps unchanged (attribution scenario −8 B; wiring floor identical).
  • Changeset: @solidjs/signals, @solidjs/diagnostics (patch, prerelease).

Deferred to release, per direction: the vite-plugin work (C5, the SSR componentNames bump).

…RVE.subjectOf), artifact timeOrigin (v7), observe idle-cost cap

observe-tier-plan PR B's remainder. `RerunEvent` drops the live `node` for
`nodeId` — the engine's per-node id, stable across a scope's runs and
distinct between scopes — so re-run records leave the process as emitted;
`OBSERVE.subjectOf` answers for them as it does for diagnostic events,
keyed by the record object for as long as a consumer holds it.

Clocks: every `at` stays on the `performance.now()` clock; the anchor
travels once as `DiagnosticsArtifact.timeOrigin` (format v7, JSONL meta,
bridge payload) rather than a second clock per record. Re-runs are stored
verbatim (`RerunRecord` = `RerunEvent`).

`tests/observe-idle-cost.test.ts` caps the observe artifact's idle wiring
at 1.25x the prod artifact on one workload in one process (measured
1.03–1.09), re-measuring a round over the cap so contention cannot fail
it. The engine diet was measured (formatters ~4% of the engine) and is
written up in the plan as a decision, not taken.

Co-authored-by: Claude via Cursor <noreply@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@changeset-bot

changeset-bot Bot commented Sep 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 47601b5

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
@solidjs/diagnostics Patch
test-integration Patch
@solidjs/web Patch
@solidjs/babel-plugin Patch
@solidjs/compiler 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

@coveralls

coveralls commented Sep 16, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 35086846455

Coverage remained the same at 71.46%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 1018
Covered Lines: 772
Line Coverage: 75.83%
Relevant Branches: 790
Covered Branches: 520
Branch Coverage: 65.82%
Branches in Coverage %: Yes
Coverage Strength: 14.94 hits per line

💛 - Coveralls

…ps when unbuilt

Literal '../dist/...' specifiers had the build's TS pass emit TS2307 on
every build (tsconfig includes tests/) and the test hard-fail on an
unbuilt checkout. Same pattern as treeshake.test.ts: resolved paths,
describe.skipIf(!existsSync). Also corrects the cap's framing — 1.25
trips at ~3x today's 3–9% wiring cost, not a doubling. Tests-only.

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@codspeed

codspeed Bot commented Sep 16, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 16.9%

⚠️ 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
✅ 174 untouched benchmarks

Performance Changes

Benchmark BASE HEAD Efficiency
projection derive: write one NESTED field (reference) 2.1 ms 1.8 ms +16.9%

Tip

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


Comparing attribution-records (47601b5) with next (8c80a67)

Open in CodSpeed

@ryansolid
ryansolid merged commit 59c4192 into next Sep 16, 2026
6 checks passed
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