Repository navigation
Record a release that cannot be reconciled and reconcile the rest - #253
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
anth-volk
force-pushed
the
fix/tree-builder-label-variants
branch
from
October 21, 2026 16:58
65ad119 to
3e205e0
Compare
Release reconciliation throws at the first release it cannot publish or repair, so it prints no report, never reaches the releases after it, and never updates the latest-release selection. These tests describe the behaviour staging reconciliation has had since #237: - a release the schema 8 reader refuses is reported as `failed` with the error, and the releases after it are still published; - a release whose uploaded build fails its audit gets no manifest entry; the audit now comes before the manifest write; - when the release the line pointer names fails, the latest-release selection is left as it was and the report says why; - the publisher prints the whole report, then exits with an error that names every failed release; - a dry run records the failure and writes nothing; - the recorded-digest repair test now expects a `failed` outcome instead of a rejected call. The in-memory store in publish-calibration-tree.test.ts now records the order of uploads, listings, audits and manifest writes. Tests only. All six fail on the current publisher, which throws at the failing release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Release reconciliation threw at the first release it could not publish or repair. It printed no report, left every release after it unreconciled and never updated the latest-release selection, on every later invocation. Since 2026-10-02 one US release has blocked every later US release this way. Each release's error is now recorded as that release's `failed` outcome, with the error as its reason, and the remaining releases are still reconciled, as staging reconciliation has done since #237. After printing the report, the publisher exits with an error naming every failed release, so the workflow still fails. Errors before the per-release loop (reading the manifest, listing releases or tags) still stop reconciliation. A failed release leaves no manifest entry and is never selected as the latest release: - The uploaded build is now listed and audited before its manifest entry is written, not after, so an incomplete upload is never registered. The audit reads the build's own index and the Blob listing, not the manifest. Successful publications upload and write the same bytes as before. - The latest-release selection skips a release that failed in the same run, keeping the build it named before. When the selection cannot be read or updated, the report's new `latestReleaseFailure` field says why, and the publisher exits with an error after printing the report (it used to throw without a report). The failure message reads "N of M releases failed to reconcile" for releases; the staging message is unchanged. The docs describe the new behaviour and the order of a release's writes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
force-pushed
the
fix/release-reconciliation-continues
branch
from
October 21, 2026 19:49
b83108a to
197762f
Compare
The comments and one test name added in the previous two commits called the UK fixture's tagged builds "cuts" and its `latest-national.json` pointer file a "line pointer". They now say tagged build and pointer file. Comments and a test name only; no behaviour changes, and the 20 tests in the file still pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rites When the release the country's pointer file names fails to reconcile, a run that writes leaves the latest-release selection unchanged and says so in the report's `latestReleaseFailure`. A dry run records the same `failed` outcome but returns from the selection step as soon as it has read the pointer, so its report has no `latestReleaseFailure` and its error message omits the selection. Both reports have every input the check needs: the pointer and the failed releases. The new test runs the same repository twice, once writing and once as a dry run, and expects the same `latestReleaseFailure` and the same failure message from both. It fails on the current publisher: the dry run's `latestReleaseFailure` is undefined. Tests only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The latest-release step returned as soon as a dry run had read the upstream pointer, before checking whether the release it names failed in the same run. A dry run therefore omitted `latestReleaseFailure` and the selection from its error message, although a run that writes reports both. The failed-release check now runs before the dry-run return. A dry run still writes nothing and still does not check whether the named release is completely published, since it writes no manifest entries. Runs that write behave exactly as before. The field's comment and the documentation say what a dry run reports. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The documentation said only reading the manifest and listing releases, tags or staging runs stop reconciliation before any source. The first Blob listing does too, and so do the Hugging Face lookups that build the release list before the per-release loop: the folder probe and `release_manifest.json` of each tag the manifest does not already pin, and the commit of each folder release without a tag. An error in one of those still stops every release with no report. The failure-meanings list now names them as well. Documentation only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The outcome type's comment said nothing was written for a `failed` source. That is not true for staging, which writes a run's manifest entry before it audits the upload, so a run whose audit fails has an entry; nor for a release, whose files uploaded before the error stay under its build ID. The release-reconciliation docs and the loop's comment said a failed release gets no manifest entry and is never selected as the latest release. A release that already had an entry (a refused repair, or a tag that moved) keeps it, and if the selection already named it, it still does: the run only never moves the selection to a failed release. Comments and documentation only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The outcome type's comment and the release-reconciliation docs now say a
failed release gets no new manifest entry, but leave out the case the
loop's comment names: the manifest write is stored and only its
read-back check fails ("Calibration tree manifest verification
failed."). That release is reported as failed, yet the stored manifest
has its entry. Both now name that case.
Comments and documentation only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Oct 26, 2026
…ma-9-catalogs Brings in #253's two review commits (comments and documentation only). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
marked this pull request as ready for review
October 26, 2026 13:24
This branch was successfully 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 #252
Base:
main. This was opened on #247 (head65ad119), which was stacked on #250. Both merged on 2026-10-21 (#250 at 16:51 UTC, #247 at 19:05 UTC, after #247 was restacked to3e205e0), and GitHub retargeted this PR tomain. Its two commits were rebased ontomain(9ee43ff) withgit rebase --onto origin/main 65ad119;main's tree is identical to65ad119's, andgit range-diffmarks both commits identical (1a66fa7→d592474,b83108a→197762f). The counts below were rerun on the rebased commits.#255 (reading diagnostics schema 9) is stacked on this branch. Head:
0f8321b.Problem
Release reconciliation threw at the first release it could not publish or repair. It printed no report, never reached the releases after that one, and never checked or updated the latest-release selection. Every later run stopped at the same release. Since 2026-10-02 this has blocked every US release reconciliation (runs 37036146264 and 39488424340), first at
populace-us-2024-0fb05b6-4b57d15a287c-20260930T150401Z, sopopulace-us-2024-buildo-acs-local-766b7c7-20261017T030317Zwas never reached. #247 removes that particular error; this PR removes the stop-at-first-error behaviour.Change
reconcileReleaseBuilds(frontend/packages/calibration-tree/src/publish-calibration-tree.ts) now works the wayreconcileStagingBuildshas since #237:try. An error for one release becomes that release'sfailedoutcome, with the error asreasonand the existing entry's build ID (ornull), and the loop continues. This covers every per-release error: a tag that moved ("Released dataset … changed from … to …"), a moved folder, a corrupt stored build, a diagnostics file the reader refuses, a refused repair ("Rebuilt calibration index differs from immutable manifest entry …"), a size limit, an upload error, and an audit that finds the upload incomplete.runPublisheralready printed the report and then threwreconciliationFailureMessage(report)for both modes; for releases that message was never reached. It now reads "N of M releases failed to reconcile: . The printed report gives each reason." The staging message is unchanged ("… staging sources failed …"). The process exits with status 1, so the workflow run fails.release_manifest.jsonon a tag, a non-404 error from a folder probe, a folder whose commit can't be found); they still stop every release with no report, as before this PR. See "Review" below.A failed release leaves no partial state
publishRelease), list the stored files, audit the build, then write the manifest entry. The audit used to come after the manifest write, so an upload the audit found incomplete was already registered. The audit (auditCalibrationTreeBuild) reads the build's own index and the Blob listing, never the manifest, so it gives the same answer before the write. Every error is now raised before the manifest names a new build. The one exception is the manifest write's own read-back check ("Calibration tree manifest verification failed."), which runs after the write was stored; that was already the case and is unchanged.selectLatestRelease) skips a release that failed in this run and leaveslatestReleaseBuildArtifactIdnaming the build it named before. Without this, a release whose repair failed but which already had a manifest entry would have been selected.calibration-tree-bundle.tsorders it last,uploadCalibrationTreeBundleuploads in order), so an upload that fails part-way leaves no index. An upload that completes but fails its audit leaves its files, index included, under the build's ID with no manifest entry pointing at them. The build ID is a digest of the country, the release ID and the source files' digests (calibrationTreeBuildArtifactId), so the next run rebuilds the same ID, anduploadCalibrationTreeFilecompares each existing file byte for byte before reusing it. Nothing reads files that no manifest entry names.latestReleaseFailure, with the reason, and the publisher exits with an error after printing the report. It used to throw "Latest release … is not completely published." with no report. The field is absent when the selection was updated, so a successful run prints the same report as before.Manifest writes and concurrency
calibration-trees/manifest.json(updateCalibrationTreeManifest; the dashboard's lazy comparison writes use other paths). Each write reads the stored manifest, adds one entry and writes withifMatchon the ETag it read; after a conflict it reads again and writes once more without the condition. A failed release now makes no manifest write at all, so the reads and writes for the releases after it are exactly those of a run without it. The localmanifestcopy is replaced only by a successful write's return value, and each later write re-reads the stored manifest anyway.calibration-tree-manifest-publicationconcurrency group still allows one publisher job at a time across all countries and event kinds, withcancel-in-progress: false. The workflow is unchanged.timeout-minutes: 30is unchanged; for reference, the largest recorded US tree took about 11 to 12 minutes to compile on a laptop (see the Group calibration tree geographies by geography ID, not label #250 review).Dry run
--dry-runrecords failures the same way, uploads and writes nothing, and only reads the upstream pointer in the latest-release step (as before). It exits with an error if any release failed, after printing the report.Byte identity of successful publications
Nothing in the bundle, the uploaded files or the manifest entry changed. Only the order of the listing and audit relative to the manifest write changed, and a successful run makes the same uploads and the same manifest writes (same entries, same
makeLatestcalls). Checked by:publish-calibration-tree-golden.test.ts(7 pass): dry-run repairs of the recorded US, UK and Belgium releases still rebuild their recorded index digests, and the staging tests are unchanged;calibration-tree-golden.test.ts(12 pass) andtest-support/calibration-tree-golden-cases.test.ts(2 pass), with no golden record changed;publish-calibration-tree.test.tsthat publishes tagged UK builds from their shared release folder, which checks every manifest entry and the latest-release selection, passes unchanged.Tests (red, then green)
d592474(was1a66fa7)publish-calibration-tree.test.ts; the in-memory store there now records uploads, listings, audits and manifest writes in order. Inpublish-calibration-tree-golden.test.ts, the refused-repair test now expects afailedoutcome instead of a rejected call.bun run lintfails only because the tests read the newlatestReleaseFailurefield (TS2339, two places).197762f(wasb83108a)bun run lintpasses0f8321blatest-national.jsonpublish-calibration-tree.test.ts;bun run lintpassesThe new tests:
failedwith the reader's error and no build ID; the national tagged build is still published and becomes the latest release; nothing is uploaded or written for the dense one.failedand never written to the manifest; the recorded operation order is upload, list, audit, then manifest write for each release.latest-national.json) names fails: the selection keeps naming the June release, andlatestReleaseFailuresays why.runPublisherwith two failures (a refused file and a tag that moved to another commit): it prints one report with all five outcomes, then rejects with "2 of 5 releases failed to reconcile: , . The printed report gives each reason."At
197762f:bun test packages/calibration-tree): 378 pass, 0 fail, 34 files (373 onmain, plus 5 new);publish-calibration-tree.test.ts20,publish-calibration-tree-golden.test.ts7,calibration-tree-golden.test.ts12,test-support/calibration-tree-golden-cases.test.ts2,package-boundary.test.ts9,calibration-tree-artifact.test.ts16,calibration-tree.test.ts41, all 0 fail;app/api/hf-webhook/route.test.tsandlib/microcosm/hf-webhook-config.test.ts(the webhook that dispatches this workflow): 11 pass;bun run lintinfrontend/(bothtsc --noEmitruns): passes.Documentation
docs/calibration-tree-artifacts.md: the release publication section says what afailedrelease means; "Failure meanings and recovery" now describes a failed release workflow the way it describes a staging one; "Historical reconciliation" no longer says release reconciliation stops at its first release, and describes the order of a release's writes, what files an error leaves, the manifest write's condition, and when the latest-release selection is left unchanged.docs/ai/workflows/publish-calibration-tree-artifacts.md: the publish steps include the audit before the manifest entry and the rule that a failed release is never selected; the reconciliation paragraph describes thefailedoutcome.Environment variables
No workflow, deployment script or configuration changes, and the changed code reads no new environment variable. The publisher's existing variables, for reference (names checked with
gh secret list/gh variable list, values not read):BLOB_READ_WRITE_TOKENrunPublisherenv:ofpublish-calibration-tree.yml, inherited bybun runHF_TOKEN(fallbackHUGGINGFACE_TOKEN, not set)env:POPULACE_UK_STAGING_HF_TOKENtoken_env)env:CALIBRATION_TREE_BUILD_ENABLEDif:vars.contextCALIBRATION_TREE_MAX_RAW_BYTES,CALIBRATION_TREE_MAX_GZIP_BYTESNot changed
--latest,--release), which still throw on error.Review (2026-10-21)
The change is correct (review report kept in the maintainer's notes). Pushed by the review, test first:
73a2c32latestReleaseFailurewhen the release named by the pointer file failslatestReleaseFailurewasundefined)bb77ca6f0d1e54At
f0d1e54: the calibration-tree package 379 pass, 0 fail (the counts above are from0f8321b); golden tests 12, 2 and 7; boundary 9; webhook 11; typecheck passes.Confirmed: the run still exits 1 when a release fails, and nothing reads the old output; the audit never reads the manifest, so skip and repair decisions and successful publications are unchanged; partial uploads from a failed release are reused safely, because build IDs and index bytes are reproducible and existing files are compared byte for byte; compare-and-swap and the concurrency group are unchanged. Every environment variable is wired:
BLOB_READ_WRITE_TOKEN,HF_TOKENandPOPULACE_UK_STAGING_HF_TOKENare repository secrets passed through the job'senv:;CALIBRATION_TREE_BUILD_ENABLEDis a repository variable read in the job'sif:; the size limits,HUGGINGFACE_TOKEN,MICROCOSM_UK_LOCAL_CALIBRATION_DIRand the repository overrides (POPULACE_HF_REPO,POPULACE_UK_HF_REPO/_REVISION,POPULACE_BE_HF_REPO/_REVISIONand the staging ones) have no provider and fall back to defaults, as intended.Not fixed (follow-ups):
main: one bad tag or folder still stops every release with no report (see "Change").buildArtifactId: nulleven when its files were uploaded; staging reports the new ID.timeout-minutes: 30; on a timeout no report is printed, and releases already written keep their entries.Review round 2026-10-26
Reviewed at
f0d1e54, as it would merge into the currentmain(c84a916). A local merge has no conflicts. On the merged tree,bun test packages/calibration-treegives 386 pass and 0 fail, andbun run lintandbun run buildboth pass.mainwas not merged into this branch, because nothing here needed it.bb77ca6, a dry run reportslatestReleaseFailurewhen the pointer file names a release that failed. A dry run still does not check whether the named release is completely published.f0d1e54, the docs list every lookup that still stops the run before the per-release loop.mainsince then. List releases published only as a Hugging Face tag in the release picker #258 adds releases that are published only as a tag to the release list. It reads the dashboard manifest and the folder listing, and it doesn't change the publisher. A failed new release has no manifest entry, so the release list never offers it as a tag-only release. Refuse a country with no configured release instead of serving the US one #248, Start production deploy runs only for main CI runs #261 and Stop trusting the compromised eMudhra CA certificates #256 don't touch the publisher or its workflow. I found no bad interaction with the per-release error handling, the latest-release selection or the dry-run report.3034a78: theReconciliationOutcomecomment said nothing is written for afailedsource. In fact, staging writes its entry before the audit, and a release's uploaded files stay in storage.3034a78and64f9cf4: the docs and the loop comment said that a failed release gets no manifest entry and is never selected as the latest release. In fact, an entry the release already had stays, and so does an existing selection. A failed new release does get an entry when the manifest write is stored but its read-back check fails.buildArtifactId: null;timeout-minutes: 30.bb77ca6, a dry run also reads the pointer file and reportslatestReleaseFailurewhen the pointer cannot be read or when it names a release that failed in the same run.e634386, with no rebase.🤖 Generated with Claude Code