Repository navigation
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. 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 29 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. 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 eight 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 latest updates on your projects. Learn more about Vercel for GitHub.
|
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.Commits
0d5ce7b)bun test619 passed, 0 failed;tsc --noEmitclean;next buildpassed. The first build attempt failed to fetch Google Fonts, a network error; the rerun passed.e337d3d)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).e78b672)./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.Package boundary
The package is the transitive import closure of the publisher: 30 modules and 15,542 lines, 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,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);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.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 29 modules and their moved tests are byte-identical to the originals except 10 import lines that used the
@/alias, instaging-artifact.ts,latest-artifact.tsandsource-label.ts. The publisher differs only in its 8 import lines.countries.tsis a 100% copy (git diff -M -C).Risks and follow-ups
countriescompatibility re-export.frontend/lib/microcosm/countries.tsre-exports the package's registry. Its only remaining user is a type-only import inlib/server/microcosm/telemetry-collector-client.ts, which this PR deliberately leaves untouched because another change to that file is in flight. Switch that import and delete the re-export afterwards.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.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.Environment variables
No variable is added, removed or renamed. The publisher workflow and
deploy.ymlare unchanged.BLOB_READ_WRITE_TOKENenv:and inherited bybun runHF_TOKENenv:POPULACE_UK_STAGING_HF_TOKENtoken_env)env:CALIBRATION_TREE_BUILD_ENABLEDif:HUGGINGFACE_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)🤖 Generated with Claude Code