Repository navigation
Read Microcosm releases from releases.json wherever a repository has it, and from tags otherwise - #241
Merged
Conversation
The tree builder is about to move out of the dashboard into its own package. Its outputs are byte-exact contracts: the index records every part's raw-JSON SHA-256 and gzip size, build ids derive from the inputs, and a repair refuses any change to the index digest. So the move has to be proved to change no byte. This records, from the builder as it is today, what it writes for representative inputs built from the fixtures the module tests already use. The inputs cover US and UK, releases and staging runs (version 1, version 2, and a staged dataset bundle), Belgium, and a UK release with reported loss attribution. For each, the record holds every part's path, raw JSON digest and size, gzip size and deflate-stream digest, plus the build id. It also records release-to-staging comparison bundles built through the comparison reader, the explorer's root responses read back from the stored bytes, and the manifest serialization. The gzip header's operating-system byte varies by platform, so the header is asserted separately and the digest covers the stream after it. A second test drives the publisher's dry-run repair of the recorded US, UK and Belgium releases against a stubbed Hugging Face repository. The publisher refuses a rebuilt index whose digest differs, so a successful repair proves its own reads and hashing reproduce the recorded index. The stubbed repository serves each country's release pointer at the path the publisher reads it from: `latest.json`, or `latest-national.json` for the UK since #223. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tree builder and the schema 6 code the dashboard shares with it are moving to the collector as a Bun worker, and its types and readers will be published as a package the dashboard imports. That needs the builder to be one self-contained unit with explicit dependencies and its own tests. Inside frontend/lib nothing stopped a dashboard-only import from reaching it. The package, @policyengine/calibration-tree in frontend/packages/, is the builder's import closure: the schema 6 types, parsers, serializers and bundle builder, the comparison builder, Blob I/O behind the existing injectable client, and the release and staging readers the builder reads from, including the country registry. Its 30 modules move unchanged apart from ten import lines that used the dashboard's path alias. Their tests and fixtures move with them. Next.js routes, the HTTP helpers and the client hooks stay in the dashboard and import the package through its subpath exports. One of the 30 is release-cut.ts, added by #223. It parses the Hugging Face tag names Microcosm gives each build of a UK release, and latest-artifact.ts and calibration-tree-manifest.ts import it, so it moves with them and is exported as release-cut. The dashboard's shared formatter and the publisher now import it through that export. Two other additions since this branch began are adjusted to fit. #223's releases route and its test import the moved modules through the package. #229's check that the mapped measure concept table matches integrations/microcosm_policyengine_us/mappings.yaml moves out of the package's latest-artifact tests into lib/microcosm/mapped-measure-concept-variables.test.ts, because it reads a file of this repository that will not move with the package. A boundary test requires package modules to import only each other, Node built-ins and declared dependencies (@vercel/blob), and requires every module to be exported. The package has its own typecheck, chained into the frontend lint script, so CI runs it without a workflow change. The readers pass Next.js's fetch cache hint, which Bun ignores; a declaration seen only by the package's typecheck mirrors Next's. The golden tests now import the builder through the package and reproduce the record taken before the move, which moved unchanged. The workspace sits inside frontend/ because Vercel, CI and the publisher workflow all install from there. bunfig keeps Bun's hoisted linker so node_modules keeps its current layout. The former countries path stays as a re-export for code not yet switched to the package path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The publisher, meaning its reconciliation, repair, size limits and manifest promotion, is the part the collector will run as its worker. Leaving it in frontend/scripts would split the builder across the package boundary and give the collector move two pieces to carry. It moves into the package unchanged except for its nine import lines, keeping its command-line entry point. Its unit tests and the golden dry-run repairs move with it, and the golden tests import it through the package's publish-calibration-tree export. `bun run publish:calibration-tree` in frontend/ now runs the package module, so the workflow and its arguments are unchanged; the package also carries the same script for running it on its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The calibration smoke script now imports @policyengine/calibration-tree/countries, a workspace package that Bun resolves only through the link `bun install` creates in frontend/node_modules. The production deploy job set up Bun but never installed, so in its fresh checkout both calibration checks would stop with "Cannot find module '@policyengine/calibration-tree/countries'" and the candidate would never be promoted. CI did not notice: its jobs install first, and the workflow test only checked the step's command text. The deploy job now runs `bun install --frozen-lockfile` in frontend before the first calibration check. The step reads no secrets. It runs after the Vercel candidate upload, because `vercel deploy .` sends the repository root, and installing earlier would put node_modules into the uploaded working tree. Restoring the relative import would also work today, but the deploy will need the install once the package is published. Two tests would have caught this. A workflow test requires every job step that runs a Bun script to follow a locked frontend install in that job. A Bun test copies the checkout without node_modules, replays the deploy job's install steps, and loads the smoke script at each calibration check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…test The package boundary test found imports with regular expressions over .ts files only. It missed require() and require.resolve(), triple-slash references and every other source extension, so a relative require() of the dashboard's base-path module in a package module passed both the boundary test and the package typecheck. The regular expressions also matched import text inside string literals. The check now lives in test-support/package-boundary.ts. It parses every .ts, .tsx, .mts, .cts, .js, .jsx, .mjs and .cjs file in the package with the TypeScript compiler and collects: - static and type-only imports and re-exports; - import-equals declarations, import() expressions and import types; - require(), require.resolve(), import.meta.resolve() and mock.module(); - triple-slash path and types references. A module specifier that is not a string literal, or any use of createRequire, is reported, because the check cannot follow it. Relative imports resolve with every source extension and with TypeScript's mapping from .js names to .ts sources. Bun's Transpiler.scanImports() was not used because it drops type-only imports and triple-slash references. New tests plant packages in a temporary directory beside a stand-in dashboard module. The check must report each kind of reference, and must accept package files, Node built-ins and declared dependencies in every source format. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The deploy job authenticates to Google Cloud before it installs the frontend's dependencies, and it may request GitHub OIDC tokens. A plain `bun install --frozen-lockfile` runs the project's and its workspace packages' lifecycle scripts, and those of any trusted dependency, with that credential file and those token variables in the environment. The calibration checks need no install script, so the step now passes --ignore-scripts. With Bun 1.4.3, the version CI installs, that flag skips project, workspace and trusted-dependency scripts alike. A workflow test pins the exact install command. A Bun test plants install scripts in the checkout copy's frontend and calibration-tree manifests, replays the deploy job's install steps, and requires that none of the scripts ran. The existing checkout test confirms the smoke script still loads after an install that skips scripts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When a script runs in a directory tree without node_modules, Bun installs any missing package from the npm registry as the script imports it. In a clean checkout, `bun run frontend/scripts/calibration-deployment-smoke.ts` asks the registry for @policyengine/calibration-tree. The package is not published, so the request fails today. Once it is published, a deploy job whose workspace install went missing would load an unpinned copy from npm, not the reviewed workspace code. Both calibration checks, before and after promotion, now run `bun run --no-install`. With Bun 1.4.2 and 1.4.3 (the version CI installs), the flag takes effect before the script path, as here or as `bun --no-install run`. After the path it is passed to the script. A workflow test requires --no-install among the Bun words before the script path in both steps. The checkout tests now run each step's own Bun command, taken from its run text, against a local registry that records requests. A new test runs both commands in a clean copy without an install: each must fail on the missing workspace package without asking the registry for anything. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The package move kept frontend/lib/microcosm/countries.ts as a re-export, because telemetry-collector-client.ts type-imported the registry from there and #216 was still changing that file. #216 is now merged, so the client imports MicrocosmCountry from @policyengine/calibration-tree/countries and the re-export is deleted. The registry now has a single import path. A new test fails while any file exists at the former location, or while any dashboard module names it, through `@/` or a relative path. It reads module references from the TypeScript syntax tree, as the package boundary check does, so type-only imports, import types and mock.module() count. The use-microcosm hook's comment now points releaseCountry at the package's latest-artifact module. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#223 now checks the UK's latest-national.json whenever the registration names it: the file must carry schema_version 1, the matching line and a revision, and the tag it names must be the build its release manifest names. The golden repair harness served only {release_id}, so the UK dry-run repair stopped at "latest-national.json has unsupported schema_version undefined." For a country registered with its own pointer file, the stubbed repository now serves a pointer naming a tag of the recorded release at another commit, with that tag's folder listing and a release manifest naming the tag. The tag is not among the repository's refs, so the reconciliation the test checks is unchanged. Countries on latest.json keep the old pointer. The golden record is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dashboard groups, filters and lists calibration targets by the geography's label. Microcosm's UK local-area builds calibrate parliamentary constituencies and local authorities side by side, and 115 labels belong to one of each. In the published tree of the 19 Sep local candidate, the explorer's geography map therefore shows one "Ashfield" tile holding 18 constituency and 26 local-authority targets, and its Geography filter, the targets page and the comparison maps select both areas at once. The hierarchy validator already keys a geography by its level and code; only the label merged them. A label that more than one level uses now carries its level, for example "Ashfield (constituency)" and "Ashfield (local authority)". Labels used at one level stay as published, and so does the country's own national label, because a target with no resolvable geography falls back to it at the national level and still means the whole country. Of the 45 trees published today, only the three UK local candidates have such labels, so US, Belgian and UK national outputs are unchanged; the golden record still reproduces byte for byte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Staging page compared every candidate with the country's latest release. For the UK that is the national build, so a local-area candidate (21,523 targets in September) was set against the national release (1,168 targets): 634 targets matched, 20,889 counted as added and 534 as removed. A candidate whose run manifest records a pipeline the country registers as local-area (the UK's uk-local-candidate) is now compared with the local-area release. That release has no pointer file and is never promoted, so its most recently published build, as the dashboard manifest records it, is used; release roles come from each release manifest's dataset_role, as in the release picker. No staged receipt or run manifest records a dataset role, and the local dataset file was microcosm_uk_2025_local.h5 in September and microcosm_uk_2024_25_local.h5 in October, so the contract-validated pipeline id is the stable signal. With no published local-area release, the current release is used and the page says so. National candidates, US and Belgium are unchanged: their comparison still resolves to the current release, and the response gains a comparison_release field only for local-area candidates. The page names the release in the comparison column, the map tab and a note, and the comparison map starts from that release's build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
goldenComparisonRecord builds both sides of a comparison under Promise.all, so two withRegisteredRepositories scopes overlap. The first to finish restored the repository overrides while the second was still building, and the second then restored "unset", deleting the overrides. With a UK staging repository override set, the UK comparison case read its staging run from the override and failed. Overlapping repository scopes now share one cleared environment: the first to start saves and clears it, and the last to finish restores it. withHuggingFaceFiles had the same defect with globalThis.fetch, so its scopes now run one after another. The golden record description also names the Belgium release it covers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dashboard groups, filters and lists calibration targets by the geography's label. Microcosm's UK local-area builds calibrate parliamentary constituencies and local authorities side by side, and 115 labels belong to one of each. In the published tree of the 19 Sep local candidate, the explorer's geography map therefore shows one "Ashfield" tile holding 18 constituency and 26 local-authority targets, and its Geography filter, the targets page and the comparison maps select both areas at once. The hierarchy validator already keys a geography by its level and code; only the label merged them. A label that more than one level uses now carries its level, for example "Ashfield (constituency)" and "Ashfield (local authority)". Labels used at one level stay as published, and so does the country's own national label, because a target with no resolvable geography falls back to it at the national level and still means the whole country. Of the 45 trees published today, only the three UK local candidates have such labels, so US, Belgian and UK national outputs are unchanged; the golden record still reproduces byte for byte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Staging reconciliation threw at the first finalized run it could not publish or repair, such as a repair whose rebuilt index differs from the digest the manifest records. Every run after it then stayed unpublished on every later invocation. The September UK local-area trees are staging builds, and the level-qualified geography labels change what the builder writes for them, so repairing one would stop UK staging publication. Each run's error is now recorded as that run's "failed" outcome, with the error as its reason, and the remaining runs are still reconciled. After printing the report, the publisher exits with an error naming every failed run, so the workflow still fails. Errors before the per-run loop (an unavailable repository, incompatible runs) still stop reconciliation. reconcileStagingBuilds now takes the manifest store that release reconciliation already takes, and runPublisher accepts that store, so both can be tested without Vercel Blob. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The calibration map hierarchy document still said the dashboard does not construct labels for schema 8. buildCalibration now qualifies a geography label that more than one level uses in a build, so the document describes that rule and its exceptions, and the tree artifact document notes that published trees keep the labels they were built with. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Staging page compared every candidate with the country's latest release. For the UK that is the national build, so a local-area candidate (21,523 targets in September) was set against the national release (1,168 targets): 634 targets matched, 20,889 counted as added and 534 as removed. A candidate whose run manifest records a pipeline the country registers as local-area (the UK's uk-local-candidate) is now compared with the local-area release. That release has no pointer file and is never promoted, so its most recently published build, as the dashboard manifest records it, is used; release roles come from each release manifest's dataset_role, as in the release picker. No staged receipt or run manifest records a dataset role, and the local dataset file was microcosm_uk_2025_local.h5 in September and microcosm_uk_2024_25_local.h5 in October, so the contract-validated pipeline id is the stable signal. With no published local-area release, the current release is used and the page says so. National candidates, US and Belgium are unchanged: their comparison still resolves to the current release, and the response gains a comparison_release field only for local-area candidates. The page names the release in the comparison column, the map tab and a note, and the comparison map starts from that release's build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…kage Adds release-record.ts: it parses a dataset repository's releases.json with the rules of microcosm-emitter#24's microcosm_emitter_interface.releases (exact keys, schema version 1, safe release and build names, role identifiers, each current build listed and not withdrawn, the default release listed), names a build by its folder under releases/ (<release>/<build>), and checks that a build folder's release manifest names its release and build in its top-level fields. Nothing calls it yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… comparison arrives The Staging calibration map stored its "From" build in state and only replaced it when the stored value was not a published build. The build manifest usually responds before /microcosm/staging/compare, so the panel first stored the manifest's latest (national) build and kept it when the comparison then named the local-area release. Because the target-change query prefers a build over a release, the "Change from local-area release" tab showed the national comparison. The panel now stores only the builds the user picks. comparisonMapBuilds works out both selectors on every render from those picks and the current responses, so the defaults follow the comparison until the user picks a build. A pick of the live candidate as "To" also holds now, instead of being replaced on the next manifest refetch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
For a UK local-area candidate, the "Change from local-area release" tab still said "Current release calibration target" above a target from the compared build, and its mode help said removed targets subtract their "current-release contribution". The target detail both maps open also labelled its estimate, error, chart series and summaries "current release". The release label the Staging page already derives for the candidate (stagingComparisonBaseline, whose columnLabel is now releaseLabel because the maps use it too) is passed to StagingTargetChangeMap and to the candidate-fit map's target detail. targetChangeModeHelp and stagingTargetDetailPresentation name that release, and the detail's eyebrow now comes from the presentation. National, US and Belgian candidates keep "Current release"; the removed-target help now reads "subtract their contribution in the current release". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
newestPublishedLocalAreaRelease sorted builds with its own buildPublishedInstant, which computes the same order as the dashboard's calibrationBuildsNewestFirst: createdAt, else updatedAt, an undated build last, and ties by build ID. The package cannot import from the dashboard, so calibrationBuildsNewestFirst and its two tests move into the package's calibration-tree-manifest module, and both callers use it. No ordering changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The route's loadComparison dependency returned Promise<object>, which accepts any object. It now returns StagingComparison, the type loadStagingComparison returns, exported from the package. The route test's stub, which carries only the field the page reads, says so with an explicit cast. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A country registration can now say release_layout: "build_folders": its dataset repository publishes each build in releases/<release>/<build>/ and lists every build and each release's current build in releases.json. For such a country the tree publisher reads releases.json at the head commit and nothing else (no tags, no latest*.json pointer files, no listing of releases/): - reconciliation publishes every listed build whose folder holds calibration diagnostics and whose release_manifest.json names the same release and build, pinned to the commit that wrote the folder and dated by the build's published_at; - the latest release becomes the current build of the record's default release; without one, reconciliation fails without moving it; - a build's release id in the dashboard manifest is its folder, <release>/<build>, and its comparison metadata names the build; - --latest and --release <release>/<build> read the same record; - entries published from tags or release folders stay as they are, and the timestamp migration leaves build-folder entries alone. No country uses the layout yet, so nothing changes in production. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The README's API table said only that staging/compare diffs a candidate against a published release. It now says that release=latest resolves to the newest published local-area build for a run whose pipeline is registered as local-area, documents the comparison_release field, and documents the staging registration's local_area_pipelines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
For a country whose registration says release_layout: "build_folders": - the release picker (/api/microcosm/releases) lists every build releases.json names, as <release>/<build>, dated by its published_at, with its release's role (the record's default release is the default dataset) and its withdrawn flag; a build is selectable for comparison once the dashboard manifest holds its tree. Nothing else is read from the dataset repository: no listing of releases/, no tags, no pointer files; - the Staging page compares a local-area candidate with the newest published build of a local-area release releases.json lists; - the release loader accepts a build folder as a release, reads its files from releases/<release>/<build>/ at the pinned commit, keeps the folder as its release id when its diagnostics name only the release, and cites that folder and commit in the target investigation packet; - the picker labels such a build by its release and whole build name. How the picker shows a withdrawn build is left open; the response carries the flag. No country uses the layout yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The UK registration now says release_layout: "build_folders", so its tree publisher, release picker and Staging comparison baseline read releases.json and the build folders it lists, and nothing else: no tags, no latest-national.json or latest.json, no listing of releases/. Must not be deployed before Microcosm's one-time migration (P1) has written the UK build folders and releases.json. The UK keeps pointer_path, unread while it publishes build folders, so that removing release_layout returns it to the release-folder readers until Microcosm deletes the pointer files (P5). The tests of those readers now name the layout they cover. The artifact and country docs describe the build-folder layout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tree builder is about to move out of the dashboard into its own package. Its outputs are byte-exact contracts: the index records every part's raw-JSON SHA-256 and gzip size, build ids derive from the inputs, and a repair refuses any change to the index digest. So the move has to be proved to change no byte. This records, from the builder as it is today, what it writes for representative inputs built from the fixtures the module tests already use. The inputs cover US and UK, releases and staging runs (version 1, version 2, and a staged dataset bundle), Belgium, and a UK release with reported loss attribution. For each, the record holds every part's path, raw JSON digest and size, gzip size and deflate-stream digest, plus the build id. It also records release-to-staging comparison bundles built through the comparison reader, the explorer's root responses read back from the stored bytes, and the manifest serialization. The gzip header's operating-system byte varies by platform, so the header is asserted separately and the digest covers the stream after it. A second test drives the publisher's dry-run repair of the recorded US, UK and Belgium releases against a stubbed Hugging Face repository. The publisher refuses a rebuilt index whose digest differs, so a successful repair proves its own reads and hashing reproduce the recorded index. The stubbed repository serves each country's release pointer at the path the publisher reads it from: `latest.json`, or `latest-national.json` for the UK since #223. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tree builder and the schema 6 code the dashboard shares with it are moving to the collector as a Bun worker, and its types and readers will be published as a package the dashboard imports. That needs the builder to be one self-contained unit with explicit dependencies and its own tests. Inside frontend/lib nothing stopped a dashboard-only import from reaching it. The package, @policyengine/calibration-tree in frontend/packages/, is the builder's import closure: the schema 6 types, parsers, serializers and bundle builder, the comparison builder, Blob I/O behind the existing injectable client, and the release and staging readers the builder reads from, including the country registry. Its 30 modules move unchanged apart from ten import lines that used the dashboard's path alias. Their tests and fixtures move with them. Next.js routes, the HTTP helpers and the client hooks stay in the dashboard and import the package through its subpath exports. One of the 30 is release-cut.ts, added by #223. It parses the Hugging Face tag names Microcosm gives each build of a UK release, and latest-artifact.ts and calibration-tree-manifest.ts import it, so it moves with them and is exported as release-cut. The dashboard's shared formatter and the publisher now import it through that export. Two other additions since this branch began are adjusted to fit. #223's releases route and its test import the moved modules through the package. #229's check that the mapped measure concept table matches integrations/microcosm_policyengine_us/mappings.yaml moves out of the package's latest-artifact tests into lib/microcosm/mapped-measure-concept-variables.test.ts, because it reads a file of this repository that will not move with the package. A boundary test requires package modules to import only each other, Node built-ins and declared dependencies (@vercel/blob), and requires every module to be exported. The package has its own typecheck, chained into the frontend lint script, so CI runs it without a workflow change. The readers pass Next.js's fetch cache hint, which Bun ignores; a declaration seen only by the package's typecheck mirrors Next's. The golden tests now import the builder through the package and reproduce the record taken before the move, which moved unchanged. The workspace sits inside frontend/ because Vercel, CI and the publisher workflow all install from there. bunfig keeps Bun's hoisted linker so node_modules keeps its current layout. The former countries path stays as a re-export for code not yet switched to the package path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The publisher, meaning its reconciliation, repair, size limits and manifest promotion, is the part the collector will run as its worker. Leaving it in frontend/scripts would split the builder across the package boundary and give the collector move two pieces to carry. It moves into the package unchanged except for its nine import lines, keeping its command-line entry point. Its unit tests and the golden dry-run repairs move with it, and the golden tests import it through the package's publish-calibration-tree export. `bun run publish:calibration-tree` in frontend/ now runs the package module, so the workflow and its arguments are unchanged; the package also carries the same script for running it on its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The calibration smoke script now imports @policyengine/calibration-tree/countries, a workspace package that Bun resolves only through the link `bun install` creates in frontend/node_modules. The production deploy job set up Bun but never installed, so in its fresh checkout both calibration checks would stop with "Cannot find module '@policyengine/calibration-tree/countries'" and the candidate would never be promoted. CI did not notice: its jobs install first, and the workflow test only checked the step's command text. The deploy job now runs `bun install --frozen-lockfile` in frontend before the first calibration check. The step reads no secrets. It runs after the Vercel candidate upload, because `vercel deploy .` sends the repository root, and installing earlier would put node_modules into the uploaded working tree. Restoring the relative import would also work today, but the deploy will need the install once the package is published. Two tests would have caught this. A workflow test requires every job step that runs a Bun script to follow a locked frontend install in that job. A Bun test copies the checkout without node_modules, replays the deploy job's install steps, and loads the smoke script at each calibration check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 22, 2026
Conflicts, resolved to keep both sides: - publish-calibration-tree.ts: main's reconciliation reports a release that fails and goes on (#253), and reads each tag's commit from /revision (#273). The build-folder inventory joins main's loop unchanged, keeps dating a build by releases.json, and returns no failed lookups yet. main's selectLatestRelease now takes the function that reads the latest release, so a build-folder country selects releases.json's current build of its default release and a missing one becomes the report's latestReleaseFailure instead of an exception. - publish-calibration-tree-golden.test.ts: main's report-based assertion, with this branch's release-folder layout argument. Tests adjusted to the merged behaviour: - main's eight new UK reconciliation tests failed after the merge (the UK registration selects build folders, so they read releases.json), and now name the release-folder layout they cover. - The build-folder test of a record without a current build expected an exception; it now expects the report's latestReleaseFailure, as main's tests do for the pointer file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main's reconciliation (#253) reports a release whose commit cannot be read as failed and reconciles the others. A listed build whose folder listing fails still stops the whole build-folder reconciliation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A listed build whose folder listing or release manifest cannot be read is now a failed outcome, and the other listed builds are still reconciled, as main's reconciliation does for a release whose commit cannot be read. This also covers a release manifest that is not JSON, which used to stop the whole reconciliation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tting Every reader is to read a country's releases from releases.json and its build folders when the dataset repository has releases.json, and from its tags and pointer files when it has none, so a repository's switch needs no dashboard change: - No registration names a release layout (countries). - The tree publisher reconciles a repository with releases.json from it whatever its country (Belgium here), and the UK repository without it from its tags and pointer files. A single-build publication takes a build folder only where releases.json exists, and a tag only where it does not. - The release picker's route and the Staging baseline do the same. - Only Hugging Face's "entry not found" 404 means no record: a 404 that says the revision is missing, an unreadable record or a refused one is an error, with no fallback to tags or pointer files. - The Staging baseline is the newest local_area build that is not withdrawn, and the picker labels a withdrawn build "Withdrawn". - Fixture releases use the roles national and local_area. The old-layout test repositories now answer releases.json with Hugging Face's "entry not found" 404 (test-support/releases-record-responses.ts), and the tests no longer pass a layout argument. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…says so The release_layout registration setting is gone. Every reader now asks the dataset repository for releases.json: - With it, the readers read it and its build folders and nothing else. - Without it (Hugging Face's 404 for the file), they read the tags and pointer files as before. - A 404 whose error code says the repository or revision is missing, any other failure, or a record the writer's rules refuse is an error. The picker answers 502, reconciliation publishes nothing and leaves the latest release as it was, and Staging has no baseline. No reader falls back to tags or pointer files (releasesRecordFromResponse). The tree publisher reads releases.json at the commit it publishes, so reconciliation, --latest, --release and the promotion check all follow the repository. A build folder is refused for a repository without releases.json, and a tag for one with it. The release route and the Staging baseline read it at the configured revision (loadReleasesRecord, recordedReleaseInventory). The Staging baseline skips withdrawn builds, and the picker labels them "Withdrawn". The docs describe the detection, and the roles national and local_area. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comments only: three test comments still spoke of a registration that publishes build or release folders. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… build Review round 1 found that resolveHfBuildFolderSha sends its Hugging Face request without a timeout, unlike every other request in the locator and the publisher, so a listing that never answers holds the publisher job until its 30-minute limit. The listing should ask for the same 30-second timeout, and a stalled one should fail only its own build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
resolveHfBuildFolderSha now aborts its Hugging Face request after 30 seconds, as the module's other lookups do, so a listing that never answers fails only its own build instead of holding the publisher job. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
--latest and --release now upload, register and promote through the same CalibrationManifestStore as reconciliation. Its default is the Blob store, whose calls are the ones these modes made before, so production behaviour is unchanged. Tests can now give these modes a store in memory. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… named Review round 1's quality points, as tests: - With releases.json, --release of another build is published without the promotion check listing the current build's folder. --release of the current build succeeds even when that folder can't be read again, and leaves the latest release to reconciliation. --latest publishes and promotes the current build (this one already passed). - Without releases.json, reconciliation resolves main's head once, and --latest reads the pointer file at the commit where releases.json was looked for. - When the current build is ineligible, the latest-release failure gives the reason, not "<id>@ is not completely published". - The picker's Latest option is labelled Withdrawn when the latest release is a withdrawn build. These pass already and guard decisions 1 and 2: - a 404 without an error code means no record; - RepoNotFound, RevisionNotFound, 503 and 401 answers are errors; - a refused releases.json leaves a local-area Staging candidate without a baseline, rather than comparing it with the current release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…st failed - The promotion check, for a repository with releases.json, compares the published build with the record's current build by name, and reads the current build's folder only when they match. If that read fails, the build stays registered, a warning says the latest release was not moved, and the run succeeds; the next reconciliation moves it. - Without releases.json, the pointer file is read at the commit where releases.json was looked for: --latest passes that commit to readUpstreamLatest, and so do reconciliation and the promotion check, so main's head is resolved once per step. - When the current build of the default release is ineligible, the latest-release failure gives its reason. - The picker's Latest option carries " · Withdrawn" when the latest release is a withdrawn build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Documentation only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Nov 4, 2026
Brings in microcosm-releases-json-readers at 2571995: the build folder listing's timeout, single-build modes through the manifest store, the promotion check by name, one head lookup per step and the docs. No conflicts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…failure Review round 2: since 746e2c4, when the current build's folder listing fails during reconciliation, the latest-release failure says "releases.json lists it, but reconciliation found no candidate", because recordedLatest looks for the reason among the ineligible builds only. It should name the read failure. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
recordedLatest now looks for the current build among the failed builds as well as the ineligible ones. A failed one gives #253's "failed to reconcile, so the latest-release selection was not changed", followed by the read failure, for example "Hugging Face build folder lookup failed with 503." Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CalibrationManifestStore.uploadBundle is now required, and publishRelease and staging reconciliation no longer fall back to the Blob upload when a store leaves it out. The Blob store defines it, as before, so production is unchanged; a store in a test can no longer send a bundle to the real Blob store. The golden tests' two dry-run stores gain an upload that refuses, as their manifest writes already do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Nov 4, 2026
Brings in microcosm-releases-json-readers at d06face: the current build's read failure in the latest-release message, and the store's required upload method. No conflicts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 #240
Every reader of a Microcosm dataset repository now asks that repository for
releases.json. A repository that has it is read from it and its build folders only. A repository that has none is read from its tags andlatest*.jsonpointer files, as today. No country registration names the layout any more, so when Microcosm's switch commit writes the UKreleases.jsonand deletes the UK pointer files, the dashboard follows with no change of its own.Merging is safe before the switch. Until a repository has
releases.json, its readers give the same results as onmain.releases.json. Each publisher step reads it at the commit it resolves for main's head, and the pointer file at that same commit.--release <tag>without--shanow also resolves the head once, which it didn't need before.The layout Microcosm publishes
release-record.tsreadsreleases.jsonwith the rules of microcosm-emitter'smicrocosm_emitter_interface.releases: at most 4 MiB, exact keys,schema_version1, safe release and build names, role identifiers, eachcurrentlisted and not withdrawn, anddefaultlisted.How a reader finds the layout (
releasesRecordFromResponse)releases.jsonreleases/RepoNotFound/RevisionNotFoundRepoNotFoundorRevisionNotFound, any other status, or a record the rules refuseAn error means:
/api/microcosm/releasesanswers 502 for that country, with the reason.The readers
publish-calibration-tree.ts): it readsreleases.jsonat the commit it publishes. For reconciliation and the promotion check, that is the head ofmain; for--latestand--release, it is--shaor the head ofmain. All release modes write through the same manifest store.calibration_diagnostics.jsonand whoserelease_manifest.jsonnames the samereleaseandbuild.published_at.failed, and the other builds are still reconciled.--release <release>/<build>is refused for a repository withoutreleases.json, and--release <tag>for one with it.--latestor--release: it compares the published build with the record's current build by name, and reads the current build's folder only when they match. If that read fails, the build stays registered, a warning says the latest release was not moved, and the run succeeds; the next reconciliation moves it.uploadBundleis required, so a store in a test can never fall back to a real upload.loadReleasesRecordreads the record at the configured revision, andrecordedReleaseInventorylists every build as<release>/<build>, dated bypublished_at, with its release's role and itswithdrawnflag. Without a record, the picker lists folders and tagged builds as before.stagingBaselineReleasesuses the same record. The local-area baseline is the newest published build of alocal_arearelease that is not withdrawn.<release>/<build>as a release id and reads that folder at the pinned commit, whatever the repository's layout.Decided on 2026-10-29 and 2026-11-04, and applied here
release_layoutregistration field from the first version of this PR is gone. Detection is what lets the switch need no dashboard change.releases.jsonis refused, as in the table above.nationalandlocal_area(microcosm#1505). The fixtures and docs use those names. The classification already treated any role containinglocal_areaas local-area.Compatibility with published entries
releases.json, reconciliation no longer repairs them and the picker no longer lists them.releases.jsonwere removed again, its readers would return to tags and pointer files. Build-folder entries already in the manifest are then skipped by the tag listing (loadPublishedCutReleases).<release>/<build>entry. That older code refuses release ids containing/.releases.jsonthrough it (revalidate). Next 16.2.6 stores only status-200 responses, so the "noreleases.json" 404 is never cached, and the first request after the switch commit reads the record. Neither reader reads a pointer file.Before merging
release_manifest.jsonmust have top-levelreleaseandbuildstrings equal to the folder's names; emitter#24'scheck_repositoryenforces this.main. The production code before this PR can't read a<release>/<build>entry, so this PR must be deployed before Microcosm's switch commit lands in the UK repository.Environment variables
This PR adds no environment variable and changes no workflow or deployment file. The changed readers use variables that already exist:
HF_TOKEN(orHUGGINGFACE_TOKEN)releases.jsonHF_TOKENpublish-calibration-tree.ymlsets it in thepublishjob'senv:block, and the secret exists.HF_TOKEN(orHUGGINGFACE_TOKEN)releases.json(picker, Staging)deploy.ymlpasses the repository Actions secretHF_RELEASE_READ_TOKEN(it exists) todeploy-dashboard.sh, which sets it withvercel deploy --env HF_TOKEN=…on production deployments. The Vercel project also stores its own ProductionHF_TOKEN, and Previews use the Preview one. Which value a production deployment uses, and the token's scope, are unverified. It already reads the same private UK repository.BLOB_READ_WRITE_TOKENenv:block; the secret exists.POPULACE_UK_HF_REPO,POPULACE_UK_HF_REVISION(and the other countries'repo_env/revision_env)Commits and tests, review round 2026-11-04
Only the tests this round adds or changes, plus the existing tests that use a test repository fake this round changes, were run locally. Each was selected by exact name:
bun test <file> -t "^(<names>)$". CI runs the rest. All fixtures are synthetic; nothing was read from or written to Hugging Face or Vercel Blob.6c3a498main(#253 to #276) with a merge commit. There were two conflicts: the publisher and its golden test. Main's reconciliation reports a failed release and moves on (#253). The build-folder inventory joins that loop, andselectLatestReleasetakes the function that reads the latest release.publish-calibration-tree.test.tsfailed: the UK then readreleases.json. The build-folder test "without a current build of the default release, …" expected an exception that main's reporting replaces.latestReleaseFailureand passed. The 65 tests the branch changes passed.aac028e/5c4b634publish-calibration-tree-build-folders.test.ts -t "^a build whose folder cannot be read is reported as failed, and the other listed builds are still reconciled$"failed onexpect(received).resolves.toBeDefined()(the promise rejected).c0822b0/ba95f64release_layout, the error rule, Withdrawn, the Staging baseline, and the roles.publish-calibration-tree-golden.test.ts -t "^a dry-run repair of the recorded"(3: UK, US and Belgium). 28 failed and 27 passed (details below).9cafbdblatest-artifact.test.ts -t "^a build-folder entry in the manifest does not stop the tagged builds being listed$"passedThe 28 failures at
c0822b0:countries.test.ts, 1: "no registration says how its releases are published…" failed on the UK registration'srelease_layout.loadReleasesRecorddependency, so at red the handler, still asking the registration for the layout, answered 502 instead of 200. These tests didn't type-check at red.use-microcosm.test.ts, 1: the Withdrawn label, failed ontoEndWith.latest-artifact.test.ts, 1: "the release picker reads no record from a repository without releases.json" failed onresolves.toBeNull().publish-calibration-tree-build-folders.test.ts, 3: the single-build refusals (toThrow), a Belgian repository withreleases.json(resolves.toBeDefined()), and aRevisionNotFound404 (toThrow).publish-calibration-tree.test.ts, 13: each old-layout UK test, because the UK readers readreleases.jsonand refused its 404 ("Hugging Face returned 404 for releases.json."), plus the new "a repository without releases.json is reconciled from its tags and pointer files" (resolves.toBeDefined()).-tpattern), for the same reason.staging-comparison-release.test.ts, 2: the withdrawn baseline (toBe) and the repository withoutreleases.json(resolves.toBeDefined()).The 27 that passed at red guard behaviour that already held:
RevisionNotFoundtest (it already refused any non-200);readUpstreamLatest).bun run lint(tsc --noEmitfor the app and the package) is clean at9cafbdb.Review round 1, 2026-11-04
The review found one low-severity bug, five quality points and some test gaps. Each fix is test first, except the store change (a refactor with no behaviour change) and the docs.
b26c767/640f8e9resolveHfBuildFolderShahad no request timeout. It now aborts after 30 seconds, and a stalled listing fails only its own build.publish-calibration-tree-build-folders.test.ts -t "^a build folder listing that never answers times out after 30 seconds and fails only its own build$"failed ontoEqual: the request asked for no timeout.8213181--latestand--releaseupload, register and promote through the manifest store, whose default is the same Blob calls, so tests can give them a store in memory.a16ba8c/746e2c4releases.jsonwas looked for. Messages: the ineligible current build's reason; Withdrawn on the Latest option.--latestwithreleases.json; threereleasesRecordFromResponsetests (a 404 without a code,RepoNotFound/RevisionNotFound, 503 and 401); the refused record leaving a local-area Staging candidate without a baseline; and the timeout test.2571995The 6 failures at
a16ba8c:publish-calibration-tree-build-folders.test.ts:toBe: the folder was listed);resolves.toBeUndefined(): the run failed);toContain).publish-calibration-tree.test.ts:toHaveLength: 2 head lookups);toBe: the pointer was read at the later commit).lib/api/hooks/use-microcosm.test.ts: "the picker's Latest option is labelled Withdrawn when the latest release is a withdrawn build" (toEndWith).After
2571995, the earlier rounds' 55 tests and the golden dry-run repairs pass again, andbun run lintis clean.Review round 2, 2026-11-04
7542eb8/a6b9879746e2c4: when the current build's folder listing failed (timeout or server error), the latest-release failure said "releases.json lists it, but reconciliation found no candidate".recordedLatestnow also looks among the failed builds, and says "Latest release failed to reconcile, so the latest-release selection was not changed:", followed by the read failure.publish-calibration-tree-build-folders.test.ts -t "^when the current build's folder cannot be read, the latest-release failure names the read failure$"failed ontoContain, receiving the "found no candidate" messaged06faceCalibrationManifestStore.uploadBundleis required, andpublishReleaseand staging reconciliation lose their fallback to the Blob upload. The golden tests' two dry-run stores gain an upload that refuses, as their manifest writes already do.bun run lintclean. The 6 golden tests using those stores passed (the 4 release repairs and the 2 refused staging repair tests), and so did this PR's tests from earlier rounds.Earlier rounds
ba0bdd4,59b29a8,aa5408e,4b68e4c), red-green throughout.708a186/1c311ab);b32e1f4/53845a5);currentnamed like a built-in object property (71bef39/e03ef2e);79cd203/43348c0).main(List releases published only as a Hugging Face tag in the release picker #258) in (6f0e193). Fixed, test first:13564ff/50c501f);9bd2471/5a53794).🤖 Generated with Claude Code