Skip to content

fix(web): revoke GET grants on rebind; never fold single-flight into reads - #3150

Merged
ryansolid merged 2 commits into
nextfrom
fix/server-fn-get-grants
Aug 31, 2026
Merged

fix(web): revoke GET grants on rebind; never fold single-flight into reads#3150
ryansolid merged 2 commits into
nextfrom
fix/server-fn-get-grants

Conversation

@ryansolid

Copy link
Copy Markdown
Member

Closes #3129. Closes #3128.

Two grant-lifetime fixes on the server-function HTTP surface, both one-line runtime changes.

#3129 — a GET() declaration outlived the function it was made about

GET() records its grant against a function id; registerServerFunction rebinds ids freely and never touched METHODS. A mutation registered onto a once-declared id (an id collision between integrations, or a module re-evaluated in a live process after an edit dropped the wrapper) inherited both things the declaration grants: GET dispatch and the origin-gate exemption (#3114).

The fix binds the declaration's lifetime to the binding: rebinding an id to a different function deletes its METHODS entry. Module order makes this self-healing — compiled output runs registerServerReference and then GET() in sequence, so a function that still declares GET re-grants itself immediately after the rebind revokes. Re-registering the same callback keeps the grant (integrations re-running their registration path lose nothing).

The regression test pins both halves in gate order: after the rebind, a bare GET meets the re-armed origin gate (403) and a same-origin GET finds the allowlist no longer advertising the reads (405, Allow: POST), while gated POST dispatch of the new function is untouched.

#3128 — the single-flight request header reshaped a cacheable GET body

The fold check keyed off the address (scripted), not the method, so a GET()-declared read honored X-Single-Flight — two bodies at one cacheable url (the plain value and a { value, data } envelope carrying data the flight hook computed from that caller's request), under the author's own public Cache-Control, with no Vary naming the variance. One curl could store the envelope under the plain key for everyone behind a shared cache.

The client half of the rule already existed (client.ts: reads "stay plain — folding per-request flight data into them would defeat caching"); this is the server half: the flight header is honored on POST only. The test verifies a declared read called with the header answers the plain body, emits no flight response header, and never runs the collector.

Both from @frenzzy's HTTP-surface audit. All 671 web tests pass, including the #3132 pins merged ahead of this.

Made with Cursor

ryansolid and others added 2 commits August 31, 2026 01:24
…ortcomings.

Adds a "Why Solid specifically" section: iterator close as the only
protocol-level interruption hook among frameworks, action transactions as
the UI half of a saga Effect can't provide, and matching pull-based
execution models as the reason no binding library is needed.

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

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

changeset-bot Bot commented Aug 31, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: bcdf79c

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

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

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
ryansolid changed the base branch from main to next August 31, 2026 08:35
@ryansolid
ryansolid merged commit af4cfc8 into next Aug 31, 2026
2 checks passed
@ryansolid
ryansolid deleted the fix/server-fn-get-grants branch August 31, 2026 08:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant