Repository navigation
VALIDATION: independent review of the 2026-09-07 schema-decisions, rename and manifest-driven validator work #127
Description
Everything below was verified during the work by the same agent that did it, in one session on 2026-09-06/07: the four schema and naming decisions (#120, #121, #122, #113), the rename of business_cycle_data.csv, the shared manifest-driven validator and its PR check (#119), the close of #14, and the tracker and dashboard updates around them. Model for this issue: #45 and its validation run (#46 was found in the margin, not on the list).
Bias to test for. The session's verification monoculture is the validator it wrote: scripts/validate_datasets.py over builders/_validate.py, run locally through uv run under pandas 2.3.3 and 3.0.5, plus a scratchpad mutation script that was never committed. Every "44 of 44 pass" claim, both builder dry-runs, and the manifest facts the decisions rest on (the dtype tally, the null-position table, the 212-column comparison) were produced by that code or by ad-hoc pandas one-liners from the same author in the same environment. The second monoculture is a curl loop for the published-site probe. Confirm reader-facing outcomes without either: fetch URLs from a different tool, read files with a different reader, re-derive every count from the bytes, and run the validator only where a check says so — and then look for what it cannot see.
What landed
| Repo | Merged | Issues | Other writes |
|---|---|---|---|
| QuantEcon/data-lectures | #124 (c4a286c, squash of two commits incl. Copilot fix a2feea8): rename + capture groups + nulls: blocks + dtype sweep + schema/AGENTS text, 24 files. #126 (818811b, squash incl. Copilot fix be76a6a): builders/_validate.py, scripts/validate_datasets.py, .github/workflows/validate-datasets.yml, both dynamic builders and _template.py rewired, countries.csv.yml and us_business_cycle_monthly.csv.yml contract fixes, 13 files. (#125, a one-line AGENTS.md change, merged between them and is not this session's.) |
Closed: #120 #121 #122 #113 (by #124), #119 (by #126), #14 (by hand, with a closing table). Decision comments on #120–#122, #113; a correction on #121; two revision comments on #118; stamp of #14 re-dated then closed | No data bytes changed; no tags pushed anywhere |
| QuantEcon/status-projects | #64: projects.yml data-lectures-automation → done, ended: 2026-09-07 |
Opened #67 (a "Recently closed" strip) | — |
| QuantEcon/workspace-lectures | — | Comment on #40: first verification pass, 0 of 60 Track X rows cleared, per-host publish table, the .ml keep_files finding |
— |
| QuantEcon/lecture-wasm | — | Comment on #70: the four final URLs and a call-by-call mapping | — |
1. Reader-facing outcomes
The one thing a reader can see is a URL that stopped resolving. business_cycle_data.csv had been served publicly since 2025-02 (QuantEcon/data, then this repo) and the session renamed it on the strength of the manifest's consumers: [] — no org-wide sweep was run, which the workspace rules require before deleting anything another repo might read.
-
https://raw.githubusercontent.com/QuantEcon/data-lectures/main/lectures/business_cycle_data.csvanswers 404 and…/lectures/gdp_growth_annual.csvanswers 200 with sha25641df23233ea238e4166c5c21cc383791015d4c9af6868b840901a8ce083c9da8(the manifest's recorded value; compute it from the fetched bytes, not from the file in a clone). - Nobody reads the old name.
gh search code 'business_cycle_data org:QuantEcon'(the token is distinctive, so search works here) returns only this repo's dated history notes (PLAN.md,migration.yml,scripts/audit_annotations.yml, the manifest header). Then the sweep search cannot do: a Trees-API pass over all ~277 org repos' default branches for the basename, including the translation repos,lecture-wasm's non-default branches (wasm, anyrepoint/*), course forks (tom-econ370-2025,2026-tom-course) and.notebooksmirrors. Expected: zero readers outside this repo. Record the repo count swept. Swept 2026-09-07 (Sweep every QuantEcon repository for a reader of business_cycle_data.csv #134). Enumeration reconciles with the org: 215 public + 71 private = 286, so private and archived repositories are in scope and were swept. 283 of the 286 were swept twice — a recursive Trees-API pass over every default branch (34,179 entries,truncated: falseon all 283, so no depth-1 fallback was needed) and a depth-1 clone withgit grepfor content, which is what covers the 26 archived repos and 3 forks that code search does not index. The 3 not swept (test-cli,numfocus,quantecon-book-dp) are empty —size: 0, zero branches, Trees returns HTTP 409Git Repository is empty. Zero path hits forbusiness_cycle_dataanywhere in the org. The 14 content hits are all in this repository and all the dated history this box exempts (PLAN.md27, 34, 301, 308, 309, 324, 343;AGENTS.md:58;builders/business_cycle.py:20;manifest-schema.yml49 and 228 — a survival the checklist did not name;migration.yml:839;scripts/audit_annotations.yml:48; thegdp_growth_annual.csv.ymlheader).gh search codeindependently returned 9 lines, all from this repository. An adversarial re-run then closed three gaps beyond the box: every branch of the 8 audit repos and this one (268 branches, 43,118 entries) plus a full-history all-refs content grep of those 8 (883 refs, includinglecture-wasm's 16 and itsgh-pages) — the only path hits are 12, every one inside this repository on stale pre-rename branches;gh-pagesorg-wide (39 repos, 18,079 files), which no pass had covered and which is where the published site actually lives; and the org's only two submodules (both inlecture-mapping), which neither the Trees API nor a non-recursive clone expands. All zero. Residual, named rather than closed: non-default branches of ~245 low-risk repositories, fork PR heads outside the audit repos, and any out-of-org or non-GitHub reader. - The old URL at the previous org and repo names —
github.com/QuantEcon/data/raw/main/lectures/business_cycle_data.csvand the pre-flatten path…/data/raw/main/business_cycle/business_cycle_data.csvif it existed — either 404 or redirect to a 404; neither serves bytes that would let a stale reader keep working silently. - The live audit dashboard
https://quantecon.github.io/data-lectures/still reports 40 static files, 40 migrated, 1 orphan, 23 live-API lectures (re-read fromaudit.json'sstats, not the page), andCATALOG.mdonmainhas agdp_growth_annual.csvrow and nobusiness_cycle_data.csvrow. - The status-projects home page
https://quantecon.github.io/status-projects/shows, under the infrastructure programme, a collapsed row reading "3 completed projects" and, expanded,data-lectures automationas "done September 2026"; the Pipeline page'sdonetab lists it newest-first abovedata-lectures scaffolding; its project page shows "ended 2026-09-07" and "4 of 4 work items closed" with no compliance findings (the scaffolding row carries three; the automation row must carry none). Reworded and confirmed 2026-09-07 (Decide how each of the eight open #127 boxes closes #133) against the live rendered DOM — these pages render from JSON client-side, so the served HTML carries none of this text. The collapsed row reads "▸ 3 completed projects":upstream-delta-registerwas set done/ended 2026-09-07 by QuantEcon/status-projects#53 at 00:41:34Z, 3h13m before PLAN: record the P3 data half, and retract a box that is now wrong #64 did the same here. Every other clause holds. Two wording notes: the visible work-items label is "all 4 closed" ("4 of 4 work items … closed" is its tooltip), and the done-tab tie order among the three 2026-09-07 projects rests onArray.prototype.sortstability over registry row order, not a documented rule.
2. Artifact integrity
- Every data file under
lectures/onmainhashes to its manifest'sintegrity.sha256: runpython .github/scripts/check_consumed_files.pyand an independentshasum -a 256over the 44 files compared againstgrep sha256 lectures/*.yml. Expected 47 files hashed (44 datasets plus 3 undersources/as LFS pointers), 0 mismatches. The two PRs claim no bytes changed:git diff --stat e318f06..818811b -- 'lectures/*' ':!lectures/*.yml'must show only the rename (R100). - The renamed pair is a pure rename:
git log --follow --oneline lectures/gdp_growth_annual.csvreaches back to the 2026-07-16 flatten (Flatten the consumer-keyed tree into the published layout #10) and the blob hash atc4a286cequals the blob hash ate318f06for the old path. Confirmed as written 2026-09-07 (Decide how each of the eight open #127 boxes closes #133) on a full clone: the box's own command returns 5 commits and reaches52dbb89, the merge commit of Flatten the consumer-keyed tree into the published layout #10 (mergedAt2026-07-16T22:45:04Z), then one further tob857c5c(2025-02-16);e318f06:lectures/business_cycle_data.csvandc4a286c:lectures/gdp_growth_annual.csvare the same blob29250cae27e1422a69d0e42aabc7502ba08a14e5; the rename diff is exactly oneR100line. The verification comment's contrary finding — single root931d626, 50 commits, no flatten commit — is a shallow-clone artefact:931d626is an ordinary commit with parentc044b8cand is precisely the 50th ancestor of818811b, the head VALIDATION: independent review of the 2026-09-07 schema-decisions, rename and manifest-driven validator work #127 was written against.git clone --depth=50there reproduces that finding bit for bit. The sole root is77ece40(2025-02-09);maincarried 104 commits at47017ea.
3. The highest-value claim to re-test: the validator catches what it says it catches
Every future refresh PR and every future manifest edit is gated by builders/_validate.py. The session proved it with a mutation script it never committed, on frames it built itself. Reproduce from scratch, in a fresh clone, without that script:
-
python scripts/validate_datasets.pyonmainunder pandas 2.3.3 prints44 manifest(s): 44 pass, 0 failwithconformance-only: {'xlsx': 5, 'npy': 2, 'dta': 4, 'json': 1, 'xls': 1}; the same under pandas 3.0.x (the lectures'anaconda=2026.07pin). Record both pandas versions used. - Break one thing per rule, each as a one-line edit to a copy of the committed CSV or its manifest, and confirm the named failure: (a) append a column
extratogdp_growth_annual.csv→ "unexpected column(s) not claimed"; (b) blank oneCountrycell in the same file → "Country: 1 nulls but not declared under known_nulls" (this is the Copilot-review fix in Manifest-driven validate(): the schema block is the spec, shared by the builders and a PR check #126,be76a6a); (c) blankDEU'sYR2000→ "hole at YR2000 inside the series"; (d) blankDEU'sYR2025→ passes (recent: 2); (e) blankUNRATEat2020-05-01inus_business_cycle_monthly.csv→ "hole at 2020-05-01 inside the series is not declared under nulls.inner" — this one matters most, because Manifest-driven validate(): the schema block is the spec, shared by the builders and a PR check #126 removed the exact count that used to catch it, so placement alone must; (f) blankUNRATEat2026-07-01(the newest row) → passes (recent: 1); (g) blankINDPROat1919-01-01→ passes the shared validator (leading nulls allowed) and failsbuilders/business_cycle_fred.py's ownvalidate()with "INDPRO: first observation is 1919-02-01"; (h) drop theYR1990column → passes the shared validator (a regex cannot see a grid) and failsbusiness_cycle.py'svalidate()with "gap in the year grid"; (i) setlingcod_msy_recovery.csv.yml'sknown_nullsto{F_over_Fmsy: 2}→ "1 nulls, manifest says exactly 2". Cases (g) and (h) are the documented builder-layer margin; if either passes both layers, that is a regression. - The CI job actually gates: on a throwaway branch, commit mutation (a) and open a draft PR;
validate-datasetsmust go red with a::error file=lectures/gdp_growth_annual.csv.yml::annotation in the checks tab, andconsumed-file-checkmust go red too — mutation (a) rewrites the data file, andcheck_consumed_files.pyhashes every file with a recorded sha256 whether or notconsumersis empty. To seevalidatered whileconsumed-file-checkstays green, use a manifest-only mutation such as (i). Close the PR without merging. Reworded and confirmed 2026-09-07 (Decide how each of the eight open #127 boxes closes #133) from the API: at5e23ade(mutation (i)) validate failed (34085718978) and consumed-files succeeded (34085718933); at93d0173(mutation (a)) both failed (34085829077, 34085829084, the latter withgdp_growth_annual.csv: bytes do not match the manifest). The annotation path is confirmed through the check-runs API —gh run view --logrenders::error file=X::msgas##[error]msgand drops the path, so the log alone cannot show it. Throwaway, do not merge: #127 §3.3 CI-gating check with a deliberate manifest mutation #130 is closed, not merged; its branch carries neither mutation. - The two builders still run end to end against the live sources, from a runner-like environment if possible (FRED stalls custom User-Agents from GitHub-hosted runners,
builders/_fred.pydocstring):python builders/business_cycle.py --out-dir /tmp/wb --summary-json /tmp/wb.jsonexits 0 and reports0 of 585 cells revisedforgdp_growth_annual.csvunless the World Bank has moved;python builders/business_cycle_fred.py --out-dir /tmp/fred --summary-json /tmp/fred.jsonexits 0 and its summary'soverlap.new_columnslists the month(s) after2026-07-01. Thenpython scripts/snapshots.py pr-body us_business_cycle_monthly.csv --summary /tmp/fred.jsonrenders a title of the formRefresh us_business_cycle_monthly.csv: 2026-07-01 → 2026-0M-01. - The Monday canary is the real test the session could not run: the
refresh-snapshotsrun of 2026-09-07 ~06:17 UTC (or the next one) has two green canary legs (one per builder) with the shared validator in the path, and the FRED leg's log shows the placement rule accepting the newest month's three blanks rather than failing on them. If it failed on "UMCSENT: N nulls" or "hole at 2026-0M-01", therecent: 1fix did not reach the runner path.
4. Records written
- All 44 sidecars,
manifest-schema.ymlandmigration.ymlparse with PyYAML; everyfilenameequals its sidecar's name; everyclass: dynamic-snapshotmanifest (4) has anulls:block whose keys are a subset of{along, leading, recent, ended, inner}; no manifest hasknown_nulls_totalatschemalevel (it survives only insidesheets[].known_nulls_totalonassignat.xlsx.yml(4) anddette.xlsx.yml(5), each withread_as.header: null). - The dtype sweep was complete and did not over-reach:
grep -h "dtype: " lectures/*.yml | sort | uniq -cshows nostring,objector baredatetime;float32remains only in the four.dtamanifests; the 2datetime64in the hansen files were already there. Re-derive the sweep count the session claimed (21string, 9object, 2datetime→ 32 edits) fromgit diff e318f06 c4a286c -- lectures/. - The three capture-group patterns are
'YR(\d{4})'ingdp_growth_annual,unemployment_rate_annual,private_credit_to_gdp, andre.compile(p).groups == 1for each;chapter_3.xlsx.yml'ssheets_pattern: 'Table3\.\d+'was deliberately left alone (sheets, not columns) — confirm it still has no capture group and the conformance pass does not complain. -
countries.csv.ymlnow declaresCountry code: 1for Namibia: confirm with a reader that is not pandas (e.g.awk -F';' '$4=="\"NA\""' lectures/countries.csvorcsvkit) that the bytes hold the stringNAfor Namibia and that pandas withkeep_default_na=Falseyields 0 nulls in that column. Then check the two consumers named in the manifest —lecture-python-programming/lectures/pandas_panel.mdandlecture-python.myst— for whether either usesCountry codesuch that Namibia dropping out changes a figure or a table row on the published site. -
forbes-billionaires.csv.ymlnow saysgovernmentisbool: confirm the bytes holdTrue/False/empty (2606 empties of 2935 rows) and that its consumerlecture-python-intro(heavy_tails) never reads that column. -
us_business_cycle_monthly.csv.ymlsaysrecent: 1andknown_nulls: {}and its comment gives first-vintage totalsUNRATE 349, UMCSENT 616, CPILFESL 457, M0892AUSM156SNBR 1132; re-derive all four from the committed bytes with a non-pandas count of empty cells per column. -
PLAN.mdPhase 5's PR-validation box is ticked with a 2026-09-07 note and the two boxes below it (consumer fan-out, reusable workflow) are still unticked;manifest-schema.yml's header no longer says "nothing validates against it".
5. Tracker consistency
- Builder architecture: four-stage template, traceback-clean failures, notebook policy (Phase 5) #14 is closed with all four sub-issues closed (
#119 #120 #121 #122), its body stamp readsverified 2026-09-07, and the closing comment's table cites PRs that exist and merged (Give builders their own directory, one per published dataset #60, P4 groundwork: manifest, four-stage builder and provenance/ for business_cycle #109, Phase 5: the weekly canary and refresh-as-PR for dynamic snapshots #110, Refresh business_cycle_data.csv: 2023 → 2025 #112, P4: the business_cycle snapshot set — three World Bank tables and a FRED composite #114, FRED from GitHub runners: no custom User-Agent; canary one leg per builder #117, Record the four schema and naming decisions: rename, pattern capture groups, null placement, dtype sweep #124, Manifest-driven validate(): the schema block is the spec, shared by the builders and a PR check #126). -
CATALOG.mdonmainis current:python scripts/build_catalog.py && git diff --exit-code CATALOG.md. - Main's three workflows are green on
818811b(validate-datasets,consumed-file-check,audit-dashboard), andaudit.jsonon the Pages site carriesgenerated= 2026-09-07. - status-projects
data/latest.json(after the 2026-09-07 06:30 UTC collection or later) hasdata-lectures-automationwithdeclared.stage: done,declared.ended: 2026-09-07,observed.tracker.state: closed,observed.progress: {completed: 4, total: 4},compliance.findings: []; anddatasets-migrationstillactive, withcompliance.findings: []andobserved.tree.coverage: spans-start(unchanged by this session). Reworded and confirmed 2026-09-07 (Decide how each of the eight open #127 boxes closes #133) against the live collectiongenerated_at2026-09-07T06:58:20Z (the Pages copy and the repo copy are byte-identical). The automation half is exact in all five fields. Ontree-postdates-startboth the box and the verification comment were wrong: replaying the 47 collections indata/history/, the flag was introduced by status-projects collector commit57db2e3at 03:40 UTC on 2026-09-07,datasets-migrationcarried it for exactly three collections (03:40:50Z, 03:54:57Z, 04:01:50Z) and it cleared at 04:19:44Z when the collector began seeing 9 private sub-issues on QuantEcon/workspace-lectures#14 instead of 4. The validating session read the 04:19Z collection, eighteen minutes after it cleared. The flag is still live onci-migration,lecture-monorepoandworkplan-skills.
6. Known blind spots
- Non-CSV formats are conformance-only (13 manifests: 5 xlsx, 4 dta, 2 npy, 1 json, 1 xls). Confirm the workflow log says so in words, and pick one —
fp.dta(float32columns) ordette.xlsx(positional ranges) — and check by hand that its manifest'sdtypes/shape/known_nulls_totalclaims are true of the bytes, since nothing now or before does. - The published-site probe shares the settle policy's blind spot. The ws#40 comment says 0 of 60 rows cleared and attributes it to no publish tag since 2026-09-01. Re-derive: for each of the 8 hosts, the newest
publish*-triggered run viagh run list --workflow publish.yml(tags are not date-ordered), and re-probe 5 rows per host with a different client (wget --spideror a browser). If any host published since 2026-09-01 and still serves, the cache theory needs revisiting. Ran 2026-09-07 (Re-probe the eight published hosts and record each host's newest publish run #135); the per-host table is on QuantEcon/workspace-lectures#40, which owns the rows. A census of all 60 rows, not a sample, on two clients that are notcurl(wget, then Python urllib): 0 cleared, and every served body is byte-identical (sha256) to the pre-deletion git blob, so nothing cleared and nothing changed. Controls discriminate on all 8 hosts — eight distinct/intro.htmlbodies, one identical 9,379 B stock Pages 404. The 60 is now derived rather than asserted: the eight Track X commits removed 64 files, 60 of them under_static/lecture_specific/. The attribution is measured, not inferred — cache-buster GETs on a fresh CDN key returned identical bytes, and for the 3gh-pageshosts the deleted file is still physically in the deployed tree. Seven hosts have not published since the deletions;.mlhas deployed three times — including on the Track X commit itself — and still serves all six rows, which is positive proof of the structuralkeep_files: trueexemption rather than an inference, so the cache theory is not impugned. Two things changed since the 00:30Z pass: the rebuild half of the gate is now met on all seven cache-bearing hosts (this morning'scache.ymlruns), so only the tag is outstanding anywhere; and the deploy mechanism is now verified for all eight —quantecon/actions/publish-gh-pagesisupload-pages-artifact+deploy-pages, a full replace, so every non-.mlhost will prune on its next publish. Method note:gh run listcreatedAtis the run start, not the deploy;gh api repos/<r>/deployments?environment=github-pagesis authoritative and works for all eight. -
.mlnever clears:lecture-python-programming.ml/.github/workflows/publish.ymldeploys withpeaceiris/actions-gh-pages@v4andkeep_files: true; confirm on thegh-pagesbranch that_static/lecture_specific/pandas/data/test_pwt.csvis still present in the tree, and check whether any other repo in the org deploys withkeep_files: true(the session did not look). - The org sweep the rename skipped — item 1's second box — is the largest blind spot of the session; treat a hit there as a regression to file, not a caveat. Closed 2026-09-07 (Sweep every QuantEcon repository for a reader of business_cycle_data.csv #134): 286 repositories enumerated, 283 swept, zero hits outside this repository. No regression to file. See item 1's second box for the method.
7. Decisions settled today (re-checkable facts)
- Under pandas 3.0.x a default
read_csvyields dtypestrfor text anddatetime64[us]withparse_dates; under 2.3.3,objectanddatetime64[ns]. Of 212 named CSV columns, 169 match exactly under pandas 3 and 142 under 2.3.3 before the sweep (ate318f06); after it, the family compare gives 44/44. Re-derive the 212 and the two match counts. - The FRED composite on 2026-09-07 ran to
2026-08-01with onlyUNRATEandUSRECpopulated in that month (the basis ofrecent: 1). Re-fetchhttps://fred.stlouisfed.org/graph/fredgraph.csv?id=UMCSENT,CPILFESL,INDPROand check whether August has since been published; either way the rule stands. Re-measured 2026-09-07 (Record the date the FRED August publication lag closed #136) and confirmed:UMCSENT,CPILFESLandINDPROall still end2026-07-01, whileUNRATEandUSRECcarry August — the exact state the rule was written against, and today's canary (run 34092578823) reports the same frame end independently. The lag had NOT closed when this issue was closed out, so there is no date to record; FRED's calendar puts the next releases at CPILFESL 2026-09-11, INDPRO 2026-09-18 and UMCSENT 2026-09-25, so the earliest full close is 2026-09-25. UMCSENT's 2026-08-28 release delivered no new observation at all (its ALFRED vintages for 08-27 and 08-31 are identical), so that date is the least reliable of the three. The date the lag closed is carried as an accepted residual, not a gate — the rule stands either way. - Namibia's ISO alpha-2 code is
NAand pandas' defaultna_valuesincludes the stringNA(pandas docs,read_csvna_values). - The weekly
cache.ymlcron is0 3 * * 1UTC onlecture-python-programming,lecture-python.myst,lecture-python.zh-cn,lecture-python-programming.zh-cn;lecture-dp,.frand.fahad push-triggered clean rebuilds on 2026-09-04.
8. Deliberately not done
- The four Batch rename for the §5.1 generic filenames, paired with their prose edits #87 renames (
fred_data,test_pwt,acs_data_summary,dataBHS) were not made — decision recorded on Naming policy for published files: what a composite is called, and when a file may be renamed #113 (they have consumers; rule 6). Confirm the files still exist under those names onmain. - Consumer fan-out and the reusable refresh workflow stay unticked in PLAN Phase 5 — recorded in Builder architecture: four-stage template, traceback-clean failures, notebook policy (Phase 5) #14's closing comment as belonging to lecture-wasm dynamic snapshots: business_cycle reads the four data-lectures snapshots (P4) #118 block D and to "once it stabilizes".
- The mutation script was not committed — recorded in Manifest-driven validate(): the schema block is the spec, shared by the builders and a PR check #126's "Not in this PR";
validate-datasetswas not made a required status check — same place. -
business_cycle_data.csv's history notes inPLAN.mdlines 27 and 34,migration.ymlandscripts/audit_annotations.ymlintentionally keep the old name as dated history.
Where the reasoning lives
AGENTS.md ("Naming a published file", "The schema block is executable", "Builders"), manifest-schema.yml (the comments beside filename, pattern, dtype, known_nulls, nulls), PLAN.md Phase 5 and Phase 8 P4, the decision comments on #120 #121 #122 #113 (with the #121 correction), the closing table on #14, the work plan #118, and the PR descriptions of #124 and #126 (each lists what it deliberately left out).
For the validator: work in a session that did not do this work. Do not use the tool named under "Bias to test for" except where a check explicitly says to run it. Re-derive counts rather than confirming them. Where a check can be run against a surface the original session did not exercise, do that too — the margin beyond the checklist is where regressions hide. Deliver: one comment on this issue with a per-item verdict (confirmed / confirmed with caveat / refuted / not completable, with evidence), a new issue for any regression found (do not bury findings in the comment), and leave the checkboxes to the issue owner unless told otherwise.
Activity
Throwaway for #127 section 3.3: mutation (a), an unclaimed column in …
Validation fixes: pandas-3 band check (#128), relative-path annotation (
quantecon-services commentedon Sep 7, 2026
Independent verification, 2026-09-07 — a session that did none of the work
Method. Fresh clone of
mainat818811b. Every count re-derived from the bytes withsha256sum,awk, Python'scsvmodule and PyYAML; the session's validator was run only where a box says to run it. Two throwaway environments built withuvfor the pandas checks: pandas 2.3.3 and pandas 3.0.5 (both numpy 2.4.6, PyYAML 6.0.3, Python 3.11.15). URLs probed with three clients (curl, wget, Python urllib). Other repositories read from depth-1 git clones (32 public repos plusworkspace-lecturesandstatus-projects), never throughgh search code.Limits of this environment, which account for every "not completable" below: the egress proxy denies
quantecon.github.io, every*.quantecon.orghost,api.github.com,fred.stlouisfed.org,api.worldbank.organd the Actions artifact store;ghis unavailable. The CI-gating draft PR (#130) was opened only after @mmcky authorised it, because the session's permission layer had refused to commit a deliberately broken data file unprompted. Verdicts: confirmed / confirmed with caveat / refuted / not completable. Two defects filed: #128, #129.1. Reader-facing outcomes
…/lectures/business_cycle_data.csv→ 404 from curl, wget and urllib.…/lectures/gdp_growth_annual.csv→ 200, 10,628 bytes; sha256 of the fetched bytes41df23233ea238e4166c5c21cc383791015d4c9af6868b840901a8ce083c9da8(sha256sum and hashlib agree; equals the manifest).git grep business_cycle_datahits only dated history:PLAN.md:27,34,migration.yml:839,scripts/audit_annotations.yml:48,AGENTS.md:58,builders/business_cycle.py:20, the manifest header. The Trees-API pass over ~277 repos could not run here. Substitute: depth-1 clones of 34 repos searched withgit grepandgit ls-files— the 8 audit repos,lecture-stats, five translation repos (lecture-python.zh-cn,lecture-intro.zh-cn,lecture-python-programming.{zh-cn,fr,fa,ml}), seven.notebooksmirrors,tom-econ370-2025,2026-tom-course,test-actions-lecture-intro,python-lecture-sandpit.myst,QuantEcon.lectures.code,quantecon-notebooks-python,meta,website,skills,qeps,workspace-lectures,status-projects— plus all 15 branch tips oflecture-wasm(there is nowasmbranch and norepoint/*branch; the non-default branches aredatasets/track-a-remainder,track-x-orphans,ci/data-url-guard,gh-pagesand eleven theme/docs branches) and.ml'sgh-pages. Zero hits in content or paths outside this repository.lecture-dp.notebooksandlecture-python-intro.fado not exist. The remaining ~240 org repos are unswept.raw.githubusercontent.com/QuantEcon/data/main/lectures/business_cycle_data.csv→ 404;…/data/main/business_cycle/business_cycle_data.csv→ 404. The raw host does follow the repo rename (…/QuantEcon/data/main/lectures/gdp_growth_annual.csv→ 200), so those 404s are real.github.com/QuantEcon/data/raw/main/…(both paths) → 403 with a 378-byte body and no redirect; through the same proxygithub.com/QuantEcon/data-lectures/raw/main/lectures/gdp_growth_annual.csv→ 302 → 200, so the 403 is GitHub's answer. Nothing serves bytes under the old names.github-pagesartifact of run 34080816745 (13.3 MB) sits on a blob host the proxy also denies. Re-derived instead:build_audit.py scan --strictover fresh depth-1 clones of the 8 lecture repos →static_files 40, migrated 40, orphans 1, api_lectures 23, generated 2026-09-07; the run that built the live site (818811b, 03:47 UTC) succeeded through its deploy job.CATALOG.mdon main has onegdp_growth_annual.csvrow (line 35) and nobusiness_cycle_data.csvrow.data-lectures-automationisdone,ended 2026-09-07, tracker closed, 4 of 4,findings: [];data-lectures-scaffoldingcarriesno-sub-issues,unstamped,untyped. But the infrastructure programme has three done projects, not two:upstream-delta-registerwas setdone/ended 2026-09-07in status-projects #53 (00:41 UTC, "one repoint, one priority, two done"), three hours before #64. The collapsed row therefore reads "3 completed projects". The done tab sorts bydeclared.endeddescending, so automation sits above scaffolding, tied on the date withupstream-delta-registerandlecture-proposals. Live pages not fetched (host denied).2. Artifact integrity
sha256sumover the 44 data files against the manifests'sha256:lines: 0 mismatches.check_consumed_files.py: 44 manifests, 47 files hash-checked (44 + 3 LFS pointers undersources/), 0 errors.git diff --name-status -M e318f06..818811b -- 'lectures/*' ':!lectures/*.yml'→ exactly one line,R100.29250cae27e1422a69d0e42aabc7502ba08a14e5ate318f06:lectures/business_cycle_data.csv, atc4a286cand at818811b:lectures/gdp_growth_annual.csv. Butgit log --followreaches931d626(2026-08-10, #57) and stops: this repository's history has a single root,931d626, 50 commits in total. No commit for the 2026-07-16 flatten (#10) exists in it, so the file's earlier history is recorded only in prose.3. The validator catches what it says it catches
44 manifest(s): 44 pass, 0 fail; bytes-validated formats: csv; conformance-only: {'xlsx': 5, 'npy': 2, 'dta': 4, 'json': 1, 'xls': 1}. CI job 101615598693 on818811b(pandas 2.3.3, Python 3.12) printed the identical line.unexpected column(s) not claimed by any entry: ['extra']. (b)Country: 1 nulls but not declared under known_nulls. (c)DEU: hole at YR2000 inside the series is not declared under nulls.inner. (d) passes. (e)UNRATE: hole at 2020-05-01 inside the series is not declared under nulls.inner. (f) passes. (g) shared validator passes;business_cycle_fred.validate()failsINDPRO: first observation is 1919-02-01 00:00:00, not 1919-01-01. (h) shared validator passes;business_cycle.validate()failsgdp_growth_annual.csv: gap in the year grid. (i)F_over_Fmsy: 1 nulls, manifest says exactly 2. Neither (g) nor (h) passes both layers. Margin: blankDEUYR2023→ fails (therecent: 2boundary is exact); blankUMCSENTat 2026-06 → fails (recent: 1boundary exact); blankDATEcell →DATE: 1 nulls but not declared under known_nulls; blankUSRECin the newest month → shared validator accepts (recentcovers every series), builder catchesUSREC has a missing month; a dropped economy →8 rows, floor is 9;YR2000→YR200X→ unclaimed columns; the 2026-09-07 live shape (2026-08 appended with UMCSENT, CPILFESL, INDPRO blank; previous = committed) → passes withnew_columns ['2026-08-01']; two lagging months → fails as designed. Caveat: with a relative manifest path — the form the script's own usage line shows — a failing run dies inValueErroratpath.relative_to(REPO)instead of printing the::errorline (#129).main. Head5e23ade, mutation (i) alone (manifest saysF_over_Fmsy: 2):validatefailure (run 34085718978) — the log carries##[error]F_over_Fmsy: 1 nulls, manifest says exactly 2,FAIL lingcod_msy_recovery.csv.yml: 1 problem(s),44 manifest(s): 43 pass, 1 fail;consumed-filessuccess (run 34085718933); audit-dashboardbuildsuccess. Head93d0173, mutation (a) added (anextracolumn ingdp_growth_annual.csv):validatefailure (run 34085829077) —##[error]unexpected column(s) not claimed by any entry: ['extra'],FAIL gdp_growth_annual.csv.yml: 1 problem(s),42 pass, 2 fail; andconsumed-filesfailure (run 34085829084) —gdp_growth_annual.csv: bytes do not match the manifest (sha256 384d6d98… != recorded 41df2323…). So the schema gate does block a PR, but the box's "consumed-file-check stays green" holds only for a manifest-only mutation:check_consumed_files.pyhashes every file with a recorded sha256 regardless ofconsumers, so mutation (a) turns both checks red. (The runner renders the CLI's::error file=lectures/….yml::command as the##[error]line above and attaches the file to the checks-tab annotation.)validate()on the committed bytes as both current and previous → FRED overlap0 cells revised,new_columns []; under pandas 2.3.3 all four tables pass. Under pandas 3.0.5 the World Bank builder rejects its own committed snapshots withvalue out of bandon all three tables — #128 (pre-existing since #114, gated only by therequirements.txtpin).snapshots.py pr-bodynot exercised (needs a live summary).17 6 * * 1. As of posting (05:25 UTC) the workflow has exactly four runs, allworkflow_dispatch, the newest 2026-09-01 06:36 UTC (successona0e1415); no scheduled run has fired yet. The 06:17 UTC run's two legs, and whether the FRED leg's log shows the placement rule accepting the newest month's blanks, will follow in a short comment once it reports.4. Records written
nulls:keys,known_nulls_totalmanifest-schema.ymlandmigration.yml; everyfilenameequals its sidecar; the 4 dynamic manifests each carry exactly{along, ended, inner, leading, recent}; no schema-levelknown_nulls_total; it appears 4× inassignat.xlsx.ymland 5× indette.xlsx.yml, every entry underread_as.header: null.string,objectordatetime;float32only infp,maketable1/2/4.dta.yml; the 2 hansendatetime64lines are already there ate318f06.git diff e318f06 c4a286c -- lectures/dtype lines: −21string, −9object, −2datetime(the 32 edits), plusstr→boolfor forbesgovernmentand 3float64pattern lines rewritten for the capture group; +30str, +2datetime64, +1bool, +3float64.'YR(\d{4})'in the three manifests,re.compile(p).groups == 1for each;chapter_3.xlsx.ymlsheets_pattern: 'Table3\.\d+'hasgroups == 0and reports "conformance only (xlsx)".awk -F';'on the bytes: row 149, column 4 is the quoted stringNA(Namibia), the only such cell;STR_NA_VALUEScontains'NA'in 2.3.3 and 3.0.5; default read → 1 null (Namibia);keep_default_na=False→ 0 nulls, code'NA'.pandas_panel.mdinlecture-python-programming,lecture-python.mystandlecture-python.zh-cn(and the .zh-cn/.fr/.fa programming translations) never mentionsCountry code, so no figure or table row changes.governmentgovernment= 2,606 empty, 328False, 1True.heavy_tails.mdinlecture-python-intro(alsolecture-wasm,lecture-intro.zh-cn,test-actions-lecture-intro) never reads the column.recent: 1,known_nulls: {}, the four totals in the comment. Empty cells by the csv module (UNRATE cross-checked with awk): UNRATE 349, UMCSENT 616, CPILFESL 457, M0892AUSM156SNBR 1132; 1,291 rows, 1919-01-01 .. 2026-07-01.[x]with "Landed 2026-09-07 (#119)"; fan-out and reusable-workflow boxes[ ];manifest-schema.ymlsays "since 2026-09-07 theschemablock IS validated" and the phrase "nothing validates against it" is gone.5. Tracker consistency
completed2026-09-07T03:49:42Z; sub-issues #119 #120 #121 #122 all closed (4 of 4); body heading "Where we stand (verified 2026-09-07)"; #60 #109 #110 #112 #114 #117 #124 #126 all exist withmerged_atset.build_catalog.pythengit diff --exit-code CATALOG.md→ exit 0.generated818811b: validate-datasets 34080816780, consumed-file-check 34080816788, audit-dashboard 34080816745 — allsuccess; the Pages artifact was produced 03:47:29Z. Liveaudit.jsonunreachable; the local rebuild'sgeneratedis 2026-09-07.latest.jsongenerated_at 2026-09-07T04:19:44Z: automationdeclared.stage done,declared.ended 2026-09-07,observed.tracker.state closed,progress {completed: 4, total: 4},compliance.findings [].datasets-migrationis stillactive— but withfindings: []andtree.coverage: spans-start; no snapshot from 09-01 to 09-07 carriestree-postdates-start(thetreefields first appear in the 09-07 collection).6. Known blind spots
conformance only (xlsx)(etc.) per manifest andbytes-validated formats: csv; conformance-only: {…}in the summary.fp.dtaviaread_stata: 5,432 × 36, columns in the declared order, all 33known_nullscounts exact, dtypes match (countrydeclaredstr, readobject, same family).dette.xlsxviaread_excelwith each sheet'sread_as: all five ranges matchshapeandknown_nulls_total(102×12 / 0, 105×1 / 32, 99×2 / 78, 15×1 / 0, 120×1 / 8), all float64 as declared.gh run listunavailable. Independent partial: newestpublish*tag bycreatordateper repo — programmingpublish-2026aug20, python.mystpublish-2026aug14, dppublish-2026jul07(dp also publishes byworkflow_dispatch, which leaves no tag), python.zh-cnpublish-2026aug24b, programming.zh-cn / .fr / .fapublish-2026aug19;.mlpublishes on push, lastgh-pagesdeploy 2026-09-04. No publish tag after 2026-09-01 on any host, consistent with the ws#40 pass..mlkeep_filespublish.yml:85 keep_files: true, with the preview-directory rationale at line 83;gh-pagesatcf37341(2026-09-04 deploy) still holds_static/lecture_specific/pandas/data/test_pwt.csv, beside 5pr-*directories. None of the other 31 public repos swept mentionskeep_filesin its workflows.lecture-wasmbranch tips; ~240 repos unswept.7. Decisions settled today
str,datetime64[us]withparse_dates; 2.3.3object,datetime64[ns]. Named CSV columns ate318f06: 212. Exactstr(dtype)matches (delimiter honoured,parse_dateson declared datetime columns,name: ""mapped toUnnamed: k): 150 under 2.3.3 and 177 under 3.0.5 — eight more than claimed in both versions, so the offset lies in the numeric columns (which match identically under both) and is a counting convention, most likely index-column handling, not a disagreement about the text columns the decision rests on. After the sweep, at main: 141 vs 206 exact; the family compare gives 44 / 44 under both.NANA;pandas._libs.parsers.STR_NA_VALUESincludes'NA'in both versions.cache.ymlcron0 3 * * 1inlecture-python-programming,lecture-python.myst,lecture-python.zh-cn,lecture-python-programming.zh-cn(also intro, advanced, jax, continuous_time_mcs, stats);lecture-dp0 2 * * 1..frand.fahave acache.ymltriggered on push tomainwith no cron, and both HEADs are 2026-09-04 — the mechanism for a 09-04 push-triggered rebuild is there; the runs themselves are unverifiable without the API.8. Deliberately not done
fred_data.csv,test_pwt.csv,acs_data_summary.csv,dataBHS.csvall present on main.git ls-files; #126's "Not in this PR" records both. Branch protection is not readable with this session's tools.PLAN.md:27,34,migration.yml:839,scripts/audit_annotations.yml:48, plusAGENTS.md:58,builders/business_cycle.py:20and the manifest header.Checkbox status
At @mmcky's request the 25 boxes whose claim held as written are ticked in the body above (some with the caveats noted in their rows). Ten stay open, in three kinds: the box's expectation is wrong even though the facts are fine — §1.5 (three completed projects, not two), §2.2 (
--followcannot reach #10; the blob-hash half holds), §3.3 (the gate works, butconsumed-file-checkdoes not stay green for mutation (a)), §5.4 (tree-postdates-startis absent; the automation half holds); the check needs a network this session does not have — §1.2 and §6.4 (the org-wide Trees-API sweep; 34 repos and 15lecture-wasmbranch tips are clean, ~240 repos unswept), §3.4 (live builders), §6.2 (site re-probe), §7.2 (FRED lag); and §3.5, pending the 06:17 UTC canary.Regressions and margin findings
builders/business_cycle.pyvalidate()rejects the committed, valid snapshots under pandas 3 (stack()keeps NaN). Pre-existing since P4: the business_cycle snapshot set — three World Bank tables and a FRED composite #114 and kept by Manifest-driven validate(): the schema block is the spec, shared by the builders and a PR check #126; the one place where the two-layer validation is not green on both pandas majors.scripts/validate_datasets.pywith a relative manifest path crashes in the failure report instead of printing::error. Introduced by Manifest-driven validate(): the schema block is the spec, shared by the builders and a PR check #126.tree-postdates-startis not present ondatasets-migrationin any 2026-09 snapshot.manifest-schema.yml:194says the validator honoursread_as;_validate.read_raw()reads onlydelimiter. No CSV manifest carriesread_as, so nothing misreads today.Generated by Claude Code
quantecon-services commentedon Sep 7, 2026
Canary result (§3.5), and the live-builder box (§3.4)
The scheduled
refresh-snapshotsrun fired at 06:49:58 UTC (cron17 6 * * 1; GitHub's scheduler was 33 minutes late): run 34092578823 onmainat47017ea— after #131 merged the fixes for #128 and #129 at 05:30 UTC, so the canary ran the corrected builders. Conclusionsuccess; thenotifyandrefreshjobs were skipped, as they should be when both legs pass and nothing is due.canary (gdp_growth_annual.csv)—builders/business_cycle.pysuccess. Log:gdp_growth_annual.csv: overlap YR1960..YR2025: 0 of 585 cells revised, max |change| 0.000; new columns none; new economies none;unemployment_rate_annual.csv0 of 243 andprivate_credit_to_gdp.csv0 of 65, likewise; all three written 1960..2025 tocanary-out.canary (us_business_cycle_monthly.csv)—builders/business_cycle_fred.pysuccess. Log:overlap window 1919-01-01..2026-07-01: 0 cells revised; max |change| by seriesall0.0;wrote us_business_cycle_monthly.csv: 1292 months x 6 series (1919-01-01 .. 2026-08-01). The live frame carries one new month, 2026-08-01, sooverlap.new_columnsis['2026-08-01'].The placement rule. The builder's log does not print per-series null counts, so the acceptance shows as the pass itself: a blank the rule rejects prints
<series>: null at 2026-08-01 after its last observation …and exits 2, which would have failed the leg and firednotify; neither happened. Given the release calendar (CPILFESL and INDPRO around the 15th, UMCSENT's final at month end), UMCSENT, CPILFESL and INDPRO were unpublished for August at 06:50 UTC on the 7th, so the three blanks were present and accepted underrecent: 1.builders/_validate.pyis untouched by #131, so this is the rule #126 shipped.pr-body.scripts/snapshots.pyis unchanged by #131. Fed a summary of exactly this live shape (previous = the committed bytes, new = committed + 2026-08 with only UNRATE and USREC populated, produced by the samevalidate()that writes the canary'ssummary.json), it rendersRefresh us_business_cycle_monthly.csv: 2026-07-01 → 2026-08-01, withNew columns | 2026-08-01in its table.Verdicts. §3.5 confirmed: two green legs, the shared validator in the path, the newest month's blanks accepted. §3.4 confirmed with caveat: both builders ran end to end against the live sources from a GitHub-hosted runner with the expected
0 of 585and the month after 2026-07-01, but the run was the canary's on47017earather than a hand-run, and itssummary.jsonis not retained on success, so thepr-bodystep was exercised on an equivalent summary. Both boxes ticked; eight stay open (§1.2 and §6.4, §1.5, §2.2, §3.3, §5.4, §6.2, §7.2), for the reasons in the comment above. #128 and #129 are closed by #131, which also puts a pandas 2.3.3 / 3.0.5 matrix and a--builderspass intovalidate-datasets, closing the gap that let #128 through.Generated by Claude Code
Record two repository facts the 2026-09-07 validation got wrong (#141)
mmcky commentedon Sep 7, 2026
Closing: every one of the 35 boxes now has a verdict, and all 35 are ticked. Twenty-seven were ticked on the two verification comments above; the remaining eight were settled today in #133, five of them by running the check rather than waiving it — this session had the network and
ghthe validating session lacked.How the eight open boxes closed
consumed-file-checkstays green only for a manifest-only mutationlatest.jsonfindings: []andtree.coverage: spans-startThe validation itself had three errors, and one of them nearly propagated
Re-measuring the four "wrong as written" boxes rather than accepting the verification comment's word changed three of the four verdicts.
§2.2 was not wrong. The comment refuted it on the grounds that this repository "has a single root,
931d626(#57, 2026-08-10), 50 commits in total", so no commit for the 2026-07-16 flatten exists. Every clause of that is false.931d626is an ordinary commit with parentc044b8c, with 54 commits before it; the sole root is77ece40("Initial commit", 2025-02-09);maincarried 104 commits at47017ea; and the box's own command reaches52dbb89, the merge commit of #10,mergedAt2026-07-16T22:45:04Z. The cause is a shallow clone:931d626is precisely the 50th ancestor of818811b, the head #127 was written against, andgit clone --depth=50there reproduces the finding bit for bit, because a shallow boundary hides its parents and so is indistinguishable from a root.This was on its way into the repository. #138 was scoped to write "the git history begins at #57" into
AGENTS.mdas a durable fact, and #139's checklist lessons repeated it. Both were corrected before merging; what landed instead is the true statement plus the check that would have caught it.§5.4's refutation was also wrong, in the opposite direction. The comment said no 2026-09 snapshot carried
tree-postdates-startondatasets-migration. Replaying the 47 collections in status-projectsdata/history/: the flag was introduced by collector commit57db2e3at 03:40 UTC on 2026-09-07,datasets-migrationcarried it for exactly three collections, and it cleared at 04:19:44Z. The validating session read the 04:19Z collection, eighteen minutes later. Both the box and its refutation were wrong about the same field.§1.5's refutation was right and is now confirmed against the live rendered DOM rather than the registry alone.
Where every finding landed
47017ea), which also put a pandas 2.3.3 / 3.0.5 matrix and a--builderspass intovalidate-datasets, closing the gap that let business_cycle.py validate() rejects the committed snapshots under pandas 3: stack() no longer drops NaN #128 sit unseen.mainis green on all three workflows, at that head and at02c220dafter the two documentation PRs landed.AGENTS.md:check_consumed_files.pyhashes every file with a recordedintegrity.sha256regardless ofconsumers, so a check that wants the schema gate isolated must mutate a manifest, not the data.business_cycle_dataanywhere in the org — 286 repositories enumerated (reconciling with the org's own 215 public + 71 private, so private and archived are in scope), 283 swept twice by a recursive Trees-API pass over every default branch and a depth-1 clonegit grep, the 3 unswept being empty repositories that return HTTP 409. All 14 content hits are this repository's own dated history. An adversarial re-run additionally closed the non-default branches of the eight audit repositories by full-history all-refs grep,gh-pagesorg-wide (39 repositories — where the published site actually lives, and which no earlier plan had covered), and the org's only two submodules. All zero. The blind spot the rename was made under —consumers: []and no sweep — is now closed on evidence.curland with controls discriminating on all eight hosts. It also moved that issue forward — the rebuild half of the settle gate is now met on all seven cache-bearing hosts, so only the publish tag is outstanding, and the deploy mechanism is verified for all eight rather than assumed.manifest-schema.ymlno longer promises aread_asreader thatread_raw()does not have) and Record two repository facts the 2026-09-07 validation got wrong #141 for Record two repository facts so the next validation checklist does not repeat them #138 (the two repository facts, with fact 1 corrected to the truth plus the shallow-clone check). Each also fixed a stale count in the file it touched — the repo map's "41 datasets, 41 manifests" is 44 of each, andsource.doi/source.versionsaid 16-of-33 and 10 against an actual 17-of-44 and 21.Accepted residuals
Named here so nothing rides on this issue staying open.
UMCSENT,CPILFESLandINDPROall still ended2026-07-01. FRED's next releases are CPILFESL 2026-09-11, INDPRO 2026-09-18, UMCSENT 2026-09-25, so the earliest full close is 2026-09-25. Therecent: 1rule is confirmed either way; the date is a record, not a gate..mlgh-pagesprune. Both belong to QuantEcon/workspace-lectures#40..mldeploys withkeep_files: true, so its six paths can never clear by publishing and need a one-off prune; the other seven clear on their next tag.test-cli,numfocus,quantecon-book-dp— all empty, zero branches, no files..mlhost has published post-deletion, so while the deploy mechanism is now read from source for all eight and says every non-.mlhost will prune, the decisive experiment — publish one site, re-probe — has not run.lecture-dpis the cleanest candidate to force, two months stale with a fresh cache.lecture-dphad a "push-triggered clean rebuild on 2026-09-04", but dp'scache.ymlisschedule: cron '0 2 * * 1'(not the0 3 * * 1the same box states) plusworkflow_dispatchplus apushrestricted topaths: [environment.yml]. Its 2026-09-07 rebuild came from the schedule.For the next validation checklist
git rev-list --max-parents=0returns the boundary andgit log --followstops there. Rungit rev-parse --is-shallow-repositorybefore concluding history is missing, and cross-check a suspected root against the API — a true root hasparents: []. A tell-tale: the commit count exactly equals a round clone depth.AGENTS.mdas a durable fact.Generated by Claude Code