review: clear stale attribution, gate numberless PUTs, cite the ADR

A Poll finding a higher number now clears series.latest_raised_by: the value
it stores is its own, so the next Poll must not credit that Reader with an
agreement they did not earn, nor charge them for a retraction of a number
they never reported.

A PUT carrying no chapter is no Sighting and defers nothing. The ceiling now
counts the Site's own rest rather than defaultRest. The userscripts PUT an
unchanged read too - the case the whole mechanism exists for was the one they
never sent.

ADR-0011 carries its citations and states honestly when a Reader's marks take
effect.
This commit is contained in:
2026-08-16 16:36:31 +07:00
parent 56afb9f237
commit 2b597921f4
13 changed files with 2959 additions and 1003 deletions
+53 -20
View File
@@ -5,9 +5,9 @@ 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:
A **Sighting** is the Latest Chapter the Reader's own browser read off the
Series page and PUT to the backend. 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
@@ -15,8 +15,9 @@ a Poll, under one restriction and one ceiling:
- **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.
- **Six-rest ceiling.** `sightingCeilingRests = 6`, counted in the Site's own
Rest — six hours everywhere today. However many Sightings arrive, a Series
unpolled that long 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
@@ -31,7 +32,9 @@ Attribution and judgement:
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.
**higher** number is the Site publishing and means nothing either way — but it
does clear the attribution (`Store.ClearSightingAttribution`), because the
value stored afterwards is the Poll's own and nobody must answer for it.
- 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.
@@ -40,12 +43,20 @@ Attribution and judgement:
- 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).
One client change was required, and only one. Both userscripts stopped short of
PUTting a read whose number had not moved (`applyLatestChapterIfChanged`), so
the case this whole mechanism exists for — visiting a Series with nothing new —
never reached the backend. `reportLatestChapter` now sends it, skipping only the
local write and the re-render. A numberless PUT (favourite toggle, progress from
a chapter page) is not a Sighting and defers nothing: nobody read the Series
page, so there would be nothing to judge later.
## 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
Chapter on every Series page visit; 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**,
@@ -72,14 +83,31 @@ therefore not a strategy, and credit cannot be banked in advance.
- **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.
- **Cross-Reader agreement, voting, weighting, consensus scoring** rejected on
evidence: every truth-discovery method estimates source reliability by
comparing sources on the same object, and the standard survey states outright
that an object provided by very few sources cannot have its confidence
evaluated — Li, Gao, Meng, Li, Su, Zhao, Fan, Han, *A Survey on Truth
Discovery*, SIGMOD Record 45(1), 2016 (arXiv:1505.02463), §"Challenges" on
sparse sources. With the two Readers this backend actually has, a
disagreement is a coin flip. The Poll is an authoritative oracle, 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.
of the fixed ceiling. Sampling an oracle against untrusted reports is the
gold-question technique from crowdsourcing quality control — Le, Edmonds,
Hester, Biewald, *Ensuring quality in crowdsourced search relevance
evaluation: the effects of training question distribution*, SIGIR 2010
Workshop on Crowdsourcing for Search Evaluation, which inserts known answers
sporadically and adjusts each worker's trust from them. The ceiling is the
same idea made deterministic: sampling prices an attack in expectation, a
guaranteed six-hour audit prices it as a certainty, which is what makes the
solitary-Series rule defensible in one sentence.
- **A trust *ratio*** (agreements over judgements, as that same gold-question
scheme uses) rejected for two thresholds: a ratio lets an attacker bank
credit first and spend it on lies later, and it needs the owner watching a
score to act. Three-and-twenty is a threshold both ways — a disagreement
resets the run to zero, so credit cannot be pre-bought, and recovery happens
without the owner in the loop.
- **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.
@@ -87,10 +115,15 @@ therefore not a strategy, and credit cannot be banked in advance.
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.
- **Timers or a background reputation job** rejected: deferral is recomputed
from live facts every round — Bookmark count and sighting timestamp — so a
Series that gains a second Bookmark stops deferring at once, with nothing to
invalidate. The Reader's marks are the one input read earlier, when the
Sighting is recorded rather than when the round runs: a Reader who crosses
the threshold, or has their marks cleared, changes behaviour from their next
Sighting on, and the standing they already bought lasts out its rest. That is
bounded by one rest and costs one subselect instead of joining `readers` into
the due query on every round.
## Constraints preserved