Series URL repair, owner-typed and gated by the poller's own fetch gate #151
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Parent
Spec #135.
What to build
The series URL column is write-once: it is set when the row is created, and no Poll and no Reader PUT ever changes it. A Series whose address went bad is therefore permanently unpollable and unrepairable by any client action. This ticket gives the owner a text input on the Series detail page that stores a new one.
Validated by the gate the poller already uses before it fetches anything — a client-supplied URL is exactly what that gate exists for. The gate is currently unexported in the poller package; export it and update its in-package callers. One implementation, never a second gate. The web package already imports the poller package.
The repair fetches nothing. Storing an address is not verifying it, and a synchronous fetch in an owner request would imply otherwise and could block on the browser sidecar. The owner presses Check now afterwards, so verification is an explicit act with a visible result.
This lifts the write-once rule for the owner only. Rejected: letting a Reader's PUT heal a stale URL — the client already sends a fresh one every time and the store drops it, deliberately. The series row is shared: one Reader could repoint a Series every other Reader reads at a different work on the same host, and the gate would pass it, because the gate stops SSRF, not mis-pointing.
Say the honest limit on the page rather than papering over it: a Site-wide host change invalidates every row of that Site at once, and a per-Series form is the wrong tool for it. That is a SQL migration and is out of scope — the owner should not sit typing the same fix into two hundred forms.
The control is unconfirmed, never ember, no danger accent, and sits in the detail page's two-column grid beside the Latest Chapter correction. The route joins the admin route list behind the owner gate and caps its form body the way the API path caps bodies. The repair records nothing: it changes what the Poll fetches, not what a Reader reads.
The fetch gate is security-critical, so say which invariant you preserved in the PR and run the full backend test suite.
Acceptance criteria
POST /admin/series/{key}/series-urlis owner-gated by joining the admin route listBlocked by
Landed on
spec-135as a--no-ffmerge ofticket/151-series-url-repair(424d2c6).Implemented:
latest.fetchableSeriesURLexported tolatest.FetchableSeriesURL(same signature and body, one implementation) with every in-package caller and all four comment references updated and no lower-case copy surviving;(*Store).SetSeriesURLas one parameterized UPDATE beside the other single-column series setters, with the write-once doc comment onSeries.SeriesURLcorrected to record its one exception;POST /admin/series/{key}/series-urljoined toadminRoutesso the existing owner-gate enumeration covers it; handleradminSeriesSetURLcapping the body withhttp.MaxBytesReaderat 64 KB, refusing a URL the gate rejects with 400 before anything reaches the store, 404 on an unknown Series, generic errors to the client and detail to the log, answering with the re-renderedseries-detail-metafragment; and a second.dformin the detail page's.detail-gridwhose copy states the honest limit - storing an address is not verifying it, and a Site-wide host change is a migration, not two hundred forms. The request performs no outbound fetch of any kind.Invariant preserved: the root AGENTS.md rule that any outbound fetch of a client-supplied URL passes the fetch gate first, and that a new path reuses the gate rather than re-deriving one. The web layer became a second caller of the one implementation; the Upsert's write-once rule for Readers was not loosened. Owner-gating is by route registration, so the existing owner-gate test covers the new pattern.
TDD red captured at both named seams:
undefined: (*Store).SetSeriesURLat the store seam, and 404-instead-of-400 plus a detail page with noseries_urlinput at the web seam.The full backend suite is green on the merged base: 571 tests, 10 packages.
One incidental fix: the first draft of the repair hint contained the literal "Check now", which broke
TestSeriesPollControlVisibility(it counts that string on the detail page). Reworded.No concerns handed back. The live CDP smoke suite is not part of this gate and was not run.