Skip to content

v0.2.0 — the audit of 8 October, worked

Latest

Choose a tag to compare

@jehrr jehrr released this 09 Oct 10:28
fc5e662

A third-party audit of 8 October, worked end to end. All four of its findings were real. Investigating the first found a second defect underneath it that the audit had not seen, and the two compounded — one was manufacturing the condition the other mistook for the end of a walk.

If you use --mode events, this release changes what you get and what the sidecar calls it. A walk used to stop at the first event page that produced no markets and still report status: complete with exit 0. Expect row counts to rise sharply, expect complete to start meaning what it says, and note that no_new_products is no longer a stop reason this project emits.

The walk stopped early and called itself complete

Reproduced end to end against the audited commit: one empty event page at position 4 of 20 left 17 event pages never fetched, with status: complete, exit 0, and pages_completed: 4 of 21 in the same sidecar.

The rule came from a sibling repo whose pages are consecutive days, where stopping early genuinely saves fetches. Here pages 2..N are the event URLs page 1 named, capped at --pages - 1 — a finite list, nothing to exhaust, nothing saved. The walk now visits every planned URL and records the empty ones as pages_without_rows.

Every page the site serves was classified as its own 404

Next.js inlines its 404 template into the payload of every page. The not-found check scanned the first 200,000 bytes for that template's wording. Counted on 2026-10-09 across the twenty event pages /predictions named, as the site serves them:

carry the wording 20 of 20
…inside the scanned window 17 of 20, always at ~134,000 bytes
…rendered into the body markup 0 of 5 checked, bogus pages included
misclassified while holding markets 12 of 20, holding 5 to 53 each

empty carries parse: False, so those rows were discarded — which produced exactly the zero-row page that ended the walk above.

A marker on every good page is not a marker, so the text markers are gone rather than narrowed, and the content check moved to the top: a page whose payload names markets is content, whatever templates it also ships. Two measurements shaped the fix — a slug that does not exist answers HTTP 200 (4 of 4), so the docstring promising "a real HTTP 404" was wrong; and digest, which looked like a clean replacement, is on 2 of 10 good pages and was not adopted.

The browser engines escaped this by byte position rather than by design: the template sits at 511,547 in a browser's DOM and 134,033 in the served bytes of the same URL.

Also in this release

  • Atomic writes. open(path, "w") truncates before a byte is written, so an interrupted run left a shorter file where a complete one was — measured here, a 204,292-byte out.json came back 28 bytes and invalid. All three writers go through one _atomic now. The mode is the part eight sibling repos get wrong, measured by calling each one: a new file gets what open() would have given it, and a target somebody tightened keeps its own.
  • The canary runs all three engines daily, compared on ids and schema rather than on prices, since the runs are minutes apart and a prediction market reprices continuously. Verified before shipping: the same 5 markets, 40 columns and 22 stable fields, with scraped_at the only field that differed.
  • The Docker base is pinned by digest, with the tag kept beside it.
  • events_on_page_1 was counting the events whose rows were not upgraded by a deeper read — 3 reported where page 1 had named 11, printed beside total_events: 23475.
  • A check that failed a fresh clone's first command, found by cloning the published repo and typing what the README says.

Declined, with reasons

  • Lock files. They would stop the daily canary testing new releases of the libraries, which is what catches a dependency change the morning it happens. The image is a build artefact and should be reproducible; the canary is a detector and should be exposed.
  • A run identifier across the JSON, CSV and sidecar. A failed run writes no sidecar at all, save refuses to overwrite good output with an empty result, and each file is now replaced by an atomic rename. An id would add a second thing to keep consistent.
  • A queue, scheduler, storage and webhook layer. A product decision rather than a defect; this is a scraper with a file output contract, and the README says so.

Every change in this release was verified by planting the fault it prevents and requiring the suite to go red on a named check — 22 controls in all. Five of them behaved unexpectedly on the first attempt, and every one of those exposed a hole in the checking rather than in the code — a control that crashed instead of naming a check, three that left the suite green because the guard they targeted could not fail as written, and a crash detector that called every control a crash because a passing run of this suite prints a traceback on purpose.