Latest Chapter correction: one numeric input, overwritten by the next machine write #149

Closed
opened 2026-08-22 08:37:29 +07:00 by sulthan · 1 comment
Owner

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}/latest is owner-gated by joining the admin route list
  • A finite number greater than zero stores that number, a derived label, and the correction stamp
  • A non-numeric, zero or negative value answers 400 and never reaches the store
  • The poller's chapter setter zeroes the stamp when it writes a different number
  • An Upsert with the same number keeps the stamp; one with a different number zeroes it
  • A correction on a Series carrying a raising Reader clears the attribution and leaves both Sighting counters untouched
  • The detail page renders the input, the copy stating the next successful Poll overwrites the value, and a corrected <age> ago marker while the stamp is set
  • The marker is gone after a machine write of the number
  • The form body is capped the way the API path caps bodies

Blocked by

  • None — can start immediately.
## 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}/latest` is owner-gated by joining the admin route list - [ ] A finite number greater than zero stores that number, a derived label, and the correction stamp - [ ] A non-numeric, zero or negative value answers 400 and never reaches the store - [ ] The poller's chapter setter zeroes the stamp when it writes a different number - [ ] An Upsert with the **same** number keeps the stamp; one with a **different** number zeroes it - [ ] A correction on a Series carrying a raising Reader clears the attribution and leaves both Sighting counters untouched - [ ] The detail page renders the input, the copy stating the next successful Poll overwrites the value, and a *corrected \<age\> ago* marker while the stamp is set - [ ] The marker is gone after a machine write of the number - [ ] The form body is capped the way the API path caps bodies ## Blocked by - None — can start immediately.
sulthan added the ready-for-agent label 2026-08-22 08:37:29 +07:00
sulthan self-assigned this 2026-08-22 08:55:02 +07:00
Author
Owner

Landed on spec-135 as merge commit for ticket/149-latest-chapter-correction (c9b1f2a).

Implemented: migration 0015_latest_correction.sql adding series.latest_corrected_at (zero means never); (*Store).CorrectLatestChapter writing the number, the derived "Chapter N" label and the stamp in one UPDATE while clearing latest_raised_by and leaving latest_checked_at alone; SetLatestChapter zeroing the stamp; one conditional CASE ... IS DISTINCT FROM clause in the series Upsert conflict update so a Reader's cached resend of the corrected number keeps the stamp; AdminSeries.LatestCorrectedAt on the admin projection; owner-gated POST /admin/series/{key}/latest joined to adminRoutes with 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 Upsert clause is compile-time-constant SQL text with no request value concatenated into it; the new route is owner-gated purely by membership of adminRoutes/AdminPatterns; validation happens at the handler boundary so a bad value never reaches the store; the admin projection still never selects latest_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.

Landed on `spec-135` as merge commit for `ticket/149-latest-chapter-correction` (c9b1f2a). Implemented: migration `0015_latest_correction.sql` adding `series.latest_corrected_at` (zero means never); `(*Store).CorrectLatestChapter` writing the number, the derived `"Chapter N"` label and the stamp in one UPDATE while clearing `latest_raised_by` and leaving `latest_checked_at` alone; `SetLatestChapter` zeroing the stamp; one conditional `CASE ... IS DISTINCT FROM` clause in the series `Upsert` conflict update so a Reader's cached resend of the corrected number keeps the stamp; `AdminSeries.LatestCorrectedAt` on the admin projection; owner-gated `POST /admin/series/{key}/latest` joined to `adminRoutes` with 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 `Upsert` clause is compile-time-constant SQL text with no request value concatenated into it; the new route is owner-gated purely by membership of `adminRoutes`/`AdminPatterns`; validation happens at the handler boundary so a bad value never reaches the store; the admin projection still never selects `latest_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.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sulthan/mangaBookmark#149