Phase 5: the weekly canary and refresh-as-PR for dynamic snapshots - #110
Merged
Conversation
One manifest-driven workflow, .github/workflows/refresh-snapshots.yml: - canary: every `class: dynamic-snapshot` builder runs with --out-dir (fetch + validate, no commit). A failure opens or updates one `upstream-break` issue, classified by exit code — 2 is a ValidationError (the data broke the contract; a human), anything else is the fetch (a retry). Consumers read the last-good snapshot either way. - refresh: datasets that are due — cadence elapsed since `retrieved`, `integrity.upstream.status: diverged`, or never refreshed — get the builder run in place, the manifest stamped, CATALOG.md regenerated and a PR on refresh/<stem> whose body is the builder's overlap summary. A later run updates the same PR. Opened with QUANTECON_SERVICES_PAT when the org secret reaches this repo, else the workflow token (documented cost: the required check will not self-start on such a PR). scripts/snapshots.py is the manifest side: `list`, `due`, `stamp` (edits the manifest TEXT so the comments survive, then re-parses to prove the stamp reads back), `pr-body`. The builder contract gains --summary-json, a ValidationError with exit code 2, and the run summary from validate(); builders/_template.py is the copy-able skeleton #14 asked for. business_cycle_data.csv.yml is made stampable: its title no longer embeds the end year, and every stamped field is a single-line value with its reasoning in comments above it. Docs: PLAN Phase 5 boxes (refresh, canary ticked; fan-out policy noted), AGENTS.md builder contract and repo map, builders/README.md, and README.md's layout table, which still said builders lived in scripts/. Tested locally end to end: due → builder --summary-json → stamp → the manifest reads back verified, check_consumed_files clean, due flips to skip, pr-body renders; the stamped state was then reverted so this PR changes no published bytes. See #14. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Implements PLAN Phase 5 automation for class: dynamic-snapshot datasets by adding a manifest-driven snapshot “planner”, a weekly canary + refresh-as-PR GitHub Actions workflow, and updating builder/docs to support the refresh contract.
Changes:
- Add
scripts/snapshots.pyto list dynamic snapshots, compute which are due, stamp manifests from builder summaries, and generate refresh PR bodies. - Add
.github/workflows/refresh-snapshots.ymlto run weekly canaries and open/updaterefresh/<stem>PRs for due snapshots. - Update
builders/business_cycle.py(and addbuilders/_template.py) to emit--summary-jsonand use exit code 2 forValidationError, plus accompanying documentation updates.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
scripts/snapshots.py |
New CLI for manifest-driven due-logic, stamping, and PR-body generation for refresh automation. |
.github/workflows/refresh-snapshots.yml |
New scheduled/dispatch workflow running canary + refresh-as-PR for due dynamic snapshots. |
builders/business_cycle.py |
Extends builder to produce --summary-json and provide exit-code semantics for canary classification. |
builders/_template.py |
Adds a copyable builder template documenting the refresh contract. |
lectures/business_cycle_data.csv.yml |
Makes the manifest stamp-friendly and updates title/description wording for dynamic end-year. |
CATALOG.md |
Regenerated catalog reflecting the updated dataset title text. |
README.md |
Corrects repo layout description and documents dynamic snapshot behavior. |
builders/README.md |
Documents dynamic snapshot builder contract and points to the template. |
PLAN.md |
Marks Phase 5 workflow + canary as landed and updates the Phase 5 plan text accordingly. |
AGENTS.md |
Updates builder guidance and repo map to include snapshot plumbing and contract details. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…a guard The canary now records its failure as JSON via jq and the notifier reads each field with jq -r — data is never evaluated as shell, in a job that holds issues: write. And snapshots.py's dataset-mismatch guard is an explicit check that emits ::error and exits 1, since python -O would have dropped the assert that stood there. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Sep 1, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The two Phase 5 automation boxes that the P4 groundwork (#109) made possible, as one manifest-driven workflow:
.github/workflows/refresh-snapshots.yml. Written to the policy recorded in AGENTS.md ("Refresh, break, or schema change — who gets told").What runs, weekly (Monday 06:17 UTC) and on dispatch
planscripts/snapshots.py listanddue→ two matricescanaryclass: dynamic-snapshotbuilder with--out-dir: fetch + validate, no commitnotifyopens or updates oneupstream-breakissue, classified by exit code — 2 = ValidationError (the data broke the contract; a human) vs anything else = the fetch (a retry) — with the builder's log tail. Same shape as the audit's drift issue. No lecture is affected either way: consumers read the last-good snapshotrefreshretrieved, orintegrity.upstream.status: diverged, or never refreshed — and only if the canary passed: builder in place → manifest stamped →CATALOG.mdregenerated →check_consumed_files→ a PR onrefresh/<stem>whose body is the builder's overlap summary. A later run updates the same branch and PRscripts/snapshots.pyis the manifest side:list,due,stamp,pr-body.stampedits the manifest text rather than a parsed-and-re-dumped copy (PyYAML would discard every comment the sidecars carry their reasoning in), replaces each stamped key line plus its continuation with a single-line value, drops thedivergeddelta block, and then re-parses the file and refuses to write unless every stamped field reads back as intended.Builder contract additions, in
builders/business_cycle.pyand the copy-ablebuilders/_template.py(#14's "when Phase 5 is actioned", step 1):--summary-json, aValidationErrorwith exit code 2,validate()returning the run summary. The manifest is made stampable — its title no longer embeds the end year, and every stamped field is a single-line value with its reasoning in comments above it, not beside.Docs. PLAN Phase 5: scheduled refresh and canary ticked; consumer fan-out left open with the
on_refreshpolicy noted (no snapshot has a consumer yet). AGENTS.md: the refresh contract under "Builders" andsnapshots.pyin the repo map.builders/README.mdlikewise.README.md: the layout table still said builders lived inscripts/; it now listsbuilders/,sources/,provenance/andscripts/correctly, plus a "Dynamic snapshots" section.Token. The refresh PR is opened with
QUANTECON_SERVICES_PAT, which is what lets it trigger the requiredconsumed-filescheck (GitHub does not runpull_requestworkflows for PRs opened by the workflow token). Access for this repo was added on 2026-09-01. The workflow falls back to the workflow token if the secret is ever absent, and the PR body says so with the close-and-reopen workaround.Tested locally end to end:
due→ builder--summary-json→stamp→ the manifest read backverifiedwith the new hash anddate_range.end: 2025,check_consumed_filesclean,dueflipped to "skip",pr-bodyrendered; the stamped state was then reverted, so this PR changes no published bytes.check_consumed_files0 errors on the branch; strict audit problem set unchanged.What happens after merge.
business_cycle_data.csvisdiverged, so it is due: the first run will open the real first refresh PR — 236 of 320 overlap cells revised,YR2024–YR2025added — for review through its overlap summary. Suggest aworkflow_dispatchstraight after merging rather than waiting for Monday, so the whole loop runs with someone watching.See #14.
🤖 Generated with Claude Code