Repository navigation
Extract the calibration tree builder into a self-contained workspace package - #220
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This was referenced Oct 19, 2026
anth-volk
force-pushed
the
calibration-tree-package
branch
from
October 19, 2026 20:22
6bd770f to
562be80
Compare
anth-volk
changed the base branch from
main
to
uk-release-cuts-calibration-fit
October 19, 2026 20:23
anth-volk
force-pushed
the
calibration-tree-package
branch
from
October 19, 2026 21:09
562be80 to
3fed47b
Compare
anth-volk
force-pushed
the
calibration-tree-package
branch
from
October 20, 2026 13:15
ca4edf5 to
c60479c
Compare
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>
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>
anth-volk
force-pushed
the
calibration-tree-package
branch
from
October 20, 2026 14:13
c60479c to
4d03579
Compare
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 #218
Summary
This moves the calibration tree builder (about 15.5k lines) into a self-contained Bun workspace package,
@policyengine/calibration-treeinfrontend/packages/calibration-tree/. Its contents:The dashboard and the publisher both import it. It is a refactor only: a golden record taken from the current code, before anything moved, shows the package reproduces every byte the builder writes.
The package has no imports from the rest of
frontend/(no@/aliases, Next.js orserver-only), declares its dependencies, and carries its own tests and typecheck.bun run publish:calibration-treeand.github/workflows/publish-calibration-tree.ymlare unchanged. Moving the package into the collector repository and publishing it to npm is out of scope.Stacked on #223
This PR is now stacked on #223 (María's UK release fixes). Its base is #223's branch,
uk-release-cuts-calibration-fit, so the diff here shows only the package move. The plan has #223 merge first; when it does, GitHub retargets this PR tomain.649a299, which mergesmainat5cc4723. The old head was6bd770f. The rebase dropped this branch's own merge ofmain(1a292d2), becausemainis now part of the base. The branch was pushed with--force-with-leaseagainst6bd770f.gh stackmanages the three branches as GitHub stack #230: Publish Microcosm UK release cuts, open the UK page on the national line, and show local candidates on Staging #223, then this PR, then Keep same-named UK constituencies and local authorities apart in the calibration views #237.gh stack initadopted Publish Microcosm UK release cuts, open the UK page on the national line, and show local candidates on Staging #223's branch and this one,gh stack addadopted Keep same-named UK constituencies and local authorities apart in the calibration views #237's, andgh stack linkcreated the stack on GitHub.8a01c06,5fea679,3614ca7,1079fa9).gh stack rebase --no-trunkreplayed this branch and Keep same-named UK constituencies and local authorities apart in the calibration views #237 onto Publish Microcosm UK release cuts, open the UK page on the national line, and show local candidates on Staging #223's new head1079fa9without conflicts.--no-trunkrebases only the branches above Publish Microcosm UK release cuts, open the UK page on the national line, and show local candidates on Staging #223, so Publish Microcosm UK release cuts, open the UK page on the national line, and show local candidates on Staging #223 itself was not rebased. Git's rename detection carried all four fixes intofrontend/packages/calibration-tree/src/publish-calibration-tree.ts,staging-artifact.tsand their tests.gh stack submit --autothen pushed. Right before it, Publish Microcosm UK release cuts, open the UK page on the national line, and show local candidates on Staging #223's branch was confirmed identical locally and on GitHub, so its push changed nothing; the branch's activity log shows no push after1079fa9.3fed47b, below) adapts the golden repair harness to Publish Microcosm UK release cuts, open the UK page on the national line, and show local candidates on Staging #223's stricter pointer check.ca4edf5(commit 11 below), fixes the golden harness for overlapping builds and names the Belgium release in the golden record's description. This description's environment table now says that publishing is paused, and "Commits before3fed47bfail one golden test" records a history finding. Publish Microcosm UK release cuts, open the UK page on the national line, and show local candidates on Staging #223 is unchanged at1079fa9.gh stack rebase --no-trunkcarried Keep same-named UK constituencies and local authorities apart in the calibration views #237 and Compare UK local-area Staging candidates with the local-area release #239 onto this branch, andgh stack submit --autopushed, after Publish Microcosm UK release cuts, open the UK page on the national line, and show local candidates on Staging #223's branch was confirmed identical locally and on GitHub.What the rebase changed
calibration-release-locator,calibration-tree-manifest,latest-artifact,staging-artifact, their tests, thecountriestests, and the publisher and its tests. Each is unchanged from Publish Microcosm UK release cuts, open the UK page on the national line, and show local candidates on Staging #223's version apart from the import lines listed under "Identity proof".countries.ts, by hand: in commit 2 the former path becomes a re-export, so git treats it as a modified file, not a rename. Publish Microcosm UK release cuts, open the UK page on the national line, and show local candidates on Staging #223's change to it (thepointer_pathfield andcountryPointerPath(), which point the UK atlatest-national.json) was applied to the package copy, which now matches Publish Microcosm UK release cuts, open the UK page on the national line, and show local candidates on Staging #223's file exactly.release-cut.tsmoves into the package (100% similarity) and is exported as@policyengine/calibration-tree/release-cut. Publish Microcosm UK release cuts, open the UK page on the national line, and show local candidates on Staging #223 added it to parse the Hugging Face tag names Microcosm gives each build of a UK release. The package'slatest-artifact,calibration-tree-manifestand publisher import it, and the boundary test rejects their./release-cutimports while it sits infrontend/lib.components/shared/format.tsnow imports it from the package.main) comparesMAPPED_MEASURE_CONCEPT_VARIABLESwithintegrations/microcosm_policyengine_us/mappings.yaml, a file in this repository that won't move with the package. Inside the package its relative path pointed at a file that doesn't exist (ENOENT). It now lives infrontend/lib/microcosm/mapped-measure-concept-variables.test.tsand imports the table from the package.a4d719e. Only the stubbed Hugging Face repository in the publisher's golden repair test changed. Publish Microcosm UK release cuts, open the UK page on the national line, and show local candidates on Staging #223 makes the publisher read the UK's current release fromlatest-national.json, so the stub now serves each country's pointer at the pathcountryPointerPath()gives.Red and green for the rewritten commits
SHAs in this section are from the first restack. The second restack only replayed these commits; their current SHAs are under "Commits".
b7c9b18). Red: 1 of 16 failed. The UK dry-run repair stopped at "Unexpected Hugging Face request …/latest-national.json". Green: 16 passed.469b17b).calibration-tree-manifest.ts: "./release-cut" does not resolve inside the package, and the same forlatest-artifact.ts;latest-artifact.test.ts: 1 failed (ENOENT onmappings.yaml);Cannot find module '@/lib/microcosm/…'in the releases route and its test.format, the releases and tree route tests, the publisher's tests and both golden suites);bun run lint(dashboard and package typecheck) clean.73aa8e7). Only import conflicts were resolved; the original red (3 failed) stands. Green, rerun at the commit: 18 passed across 3 files (boundary, publisher unit and golden repair tests);bun run lintclean.Tests run for the rebase
format.test.ts, 8 passed, andbun run lint, clean.next buildand the Python tests, because the machine is shared. CI runs them.Commits
d426778)bun test619 passed, 0 failed;tsc --noEmitclean;next buildpassed. The first build attempt failed to fetch Google Fonts, a network error; the rerun passed.6886fce)Cannot find module './countries'. The golden suite failed onCannot find module '@policyengine/calibration-tree/calibration-comparison-blob'.bun test, run from the package directory).bun test622 passed, 0 failed;bun run lintclean (frontendtscplus the package typecheck);next buildpassed;bun install --frozen-lockfileclean;bun run publish:calibration-treewithout credentials parses its arguments and stops at "BLOB_READ_WRITE_TOKEN is required.";next/serverand@/lib/base-pathimports to a package module produced 2 violations (reverted).035e204)./publish-calibration-treeand@policyengine/calibration-tree/publish-calibration-tree, and the boundary test flagged the missing module and export.bun test309 passed;bun test622 passed, 0 failed;bun run lintclean;next buildpassed;bun run publish:calibration-tree -- --country uk --reconcile-staging --sha <40 hex> --dry-run, parses and stops at the credential check, as does the package's own script;pytest frontend/tests/test_deploy_workflow.py: 12 passed.ed710c0)@policyengine/calibration-tree/countries, which resolves only through the workspace linkbun installcreates infrontend/node_modules.deploy.ymlset up Bun but never installed, and both calibration checks runbun run frontend/scripts/calibration-deployment-smoke.tsfrom the repository root. In agit archiveof the publisher-move commit (thene78b672) that command stops withCannot find module '@policyengine/calibration-tree/countries'; onmainit reaches argument parsing. CI missed it because its jobs install first and the workflow test only checked the step's command text.bun install --frozen-lockfileinfrontend, after the Vercel candidate upload and before the first calibration check. It runs after the upload becausevercel deploy .sends the repository root, so the uploaded files never includenode_modules. The relative import was not restored, because the deploy needs the install once the package is published.test_every_bun_script_step_runs_after_its_job_installs_locked_frontend_dependencieslisted both calibration steps indeploy.yml. It checks every job in every workflow: a step that runsbun run,bun testorbun <file>must follow abun install --frozen-lockfilestep infrontend.test_deploy_installs_locked_frontend_dependencies_without_credentials_after_the_vercel_uploadfound no install step.calibration-deployment-smoke-checkout.test.tscopies the checkout's files withoutnode_modules, replays the deploy job'sbun installsteps in order, and loads the smoke script at each calibration step. Both steps failed withCannot find module '@policyengine/calibration-tree/countries'.test_deploy_workflow.py15 passed; Bun 22 passed (the checkout test and the 21 smoke-script tests). The checkout test takes about 2 s; the install from Bun's cache takes 0.8 s.bun run lintclean;actionlintclean ondeploy.yml.72c3bc6)Gap, found in review: the boundary test matched imports with regular expressions over
.tsfiles only, so it missedrequire(),require.resolve(), triple-slash references and other extensions. A relativerequire()of the dashboard's base-path module passed both the boundary test and the package typecheck.Fix:
src/test-support/package-boundary.tsparses every.ts,.tsx,.mts,.cts,.js,.jsx,.mjsand.cjsfile in the package with the TypeScript compiler. It collects:import()expressions and import types;require(),require.resolve(),import.meta.resolve()andmock.module();pathandtypesreferences.A specifier that is not a string literal, or any use of
createRequire, is reported, because the check cannot follow it. Bun'sTranspiler.scanImports()was not used: a probe showed it dropsimport type, triple-slash references,require.resolve(),typeof import()andmock.module().Red: 6 of 9 failed.
require()of the dashboard's base-path module, the other source extensions, triple-slash references, and computed specifiers withcreateRequire../labels.jstolabels.ts.Green: 9 passed.
Checks run:
bun test315 passed;bun run lintclean;frontend/: 31 passed;pytest frontend/tests/test_deploy_workflow.py: 15 passed;require("../../../lib/base-path")toprogram-label.tsnow fails the boundary test withsrc/program-label.ts: "../../../lib/base-path" does not resolve inside the package, while the package typecheck still passes (reverted).1190deb)bun install --frozen-lockfile --ignore-scripts. CI'ssetup-buninstalls Bun 1.4.3. With that version, a probe confirmed that a plain frozen install runs the project's, a workspace package's and a trusted dependency'spostinstall, and that the flag skips all three.test_deploy_dependency_install_skips_lifecycle_scripts: the command wasbun install --frozen-lockfile.frontendandcalibration-treemanifests ran in both.bun run lintclean;actionlintclean.62cd502)node_modules, Bun installs any missing package from npm as a script imports it. In a clean checkout the smoke script asks the registry for@policyengine/calibration-tree. That fails today, but once the package is published, a job whose workspace install went missing would load an unpinned copy from npm.bun run --no-install frontend/scripts/calibration-deployment-smoke.ts. On Bun 1.4.2 and 1.4.3,bun run --no-install <file>andbun --no-install run <file>made no registry request.bun run <file> --no-installpasses the flag to the script, and Bun still asks the registry.test_every_calibration_check_turns_off_bun_auto_install: neither step had--no-installbefore the script path./@policyengine%2fcalibration-tree.runtext up to the script path, so they use the workflow's flags.bun run lintclean;actionlintclean.1a292d2, a merge commit): dropped by the rebase onto Publish Microcosm UK release cuts, open the UK page on the national line, and show local candidates on Staging #223, whose head already containsmain.38da4f0)telemetry-collector-client.tsimportsMicrocosmCountryfrom@policyengine/calibration-tree/countries, andfrontend/lib/microcosm/countries.tsis deleted. Theuse-microcosmhook's comment now pointsreleaseCountryat the package'slatest-artifactmodule.country-registry-imports.test.tsfoundlib/microcosm/countries.tsat the former location, and the referencelib/server/microcosm/telemetry-collector-client.ts: "@/lib/microcosm/countries". The test reads module references from the TypeScript syntax tree, so type-only imports, import types andmock.module()count.mock.module()and animport()type of the old path was reported, then removed.bun test315 passed; package typecheck clean;bun run lintclean;next buildpassed;test_deploy_workflow.py: 18 passed.3fed47b)latest-national.jsonwithoutschema_version1, the matchinglineand arevision, and checks that the release manifest at the named tag names it. The harness served only{release_id}.bun run lintclean.ca4edf5, from review)goldenComparisonRecordbuilds both sides of a comparison underPromise.all, so twowithRegisteredRepositoriesscopes overlap. The first to finish restored the repository overrides while the second was still building. The second then restored "unset", which deleted the overrides.withHuggingFaceFilesswappedglobalThis.fetchin the same way.POPULACE_UK_STAGING_HF_REPOset, the UK comparison golden case run on its own failed with "artifact calibration_diagnostics is declared but absent", because its staging side read the overridden repository. With the fix it passes and the override is still set afterwards.docs/calibration-tree-artifacts.mdand the harness's header comment now say the record covers US, UK and Belgium releases, US and UK staging runs, and a US and a UK release-to-staging comparison.test-support/calibration-tree-golden-cases.test.tsfailed. In one, the second scope read the override after the first finished. In the other, the second Hugging Face scope's request reached the outsidefetch. For this runwithHuggingFaceFileswas already exported, unchanged, so the file loaded.bun run lintclean.Commits before
3fed47bfail one golden testA review found that every commit before
3fed47bfails one golden test. At38da4f0it is "a dry-run repair of the recorded UK hierarchy release rebuilds its recorded index", which stops with "latest-national.json has unsupported schema_version undefined." The rebase onto #223 brought in #223's stricter pointer check, and3fed47bupdates the harness for it. History isn't rewritten here.main, and each fails that test when checked out, for example duringgit bisect.The merge method is the reviewer's choice.
Package boundary
The package is the transitive import closure of the publisher: 31 modules and 15,964 lines (on #223), plus their tests and fixtures. That closure already imported nothing outside itself except Node built-ins and
@vercel/blob. It contains:calibration-tree-artifact,calibration-tree-bundle,calibration-tree-manifest,calibration-tree,calibration-explorercalibration-comparison-bundle,calibration-comparison-blob,calibration-build-comparison,target-changecalibration-tree-blob,calibration-release-locatorlatest-artifact,staging-artifact,staging-contract,countries,release-cut(from #223),reforms, the target readers, loss attribution, label helperspublish-calibration-tree, keeping its command-line entry pointBlob I/O moved into the package. It already sits behind the injectable
CalibrationTreeBlobClient, which defaults to@vercel/blob. The plan keeps both Vercel Blob stores at first, and the collector becomes their writer. That writer needs the same upload verification and manifest compare-and-swap, and those are part of the byte-exact contract. Removing the default client would have changed dashboard code paths, so it stays.What stayed in the dashboard:
calibration-tree-http(importsnext/server);calibration-tree-request,target-change-treeandcalibration-prefetch;use-microcosmhook;third-country-conformance.test.tsand itszz-releasefixture;reviewed-repository-importtest, which spawns app routes.They import the package through explicit subpath exports, one per module, such as
@policyengine/calibration-tree/calibration-tree-bundle. There is no root barrel, so client bundles pull in only what they import.Location: inside
frontend/.frontendas its root directory. Whether files outside it are available is a project setting this repository cannot verify.bun install --frozen-lockfileinfrontend/.frontend.The boundary is enforced by a test and by the package's own
tsconfig, not by its directory.Wiring:
frontend/package.jsongainsworkspaces: ["packages/*"]and theworkspace:*dependency;lintalso runs the package typecheck, so CI covers it without a workflow change;frontend/bunfig.tomlsetslinker = "hoisted", because Bun otherwise installs a project with workspaces using its isolated linker, which would change thenode_moduleslayout that Next.js and Vercel build against;frontend/tsconfig.jsonexcludespackages;bun.lockgains the workspace entry.Enforcement:
package-boundary.test.tsrequires that:node:built-ins and declared dependencies (@vercel/blob). The test reads every module reference from each JavaScript or TypeScript source's syntax tree, includingrequire()and triple-slash references;bun:test, package self-references or development dependencies;Identity proof
The golden record was produced from c630c91's code in commit 1, before any move. It is
frontend/packages/calibration-tree/src/fixtures/calibration-tree-golden.json; commit 2 moved it at 100% similarity, with no content change. After the rebase onto #223 it is still the same blob (a4d719e), and every golden test passes.The inputs reuse the module tests' fixtures:
loadCalibrationComparisonSourceover the stored bytes, in both modes.For every bundle file the record holds the path, the raw JSON SHA-256 and size, the gzip size, and the SHA-256 of the deflate stream. It also holds the build IDs, the index digests (the
indexfile entries), the explorer root responses read back from the stored bytes, one comparison target detail, and the manifest serialization digest. The gzip header is asserted separately, because its operating-system byte is 3 on Linux and 19 on macOS.publish-calibration-tree-golden.test.tsruns the publisher's own dry-run repair of the recorded US, UK and Belgium releases against a stubbed Hugging Face repository. The manifest entry carries the recorded build ID and index digest, and the publisher refuses any rebuilt index that differs. A further test confirms that a wrong digest is refused.All 30 modules and their moved tests are byte-identical to their versions on #223's branch except 10 import lines that used the
@/alias, instaging-artifact.ts,latest-artifact.tsandsource-label.ts. The publisher differs only in its 9 import lines. One test also moved out:latest-artifact.test.tsno longer holds #229's mapped-measure test, which now lives in the dashboard (see "What the rebase changed").countries.tsis a 100% copy (git diff -M -C).Risks and follow-ups
countriescompatibility re-export (resolved). The move first keptfrontend/lib/microcosm/countries.tsas a re-export, becauselib/server/microcosm/telemetry-collector-client.tstype-imported the registry from there while Fetch only the collector runs the build monitor shows #216 was changing that file. After Fetch only the collector runs the build monitor shows #216 merged, commit 9 switched that import and deleted the re-export, and a test keeps the old location from coming back.next: { revalidate }, which the dashboard's Next.jsfetchhonors and Bun ignores. A declaration seen only by the package typecheck (types/next-fetch-cache.d.ts) mirrors Next's type. The collector move should replace this with an injected fetch or caching policy.process.envdirectly (Hugging Face tokens, repository overrides, size limits, the UK local-run directory), exactly as before.bun installfrom Bun's cache twice, in temporary directories.next buildtraces the package's JSON fixtures into the standalone output (52 KB), as it already does forlib/fixtures. Server code is bundled.private: trueuntil it is published.Deploy implications
deploy.ymlruns after CI succeeds on a push tomain, so merging this deploys:backend/is unchanged);The frontend change is import paths plus workspace wiring, with the same runtime behavior. Vercel installs from
frontend/, where the package lives, and reads the hoisted-linker setting there; an older Bun ignores that key and is hoisted by default. The publisher workflow is unchanged, and its next run executes the package module through the same npm script. Publishing has been paused since 2026-10-19 at 20:58 UTC (CALIBRATION_TREE_BUILD_ENABLED=FALSE), so that run happens only once the variable is re-enabled. The plan re-enables it after #237 and #239 are deployed.The deploy job gains one step,
bun install --frozen-lockfile --ignore-scriptsinfrontend, after the Vercel upload and before the first calibration check. Without it the job would stop at that check, before promotion (commits 4 and 6). Both calibration checks runbun run --no-install, so they fail if that install is missing, and never install from npm (commit 7).Environment variables
No variable is added, removed or renamed. The publisher workflow is unchanged.
deploy.ymlgains one install step, which reads no variables or secrets (see the deploy job table below).BLOB_READ_WRITE_TOKENenv:and inherited bybun runHF_TOKENenv:POPULACE_UK_STAGING_HF_TOKENtoken_env)env:CALIBRATION_TREE_BUILD_ENABLEDif:FALSEon 2026-10-19 at 20:58 UTC, so the publish job is skipped and nothing publishesgh variable listHUGGINGFACE_TOKEN,CALIBRATION_TREE_MAX_RAW_BYTES,CALIBRATION_TREE_MAX_GZIP_BYTES,POPULACE_*_HF_REPOand_REVISIONoverrides,MICROCOSM_UK_LOCAL_CALIBRATION_DIRHF_TOKEN(dashboard)deploy.ymlpasses--env HF_TOKEN=$HF_RELEASE_READ_TOKENfrom the repository secretBLOB_READ_WRITE_TOKEN,POPULACE_UK_STAGING_HF_TOKEN(dashboard)Deploy job: the install step and its neighbors
These are the variables read or declared by the new step and by the steps from the Vercel upload to the end of the job. The job runs with
environment: Production, so it can use repository, organization and Production environment secrets. In these steps, each secret reaches a command only through that step'senv:block. Provider names were checked withgh secret listandgh variable list, which list names only.bun install --frozen-lockfile --ignore-scriptsenv:block and no${{ }}expression. It installs anonymously from the public npm registry: the repository has no.npmrcandbunfig.tomlsets only the linker.--ignore-scriptsskips every lifecycle script (project, workspace and trusted dependency; checked with Bun 1.4.3), so no project or dependency code runs during the step.MODAL_ENVIRONMENT,VERCEL_CLI_VERSION,VERCEL_ORG_ID,VERCEL_PROJECT_IDVERCEL_CLI_VERSIONpins the CLI in the upload and promote commands, and the Vercel CLI readsVERCEL_ORG_IDandVERCEL_PROJECT_ID. Only the earlier Modal steps useMODAL_ENVIRONMENT.env:block, not secretsHF_RELEASE_READ_TOKENenv:; passed to the candidate as--env HF_TOKEN=...MICROCOSM_MODAL_KEY,MICROCOSM_MODAL_SECRETenv:MICROCOSM_TELEMETRY_COLLECTOR_URLvars.); stepenv:MICROCOSM_CALCULATION_URL,MICROCOSM_BACKEND_SOURCE_COMMITbackendandidentitysteps; stepenv:VERCEL_TOKENenv:in eachVERCEL_AUTOMATION_BYPASS_SECRETenv:in each, passed to the scripts as--vercel-bypass-secretTOKEN,SECRETorKEYbun run --no-install)env:. These steps check the public aliases without a bypass secret.The install step needs no secrets, and none are added to its environment. Like every step after
google-github-actions/auth, it inherits the credential-file path that action exports throughGITHUB_ENV, and the OIDC request variables thatid-token: writeprovides. It does not read them, and because it runs no lifecycle scripts, no dependency code runs alongside them. The calibration checks load only the workspacecountriesmodule andnode:util.Re-checked after commits 6 and 7:
--ignore-scriptsand--no-installread no variables. The install step still has noenv:and no expression. The staged calibration check reads onlyVERCEL_AUTOMATION_BYPASS_SECRETand thefrontendstep's URL output, and the promoted check reads neither.Unverified: the secret values. Only their names are visible, so whether each value is current can only be confirmed by a deploy. This PR does not change them.
🤖 Generated with Claude Code