R-0059: evidence review of LH044's brief, draw and baseline checkpoint (8 November 2026)
In short. We checked the closing-web study's first read against everything it kept. The brief and the draw were committed before any panel service was asked anything, the draw reproduces exactly, and the classifier gives the same answer as our own reading for all 30 services we drew at random and for every wall. Four statements in the checkpoint were wrong and are corrected there: responses did echo our addresses, the drawn services were mostly read from addresses outside Anthropic's block, one drawn journal's "empty" answer is a captcha page, and CRAN's robots.txt disallows the probe only through a line cut off at the 64 KiB read limit, so robots.txt hides 31 services, not 32. Three things must be fixed in the instrument before read 1 on 15 November.
Reviewed: studies/LH044/brief.md (protocol 0.1, commit 0adb294; amendment 1, 1789e54; amendment 2, a430182; amendment 3, 07b3f9f), panel/named.csv and panel/drawn.csv, the frames in data/prebrief-2026-11-08T1915Z/, the baseline read data/read-2026-11-08T1920Z/ (readlog.csv, services.json, classes.csv, robots.csv, summary.json and the kept responses), data/statements-2026-11-08T1920Z/ with statements/seed.csv and statements/coded.csv, data/httparchive- and data/wayback-2026-11-08T1920Z/, scripts/ (classify.py, collect.py, lh044lib.py, draw_panel.py, prebrief.py, test_lh044.py) and checkpoints/2026-11-08.md as at 07b3f9f. RFC 9309 from my knowledge of its text (sections 2.2.1, 2.2.2, 2.3.1.2 to 2.3.1.4, 2.4 and 2.5), not re-read online. I did not read other notes in notes/ or the study's for-driver.md before forming this view.
Reviewer: evidence-review subagent in Claude Code on Opus 5.5, dispatched by the driving session. Same model family as the writer; agreement is a review contribution, not independent confirmation.
Date: 8 November 2026, about 20:15 to 20:45 UTC.
Code and output: studies/LH044/review/ (redraw.py, reclassify.py, robots_check.py, ai_tokens_check.py, sample.py, each with its -output.txt; sample-reading.csv; test_lh044-output.txt; site-check/). Replay: python3 -I studies/LH044/review/<script>.py from the repository root; none makes a network request.
Requests I made: one, to Lighthouse's own site (https://lighthouse.harbour.cat/studies/LH044/, 404 at 20:21:57 UTC, kept in review/site-check/). None to any panel service, statement host, archive or registry.
What I read
The brief in full, with its three amendments; the commit log of studies/LH044 with times; the prebrief readlog and the two frames as committed with the brief; every script; classes.csv, robots.csv, summary.json and the readlog of the baseline; the kept headers and bodies of my 30 sampled services, of all 14 services the classifier walls, and of every open_unconfirmed, unread_proxy, redirect, server-error and 418 case; the kept robots.txt of every service it disallowed; the retained text of every coded statement (kernel.org's post in full; Wikimedia's rate-limit page in full; the Diff post, Read the Docs, GitHub's changelog, LWN and COAR's page by the passages coded; COAR's slides through pdftotext); HTTP Archive's kept answer.
What I checked
- Order of commitment. Brief 0adb294 at 19:18:31 UTC, draw 1789e54 at 19:18:48, first panel request 19:18:56 (readlog row 1), checkpoint a430182 at 19:35:01. The prebrief readlog touches one panel host, archive.org (A01), by its Wayback availability API (200 at 19:05:07, 429 at 19:05:34), not by A01's probe URL or robots.txt; the brief discloses both. No other prebrief host is a panel host. The scratch-directory trial of the collector the brief mentions (web.archive.org reset at 19:15:18) is not retained, so which "neutral hosts" it touched cannot be checked. The slip (draw_panel.py run once before the brief) is disclosed as described: drawn.csv is not in 0adb294, the script reads only the frames, and the draw is deterministic (next item), so the slip could not have exposed or changed anything.
- The draw. review/redraw.py re-implements the rule from the brief's text, parses the OAI frame with an XML parser rather than the script's regex, reads the frames and named.csv from 0adb294 and compares: drawn.csv at 1789e54 and at HEAD are identical to the redraw, 60 rows, no host overlapping the named list. Frame hashes match the prebrief readlog. The OAI frame holds 7,103 URLs on 5,247 hosts (5,246 after the drops); the Debian frame holds 387
/debian/links, 351 distinct URLs, on 351 hosts. - Reclassification. (a) review/reclassify.py re-runs classifier 0.2 over all 161 services from the kept files: 0 differences from classes.csv. Classifier 0.1 (from 1789e54) differs only at F03, F04 (login, api_closed) and D08 (wall_at_robots), as amendment 2 and the checkpoint say. (b) My own reading of a random sample of 30 services (seed in review/sample.py, fixed before any sampled response was opened; readings in review/sample-reading.csv) agrees with classes.csv in all 30. (c) My reading of every wall agrees: three challenge pages (stackoverflow.com's 403 with
cf-mitigated: challenge; salsa.debian.org's and wiki.debian.org's 302 to/.internal/challenge.htmlfrom Varnish), six walls at robots.txt (five Cloudflare 403 challenges, data.humdata.org's 202 withx-amzn-waf-action: challenge), crossref's 429 at robots.txt, Maven's and Sonatype's 429 through Cloudflare, Semantic Scholar's 429 suggesting a key, and invent.kde.org's bare nginx 403. (d) Amendment 2's two rules hold on the evidence: F03 and F04 carry this container's own policy message, not GitHub's; readthedocs.org's 307 carries Cloudflare's injected/cdn-cgi/challenge-platformscript and no challenge. (e) A scan of every non-wall response for wall-like words found real content in all but P04 (Found, 2). No marker overrode a challenge signature at the baseline. (f)scripts/test_lh044.py: 13 checks, 0 failed (review/test_lh044-output.txt); it left the working tree unchanged. - Counts. From classes.csv: named walls 13 (3 challenge, 1 refused, 3 rate limited, 6 at robots.txt) of 67 read (61 probes answered, excluding the two container refusals, plus 6 walls at robots.txt); drawn 1 of 26 (25 plus 1). robots_disallowed 23 named and 9 drawn, 32 of 161. robots.csv: 91 parsed, 15 disallowing
/to*, 32 disallowing the probe, and 11 that allow*at/but disallow at least one of the 17 AI tokens; by token among those 11, GPTBot and ClaudeBot 10, CCBot, Bytespider and Amazonbot 9, Google-Extended and meta-externalagent 8, anthropic-ai 7, the rest 2 to 6. All as the checkpoint states, except CRAN (Found, 1). I also read the raw user-agent lines of every parsed file (review/ai_tokens_check.py): two files that name AI tokens are rightly counted as allowing them at/(openlibrary.org's groups carry only Crawl-delay; www.data.gouv.fr's disallow only some API paths). Counted strictly as the brief's measure 2 words it ("among those with a probe answered"), the named share is 7 of 61 probes, with 6 more walls met at robots.txt; the checkpoint's table shows both rows, so its 13 of 67 is transparent. - Operator statements. kernel.org: every coded figure and quotation is in the kept post (about 6M daily requests, 66 per cent stopped and 33 per cent solving Anubis, "only about 2%" with "a bunch of generous assumptions", 14 to 16 of 90 cores, "Expect to lose some functionality, at least when accessing our resources anonymously.", "It's impossible to tell with certainty which of these are bots and which are real humans", and "poorly designed CI systems" as what usually takes it down). The post names AI crawlers (in quotation marks, in its first line), LLM training data and "feeding a learning model", so the checkpoint is right that the gate's "scrapers, not AI" needs revising. The attribution to AI is the operator's own inference, and the coding keeps it as his. Wikimedia's rate-limit page: no occurrence of AI, artificial, LLM, language model, training, scraping or crawling; the 33 per cent, "high-volume commercial consumers" and the 10 and 200 requests a minute are in the kept text. Its 2025 Diff post does attribute the multimedia growth to scraping "to feed images to AI models" and LLM training, as coded, and is kept apart. Read the Docs ("mainly from AI products and services", 73 TB, over $5,000), GitHub ("an increase in scraping activity targeting our API", no AI named), LWN (a journalist's account; GNOME appears in its history of Anubis), COAR's page and slides ("Effective" for proof-of-work; the IRD robot blocked): all as coded. CBC unread (robots.txt timeout) and SourceHut unread (redirect stub) are recorded as unread, not as absent.
- Disclosures and amendment 3. Anthropic's crawlers and the address block are disclosed in the brief and checkpoint. Amendment 3 against RFC 9309: following up to five redirects, across hosts, and applying the final file's rules to the original host is what section 2.3.1.2 asks (crawlers SHOULD follow at least five; rules apply in the context of the initial authority). Treating a chain longer than five as no probe is stricter than the RFC's "MAY assume unavailable", and treating 429 at robots.txt as a wall is stricter than its 4xx rule; both are allowed. 5xx and network failure as complete disallow match section 2.3.1.4; 4xx as no rules matches 2.3.1.3. The offline tests exercise these paths. Where the collector departs from RFC 9309 is the parse limit (Found, 1).
- The compatible User-Agent. Every page probe carried the compatible form and every other request the plain one (0 mismatches in the readlog). See Found, 6.
- HTTP Archive. The six mobile pairs in the checkpoint match the kept answer (33 months, January 2024 to September 2026).
What I found
- CRAN's robots.txt is cut mid-line, and the fragment disallows the probe (must-fix, corrected in the checkpoint). CRAN's robots.txt was the only one truncated at the collector's 64 KiB cap. Its last kept line is
Disallow: /web/pac, the start of a longer DESCRIPTION rule, and that fragment is the only rule that matches the probe/web/packages/ggplot2/index.html(review/robots_check-output.txt). Nothing in the part read disallows the probe. So 31 robots.txt files, 22 of them named services, disallow the probe in the text read, and CRAN's rule is unknown. The deeper fault: RFC 9309 section 2.5 says a parsing limit MUST be at least 500 KiB, so the brief's "robots.txt obeyed, RFC 9309" does not hold for long files, and the error can run either way: here it hid a service, and a cut that drops a later Disallow would send a probe the operator refused. - P04 is a captcha page, at robots.txt and at the probe (must-fix, corrected). The checkpoint called it "a 202 with an empty body". Both kept responses are 206-byte pages that refresh at once to
/.well-known/sgcaptcha/, a captcha path (the vendor is my reading of the path, not verified). The robots.txt answer was parsed as an empty robots.txt, so the collector sent a probe where the brief means no probe to follow a challenge. No signature in the brief covers it, so the class staysopen_unconfirmed; read as a challenge, post hoc, 2 of 26 drawn services read were walled. - Responses did echo our addresses (must-fix, corrected). summary.json's
egress_ip_echoedis empty because collect.py looks only for anx-ipheader. In the read, Wikimedia'sx-client-ip(five responses) and Docker Hub'sx-ratelimit-ip(one) echoed six different addresses in 160.79.106.0/24, inside the block the brief cites as Anthropic's; the statement run's Wikimedia responses add a seventh. The proxied route does not leave from one address. P04's captcha URLs echoed 34.172.54.233 and 34.136.29.35 on the plain-HTTP route, one per request; I did not look up their registrant. - The two strata were read largely from different addresses (must-fix as a statement, corrected; a design limit). The plain-HTTP route carried 44 of the drawn stratum's 60 robots.txt requests and 15 of its 25 probes, against 2 of the named stratum's 164 requests. The brief's comparison that matters, named against drawn, is therefore also, at the baseline, Anthropic's block against other addresses. A wall set for Anthropic's addresses would show in the named stratum and not the drawn, which the brief's selection reading would not separate. The checkpoint's model disclosure said the study's requests leave from Anthropic's block; most do, not all.
- The study page the User-Agent names did not exist (must-fix before read 1). https://lighthouse.harbour.cat/studies/LH044/ answered 404 at 20:21:57 UTC; the site is built from main, which by this checkout's copy did not yet hold the study, so an operator following the link during the read most likely found nothing. Recorded in the checkpoint's correction.
- The compatible User-Agent is honest in form, with three qualifications (interpretation).
Mozilla/5.0 (compatible; Lighthouse-research/1.0; +URL)is the convention self-identifying crawlers use; it carries no browser engine or version token, names Lighthouse, carries the RFC 9309 product token, and gives a URL. It does not pretend to be a browser. But: (a) the URL led nowhere at the baseline (5); (b) the brief chose theMozillaprefix so that walls set for browsers are shown to it, and filters that treat a Mozilla-prefixed client from a cloud address as a fake browser (kernel.org's post describes banning cloud ASNs for exactly that) may wall it for the prefix, so a page-surface wall may partly measure this choice; that belongs in the record's limits; (c) it honours robots.txt for its own token only, and 8 probes went to hosts whose robots.txt disallows an Anthropic token at the probe path, two of them (wiki.gentoo.org, ftp.eq.uc.pt) Claude-User and one (ftp.eq.uc.pt) aClaude-Codegroup (review/ai_tokens_check-output.txt). RFC 9309 asks no more, and Lighthouse is a distinct client; but the requests are made by an Anthropic model's session from Anthropic's addresses, and an operator who refuses Claude-User has said something about that. Not must-fix under the brief; a question for the keeper (Suggestions, 1). - The rate-limit sentence is careful but loose (should-fix). "One request each from this study cannot exceed a rate limit by itself: these limits were spent, if by anyone, by other traffic from the same address." The proxied route rotates among addresses (3), the 429s echoed none, and a 429 can be a policy rather than a count. The sentence also leaves unsaid whose traffic shares the address: other sessions on Anthropic's infrastructure, which the study cannot see. Proposed wording in Suggestions, 4.
- Smaller points. (a) "Open, by marker: 51 named" is 50 by marker and 1 with no marker set (M10). (b) Amendment 1 says the Debian frame holds "387
/debian/URLs on 351 hosts, not '351 distinct URLs'"; the original was right: 387 links, 351 distinct URLs, 351 hosts. Nothing in the draw changes. (c) coded.csv says Wikimedia's edit date was "not re-read"; the kept page says "last edited on 15 October 2026, at 22:40". (d)robots_outcomecounts aswall_at_robotsany robots.txt the classifier callsrate_limited(also 503 with Retry-After, and 403 or 503 with rate wording), wider than the brief's "a challenge or a 429"; stricter, and only crossref's 429 was affected at the baseline. (e) 42 of the 101 named markers are substrings of their own probe URL (for examplegtk,pip,open), so a 2xx challenge page that echoes the requested URL would carry the marker and be calledopen, overriding a challenge signature. It did not happen at the baseline. (f) The brief's commit 0adb294 holds two email addresses in the prebrief frames (Debian's page, ARIN's record), redacted in the tree since a430182; history is not rewritten. COAR's PDF is kept as received with one address in it, by the redaction script's design. Readlog hashes no longer match the redacted files, as disclosed.
What I changed
In studies/LH044/checkpoints/2026-11-08.md only, each marked "(corrected)" and listed in a dated Correction notice under the Read paragraph (earlier edition at 07b3f9f):
- In short: robots.txt hides 31 of 161, not 32; the drawn count gains its post hoc alternative (2, counting P04's captcha); a sentence that the strata were read from different places.
- Our own requests: "No response echoed the sending address" replaced with the addresses echoed and the route split by stratum.
- Measure 1 table: the robots-disallowed cell notes that one of the 23 (CRAN) rests on a cut-off line.
open_unconfirmed: P04's body described as kept (a refresh to a captcha path, as its robots.txt answer was).- Measure 4: 31 files and 22 named services disallow the probe in the text read; CRAN removed from the list, with why.
- Model disclosure: "most of" this study's requests leave from Anthropic's block; the plain-HTTP ones did not.
- The 404 of the study page the User-Agent names, in the Correction notice.
classes.csv, robots.csv, summary.json, the brief and the scripts are unchanged. No other figure in the checkpoint changed.
Suggestions
Protocol changes for the driving session to make by dated amendment before read 1 (15 to 17 November), each labelled post hoc since the baseline was seen:
- robots.txt parse limit (must-fix 1 in the Verdict). Read robots.txt up to at least 512 KiB, as RFC 9309 section 2.5 requires; if a file is still cut, drop the incomplete last line and treat the service as
robots_unreachablefor that read rather than apply a partial file. Re-read CRAN at read 1 under the new rule; its baseline stays as read. - Echoed addresses (must-fix 2 in the Verdict).
egress_ip_echoedshould readx-client-ip,x-ratelimit-ip,x-forwarded-for-style echoes andipr:-style addresses in kept bodies, per route, so the brief's "a different address block echoed" condition can actually fire. Record the route in classes.csv, and report the named and drawn tallies within route as well as by stratum (Found, 4). A single RDAP lookup of the plain-HTTP addresses would say whose they are. - A captcha signature for P04's kind. Add a signature for a page that refreshes or redirects at once to a captcha path (such as
/.well-known/sgcaptcha/), so a robots.txt answered that way stops the probe, and say the baseline's P04 would have read aswall_at_robots. - Wording for the rate limits (checkpoint and record): "A 429 to a single request says nothing about that request's own rate. Whatever limit it reflects counts traffic we can't see: the address it left from, which is shared with other sessions on Anthropic's infrastructure and rotates within its block, or a policy that answers 429 without counting."
- For the keeper (Found, 6c). Whether Lighthouse, run by Anthropic models from Anthropic's addresses, should also honour robots.txt groups for Anthropic's agent tokens (Claude-User, and a
Claude-Codegroup where a file names one) as well as its own. Doing so from read 1 would stop probes to wiki.gentoo.org and ftp.eq.uc.pt; honouring ClaudeBot and anthropic-ai too would stop six more. Either way, the record should list the eight. - Markers. Let a challenge signature win unless the marker is found outside any echo of the request URL, or replace markers that occur in their own URL with content-only strings (Found, 8e).
- Coding additions. kernel.org's post also says the crawlers moved to "millions of random residential or mobile IPs" and that it banned whole cloud ASNs whose clients posed as browsers; Read the Docs says it "temporarily blocked all traffic from bots Cloudflare identifies as AI Crawlers". Both bear on what this instrument can see from a cloud address and should be coded. Fill in Wikimedia's edit date from the kept page.
- The record's limits should carry Found, 6b (the
Mozillaprefix may draw walls set against fake browsers) and the denominators of Checked, 4 (7 of 61 probes answered, plus 6 walls at robots.txt). - Amendment 1's Debian sentence could gain a dated note that the original "351 distinct URLs" was right (Found, 8b).
- Under amendment 3, P31's robots.txt redirect ends at an article page, which will parse as a robots.txt with no rules and so allow the probe. That follows RFC 9309, but it is worth listing when read 1 is reported, with any similar chain.
Verdict
For the checkpoint: after the corrections above, no must-fix item remains in its text.
Three must-fix items remain for the instrument, outside my paths, to be settled by the driving session before read 1 on 15 November:
- The robots.txt read cap of 64 KiB is below RFC 9309's 500 KiB parsing minimum, and a cut file's partial last line is applied as a rule (Suggestions, 1).
egress_ip_echoeddoes not record the addresses responses echo, which the brief says each read records and on which its inconclusive rule depends (Suggestions, 2).- The study page named in every User-Agent must resolve before the next request is sent (Found, 5); fast-forwarding main should settle it, and the read should check it first.
From notes/R-0059-lh044-evidence.md in the repository, last changed 8 November 2026.