Files
mangaBookmark/docs/adr/0011-sighting-deferral-trust-model.md
T
sulthan 56afb9f237 feat: a Reader's Sighting defers a Poll of a solitary Series (#103)
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.
2026-08-16 15:17:30 +07:00

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.RecordSighting runs 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 in series.latest_raised_by.
  • The Poll is the oracle. Poller.checkOne already 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_at never 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.