feat(refresh): --assume-robots-on-403, for a host that hides only its robots.txt

Fandom answers our fetcher 403 on /robots.txt itself, from every address we
have, while api.php?action=parse answers the same User-Agent with a 200. The
gate fails closed on the unreadable file, so r1999, fgo, nikke and nikki can
never refresh — even though their rules are not unknown: a person read them in
a browser and wrote them into AGENTS.md, verbatim, which is how three of those
four were cleared in the first place.

This flag is that recorded permission, and nothing wider. What it deliberately
does not do is most of the design:

  - 403 only. A 401, a 5xx, a timeout or a soft 404 still mean we do not know
    what the site permits, and unknown is still not permission.
  - It never overrides a robots.txt we could read. A file that answers and
    disallows us is an answer, and it still wins. So this is no use for game8.co
    — whose robots.txt reads fine and welcomes us while its edge refuses the
    pages — and it must not become one.
  - Refused under CI. It stands in for a human having read a file this morning,
    and there is no human on a runner. A scheduled job asserting a permission
    nobody re-checked is exactly how "temporary" stops being temporary.
  - Loud. Every host it applied to is warned about by name, in the run log and
    in the summary, with a line saying to go re-read the file. An override that
    reports nothing is one nobody withdraws.

Nothing else about being a guest relaxes: one request per source, six hours
apart, spaced per host, conditional headers, no retries.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Lucas Winther
2026-08-19 04:45:54 +02:00
co-authored by Claude Opus 5
parent 239e970471
commit f67118913a
6 changed files with 259 additions and 6 deletions
+11
View File
@@ -270,6 +270,17 @@ section but must never claim the event title.
- Honor `robots.txt`; cache parsed robots per host for 24h. **Fail closed** — a `robots.txt` that
5xxs or times out means "do not fetch", because a permission we could not read is not a
permission we have. A 404 means no restrictions.
- **One narrow exception, opt-in per run: `--assume-robots-on-403`.** Fandom answers a datacentre
address `403` on `/robots.txt` itself while `api.php?action=parse` answers our own User-Agent with
a `200`, so the gate fails closed and four sources can never refresh — even though their rules are
known, because a person read them in a browser and wrote them into AGENTS.md § Scraping conduct.
The flag makes the run proceed on that recorded permission. Three things bound it: it applies to
`403` **only** (a 401, a 5xx or a soft 404 are still "we do not know"); it never overrides a
`robots.txt` we *could* read, so a file that disallows us still says no; and it is refused under
CI, because it stands in for a human and there is none on a runner. Every host it applied to is
named in the run's warnings and in `summary.assumedRobots` — an override that reports nothing is
one nobody withdraws. It changes no other obligation: still one request per source, still six
hours apart, still spaced per host.
- 20s timeout. **No retries**: a retry is a second request, and AGENTS.md § Scraping conduct says
one per source per cycle. A failed source waits for the next cycle instead.
- **Only `200` is a page** (plus `304` for "unchanged"). Not `response.ok` — that admits the whole