# The follow-up run, and the seams the two seals leave open

Written 2026-11-02 before the first follow-up request, and committed before it.
The seals are `rule.agent-site-change` (f5592f7e...) and
`rule.agent-site-change-screen` (b93ce4f1...); the served copies of both hash to
the sealed values, checked at 06:47 Rome today. The window opened 2026-10-21, so
this run is 42 days after the before-side, not the 30 the seal asked for at least.

Where a seal is silent or the two disagree, this is what I decided, before data:

1. **Two user agents, two timeouts.** The change rule says 15 s and its own UA; the
   screen says 20 s and its UA, and also says it runs on the same follow-up bodies
   in the same pass. So one fetch per host, under the PRIMARY's settings (15 s,
   change UA, 5 redirects), supplies SERVING and the bodies for both columns. Only
   the catch-all request (C1) uses the screen's settings (20 s, screen UA).
2. **`best_url` exactly, then `https://<host>/`, then dark.** The September probe
   fell back to http on a transport failure. The sealed follow-up does not, so this
   run does not. A host that serves only over http reads dark here and I will say
   so if it happens.
3. **Body cap.** At most 400,000 bytes read per response (the screen's MAX_BYTES).
   SERVING needs a final 2xx and more than zero bytes read.
4. **"Stripped to text" was never sealed.** `agent-site-parked` ran phrases on the
   raw body and has no stripper. So: Column V reads the raw body, lower-cased,
   exactly as `agent-site-parked` did. Column C reads text from `strip_text` in
   `run.py`: script, style and comments removed; the `<title>` text and any meta
   description content kept; all other tags replaced by a space; entities
   unescaped; whitespace collapsed. The C1 comparison uses the same function on
   both bodies.
5. **"Answerable" for the 120 gate.** Any HTTP status, a DNS failure, a refused
   connection and a TLS failure each count as answerable: they are facts about the
   far side. A timeout does not, because it could be on either side. Both counts
   are reported.
6. **What counts as a control host moved.** Column V returning PARKED or
   PLACEHOLDER, or Column C returning DEAD, as `column_v`'s docstring says. A
   NO_CLAIM_TOKEN or FLAGGED is a hand read, not a reclassification. Any moved
   control host voids the addendum, whatever the hand reading says (the sealed text
   gives both branches as void).
7. **Titles.** `titles-2026-09-22.csv` has 132 rows and 101 non-empty titles, so the
   fallback (at least 100) does not fire, by one. The entry prints 101 of 132.
8. **Hand reads.** Every automatic reclassification, every NO_CLAIM_TOKEN and FLAGGED,
   and every disagreement between the columns gets a hand read. No pattern repair
   inside the run; a failed screen is a void and a new rule in a later session.

Bodies are kept in `~/data/agent-site-change/2026-11-02/`, not committed: they are
other people's pages. `paths-2026-11-02.csv` holds the 32-hex path per host, fresh
for this run, and `control-2026-11-02.txt` the ten control hosts from CONTROL_SEED,
both written before the first request.

## Changed after the fetch, before any screen output

`strip_text`'s meta-description pattern ran quadratically on a large body and hung the
analysis at 06:53, before it had printed a thing. It now finds each `<meta>` tag first
and looks inside it. The kept text is the same in intent; a description containing
a literal `>` would be cut there. No screen result had been seen when I changed it.
