Repository navigation
Conversation
Builds from before #1144 recorded their graph declaration but no run binding. Their execution therefore cannot be validated, and they have no Orrery artifact. Their declared graph is still exact, and is worth showing, provided nothing presents it as recorded execution. microcosm.graph.reconstruction projects the recorded declaration bytes alone. It stores a microcosm.reconstruction marker (protocol microcosm.graph.reconstruction.v1) in the schema's producer extensions. The marker names the source revision and where it came from, and binds every input file by digest, with whether a digest the build itself recorded was matched. The marker lives in graph.schema.json, so any later projection of the saved schema keeps it, and Orrery documents carry it under metadata.microcosm.extensions. The declaration must reproduce its recorded bytes through the current serializer, so the schema always describes the file that was recorded, not a reinterpretation of it. The module adds new files only. It leaves orrery.py and orrery_evidence.py untouched and imports only the declaration, schema and serializer modules. A new Orrery contract script checks that Orrery's public parser accepts the result and finds no execution claims. Part of #1291. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UK dense and national builds from before #1144 can only get an Orrery artifact by being rerun, and their inputs often no longer allow that. They did write graph.json beside their outputs, and a code pin in rowwise_candidate_manifest.json. python -m microcosm.build.uk_runtime.orrery_reconstruction reads a past build's output directory, which it only reads. It writes a new directory with the normal publication layout (graph.schema.json, graph.orrery.json, orrery.upload.json) plus a summary report. The declaration is the build's own graph.json, never one re-derived from current code. The revision is the recorded pin; --revision must match it, or supplies one, marked caller-asserted, when the build recorded none. Inputs are checked against the digests build.json and sha256sums.txt recorded. A build that already records Orrery evidence is refused and pointed at python -m microcosm.graph.orrery. The report lists what was used, what is missing, the evidence found but not bound (run manifests, the artifact index, the content store) and warnings. Output is deterministic and is moved into place only once complete. Nothing is uploaded and no emitter service starts, so publication stays a separate step. Fixes #1291. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The calibrator reports a calibration_epoch event for every epoch. The UK build forwards every tenth epoch and each solve's last to its staging bundle and its telemetry emitter, with a stride that grows so no run forwards more than about 2,400 rows. The US build passed its progress coordinator straight to the solver, so every epoch became a collector event and a full rewrite of a growing calibration_progress.json: 3,000 of each on a default L0-plus-refit run, against 300 under the UK rule. Define the rule once in microcosm.build.calibration_epoch_telemetry (stride 10, the 2,400-row cap, the stride function and the forwarding predicate) and use it in both countries: - The UK rowwise staging helpers now read it instead of their own constants, with no change in behavior. - The US _BuildProgress forwards only the rule's epochs to both sinks, at a stride computed from --epochs and the run's solves (one for --dense-default-dataset, otherwise a selection and its refit). Each solve's last epoch is always forwarded, so every solve's final loss and the latest-epoch summary in progress.json stay exact. Refs #1292 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The calibration-diagnostics build monitor starts a new solver pass only when a calibration row's epoch is lower than the previous row's or its epoch total changes. With --epochs of 10 or fewer, the shared rule forwarded only each solve's last epoch, so an L0 run, or an exact-k run whose k is below the pool, forwarded (epoch N, epochs N) for both the selection and the refit. The monitor merged the two solves into one pass, with no start time it could back-date and no epoch rate. A UK size run, whose dense solve, probes and refit all run --epochs, merged the same way. Forward epoch 1 of every solve too, in the shared rule, so both countries get it. Each solve's forwarded rows now start at epoch 1, and the forwarded epochs fall at every solve boundary where the full per-epoch stream does. That adds one row per solve: 302 rows instead of 300 on a default US run, and 2,412 instead of 2,400 on a default UK size run. The stride still keeps the epochs on the stride within CALIBRATION_MAX_EPOCH_ROWS. Each solve's first and last epoch add at most two rows, so a run forwards at most CALIBRATION_MAX_EPOCH_ROWS + 2 rows per solve, where the bound was one per solve before. Folding the new rows into the 2,400 would move the default UK size run from stride 10 to 11 to save about 1% of the row budget, so the stride is unchanged. Refs #1292 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dense driver merged with #901 (2026-09-29) recorded the candidate manifest's digest in build.json with the note "staging receipts are appended after publication". Its close step then appended staging_delivery and staged_dataset to the manifest and refreshed only sha256sums.txt. build.json was not reissued over the final bytes until _refresh_completion arrived with #1057 (cb0d390, 2026-09-30). Treating every digest mismatch as fatal therefore refused every completed dense build from that window, even with --revision. A build.json digest carrying that exact note is now skipped when it no longer matches, and the manifest must match sha256sums.txt instead. The marker records this as fallback_digest_matched. With no digest list (staging disabled), nothing the build recorded checks the manifest, so the marker records only_stale_digest_recorded. The report lists each skipped digest with its note and what the file was checked against, and warns. Any other mismatch is still refused. The fixture now replays the drivers' write sequence instead of pairing the note with a digest of the final manifest, which no real build wrote. It has three layouts: the #901 window (note and stale digest), the refreshed dense layout, and the national completion record (uk_national_build_completion with build_record), which arrived in #1057 together with the refresh. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The serializer reports JSON of the wrong shape as a TypeError: a top-level array or string, or a graph object whose node list is not an array. reconstructed_schema passed that error through, and the UK command's main turns only OSError, ValueError and RecursionError into a refusal. So a graph.json of [] reconstructed with --revision ended in an uncaught traceback and exit status 1, instead of a refusal with status 2. reconstructed_schema now converts the serializer's TypeError into a ValueError that says the recorded declaration is not a graph. A recorded file is data, so a malformed one is refused like any other input that fails a check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The CLI help and the docs say the build directory is only read, but an --output inside it was accepted: the command created its hidden staging directory and the publication inside the build. Output equal to the build was refused only because it already exists, with a message that did not name the problem. The output path is now resolved, and each of its existing ancestors is compared with the build directory by os.path.samefile. An output equal to the build, or beneath it, is refused before anything is written, so "..", symbolic links and case-insensitive file systems cannot disguise the build directory. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The docs, the changelog and the PR said nothing in a reconstruction is re-derived from current code. Only the declaration's bytes are the build's own, though: the schema's compiled, fields and input_bindings sections, and the Orrery document projected from it, are computed by the code that runs the reconstruction. The marker named only the source revision, so a reader could not tell which code produced those parts, or that a reconstruction at another revision may differ in them. The marker now has a reconstruction_revision field next to source_revision, with git_commit and git_dirty. code_revision(path) reads them from the git checkout that tracks the reconstructing module's own file, not from the current directory, and returns nulls when no checkout tracks that file, so an installed copy inside some unrelated repository is never attributed to it. The UK command records it, and shows it in the report and the summary. It warns when the revision is unknown, or when the working tree is dirty or could not be checked. The scope text, docs and changelog now say which parts come from the build and which come from the reconstructing code. The protocol stays v1: it has not been released. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 19, 2026
Stack this branch on orrery-projection-without-store. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stack this branch on orrery-graph-reconstruction. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added this pull request to stack #1323
October 19, 2026 21:09
code_revision ran git in the reconstructing file's directory with this process's whole environment. An exported GIT_DIR, GIT_WORK_TREE, GIT_INDEX_FILE or GIT_COMMON_DIR, set by an outer git command, a hook or a shell, overrides git -C. git then reads that other checkout. With a second checkout that tracks a file of the same name, the marker recorded the other checkout's commit as the reconstruction revision, reported as clean. A file only the right checkout tracks was reported as an unknown revision. git now runs without the variables the installed git lists as local to one repository (git rev-parse --local-env-vars), as the Axiom checkout check already does. If git cannot list them, the revision is unknown. The adapter's helper is not imported, because it lives in a heavy module and this one stays light. Found by Codex's review of this pull request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
reconstruct_uk_publication checked the resolved output against the build directory, but wrote to the path as given. build/missing/../../publication names a folder beside the build, so the check accepted it. Creating its parents then made build/missing inside the build, which the command must only read. With a read-only build, that mkdir failed, so such a build could not be reconstructed at all, even into a writable destination. The output is now resolved once, and that path is used both for the check and for every write. An output that already exists under the spelling the caller gave, such as a dangling link, is still refused rather than followed. A new test sends that path for a writable build and for a read-only one. The build's files and folders must be unchanged, and the publication must appear beside the build. Found by Codex's review of this pull request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Carry orrery-graph-reconstruction up the stack. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 19, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main passed the --output spelling to summary_text, but the publication is written at the resolved path, which the spelling need not reach. For --output <build>/missing/../../cli_publication the printed "Wrote ... to" and "report:" paths did not exist, because missing cannot be entered. main now resolves the output before the write, as reconstruct_uk_publication does, and prints that path. It still passes the given spelling, so the checks on it run. New tests: - the command prints the resolved path, for both lines, and never the given spelling; - an output whose given name exists as a symbolic link but whose resolved path does not (a dangling link leading outside the build) is refused as already existing, and nothing is written where the link points. The adapter doc says the output must not exist as given or once resolved, and that the command prints the resolved path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in main (#1289, #1305) through the base branch. GitHub builds each stacked pull request's test merge on top of main, and #1305's commit 7965d69 added a withhold_loss keyword to thinned_epochs, whose every default this branch moved to the shared CALIBRATION_EPOCH_EVERY. The signature conflict is resolved by keeping both: every defaults to CALIBRATION_EPOCH_EVERY, and withhold_loss stays keyword-only with its default of False. The callers #1305 added already pass staging_epoch_every(args), which this branch defines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
removed this pull request from stack #1323
October 20, 2026 18:01
anth-volk
added this pull request to stack #1328
October 20, 2026 18:03
This branch has not been deployed
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.
Fixes #1292
Stack
main← #1295 ← #1297 ← #1296 ← #1298 (GitHub stack #1328, linked on 2026-10-20 withgh stack link).#1322 taken out of the stack (2026-10-20): CI runs only for pull requests into
mainon purpose, to limit the number of simultaneous runs on the organization's shared runners. #1322, which removed that filter, is closed, and the old stack #1323 was removed.4ced66de0on #1295 restores the four files #1322 changed to their versions onmain, and each branch above merges the one below it: #129794a5bff93, #1296b0406fd76, #12987cb857716. None of those merges had conflicts, andtest_ci_integration_job.pypasses on every branch (9 passed). No commit was rewritten. From now on only the bottom pull request, which targetsmain, gets CI; each one above gets CI once it becomes the bottom.orrery-graph-reconstruction(Reconstruct declaration-only Orrery publications for UK builds before #1144 #1297). This PR is independent of the Orrery work, and it sits here because that is the only position between Reconstruct declaration-only Orrery publications for UK builds before #1144 #1297 and Publish a JSON Schema for calibration diagnostics schema 8 #1298.f30d5185dmerges Reconstruct declaration-only Orrery publications for UK builds before #1144 #1297's branch,32ff36803andae57d8c8emerge Reconstruct declaration-only Orrery publications for UK builds before #1144 #1297's review fixes, and49169dd96mergesmain(a90eec3cb) through Reconstruct declaration-only Orrery publications for UK builds before #1144 #1297. The first three had no conflicts;49169dd96's conflict is described under commit 4 below.49169dd96. Tests after the last merge: this PR's three changed test files gave 406 passed and 8 skipped.Why
mainwas merged in (2026-10-20): GitHub builds each stacked pull request's test merge on top of the test merge of the pull request below it, starting frommain:refs/pull/1322/mergehasmainas its first parent, and each later one has the previous one's. Oncemaingained #1305, #1296's change torowwise_staging.pyconflicted with it, so GitHub could not build #1296's test merge. It reported #1296 as conflicting although its branch contained its base, and started no CI for #1296 or #1298.main(a90eec3cb) was merged into #1322's branch, and each branch into the one above, with merge commits; #1296 is now mergeable.Merge order: #1295, #1297, #1296, #1298, each after the one below it, with merge commits (or
gh stack merge). Every branch above #1295 has its parent merged in rather than rebased, so no commit was rewritten. Squash-merging a lower pull request would leave its original commits in the branches above, which would then need rebasing.Summary
US builds forwarded a
calibration_epochtelemetry event for every solver epoch, while UK builds forwarded only a subset. This PR defines one rule for both countries and makes the US use it. The rule forwards each solve's first and last epoch and every tenth epoch.microcosm.build.calibration_epoch_telemetryholds the rule:CALIBRATION_EPOCH_EVERY = 10;CALIBRATION_MAX_EPOCH_ROWS = 2400;calibration_epoch_stride(total_epochs), which returnsmax(10, ceil(total / 2400));forwards_calibration_epoch(event, *, every), which forwards epocheofEwhene == 1 or e % every == 0 or e == E. It forwards onlycalibration_epochevents.uk_runtime.rowwise_stagingnow takes the shared definition instaging_epoch_everyandthinned_epochs, and its ownSTAGING_EPOCH_EVERYandSTAGING_MAX_EPOCH_ROWSconstants are removed._BuildProgress.calibration_progressintools/build_us_fiscal_refresh_release.pyforwards only the rule's epochs, to both the telemetry emitter and the staging v1 bundle.--epochsand the run's solve count: 1 for--dense-default-dataset, otherwise 2 (a selection and its refit).progress.jsonstay exact.2,400 + 2 × solvesrows.tools/route_a/README.mdsays which epochscalibration_progress.jsoncarries.docs/uk-full-build-graph.mdno longer calls the UK rows "per-epoch", which they weren't, and now names the first epoch.A default US run (1,500 epochs × 2 solves) now sends 302 calibration events to the collector instead of 3,000. Staging v1 also rewrites its growing
calibration_progress.json302 times instead of 3,000.Commits
1. Thin US calibration epoch telemetry to the UK's rule (69bd5e0)
Red, run before the implementation:
test_us_build_progress_forwards_every_tenth_and_each_solve_final_calibration_epoch: 51 events reached the emitter (50 epochs plus abudget_probe), against the 6 expected.test_us_build_progress_stride_keeps_long_runs_under_the_row_cap, with three cases:engine_free/shared/test_calibration_epoch_telemetry.pyraisedModuleNotFoundErrorformicrocosm.build.calibration_epoch_telemetry. That's the module this commit adds, so the error was the intended one, not a typo.Green:
New tests: 13 passed. That is 8 for the shared rule, 4 for the US, and the 1 UK stride test, which now reads the cap from the shared module.
All three added or modified test files: 387 passed and 8 skipped.
engine_free/shared/test_calibration_epoch_telemetry.pyengine_free/uk/test_uk_rowwise_candidate.pyengine_free/us/test_us_fiscal_refresh_builder.pyThe skips were already there: they need the
[us]extra or the pinnedMICROCOSM_US_CHRONICLE_FACTSfeed.Commit 2 changes some of these tests' names and expected values.
2. Forward each solve's first calibration epoch (d0d2ef4)
A review found that commit 1 merged short runs' solves on the dashboard.
calibrationPointsin calibration-diagnostics'frontend/lib/microcosm/build-monitor.tsstarts a new solver pass only when a row's epoch is lower than the previous row's, or when itsepochstotal differs.--epochsof 10 or fewer, commit 1 forwarded only each solve's last epoch. An L0 run, or an exact-k run with k below the pool, therefore forwarded(epoch N, epochs N)for both the selection and the refit. Neither condition fired._BuildProgresswrote real staging v1 bundles under a fake clock with one second per epoch.buildTimeline, taken from calibration-diagnosticsmainat 69b6a9d and run with Node.--epochs 10and with--epochs 5, the bundle held 2 rows. The dashboard showed 1 solver pass withstart_known=false, andcalibrationRatenever returned a rate.Fix and where it lives:
--epochs.--epochsof 10 or fewer merged the same way.full_build_cli._solve_observerusesthinned_epochsfor the emitter and the v2 bundle. Its stderr progress lines come from a separate sink and don't change.staging_epoch_everyand theepoch_everystage detail are unchanged.epochis a non-negative integer.tools/generate_staging_contract_fixtures.pywrites its rows through the writer directly, not through the rule. The committed fixtures, and the dashboard's copies of them, are unchanged.test_uk_full_build_cli.py, which this PR doesn't modify, drives 2-epoch solves through the observer. From reading it, not running it: the content-refusal test should still warn once, because forwarding stops after the first refused row, and the remote-upload test should still see as many upload successes as attempts.calibration_epoch_strideis unchanged, and the epochs on the stride stay withinCALIBRATION_MAX_EPOCH_ROWS.CALIBRATION_MAX_EPOCH_ROWS + 2 × solvesinstead of+ 1 × solves. The module docstring and the tests now state and check it.Red, run before the implementation: 16 failed, 382 passed and 8 skipped across the three changed test files. Every failure was for the intended reason: each solve's first epoch was missing.
test_calibration_epoch_rule_forwards_each_solve_first_last_and_every_tenth_epoch:[10, 20, 25]was forwarded where[1, 10, 20, 25]was expected.test_forwarded_calibration_epochs_fall_at_every_solve_boundary: the epoch did not fall for 21 of the 78 combinations of--epochs2–40 and stride 10 or 13. Those 21 are--epochs2–10 at stride 10 and 2–13 at stride 13.test_forwarded_rows_stay_within_the_cap_plus_two_per_solve, 5 cases:test_uk_thinned_epochs_forward_each_solve_first_epoch. A dense solve, two probes and a refit of 5 epochs each forwarded only epoch 5 of each.test_us_build_progress_forwards_each_solve_first_final_and_every_tenth_epoch: epoch 1 was missing from both solves.test_us_short_run_forwards_each_solve_first_epoch_so_the_epoch_falls_at_the_refit, with L0 and exact-k phases at--epochs10 and 5 (4 cases): the staged rows were[N, N]where[1, N, 1, N]was expected.test_us_build_progress_stride_keeps_long_runs_under_the_row_cap, 3 cases: 300, 2,308 and 2,308 rows against 302, 2,310 and 2,309.Green: 398 passed and 8 skipped across the three changed test files, including all 16 tests that failed above. The 8 skips are the same pre-existing ones.
3. Merge
main(fca6416)A mechanical merge with no conflicts, so no red step.
mainhad moved 187 commits since this branch was created, and two of them changed files this PR also edits:docs/uk-full-build-graph.mdandtest_uk_rowwise_candidate.py. After the merge, the three changed test files gave 406 passed and 8 skipped, becausemainadded tests to two of them.4. Merges from the stack (2026-10-20)
ae57d8c8eMerge orrery-graph-reconstruction: Run the Tests workflow for pull requests into any base branch #1322's and Reconstruct declaration-only Orrery publications for UK builds before #1144 #1297's latest review fixes. No conflicts, so no red step. The three changed test files gave 406 passed and 8 skipped.49169dd96Merge orrery-graph-reconstruction, bringing inmainata90eec3cb:rowwise_staging.thinned_epochs' signature. Place UK private rent and BRMA at the assigned area; bind Scotland's rents at BRMA grain (#1262, #1090) #1305's commit 7965d69 added a keyword-onlywithhold_loss: bool = False, while this PR movedevery's default fromSTAGING_EPOCH_EVERYto the sharedCALIBRATION_EPOCH_EVERY. Both are kept. The callers Place UK private rent and BRMA at the assigned area; bind Scotland's rents at BRMA grain (#1262, #1090) #1305 added already passevery=staging_epoch_every(args), which this PR defines, and nothing refers toSTAGING_EPOCH_EVERYany more.test_uk_full_build_cli.py, which holds Place UK private rent and BRMA at the assigned area; bind Scotland's rents at BRMA grain (#1262, #1090) #1305's tests ofwithhold_lossthrough the solve observer, gave 129 passed.ruff checkandruff format --checkwere clean onrowwise_staging.py.Findings
The original claim held. Evidence from
mainat 8a7f79a:microcosm.calibrate.solve, in the Adam loop around line 1186 and the ISTA loop around line 1375) callsprogress_callbackwith acalibration_epochevent on every epoch. Both loops run a fixed number of epochs, so every solve reports its last epoch.rowwise_staging.thinned_epochs(..., every=staging_epoch_every(args))(uk_runtime/full_build_cli._solve_observer).telemetry.calibration_progressstraight to all three solve call sites: densecalibrate,calibrate_l0_refitandcalibrate_exact_k_ladder. Neither sink thinned:calibration_epochinto one collector event;microcosm.build.staging.StagingRunBundleWriter.calibration_progress) appends a row and rewritescalibration_progress.jsonandprogress.jsonon each call.Dashboard impact (calibration-diagnostics, read from
mainat 69b6a9d)US staging v1 rows and collector events now arrive at epochs 1, 10, 20 and so on, plus each solve's last epoch. Nothing in the dashboard assumes contiguous epochs.
frontend/lib/microcosm/build-monitor.ts,calibrationPoints):epochschanges.--epochsabove 1 the epoch falls at each solve boundary, as it does in the unthinned stream.--epochsof 10 or fewer.--epochs 1, every solve is a single(1, 1)row and the passes still merge. The unthinned stream has exactly the same rows, so only the dashboard could separate them, for example by also splitting onphase. v1 rows don't recordphase.solverPasses):first.epochepochs, at the pass's own average rate, from the time of its first row. With epoch 1 as the first row, that's one epoch of estimate instead of ten.calibrationRate, used byforecastCompletionandsolverForecast): it measures the epoch rate over the last 30 rows of the current pass. An earlier version of this description wrongly said it used a pass's first and last rows.main, which forwards every epoch: the window spans 29 epochs, and the first rate arrives at epoch 2.frontend/components/microcosm/microcosm-staging-view.tsx):main, which forwarded every epoch.staging-status.ts): "Calibration, epoch X of Y" now advances in steps of ten.staging-contract.ts): v1 calibration rows pass through unchanged, with no contiguity or count check. v1 rows still carry nophase; that's unchanged.telemetry-service/telemetry_collector/storage.py):Local checks
Local tests were limited to the test files this PR adds or modifies, at the user's request. CI runs the rest of the suite.
engine_free/shared/test_calibration_epoch_telemetry.py,engine_free/uk/test_uk_rowwise_candidate.py,engine_free/us/test_us_fiscal_refresh_builder.py):ruff check .passed andruff format --checkpassed on the six changed Python files.python3 tools/ci_test_plan.py verifyreportedverification=okat each commit. Commit 2 adds no test module.tools/graph_acceptance_burndown.py --verifyreportedverification=okat each commit.main: the three changed test files: 406 passed and 8 skipped.ruff check,ruff format --checkon the six changed Python files,tools/ci_test_plan.py verifyandtools/graph_acceptance_burndown.py --verifyall passed.ae57d8c8egot no run, because GitHub could not build its test merge (see "Whymainwas merged in");49169dd96runs them.Risks
progress.jsonand its interval-limited uploads now refresh at most every tenth epoch, instead of every epoch.test_uk_full_build_cli.py(2-epoch solves through the observer), which was run after mergingmain(129 passed). I read the others for impact; CI runs them._BuildProgressandstaging.pyand may conflict textually.buildTimelineon bundles the branch wrote. No real US or UK build was run.Open questions
epoch_everyin itscalibrationstage details. The UScalibratingstage does not, so a reader can't tell the stride from the stage record. I left it out to keep this PR to the rule; it could be a follow-up.--epochs 1, the dashboard still merges a run's solves into one pass, as it did before this PR. Separating them would need the dashboard to split onphase, and US v1 rows would have to recordphase.🤖 Generated with Claude Code