#!/usr/bin/env python3 """L0-5 robots fixtures for tools/readiness-l0.py (Reed's finding, 2026-09-19). A local server serves a robots.txt whose deciding `Disallow: /pay` sits just past byte 4,096. Tester 0.1.1 bytes (drafts/readiness-spec/versions/readiness-l0-tester-0.1.1.py) parse the 4 KiB prefix and print PASS; 0.1.2 must print FAIL, and UNOBSERVED when the file is over its limit or arrives short. usage: test-readiness-l0-5.py [path-to-tester]""" import http.server, json, os, subprocess, sys, tempfile, threading path = sys.argv[1] if len(sys.argv) > 1 else os.path.join(os.path.dirname(__file__), "readiness-l0.py") PAD = "User-agent: *\n" + "# " + "x" * 4082 + "\n" # 4,099 bytes before the rule ROBOTS = {"late": (PAD + "Disallow: /pay\n").encode(), "early": b"User-agent: *\nDisallow: /pay\n", "open": b"User-agent: *\nDisallow: /private\n", "huge": b"User-agent: *\n" + b"#\n" * 300000 + b"Disallow: /pay\n"} mode = {"v": "late"} class H(http.server.BaseHTTPRequestHandler): def log_message(self, *a): pass def _send(self, code, body, ctype="text/plain", short=False): self.send_response(code); self.send_header("Content-Type", ctype) self.send_header("Content-Length", str(len(body) + (50 if short else 0))); self.end_headers(); self.wfile.write(body) def do_GET(self): if self.path == "/robots.txt": m = mode["v"]; self._send(200, ROBOTS["open" if m == "short" else m], short=(m == "short")) else: self._send(404, b"no") def do_POST(self): n = int(self.headers.get("Content-Length") or 0); self.rfile.read(n) self._send(402, b'{"x402Version":1,"accepts":[]}', "application/json") do_PUT = do_DELETE = do_PATCH = do_POST srv = http.server.ThreadingHTTPServer(("127.0.0.1", 0), H); port = srv.server_address[1] threading.Thread(target=srv.serve_forever, daemon=True).start() tj = tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) json.dump({"commerce": [{"url": f"http://127.0.0.1:{port}/pay", "method": "POST", "body": "{}", "source": "fixture"}], "documents": [{"url": f"http://127.0.0.1:{port}/robots.txt", "source": "fixture (plain http; the tester's own https add reads UNOBSERVED here)"}]}, tj); tj.close() def run(m): mode["v"] = m; out = tempfile.mktemp(suffix=".json") subprocess.run([sys.executable, path, tj.name, "--json", out], capture_output=True, timeout=300) rows = [r for r in json.load(open(out))["results"] if r.get("req") == "L0-5" or r.get("requirement") == "L0-5" or "L0-5" in json.dumps(r)[:80]] rob = [r for r in rows if r.get("check") == "robots"] return rob[0]["result"] if rob else "NO-ROW" cases = [("deciding Disallow just past byte 4,096", "late", "FAIL"), ("Disallow inside the first 4 KiB", "early", "FAIL"), ("robots.txt that does not disallow the door", "open", "PASS"), ("robots.txt over the 512 KiB limit", "huge", "UNOBSERVED"), ("body shorter than its Content-Length", "short", "UNOBSERVED")] bad = 0 for name, m, want in cases: got = run(m); ok = got == want; bad += not ok; print("ok " if ok else "FAIL", name, "->", got, "" if ok else f"(want {want})") print(f"{len(cases)-bad}/{len(cases)}"); os.unlink(tj.name); sys.exit(1 if bad else 0)