Admin-only per-Series cover refetch (blocked on admin dashboard) #54

Closed
opened 2026-08-09 22:55:06 +07:00 by sulthan · 3 comments
Owner

Deferred out of #47's design session (see docs/adr/0007-backend-hosts-cover-bytes.md).

A Series' Cover is fetched by the backend and, once stored, never overwritten — the poll only
fills a blank one. When a Site re-arts a Series, there is currently no way to pick the new
artwork up. The decided fix is an admin-only "refetch cover" action for one chosen Series: it
re-runs the extractor against the Site and takes whatever the Site now serves. No Reader-supplied
URL or upload — that would put attacker-controlled input into a row every Reader sees, which
ADR-0003 closed deliberately.

Blocked on: there is no admin dashboard to hang the button off yet. Cover is deliberately
not-updateable until there is.

Scope when unblocked:

  • admin-only control, one Series at a time, chosen from the UI
  • re-runs the existing cover extractor + fetch; no free-text input
  • replaces the stored bytes; the new content address makes cache invalidation automatic
Deferred out of #47's design session (see docs/adr/0007-backend-hosts-cover-bytes.md). A Series' Cover is fetched by the backend and, once stored, never overwritten — the poll only fills a blank one. When a Site re-arts a Series, there is currently no way to pick the new artwork up. The decided fix is an admin-only "refetch cover" action for one chosen Series: it re-runs the extractor against the Site and takes whatever the Site now serves. No Reader-supplied URL or upload — that would put attacker-controlled input into a row every Reader sees, which ADR-0003 closed deliberately. Blocked on: there is no admin dashboard to hang the button off yet. Cover is deliberately not-updateable until there is. Scope when unblocked: - admin-only control, one Series at a time, chosen from the UI - re-runs the existing cover extractor + fetch; no free-text input - replaces the stored bytes; the new content address makes cache invalidation automatic
Author
Owner

Superseded as a decision by wayfinder ticket #120 (Per-Series cover refetch and the store's force path), on map #114. The blocker recorded here — no admin dashboard — is what that map exists to specify. Charting also turned up a fact this issue predates: SetSeriesCover only fills an empty cover_address, and the content address is the SHA-256 of the source URL, so a Site re-arting a Series behind an unchanged URL may make a naive refetch a silent no-op.

Superseded as a decision by wayfinder ticket #120 (Per-Series cover refetch and the store's force path), on map #114. The blocker recorded here — no admin dashboard — is what that map exists to specify. Charting also turned up a fact this issue predates: `SetSeriesCover` only fills an empty `cover_address`, and the content address is the SHA-256 of the *source URL*, so a Site re-arting a Series behind an unchanged URL may make a naive refetch a silent no-op.
Author
Owner

Decided on #120 (closed): there is no dedicated refetch control. The owner replaces a Cover by asking for a Forced Poll — series.force_poll_at from #119 — and checkOne takes a replace path instead of fillBlankCover when the pass was forced, using the cover it already extracted from the page. This issue's scope narrows accordingly: no button of its own, no free-text input (unchanged), and the work is (a) ReplaceSeriesCover in the store, differing from SetSeriesCover only in dropping AND cover_address = '' and writing cover too, and (b) byte-derived addresses for new cover writes, because the present address is the SHA-256 of the source URL and a Site that re-arts behind an unchanged URL is invisible — os.Link skips fs.ErrExist, the covers insert is ON CONFLICT DO NOTHING, and /covers/{address} is served immutable for 7 days. Old bytes are not deleted here; #125 owns reclamation.

Decided on #120 (closed): there is **no dedicated refetch control**. The owner replaces a Cover by asking for a Forced Poll — `series.force_poll_at` from #119 — and `checkOne` takes a replace path instead of `fillBlankCover` when the pass was forced, using the cover it already extracted from the page. This issue's scope narrows accordingly: no button of its own, no free-text input (unchanged), and the work is (a) `ReplaceSeriesCover` in the store, differing from `SetSeriesCover` only in dropping `AND cover_address = ''` and writing `cover` too, and (b) byte-derived addresses for new cover writes, because the present address is the SHA-256 of the source URL and a Site that re-arts behind an unchanged URL is invisible — `os.Link` skips `fs.ErrExist`, the `covers` insert is `ON CONFLICT DO NOTHING`, and `/covers/{address}` is served `immutable` for 7 days. Old bytes are not deleted here; #125 owns reclamation.
Author
Owner

Superseded by spec #135 and its tickets. The decision went the other way: there is no dedicated per-Series cover refetch control. A Forced Poll takes the replace path (#153) while an ordinary pass keeps filling only a blank Cover, because asking for a check is already asking to accept the page as it now stands — a second button and a second column for a rare action was rejected outright. Underneath it, #150 addresses new Cover writes by the SHA-256 of the bytes, which is what makes a re-art visible at all, and #154 reclaims the stranded old bytes. Closing; the work is tracked there.

Superseded by spec #135 and its tickets. The decision went the other way: there is no dedicated per-Series cover refetch control. A Forced Poll takes the replace path (#153) while an ordinary pass keeps filling only a blank Cover, because asking for a check is already asking to accept the page as it now stands — a second button and a second column for a rare action was rejected outright. Underneath it, #150 addresses new Cover writes by the SHA-256 of the bytes, which is what makes a re-art visible at all, and #154 reclaims the stranded old bytes. Closing; the work is tracked there.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sulthan/mangaBookmark#54