Skip to content

fix: resource identity and coarse-claimed results off-box on every event copy; SSR non-finite numbers as JavaScript spells them (rf2-uc1n9, rf2-xg1vu, rf2-dzbyz) - #11826

Merged
mike-thompson-day8 merged 4 commits into
mainfrom
worker/resources-ssr-egress-fix
Oct 30, 2026

Conversation

@mike-thompson-day8

@mike-thompson-day8 mike-thompson-day8 commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Three off-box and wire-correctness fixes.

rf2-uc1n9: a resource's identity takes its off-box rule on every event copy

Spec 016 §Resource registration spec: off box a resource's scope always tokenizes, and its params ride unless the owner makes the coarse :sensitive? / :large? claim. The FX carriers already followed that rule. The event-bearing copies did not.

Probe (JVM, a :sensitive? true resource and a plain one, each ensured with params and an identity-bearing scope, settled to a failing :reply-to target, then projected with rf/project-egress at the default posture and under each include flag). At trunk the coarse owner's key params and every scope reached off box raw here:

Copy When
The [:rf.resource/ensure …] request's :params / :scope, on the trace's :rf.event/v and the record's :trigger-event :rf.egress/include-event-args?
The reply's :resource/key and :correlation :rf.reply/resource-key (and its scopes), on :rf.event/v, :event and :trigger-event :rf.egress/include-event-args?
The same reply keys in the record's :effects[*].args (the :dispatch of the reply) :rf.egress/include-fx-args?
The same reply keys in the always-on :rf.observe/error record's :event the default sink posture

The control was the reply's own :params / :value, which the event chokepoint already tokenizes. It read tokenized throughout. The default epoch posture was already clean, because the event and fx args reduce to their ids there.

Fix. One rule, applied by the projector that already carries it for the FX carriers:

  • re-frame.resources.trace-egress: the carrier projector (project-fx-args-egress → project-embedded-keys) walks the event carriers :rf.event/v / :event beside :rf.fx/args / :rf.event/fx. It also gives a [:rf.resource/<op> {…}] request's :scope and :params the rule its scoped key takes (project-request-identity, through project-trace-scoped-key). The event id's reserved namespace is the proof.
  • re-frame.epoch.tool-pair: once an opt-in hands back :trigger-event or :effects[*].args, each is projected exactly as the tag it was captured from (:rf.event/v / :rf.fx/args), so a record and its trace cannot disagree.
  • re-frame.projection/project-event-slot: the always-on records' :event takes the same projector after the registration pass. The trusted-local :rf.egress/include-sensitive? lifts it, as it lifts the tool-pair arm.
  • The late-bind directory entry for :resources/project-fx-args-egress now describes the event carriers and the three consumers.

The on-box trace keeps the raw identity, and the new test pins that as its fixture. A plain owner's params still ride, and its scope now tokenizes on these copies too, which is the same rule.

rf2-xg1vu: a transport reply's result takes its owner's coarse claim

The same probe found one more copy. Managed HTTP settles an attempt by dispatching [<internal-reply-id> <verification-payload> <result>], so under include-event-args? a coarse owner's decoded response rode raw on that event's :rf.event/v and on the record's :trigger-event. The continuation reply carrying the same value already tokenized.

The carrier projector now gives the result a continuation reply's slot rule. :value tokenizes when the owner redacts: the owner is the resource the payload's work id names, or the mutation attempt's :mutation-id. The :error envelope tokenizes unconditionally, as reply-error-slot does everywhere in the family. Both the read twin and the mutation twin are covered, and a plain owner's value rides.

rf2-dzbyz: NaN and the infinities render as JavaScript spells them

re-frame.ssr.hash/canonical-number fell through to pr-str for a non-finite double, so [:span ##NaN] shipped <span>##NaN</span> where React prints NaN. The JVM arm gains three arms (NaN, Infinity, -Infinity).

The CLJS arm prints with str (String(n)), which agrees with pr-str on every finite number. Changing the JVM arm alone would have made the two hosts disagree, both in the emitter's text and in the install-ledger digest, which shares the function. The digest therefore reads a non-finite double as NaN / Infinity on both hosts.

No spec sentence changes. 004B already says numbers become text by JS ToString, and Spec 016's identity sentence is what now ships.

Quality gates

Exit codes are the runner's own, captured to a file.

These ran on the final tree, rebased onto trunk at 1651a9cf1c.

  • scripts/test-jvm-implementation.sh (attempt 2): exit 0, all 17 artefacts. Its printed gate root names this worktree. It covers clojure -M:test in implementation/ssr and implementation/resources. Attempt 1, run before the rf2-xg1vu fold, stopped at ssr-ring with exit 1. The cause was one HttpTimeoutException in ring-draintime-error-test's concurrent-request test while other workers' builds loaded the machine. Attempt 2 passed ssr-ring with no change.
  • npm run test:cljs (attempt 2): exit 0.
  • Scoped runs of the new namespace: exit 0. re-frame.ssr-hash-test: exit 0.
  • clj-kondo on the changed files: no new findings. The one warning, an unused re-frame.interop require in tool_pair.cljc, is also present at trunk.

The final commit changes only a late-bind directory description string, so this PR's CI is its gate. CI is also the gate for the browser, prod-elision and bundle lanes.

Test delta. The baseline is trunk CI at the merge base b037cf2b48 (tests run 40979166051).

The code under test is the same at the merge base and at the rebased base, because trunk moved by docs only. Every lane landed on its predicted count:

Lane Trunk This PR Delta
JVM implementation/ssr 325 / 1520 326 / 1527 +1 test, +7 assertions
JVM implementation/resources 597 / 1871 600 / 1896 +3 tests, +25 assertions
JVM implementation/core 1470 / 4453 1470 / 4453 none (no JVM core test changed)
CLJS :node-test 6810 / 19941 6814 / 19968 +4 tests, +27 assertions (the resources namespace, plus hash_check_cljs_test)

Plants. Each was planted, run, restored, and the restore verified by the file's blob hash against the committed object.

  • rf2-dzbyz: pr-str restored for non-finite doubles. Exit 1, with 6 failures (NaN, Infinity, -Infinity and the float infinity in text, plus the attribute and the digest). The finite-double control stayed green.
  • rf2-uc1n9, scoped to the new namespace. The reply's :params control and the on-box fixture stayed green under every plant.
    • Request arm removed: exit 1, red on :trace-event-args and :trigger-event.
    • Event carriers dropped from the projector's slot set: exit 1, red on :trace-event-args, :trigger-event and :error-record.
    • :trigger-event raw under the opt-in: exit 1, red on :trigger-event.
    • :effects args raw under the opt-in: exit 1, red on :effect-args.
    • project-event-slot without the projector: exit 1, red on :error-record.
  • rf2-xg1vu: the transport-reply arm removed. Exit 1, with 6 failures across the read and mutation twins (the coarse value as a token on every copy, no leak, the failure envelope). The plain-owner control stayed green.

The CLJS arm of canonical-number is covered by the new hash_check_cljs_test assertion through npm run test:cljs. No CLJS plant was run. The final-base gate is this PR's CI.

…2-dzbyz)

canonical-number fell through to pr-str for a non-finite double, so the
emitter wrote `##NaN` / `##Inf` / `##-Inf` in reader syntax where React's
client render prints `NaN` / `Infinity` / `-Infinity`, and hydration saw a
text mismatch. Both arms now print String(x): the JVM arm gains three
non-finite arms beside the finite-double arm, and the CLJS arm prints with
`str`, which is pr-str's form for every finite number. Changing the JVM arm
alone would have made the two hosts disagree, in the emitter's text and in
the install-ledger digest that shares the function.
… event copy (rf2-uc1n9)

Off box a resource's scope tokenizes and its params ride unless the owner
makes the coarse :sensitive? / :large? claim. The FX carriers took that rule;
the event-bearing copies did not. The [:rf.resource/ensure ...] request's own
payload, and a continuation reply's :resource/key and
:correlation :rf.reply/resource-key riding its :reply-to target, reached off
box raw on the trace's :rf.event/v and :event and the epoch record's
:trigger-event under :rf.egress/include-event-args?, on the record's
:effects args under :rf.egress/include-fx-args?, and on the always-on
:rf.observe/error record at the default posture.

The resources carrier projector now walks the event carriers beside the FX
carriers and gives a [:rf.resource/<op> {...}] request's :scope and :params
the rule its scoped key takes. The epoch tool-pair applies it to
:trigger-event and :effects args as to the tags they were captured from, and
project-event-slot applies it to the always-on records' :event. The on-box
trace keeps the raw identity.
…laim off box (rf2-xg1vu)

Managed HTTP settles an attempt by dispatching
[<internal-reply-id> <verification-payload> <result>], so under
:rf.egress/include-event-args? a coarse owner's decoded response rode raw
on that event's :rf.event/v and the record's :trigger-event, while the
continuation reply carrying the same value tokenized. The carrier projector
now gives the result a continuation reply's slot rule: :value tokenizes when
the owner the payload's work id names (a mutation attempt's :mutation-id)
redacts, and the :error envelope tokenizes unconditionally. The read and
mutation twins are both covered; a plain owner's value rides.
@mike-thompson-day8
mike-thompson-day8 force-pushed the worker/resources-ssr-egress-fix branch from 5124fd0 to e62c746 Compare October 30, 2026 01:19
@mike-thompson-day8 mike-thompson-day8 changed the title fix: resource identity off-box on every event copy; SSR non-finite numbers as JavaScript spells them (rf2-uc1n9, rf2-dzbyz) fix: resource identity and coarse-claimed results off-box on every event copy; SSR non-finite numbers as JavaScript spells them (rf2-uc1n9, rf2-xg1vu, rf2-dzbyz) Oct 30, 2026
@mike-thompson-day8
mike-thompson-day8 marked this pull request as ready for review October 30, 2026 01:58
@mike-thompson-day8
mike-thompson-day8 merged commit c0b027b into main Oct 30, 2026
81 checks passed
@mike-thompson-day8
mike-thompson-day8 deleted the worker/resources-ssr-egress-fix branch October 30, 2026 02:00
mike-thompson-day8 added a commit that referenced this pull request Oct 30, 2026
Merge: c0b027b at 2026-10-30T02:00:54Z.
Completeness: Complete: resource request identities and coarse transport replies are projected across trace, Pair event and effect carriers; SSR non-finite numeric spellings now agree across hosts. The path-only transport follow-up is separately tracked in rf2-9yvig and will be inspected in merge chronology.
Correctness: Correct: owner-frame classification, scope projection, raw on-box controls and canonical numeric parity are supported by focused regression cases, reported fault plants and passing CI. No new actionable defect found.
Implementation quality: Good: reuses carrier projection and existing owner-frame hooks, with explicit internal-event recognition and one shared numeric conversion; no unnecessary public API.
Findings: none.
Source forward pointers: none.
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.

1 participant