Release review of LH012 (record version 0.1, its brief as snapshotted with amendments 1 to 3, its retained evidence and scripts), the piece "Most downloads of old versions come from Pythons a newer version supports", its short form and figure, and the consistency surfaces changed for it
Findings
This is the release gate for LH012 and the piece it earned. The reviewer read AGENTS.md, the brief, its three amendments, the record, its sources, the piece, its short form and the two figure scripts, and the surface changes in 08c505b; checked the brief's hash against the one posted on issue #14 before any count was read; reran the replay; recomputed every headline figure from the retained files with its own code; re-read a sample of the counts and metadata from ClickPy and PyPI; read the analysis code at the five points the research agent asked a reviewer to check first; rendered both figures in light and dark; and traced every figure in the piece and short form to a table. Every figure in the record's Answer stands, and every live re-read agreed with the retained file. What had to be fixed was one figure in a secondary measure and three sentences in the piece: the wheel-exclusion test had counted litellm's older py3-none-any wheels as excluded because their platform could not be matched, where the platform is simply not known (58.1 per cent, now set apart as not testable); the piece said older by number was older by date "for every library but one" where the writer's own check shows two; its summary said three explanations were tested where the third was described, not counted; and its alt text said red was most of the bar for numpy where it is most of the older part. The verdict is that the piece can be released, with the record at version 0.2.
The reviewer's calculations are kept in studies/LH012/review/: review.py and review.txt (the older share and its descriptors, P and its classes with intervals, the week-end admission table against each day's, B with its guard for all 37 projects, the frozen-list trace under amendment 3.1 and under the brief's literal wording, the residual, the file classes with any wheels set apart, the first-day test, the "two ways" table and the read logs, all from data/ without calling analyse.py), and requery.py and requery.txt (the live re-reads, with each query's time, SQL and rows read).
What was checked against what
| Claim | Checked against | Result |
|---|---|---|
| Replay | sh studies/LH012/replay.sh, before the reviewer's change and again after --write regenerated data/analysis/files.csv |
passed both times: 21 snapshot hashes and sources.md's snapshotted part verify, the four frame files, the fifteen analysis tables, older.svg, post_hoc_reading.txt, the piece's figure and driver_checks.txt regenerate byte for byte; 434 ClickPy queries, 10,227,708,600 rows, 4,472 PyPI requests, 13 page reads and searches, all under the ceilings |
| The brief was fixed before any count | sha256 of brief.md (89649015...) against the hash in the issue comment posted at 06:35:28 UTC and against git show 0c197cd:studies/LH012/brief.md; data/read_log_phase2.csv |
identical in all three; the first phase 2 ClickPy read is at 06:40:36 UTC, five minutes after the comment; phase 1's nine queries returned no count |
| Amendments made before counts really were | the read log's times against each amendment's own account | amendment 1 names no count and precedes the first read; amendment 2's failed file-name reads for aiohttp are logged at 06:40:43 and 06:41:49 (5-row error bodies) with the split reads from 06:41:50, and boto3's by-Python read at 06:40:52 (10,005 rows) with its single-minor splits from 06:43:33, as it describes; amendment 3 (about 06:50) precedes analyse.py's file time (06:56:45), and its eight readings are the ones the code carries; that its output had not been seen rests on the agent's statement, since data/analysis/ was rewritten by later replays |
| Older share: median 37.3 [33.8 to 46.7], pyjwt 20.0 to pydantic-core 99.3, pooled 45.6; median 7 versions for 80 per cent, median age 298 days, 42.7 per cent over a year old; unlisted and pre-release volumes | review.py section A, from data/week/versions/ and versions.csv, own median and resampler (10,000 draws, nearest-rank ends) | agrees in every figure; no gap day in the week |
| P: 3 most (aiobotocore, boto3, numpy), 2 undetermined (pandas, urllib3), 32 cannot [86.5, 75.7 to 97.3], 8 little; median P 12.2 [7.1 to 20.9] and 18.0 [12.0 to 27.6]; pooled 30.9 to 35.3; edge share median 67.5, boto3 6.7, aiobotocore 0.6 | review.py section B, from data/week/python/ with admission recomputed from each file's Requires-Python | agrees in every class, interval and figure; no project has over a third of older downloads without a Python; the by-Python and per-version totals agree for all 37 |
| The week-end admission table (amendment 1.5) | excluded sets recomputed for each day of the week against the week-end set | eight projects uploaded versions at or above R during the week (boto3, botocore, filelock, litellm, platformdirs, pyjwt, starlette, uvicorn); none changes any Python's exclusion, so the week-end table is valid on every day |
| B: 4 most, 7 cannot, 26 undetermined [70.3, 54.1 to 83.8]; pydantic-core 95.8 to 100 (pydantic 1.05, current 53.2, guard 97.6), fsspec 58.5 to 93.4 (s3fs 0.53, guard 81.9), botocore 100 (boto3 2.94, guard 75.8), s3transfer 100 (boto3 3.88, guard 89.5); boto3 0 to 4.2; aiobotocore, urllib3, cryptography, protobuf lower estimates | review.py section C: requirements from requirements.csv and requirements_phase2.csv, held status per dependent version and day with the brief's marker rules and one-step rule, own guard | agrees for all 37 projects, every named range and guard; no guard failure; the one-step rule fires only for grpcio-status through protobuf (google-api-core and google-cloud-pubsub, 7.8 million fetches) |
| The co-fetch assumption and the guard (asked first) | analyse.py's B loop against brief decision 2 | the guard is as fixed: the share of D's older fetches at versions the lower estimate's dependent admits in its held versions, computed only for direct or marker holds; botocore's and s3transfer's lower estimates are 214 and 216 million held boto3 fetches against 140 and 96 million older fetches, capped at one, and pass the guard at 75.8 and 89.5 per cent; the record says in Answer, Findings and Limits that boto3 is fetched 2.94 and 3.88 times as often, and 7.51 times on Python 3.9, which is where the assumption strains. Read as the record reads it |
_hold_status and the one-step rule (asked first) |
the function read line by line; the order is plain requirement, Python marker by the dependent version's Python mix, platform marker (unknown), extra (unknown), then one step | consistent with the brief and amendment 3.5; a version whose extra requirement on D holds is counted as unknown before the one-step test is reached, which affects nothing here since the rule fires only for grpcio-status. aiobotocore is not a dependent of boto3 under the brief's rule (its newest versions 3.9.0 and 3.9.1 name boto3 nowhere; older ones only under the boto3 extra), so its narrow pins on boto3 enter neither estimate; boto3's "accounts for little" is among identified bounds as the brief defines them |
| Amendment 3.1, edges only for excluded Pythons (asked first) | review.py section D, the trace under both readings | fair: the brief explains E(p) as where "a fresh resolve on an excluded Python lands", and for an admitted Python a fresh resolve lands at or above R. Under the literal wording the trace shows for 14 projects and the median off-edge share is 60.1 per cent; annotated-types, packaging, pygments and typing-extensions drop out because most of their older downloads sit at the version just below R, which no fresh install on an admitted Python would pick. The record now says so (below) |
| Frozen-list descriptors: 18 projects; off-edge median 78.3; CI flag more often on older in 20 of 37, median +1.6; boto3 6.0 against 32.7, certifi 57.1 against 39.3; uv 59.6 against 43.2, pip 33.5 against 54.7, certifi uv 68.3 against 28.9; Linux 96.3 and 95.0; sdist about 0.1; glibc 99.1 and 98.5; musl 0.6 and 0.9 | review.py section D from data/week/pass/ and data/week/python/ | agrees; the sdist medians are 0.08 and 0.03 per cent, "about 0.1" |
| Residual ranges | review.py section E | agrees for the seven projects named |
| Files (secondary): litellm 58.1 (asked first) | review.py section F and data/week/files/litellm.csv against files.csv.gz | 59.8 per cent of litellm's older downloads are py3-none-any wheels and every litellm wheel at or above R is platform-tagged (cp310-abi3), so the test could not match them and counted them excluded; the platform is unknown, not excluded (fixed, below). No other project has any wheels among its older downloads beyond 0.1 per cent except charset-normalizer (10.3) and protobuf (1.9), which ship any wheels at or above R and were already "wheel available" |
| Tightly coupled pairs: 5 pairs where F passes, 8 where Z passes, virtualenv with filelock undetermined | data/analysis/tight_pairs.csv read against the record's text | the readings in the table match the text; F and Z were not recomputed |
| The first-day test: 355 releases all known, 257 settled; 28 of 32 [87.5, 75.0 to 96.9]; aiohttp 1 of 3, litellm 8 of 40, pydantic-core 2 of 5 (median 55.4), starlette 3 of 7; median of medians 3.2; LH011's 35 of 37 and 374 of 418 | review.py section G from data/firstday/ and releases_nov_mar.csv with the gap rule, and LH011's release_days.csv | agrees in every figure |
| The "two ways" table | review.py section H from LH008's events.csv and lags.csv and LH011's release_days.csv | agrees: 13 of 32, 18 of 34, 15 of 29, 4 of 7 by day 30; 6 and 5 by day 7; the shares as the record gives them |
| Live counts | requery.py: boto3's and botocore's week by Python minor; boto3 on 23 September from pypi.pypi by Python minor and, for Python 3.9, by installer, CI flag, system and libc; boto3's Python 3.9 week by version; numpy's week by version and Python; pydantic-core's week by version; day 2 and day 30 of requests 2.33.1, numpy 2.4.2 and pydantic-core 2.43.0 |
every total, every Python 3.9 count (372,822,043; 49,667,184; 54,946,193 of 88,297,788), the 7.51 and 2.94 ratios, the 99.4 per cent pip Linux glibc group, the 95.4 per cent below 1.42.97, pydantic-core's 190,379,025 older and 103,371,997 of 2.46.5, and the six day shares equal to the retained files; numpy's excluded and admitted counts differ by 175 downloads because the reviewer's lookup defaulted Python minors absent from the admission table (3.3 among them) to admitted, and the shares agree to one decimal |
| Live metadata | PyPI JSON for boto3 1.43.78, 1.42.97 and 1.43.0, numpy 2.5.2, aiobotocore 3.9.0, pydantic-core 2.46.5, pydantic 2.13.5 and boto3's project page | Requires-Python as reference.csv and python_admission.csv (1.42.97 admits 3.9, uploaded 27 April 2026; 1.43.0 requires 3.10, uploaded 29 April 2026 22:07 UTC, so the piece's "end of April" and "from before May 2026" hold); pydantic 2.13.5 requires pydantic-core==2.46.5; 2.46.5 uploaded 28 August 2026 09:57 UTC, after R; boto3's description says "On 2026-04-29, support for Python 3.9 ended for Boto3" |
| The two figures as rendered | headless Chromium at 720 by 620, light and with preferredColorScheme=0 for dark; the four PNGs read | title, subtitle, four-entry legend, 37 project names, the four bar segments and the axis legible in both themes; red, beige and blue distinguishable from the grey remainder on both backgrounds; the bars match python.csv and week.csv (red is most of the older part for aiobotocore, boto3 and numpy only). The piece's alt text said red was most of "the bar" for numpy, where it is 46 per cent of the bar and 63 to 67 per cent of the older part (fixed, below) |
| Every figure in the piece and short form | the analysis tables, post_hoc_reading.txt and driver_checks.txt | 88 and 55 million on 23 September; October 2025 and end of April 2026; 37 per cent, 20 to 99, 46 pooled; seven versions, about ten months (298 days), four in ten; 32 of 37, 12 to 18; boto3 76, aiobotocore 63, numpy 63 to 67; two thirds at the edge, boto3 fewer than one in fourteen (6.7 per cent), aiobotocore almost none (0.6); four by bounds, three and four times, seven ruled out, 26 undetermined with 16 upper estimates at one; 18 with the trace; uv 60 and 43; 355 releases, 32 libraries, 28 settled, 88 per cent [75 to 97], 257; the four named; nearly two thirds (64.7) and 95 in a hundred (95.4); the short form's 55 of 88 million, 37 per cent, 32 of 37, four, 28 of 32. All in the record at the same certainty |
| Absolutes in titles and summaries | the piece's and short form's title and summary, the record's title and Answer | the title's "most" holds for 32 of 37 projects and pooled (64.7 per cent of the older downloads of all 37 came from admitted Pythons); the summaries carry the week and the 37; no "never", "all" or "none" in a title or summary; "as a rule" survives only in the record and programme.md's record-side summary. The summary's "We tested three explanations" overstated (fixed, below) |
| Post hoc labels | the record's "Described after the counts were read" and the driver's bullet; the piece; the surfaces | boto3's Python 3.9 downloads are labelled as noticed after the counts in the piece's opening, its limits and its close, in README, the investigation page, programme.md's checkpoint and AGENTS.md; the "older by date" check is labelled in the piece; the first-day pattern's origin after LH011's counts is stated in the piece, the record and every notice, and the test itself as fixed before reading |
| "Older" never described against the data | driver_checks.txt against the piece's "What stays behind" | the piece defines older as "numbered below" the reference and gives pydantic-core's 2.46.5 as the exception; its "for every library but one" missed grpcio-status's 1.5 per cent (fixed, below); the summaries' "versions older than its newest of a month before" is shorthand the body defines, kept |
| House rules | grep for non-ASCII in the piece, short form, record, amendments and surfaces (only the bylines' middle dot and the record's Δ); the prose before the colophons; the short form's length | no em or en dash; British spellings; no document name, study number, commit or role before the colophon; the piece opens with boto3's Wednesday, not with Lighthouse; the short form is 261 words without title and colophon (171 after its byline), against "about 150 to 250" |
| Consistency | programme.md's LH012 answer, its "Later evidence" line after LH011 and the checkpoint of 29 September; README's "Current focus"; investigations/software-updates.md; AGENTS.md's "Where things stand"; observations.md; the four notices | all say the same: a median 37 per cent older; old Pythons most in three and ruled out in 32; identified bounds most in four and undetermined in 26; frozen lists fit what remains and cannot be counted; the first-day pattern held as a tested result for 28 of 32 from day 2 to day 30; boto3's Python 3.9 downloads noticed after the counts. The synthesis's and the pace piece's notices say "a follow-up, not a correction; the figures here stand"; nothing restates a superseded conclusion as settled |
Must fix, fixed
- Wheels tagged
anycounted as wheel-excluded (record, Findings; scripts/analyse.py; data/analysis/files.csv; amendments.md). litellm's older downloads were reported as 58.1 per cent wheel-excluded because its olderpy3-none-anywheels could not be matched to its newer platform-tagged wheels, though such a download names no platform to test. By the brief's rule that unknown stays unknown, analyse.py now sets them apart as "wheel tagged any, platform not known"; the replay regenerated files.csv (litellm 0.0 per cent excluded and 58.1 in the new class; aiohttp 33.5 to 33.4; nothing else changes). Recorded as amendment 4, labelled post hoc, and in Corrections; the record goes to version 0.2, and the piece, short form and programme.md cite it. The measure never entered P, so no classification changes. - "For every library but one, older by number was also older by date" (piece, "What stays behind"). The driver's own check gives two: grpcio-status had 1.5 per cent of its older downloads on 1.82.2, a patch uploaded after the reference. Now "For 35 of the 37 libraries ... grpcio-status had 1.5 per cent of its older downloads on a later patch to an earlier line. The exception that matters is pydantic-core".
- "We tested three explanations, fixed before reading" (piece, summary). The third, frozen lists, was described and not counted, as the brief said in advance and the body says. Now "We fixed three explanations before reading and tested the two the log can count."
- The alt text (piece). "Red makes up most of the bar only for aiobotocore, boto3 and numpy" is false for numpy, where red is 46 per cent of the bar; now "most of the older part of the bar", and "a large part of it" for the four named after.
Should fix, fixed
- "For the others, the record rules out old Pythons as the main reason and cannot rule the limits we read in or out" (piece, "What this does to what we said before") is now "For most of the others": pandas and urllib3 are undetermined for Pythons, and for seven the limits are ruled out.
- The record's frozen-list finding now says what the brief's literal wording of E(p) gives (14 projects, median off-edge share 60.1 per cent) beside amendment 3.1's 18, with the reviewer's check cited, so a reader can see how much the count rests on that reading.
- The record's header names 08c505b as the commit that carries version 0.1, cites amendment 4 and the review directory; the Method paragraph no longer says nothing was changed after counts without naming the reviewer's post hoc change.
Notes, kept
- The record's Method gives 8,595,054,592 rows read from
pypi.pypi; by the reviewer's classification (the 127 queries whose FROM names the raw table) it is 8,567,521,280, 0.3 per cent apart and nowhere near a ceiling. The total, 10,227,708,600, agrees. Not changed. - The record says "about 15" queries were answered with an error streamed into a 200 body; 19 logged reads returned 5 or 10,005 rows, which is that shape. "About 15" is kept.
- The short form is 261 words without its title and colophon, eleven over the guide's upper figure, as version 1.1 of the pace piece was six over. Kept.
- The short form calls boto3 "the most downloaded Python library" without naming August 2026, which the piece does. Kept.
- aiobotocore's older versions pin boto3 narrowly under an extra and were fetched 139 million times in the week, but aiobotocore is not a dependent of boto3 under the brief's rule, since its newest versions do not name boto3. Were those pins counted as extra-unknown holds, boto3's upper estimate would rise from 4 to about 30 per cent by arithmetic on the record's figures (139 million older aiobotocore fetches against 518 million older boto3 fetches), and its class from "accounts for little" to "cannot be the main reason". A limit of the identified-bounds rule, not an error; noted here for a later brief.
- The piece's title states the finding without a window; the summary beside it carries the week and the 37 libraries, and the finding holds per project and pooled. Kept.
What the review could not check
- ClickPy's ingestion from
pypi_rawtopypiand its documented dropping of rows without a Python or system; the record says it was not checked. The live re-reads cover 12 of 434 queries and 8 of 4,472 PyPI requests; ClickPy re-ingests days it finds wrong, so agreement now does not rule out a later difference; none was seen. - F and Z for the 37 tightly coupled pairs were read from the retained table against the record's text, not recomputed.
- That amendment 3 was written before analyse.py's first output was seen rests on the research agent's statement and the file times; the first output is not retained.
- The pages of the checkpoint's literature check (Python's version table, the vendor's post) were not opened; boto3's project JSON was, and says what the checkpoint quotes.
- The reviewer's resampler follows the brief's construction (10,000 draws, seed 20260929) with its own draws and nearest-rank ends, so the intervals' agreement checks the arithmetic, not an independent estimate.
- The site's own build was not run; the figures were rendered by Chromium from the SVGs.
- releases.md,
status: releasedand the colophons' review and release lines are the driving session's, as the dispatch said. - The review's own cost: priced by the driving session from the transcript.
Nothing remains that must be fixed before release.
From notes/R-0016.md in the repository, last changed 29 September 2026.