A userscript PUT already carries the Latest Chapter the Reader's own browser read; it now stands in for a Poll where being wrong can hurt nobody else. Deferral lives in the due query beside the rest cutoff: one Bookmark, sighted within one rest, under the six-rest ceiling. checkOne judges the Reader whose report raised the value off the comparison it already makes - three contradictions stop them deferring, twenty confirmations forgive. ADR-0011 records the trust model and the rejected alternatives.
5.7 KiB
ADR-0011: Sightings — a Reader report defers a Poll where being wrong hurts only them
Date: 2026-08-16 Status: accepted
Decision
A Sighting is what a userscript PUT already carries: the Latest Chapter the Reader's own browser read off the Series page. It is now allowed to stand in for a Poll, under one restriction and one ceiling:
- Solitary Series only. A Sighting defers the Poll of a Series exactly one Bookmark points at. A Series two Readers share is Polled on schedule no matter how recently it was sighted.
- One rest of standing. A Sighting postpones Polls for one Rest
(
defaultRest, an hour), not forever: a Series nobody visits again returns to the normal schedule by itself. - Six-hour ceiling.
sightingCeiling = 6 * defaultRest. However many Sightings arrive, a Series unpolled for six hours is Polled.
Both live in the due query's HAVING clause (store.DueForLatestCheck), beside
the Rest cutoff — the same place the schedule has always been decided, so no
timer and no second code path can disagree with it.
Attribution and judgement:
Store.RecordSightingruns before the Upsert that stores the reported value, because the raise test needs the row as it stands. A report that raises the stored Latest Chapter names its Reader inseries.latest_raised_by.- The Poll is the oracle.
Poller.checkOnealready compares what the Site publishes against what is stored, so judgement costs no extra request: a lower number contradicts the Sighting (sighting_disagreements + 1, both numbers and the Reader logged), the same number confirms it (sighting_agreements + 1), a higher number is the Site publishing and means nothing either way. - At
SightingDisagreementLimit(3) that Reader's Sightings stop deferring anything. They still write the Latest Chapter — the penalty removes a privilege, it does not silence anyone. SightingAgreementsToClear(20) consecutive confirmations forgive the disagreements. A disagreement resets the run to zero.- The owner clears marks from the administration page (issue #102, shipped first precisely so a false mark has a remedy the day the mechanism lands).
Why
Most of the backend's work was redundant. The userscript reads the Latest Chapter on every Series page visit and PUTs it; minutes later the Poll Lane fetches the same page for the same number. Deferring on a report converts a visit into a Poll saved, which is Lane capacity handed back to Series nobody is reading.
The restriction is the whole safety argument, and it is about blast radius, not about trust arithmetic:
- On a solitary Series, a wrong report can only mislead the Reader who made it. There is nobody else's ember to falsify.
- On a shared Series it could mislead someone else, so a report never postpones anything there.
The ceiling bounds the damage in time: a false value dies within six hours whatever happens, because the Poll that finds it is guaranteed. That is also what makes lying pointless — the six-hour audit is certain, not sampled, so a determined attacker buys at most three ceilings' worth of a wrong number on their own Series and then loses deferral entirely.
The cost of recovery is deliberate. An agreement is only recorded when a later Poll confirms a Sighting, so twenty agreements are twenty Polls of Series that Reader bookmarks — hours to days of real time, not twenty page views. Waiting is therefore not a strategy, and credit cannot be banked in advance.
Tradeoffs and rejections
- Trusting a Sighting on a shared Series rejected: it is the only case where one Reader's mistake reaches another Reader's list, and no amount of reputation makes that recoverable within the six-hour window.
- Cross-Reader agreement, voting, weighting, consensus scoring rejected: a single-source report cannot have its confidence evaluated by comparison, and with the typical two Readers a disagreement identifies nothing. The Poll is the only oracle in the system, so it is the only judge.
- A randomised audit (Poll a fraction of deferred Series) rejected in favour of the fixed ceiling: a sampled audit makes the attacker's expected cost a probability, while a ceiling makes it a certainty, and a certainty is what makes the solitary-Series rule defensible in one sentence.
- Blocking a marked Reader's writes rejected: the Latest Chapter they report is still the best available value, and their Sightings must keep being judged or they could never earn the privilege back.
- Per-Series flagging rejected in favour of per-Reader marks: a Series is not the thing that can be wrong. Naming the Reader and logging both numbers is also what distinguishes a broken Site adapter (every Reader of that Site contradicted at once) from one bad actor.
- Timers or a background reputation job rejected: deferral is decided from live facts every round — Bookmark count, sighting timestamp, the Reader's marks — so a Series that gains a second Bookmark stops deferring at once, with nothing to invalidate.
Constraints preserved
- A Sighting is not Progress: it may move the Latest Chapter and nothing else.
updated_atnever moves, so a report cannot reorder the list (ADR-0004). - The Latest Chapter is a Series-level fact (ADR-0003): a Sighting writes the shared row, so every Reader of a shared Series sees it immediately — deferral is the only thing the solitary rule withholds.
- Ember means new chapter only (
docs/design-system.md): a marked Reader renders no differently in their own list, and nothing about the trust model reaches the Series list's colour. - The Poll remains authoritative. Where a Sighting and a Poll disagree, the Poll's value is what gets stored.