Files
mangaBookmark/docs/adr/0003-series-shared-and-poll-owned.md
T
sulthan 17ee0bd3f8 docs: correct the bot-score claims behind the browser poller (#99)
Docs only. No code changes - `git diff origin/main --stat` touches five Markdown files and adds one research note.

## What was wrong

Several docs explained Cloudflare challenges as a "bot score" that our request rate could worsen. That mechanism does not exist on these sites.

Researched live on 2026-08-12 against Cloudflare's own documentation and blog plus RFC 9309 - 22 primary pages, every claim carrying a source URL and read date, seven areas explicitly marked `Not publicly documented`. The note is `docs/research/cloudflare-bot-scoring-and-poll-cadence.md`.

- The 1-99 bot score is **Enterprise Bot Management only**. A free-plan zone has no score at all; it gets Bot Fight Mode, which matches *signatures* (headless browsers, cloud-hosting IPs).
- **No per-IP request rate is documented as an input to challenge issuance.** Volume is policed by Rate Limiting Rules, a separate opt-in product: one rule, IP-only counting, 10-second windows on Free. Published DDoS thresholds are ~1,000 errors/sec.
- **`cf_clearance` defaults to 30 minutes**, so every cadence at or above 1 hour re-solves the challenge anyway. Cadence changes how many ~4s solves happen per day and nothing else.
- The documented risk is **fingerprint quality**, which this repo already solved (real Chrome, stock UA, non-UTC clock).

## What changed

| File | Correction |
|---|---|
| `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 worked example. Residential egress avoids the cloud-hosting-IP *signature* rather than earning a better score. The UTC measurement stands; its mechanism is now marked undocumented. |
| `backend/AGENTS.md` | Says why `_BROWSER_COOLDOWN` is longer: cost, not safety. |
| `docs/adr/0003` | Dated correction - the sites do not "bot-score" the VPS IP. Decision stands on its sweep-depth argument. |
| `docs/adr/0006` | Dated correction - no score to be better at. Decision stands on VPS memory. |
| `DEPLOY.md` | A red kagane smoke run means the Site's settings or this Chrome's fingerprint moved, not "Cloudflare's scoring". |

ADRs got dated `Corrected 2026-08-12:` paragraphs rather than silent rewrites - the record of what was decided stays intact, only the wrong mechanism is retracted.

## Deliberately not in this PR

- **The 6h browser cooldown is unchanged.** I had lowered it to 1h and reverted that; cadence is a behaviour change and belongs with the comix work in #98, not in a docs correction.
- **Two code comments still carry the myth**: `backend/main.go:83-84` ("a hammer against sites that are already bot-scoring us"). Left alone to keep this diff docs-only.

Related: #98.
Reviewed-on: #99
Co-authored-by: Sulthan Zaki <sultankiki05@gmail.com>
Co-committed-by: Sulthan Zaki <sultankiki05@gmail.com>
2026-08-12 09:32:07 +07:00

2.8 KiB

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.