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.
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user