Latest Chapter correction: one numeric input, overwritten by the next machine write #149
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
Some Series carry a Latest Chapter no machine will ever confirm or contradict: a Reader's report raised the number and the Poll cannot read that Series' page at all. The owner has no way to make it right. This ticket gives them one numeric input on the Series detail page.
It carries no authority, deliberately. The value is overwritten by the next successful Poll and by any Reader's Progress PUT, because a page read is stronger evidence than a typed number, and that is one rule rather than two. No pin, no floor: a floor would have made the downward direction unrepairable, which is the direction the motivating case actually needed. On the class this control exists for nothing overwrites it anyway — that is what "the Poll cannot read the page" means. The page says so plainly, so a stopgap is not mistaken for a pin.
One input, not two. The handler requires a finite float greater than zero, else 400; the stored label is derived from the number. A free-text label invents a failure mode where the number is right and the panel renders the typo, and leaving the old label is worse — the panel would keep naming the old chapter after the number moved. Nothing depends on matching a Site's typography.
A new stamp column means "the current value is a human's", written by the correction and zeroed by every machine write of the value. The poller's chapter setter zeroes it in the same update. The series Upsert gains one clause in its existing conflict update, conditional on the value actually changing — after a correction a Reader's cached row holds the corrected number and resends it on the next Progress PUT, so unconditional zeroing would erase the fact while the value is still the owner's. That conditional pair is the whole point of the clause and the most likely thing to get wrong.
A correction that contradicts a Reader's report clears the attribution without judging it. Clear the raising Reader; never record a disagreement outcome. A mark stays something a machine earned: recovery is twenty confirming Polls, and this control exists precisely because no Poll can read the page, so a mark landed here would be permanent in practice and an owner's typo unappealable. Leaving the attribution is wrong outright — the next Poll would credit or blame that Reader for the owner's number.
Corrections are silent to Readers: no notification, the corrected label simply is the Latest Chapter and the new-chapter accent follows the number.
The control is unconfirmed (it pulls nothing out of a list), never ember, no danger accent, and sits on the detail page unconditionally — gating it on "looks unverifiable" would wait on failure state that does not exist yet, for cosmetics.
The schema work is one column, defaulting to zero; take the next free migration number. The route joins the admin route list behind the owner gate and caps its form body the way the API path caps bodies.
It touches the security-reviewed series Upsert, so say which invariant you preserved in the PR and run the full backend test suite.
Acceptance criteria
POST /admin/series/{key}/latestis owner-gated by joining the admin route listBlocked by
Landed on
spec-135as merge commit forticket/149-latest-chapter-correction(c9b1f2a).Implemented: migration
0015_latest_correction.sqladdingseries.latest_corrected_at(zero means never);(*Store).CorrectLatestChapterwriting the number, the derived"Chapter N"label and the stamp in one UPDATE while clearinglatest_raised_byand leavinglatest_checked_atalone;SetLatestChapterzeroing the stamp; one conditionalCASE ... IS DISTINCT FROMclause in the seriesUpsertconflict update so a Reader's cached resend of the corrected number keeps the stamp;AdminSeries.LatestCorrectedAton the admin projection; owner-gatedPOST /admin/series/{key}/latestjoined toadminRouteswith a 64 KB body cap and a 400 on non-finite, zero or negative input; and the detail page's numeric input, overwrite copy and corrected N ago marker.Invariants preserved: the series
Upsertclause is compile-time-constant SQL text with no request value concatenated into it; the new route is owner-gated purely by membership ofadminRoutes/AdminPatterns; validation happens at the handler boundary so a bad value never reaches the store; the admin projection still never selectslatest_raised_by, only the anonymous flag.Backend suite green on the merged base: 562 tests, 10 packages. Sighting counters asserted untouched by a Correction.
No blocking concerns. The detail page's second grid column is left free for #151.