Files
mangaBookmark/docs/adr/0003-series-shared-and-poll-owned.md
T
sulthan 1f5d0695ad docs: correct the bot-score claims behind the browser poller
Every doc statement that explained a Cloudflare challenge as a "score"
was wrong. Researched against Cloudflare's own docs on 2026-08-12
(docs/research/cloudflare-bot-scoring-and-poll-cadence.md, 22 primary
pages plus RFC 9309): the 1-99 bot score is Enterprise Bot Management
only, free-plan zones get Bot Fight Mode signature matching and no score
at all, and no per-IP request rate is documented as an input to
challenge issuance. cf_clearance also expires in 30 minutes, so every
cadence at or above 1h re-solves the challenge regardless.

Docs only - no behaviour change. The 6h browser cooldown stays; its
justification is now cost (a serialized single-tab solve costs seconds,
a plain read costs one request), not a risk reduction nothing documents.

- AGENTS.md: the block is per-zone configuration plus request
  fingerprint, not IP reputation; comix.to turning its gate on
  2026-08-12 is the example. Residential egress avoids the
  cloud-hosting-IP signature rather than earning a better score. The UTC
  measurement stands but its mechanism is marked undocumented.
- backend/AGENTS.md: states why the browser cooldown is longer.
- ADR-0003, ADR-0006: dated corrections rather than rewrites. Both
  decisions stand on their other arguments (sweep depth, VPS memory).
- DEPLOY.md: a red smoke run means the Site's settings or this Chrome's
  fingerprint moved, not that "Cloudflare's scoring" did.
2026-08-12 09:29:27 +07:00

52 lines
2.8 KiB
Markdown

# Series is a shared entity, and only the Poll may update it
Status: accepted
Facts about a Series that are true regardless of who is reading — title, cover, canonical
URL, Latest Chapter — moved off the Bookmark onto a shared `series` row keyed
`(site, series_id)`. A Bookmark now holds only what differs between Readers: Progress,
Favourite, Lifecycle bucket. Fifty Readers tracking one Series produce fifty Bookmarks
and one Series, so the Series is polled once rather than fifty times.
## Why
The poller checks at most 84 series/hour (batch 14 per 10-minute tick). With ~50 Readers
holding ~30 Series each, polling per Bookmark means a 1,500-item sweep — roughly 18 hours
against a configured 1-hour cooldown, quietly breaking the New Chapter signal that is the
product's reason to exist. Deduplicating to distinct Series cuts the sweep several-fold,
and because the Series row now knows how many Readers hold it, the poll queue is ordered
`reader_count DESC, latest_checked_at ASC` — popular Series stay fresh and the long tail
absorbs the shortfall. That ordering is only expressible because the split happened.
Raising throughput instead was rejected: sweeping 400 Series hourly needs the stagger
cut from 20s to ~9s, doubling request rate against sites already fronted by Cloudflare
from the single VPS IP.
Corrected 2026-08-12: the original wording said those sites "bot-score" the VPS IP.
They do not — the 1-99 bot score is Enterprise Bot Management only, and no per-IP
request rate is documented as an input to challenge issuance
(`docs/research/cloudflare-bot-scoring-and-poll-cadence.md`). The decision stands on
its first argument, sweep depth versus the 1-hour cooldown; the rate-limit fear was
never evidenced.
## Only the Poll writes Series fields
A client may supply `title`, `cover` and `series_url` only when creating a Series nobody
has bookmarked yet. After that, client-supplied values are ignored; only the backend's
own fetch updates them.
This is a security boundary, not tidiness. Those values are scraped from third-party
pages, which `AGENTS.md` requires be treated as attacker-controlled. Before the split, a
hostile or compromised site could corrupt exactly one Reader's row. After it, the same
write lands on a row every Reader sees — one Reader's browser becomes a write path into
everyone else's UI, and a cover URL can point anywhere. The backend's own fetch is the
higher-trust source: its network, its parser, no third-party JavaScript in the path.
## Consequences
- `Store.Upsert` decomposes one incoming flat body across two tables and enforces the
ownership rule at that seam.
- The `updated_at` ordering rule stays on the Bookmark, where Progress lives. Unchanged.
- Per-Reader title overrides are deliberately not supported; they would reintroduce the
duplication this removes.