Skip to content

Thin US calibration epoch telemetry to the UK's rule - #1296

Open
anth-volk wants to merge 21 commits into
orrery-graph-reconstructionfrom
us-calibration-epoch-thinning
Open

anth-volk wants to merge 21 commits into
orrery-graph-reconstructionfrom
us-calibration-epoch-thinning

Conversation

@anth-volk

@anth-volk anth-volk commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1292

Stack

main ← #1295 ← #1297 ← #1296 ← #1298 (GitHub stack #1328, linked on 2026-10-20 with gh stack link).

#1322 taken out of the stack (2026-10-20): CI runs only for pull requests into main on 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. 4ced66de0 on #1295 restores the four files #1322 changed to their versions on main, and each branch above merges the one below it: #1297 94a5bff93, #1296 b0406fd76, #1298 7cb857716. None of those merges had conflicts, and test_ci_integration_job.py passes on every branch (9 passed). No commit was rewritten. From now on only the bottom pull request, which targets main, gets CI; each one above gets CI once it becomes the bottom.

Why main was 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 from main: refs/pull/1322/merge has main as its first parent, and each later one has the previous one's. Once main gained #1305, #1296's change to rowwise_staging.py conflicted 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_epoch telemetry 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.

  • New shared module: microcosm.build.calibration_epoch_telemetry holds the rule:
    • CALIBRATION_EPOCH_EVERY = 10;
    • CALIBRATION_MAX_EPOCH_ROWS = 2400;
    • calibration_epoch_stride(total_epochs), which returns max(10, ceil(total / 2400));
    • forwards_calibration_epoch(event, *, every), which forwards epoch e of E when e == 1 or e % every == 0 or e == E. It forwards only calibration_epoch events.
  • UK:
    • uk_runtime.rowwise_staging now takes the shared definition in staging_epoch_every and thinned_epochs, and its own STAGING_EPOCH_EVERY and STAGING_MAX_EPOCH_ROWS constants are removed.
    • The stride is unchanged. Commit 2 adds each solve's first epoch, which is one more row and one more collector event per solve.
  • US:
    • _BuildProgress.calibration_progress in tools/build_us_fiscal_refresh_release.py forwards only the rule's epochs, to both the telemetry emitter and the staging v1 bundle.
    • The stride comes from --epochs and the run's solve count: 1 for --dense-default-dataset, otherwise 2 (a selection and its refit).
    • An exact-k ladder with k = N solves only once, so 2 is an upper bound. The UK works the same way: it counts the most probes a budget search can run.
  • First and final epochs:
    • Each solve's last epoch is always forwarded, so every solve's final loss and the latest-epoch row in progress.json stay exact.
    • Each solve's first epoch is forwarded too, so the forwarded epochs fall at every boundary between solves. That's how the calibration-diagnostics build monitor tells solves apart; see commit 2.
  • Row cap:
    • The stride keeps the epochs on the stride within 2,400.
    • Each solve's first and last epoch add at most two rows, so a run forwards at most 2,400 + 2 × solves rows.
  • Docs:
    • tools/route_a/README.md says which epochs calibration_progress.json carries.
    • docs/uk-full-build-graph.md no 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.json 302 times instead of 3,000.

Commits

1. Thin US calibration epoch telemetry to the UK's rule (69bd5e0)

Red, run before the implementation:

  • 4 failed, for the intended reason: each assertion failed because every epoch was forwarded.
    • test_us_build_progress_forwards_every_tenth_and_each_solve_final_calibration_epoch: 51 events reached the emitter (50 epochs plus a budget_probe), against the 6 expected.
    • test_us_build_progress_stride_keeps_long_runs_under_the_row_cap, with three cases:
      • 3,000 forwarded against 300 expected;
      • 30,000 against 2,308;
      • 30,000 against 2,308.
  • 1 collection error: engine_free/shared/test_calibration_epoch_telemetry.py raised ModuleNotFoundError for microcosm.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.py
    • engine_free/uk/test_uk_rowwise_candidate.py
    • engine_free/us/test_us_fiscal_refresh_builder.py

    The skips were already there: they need the [us] extra or the pinned MICROCOSM_US_CHRONICLE_FACTS feed.

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.

  • How the dashboard detects solves: calibrationPoints in calibration-diagnostics' frontend/lib/microcosm/build-monitor.ts starts a new solver pass only when a row's epoch is lower than the previous row's, or when its epochs total differs.
  • The failure: with --epochs of 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.
  • Reproduction:
    • The branch's _BuildProgress wrote real staging v1 bundles under a fake clock with one second per epoch.
    • The bundles went to the dashboard's own buildTimeline, taken from calibration-diagnostics main at 69b6a9d and run with Node.
    • With --epochs 10 and with --epochs 5, the bundle held 2 rows. The dashboard showed 1 solver pass with start_known=false, and calibrationRate never returned a rate.
    • After this commit, the same runs hold 4 rows each and show 2 passes, both starting at their true times.

Fix and where it lives:

  • Where: in the shared rule, so it applies to the UK too.
    • A UK size run's dense solve, budget-search probes and refit all run --epochs.
    • The dashboard applies the same pass detection to every country, so UK runs with --epochs of 10 or fewer merged the same way.
    • A US-only fix would have left the UK broken and split the rule this PR exists to share.
  • What it does: with epoch 1 forwarded, each solve's rows start at epoch 1. The forwarded epochs therefore fall at every solve boundary, exactly where the full per-epoch stream's do.
  • UK consumers checked:
    • full_build_cli._solve_observer uses thinned_epochs for the emitter and the v2 bundle. Its stderr progress lines come from a separate sink and don't change.
    • staging_epoch_every and the epoch_every stage detail are unchanged.
    • The v2 and v3 staging contracts check only that epoch is a non-negative integer.
    • tools/generate_staging_contract_fixtures.py writes 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.
  • UK output change: one more row per solve. A default UK size run (2,000 epochs × 12 solves) forwards 2,412 rows instead of 2,400.
  • Row cap:
    • calibration_epoch_stride is unchanged, and the epochs on the stride stay within CALIBRATION_MAX_EPOCH_ROWS.
    • Each solve's first and last epoch add at most two rows, so the bound is CALIBRATION_MAX_EPOCH_ROWS + 2 × solves instead of + 1 × solves. The module docstring and the tests now state and check it.
    • An alternative was to fold the new rows into the 2,400. That would change the default UK size run's stride from 10 to 11, to save about 1% of a row budget that sits far under the contract's 5 MiB limit, so I didn't.
    • Even without first epochs, a flat 2,400 was never guaranteed. 7 solves × 3,428 epochs forwarded 2,401 rows because their last epochs fall off the stride.

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.

  • Shared rule, 7 failed:
    • 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 --epochs 2–40 and stride 10 or 13. Those 21 are --epochs 2–10 at stride 10 and 2–13 at stride 13.
    • test_forwarded_rows_stay_within_the_cap_plus_two_per_solve, 5 cases:
      • 300 rows against 302;
      • 2,400 against 2,412;
      • 2,308 against 2,310;
      • 2,308 against 2,309;
      • 2,401 against 2,408.
  • UK, 1 failed: 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.
  • US, 8 failed:
    • 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 --epochs 10 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. main had moved 187 commits since this branch was created, and two of them changed files this PR also edits: docs/uk-full-build-graph.md and test_uk_rowwise_candidate.py. After the merge, the three changed test files gave 406 passed and 8 skipped, because main added tests to two of them.

4. Merges from the stack (2026-10-20)

Findings

The original claim held. Evidence from main at 8a7f79a:

  • The solver (microcosm.calibrate.solve, in the Adam loop around line 1186 and the ISTA loop around line 1375) calls progress_callback with a calibration_epoch event on every epoch. Both loops run a fixed number of epochs, so every solve reports its last epoch.
  • The UK wraps both sinks, staging v2 and the emitter, in rowwise_staging.thinned_epochs(..., every=staging_epoch_every(args)) (uk_runtime/full_build_cli._solve_observer).
  • The US passed telemetry.calibration_progress straight to all three solve call sites: dense calibrate, calibrate_l0_refit and calibrate_exact_k_ladder. Neither sink thinned:
    • the emitter (microcosm-emitter 0.2.1) turns every calibration_epoch into one collector event;
    • staging v1 (microcosm.build.staging.StagingRunBundleWriter.calibration_progress) appends a row and rewrites calibration_progress.json and progress.json on each call.

Dashboard impact (calibration-diagnostics, read from main at 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.

  • Pass detection (frontend/lib/microcosm/build-monitor.ts, calibrationPoints):
    • It starts a new pass when a row's epoch is lower than the previous row's, or when epochs changes.
    • Each solve's forwarded rows begin at epoch 1, so for any --epochs above 1 the epoch falls at each solve boundary, as it does in the unthinned stream.
    • The reproduction above shows that commit 1 alone merged the passes of runs with --epochs of 10 or fewer.
    • With --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 on phase. v1 rows don't record phase.
  • Pass timing (solverPasses):
    • It estimates a pass's start by subtracting first.epoch epochs, 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.
    • A pass with only one row has no rate, so its start stays unknown.
    • The monitor view's per-pass seconds-per-epoch label divides by the time from that start.
  • Time-remaining estimate (calibrationRate, used by forecastCompletion and solverForecast): 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.
    • On main, which forwards every epoch: the window spans 29 epochs, and the first rate arrives at epoch 2.
    • After commit 1: the first rate arrives at epoch 20, from the rows at epochs 10 and 20. Once 30 rows exist, the window spans 290 epochs.
    • After commit 2:
      • The first rate arrives at epoch 10, from the rows at epochs 1 and 10. A solve shorter than 10 epochs gets its first rate at its last epoch.
      • The window grows from epoch 1 until the 30th row, at epoch 290. After that it spans 290 epochs; at the end of a default solve, that's epochs 1,210 to 1,500.
      • So the estimate tracks a change in speed over about 290 epochs instead of about 29.
  • Run internals (frontend/components/microcosm/microcosm-staging-view.tsx):
    • "Latest loss" is the last row, which is now always a solve's final epoch.
    • "Best loss" is the minimum over the forwarded rows, so it can sit slightly above the true per-epoch minimum.
    • The sparkline:
      • It draws the last 120 rows, which now span about 1,200 epochs instead of 120.
      • Its bars are scaled between the minimum and maximum loss over all rows.
      • Epoch 1 usually has the highest loss, so it sets the top of that range. That was already true for US runs on main, which forwarded every epoch.
      • UK views now include epoch 1 too, where their first row used to be epoch 10, so their bars can sit lower.
  • Status line (staging-status.ts): "Calibration, epoch X of Y" now advances in steps of ten.
  • Contract (staging-contract.ts): v1 calibration rows pass through unchanged, with no contiguity or count check. v1 rows still carry no phase; that's unchanged.
  • Collector (telemetry-service/telemetry_collector/storage.py):
    • It stores whatever calibration events it receives. Collector-backed views therefore see 302 US events on a default run instead of 3,000.
    • UK views see one more event per solve.

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.

  • Changed test files (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):
    • after commit 1: 387 passed and 8 skipped;
    • after commit 2: 398 passed and 8 skipped.
  • Lint and formatting: at each commit, ruff check . passed and ruff format --check passed on the six changed Python files.
  • Test layout: python3 tools/ci_test_plan.py verify reported verification=ok at each commit. Commit 2 adds no test module.
  • Burndown: tools/graph_acceptance_burndown.py --verify reported verification=ok at each commit.
  • Partial engine-free run: for commit 1, I started the complete engine-free category before the user limited local tests. It had reached 32% with no failures when I stopped it, as the user asked, so it isn't a test result.
  • After merging main: the three changed test files: 406 passed and 8 skipped. ruff check, ruff format --check on the six changed Python files, tools/ci_test_plan.py verify and tools/graph_acceptance_burndown.py --verify all passed.
  • CI: all 10 checks passed on d0d2ef4, and on 32ff368. ae57d8c8e got no run, because GitHub could not build its test merge (see "Why main was merged in"); 49169dd96 runs them.

Risks

  • Coarser updates: staging v1 progress.json and its interval-limited uploads now refresh at most every tenth epoch, instead of every epoch.
  • UK telemetry changes: commit 2 adds one row and one collector event per solve to UK runs.
  • Not run locally: UK test files this PR doesn't modify, apart from test_uk_full_build_cli.py (2-epoch solves through the observer), which was run after merging main (129 passed). I read the others for impact; CI runs them.
  • Possible conflicts: draft PRs Keep US staging telemetry on for every build #1082 and Record process telemetry for US release builds #1083 (from early October) edit _BuildProgress and staging.py and may conflict textually.
  • Unverified: the dashboard impact above comes from reading the code and from running its buildTimeline on bundles the branch wrote. No real US or UK build was run.

Open questions

  • Stride in the stage record: the UK records epoch_every in its calibration stage details. The US calibrating stage 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.
  • One-epoch runs: with --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 on phase, and US v1 rows would have to record phase.

🤖 Generated with Claude Code

anth-volk and others added 9 commits October 16, 2026 01:51
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>
anth-volk and others added 2 commits October 20, 2026 01:08
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
anth-volk changed the base branch from main to orrery-graph-reconstruction October 19, 2026 21:09
@anth-volk
anth-volk added this pull request to stack #1323 October 19, 2026 21:09
anth-volk and others added 3 commits October 20, 2026 01:15
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>
anth-volk and others added 5 commits October 20, 2026 16:48
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. No conflicts.

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
anth-volk removed this pull request from stack #1323 October 20, 2026 18:01
Anthony Volk and others added 2 commits October 20, 2026 22:02
Carries the restored main-only Tests workflow trigger (#1322 is not wanted) up the stack.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Carries the restored main-only Tests workflow trigger (#1322 is not wanted) up the stack.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anth-volk
anth-volk added this pull request to stack #1328 October 20, 2026 18:03

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Thin US calibration epoch telemetry to the UK's rule (every tenth epoch plus each solve's last)

1 participant