From 0d5ce7bad4613b9c5f41c4f92fa5886578c3e339 Mon Sep 17 00:00:00 2001 From: Anthony Volk <14987227+anth-volk@users.noreply.github.com> Date: Fri, 16 Oct 2026 00:12:28 +0400 Subject: [PATCH 1/3] Record golden calibration tree outputs from the current builder 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 --- .../microcosm/calibration-tree-golden.test.ts | 91 ++ .../fixtures/calibration-tree-golden.json | 742 ++++++++++++++ .../calibration-tree-golden-cases.ts | 945 ++++++++++++++++++ .../publish-calibration-tree-golden.test.ts | 176 ++++ 4 files changed, 1954 insertions(+) create mode 100644 frontend/lib/microcosm/calibration-tree-golden.test.ts create mode 100644 frontend/lib/microcosm/fixtures/calibration-tree-golden.json create mode 100644 frontend/lib/microcosm/test-support/calibration-tree-golden-cases.ts create mode 100644 frontend/scripts/publish-calibration-tree-golden.test.ts diff --git a/frontend/lib/microcosm/calibration-tree-golden.test.ts b/frontend/lib/microcosm/calibration-tree-golden.test.ts new file mode 100644 index 00000000..5f53f011 --- /dev/null +++ b/frontend/lib/microcosm/calibration-tree-golden.test.ts @@ -0,0 +1,91 @@ +import { expect, test } from "bun:test"; + +import recordedGoldens from "./fixtures/calibration-tree-golden.json"; +import { + GOLDEN_COMPARISON_CASES, + GOLDEN_SOURCE_CASES, + GZIP_HEADER_WITHOUT_OS, + goldenComparisonRecord, + goldenManifestRecord, + goldenSourceBuild, + goldenSourceRecord, + type CalibrationTreeGoldenRecord, + type GoldenComparisonCaseName, + type GoldenSourceCaseName, +} from "./test-support/calibration-tree-golden-cases"; + +// The record was produced by the builder before it moved into its own +// package. Every case must reproduce it exactly: paths, raw JSON digests and +// sizes, gzip sizes, deflate streams, build ids, and reader responses. +const recorded = recordedGoldens as CalibrationTreeGoldenRecord; + +// The US release has 5,659 targets; building and validating its bundle +// gzips about 40 MB three times. The limit is generous so the suite also +// passes on a heavily loaded machine. +const BUILD_TIMEOUT_MS = 300_000; + +const SOURCE_DESCRIPTIONS: Record = { + "us-release": "a US legacy release", + "uk-release": "a UK schema 8 hierarchy release", + "uk-release-loss-attribution": "a UK release with target loss attribution", + "be-release": "a Belgium release", + "us-staging": "a US version 1 staging run", + "uk-staging": "a UK version 2 staging run", + "uk-staging-staged-bundle": "a UK staging run read from its staged dataset bundle", +}; + +const COMPARISON_DESCRIPTIONS: Record = { + "us-comparison": "a US release-to-staging comparison", + "uk-comparison": "a UK release-to-staging comparison", +}; + +for (const name of GOLDEN_SOURCE_CASES) { + test( + `${SOURCE_DESCRIPTIONS[name]} reproduces its recorded tree bundle and reader responses`, + async () => { + expect(await goldenSourceRecord(name)).toEqual(recorded.sources[name]); + }, + BUILD_TIMEOUT_MS, + ); +} + +for (const name of GOLDEN_COMPARISON_CASES) { + test( + `${COMPARISON_DESCRIPTIONS[name]} reproduces its recorded bundles and target detail`, + async () => { + expect(await goldenComparisonRecord(name)).toEqual(recorded.comparisons[name]); + }, + BUILD_TIMEOUT_MS, + ); +} + +test( + "the tree manifest for every recorded build reproduces its recorded bytes", + async () => { + expect(await goldenManifestRecord()).toEqual(recorded.manifest); + }, + BUILD_TIMEOUT_MS, +); + +test( + "every recorded part is gzip with the fixed header ahead of its recorded deflate stream", + async () => { + for (const name of GOLDEN_SOURCE_CASES) { + const { bundle } = await goldenSourceBuild(name); + for (const file of bundle.files) { + expect([...file.compressed.subarray(0, GZIP_HEADER_WITHOUT_OS.length)]) + .toEqual(GZIP_HEADER_WITHOUT_OS); + } + } + }, + BUILD_TIMEOUT_MS, +); + +test("the recorded US release spans several target-summary and target-detail shards", () => { + const parts = recorded.sources["us-release"].files.map((file) => file.part); + expect(parts.filter((part) => part.startsWith("target-summary-")).length) + .toBeGreaterThan(1); + expect(parts.filter((part) => part.startsWith("target-details-")).length) + .toBeGreaterThan(1); + expect(parts.filter((part) => part.startsWith("tier-")).length).toBeGreaterThan(1); +}); diff --git a/frontend/lib/microcosm/fixtures/calibration-tree-golden.json b/frontend/lib/microcosm/fixtures/calibration-tree-golden.json new file mode 100644 index 00000000..a4d719e4 --- /dev/null +++ b/frontend/lib/microcosm/fixtures/calibration-tree-golden.json @@ -0,0 +1,742 @@ +{ + "schemaVersion": 6, + "sources": { + "us-release": { + "buildArtifactId": "a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887", + "files": [ + { + "part": "filter-index", + "path": "calibration-trees/us/a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887/filter-index.json.gz", + "sha256": "6a741042b02bddfbfa1c481f9d4a20e8a8cdd4d2cbe80bf2c9fe8d9f977ed51a", + "rawBytes": 138229, + "gzipBytes": 33527, + "deflateSha256": "1ca1078eb8f427e9abb1322cb06e956c8d9f81980c467b223ccb89610f2b4055" + }, + { + "part": "target-summary-0001", + "path": "calibration-trees/us/a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887/target-summary-0001.json.gz", + "sha256": "9cfc348ae34a3f8b140e938d55701ad5eb3e853611c74869fdf90477f4a5164a", + "rawBytes": 7998998, + "gzipBytes": 291909, + "deflateSha256": "9334f9fa5c1ae6058acb88fbd70b73c17074db82104ad2251a89a24b5a4a8eff" + }, + { + "part": "target-summary-0002", + "path": "calibration-trees/us/a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887/target-summary-0002.json.gz", + "sha256": "0b354b4876b07d310ea0034bf9287bd579ffebd93ed4c35944d4380d9a0ac124", + "rawBytes": 5419957, + "gzipBytes": 187997, + "deflateSha256": "f1866f4d6f4a73e9810cd3c6b4081728d9fc3810d9190be5b946e61d65c3ff5d" + }, + { + "part": "target-details-0001", + "path": "calibration-trees/us/a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887/target-details-0001.json.gz", + "sha256": "167a6a57c2ecca93b6a4e72a0ce20aaf1bde92899dc5745678094acbb49bd68c", + "rawBytes": 3998564, + "gzipBytes": 155413, + "deflateSha256": "4771b858ee5f58e95c6118a1a12827484776dee08b95ebb3e75e7fa46aae1510" + }, + { + "part": "target-details-0002", + "path": "calibration-trees/us/a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887/target-details-0002.json.gz", + "sha256": "4b12c96cd24ccec2bc2048cb19abd3947a7432d1d59d2ae47174530f56bf81fb", + "rawBytes": 3996828, + "gzipBytes": 144405, + "deflateSha256": "3dd197235316a10a9eac3ac164485cab2ee54d5ab4fc376df0419af3b47f458f" + }, + { + "part": "target-details-0003", + "path": "calibration-trees/us/a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887/target-details-0003.json.gz", + "sha256": "94a835e63c3c260ad965c86e4db81a2e120fc9e4be9ae77fa9578c2fe82e21de", + "rawBytes": 3996936, + "gzipBytes": 147792, + "deflateSha256": "f30fa7894d25957aa5b1fd2cb8b72945312af6dd0edef64c459b658e16ddd64d" + }, + { + "part": "target-details-0004", + "path": "calibration-trees/us/a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887/target-details-0004.json.gz", + "sha256": "c1c4d31d954121b632befb1f7677ae40112bce1c0ca5f2a66e19cf7fedd90ef1", + "rawBytes": 3997158, + "gzipBytes": 146814, + "deflateSha256": "ab1300872d9994cf8eac7bd8c3e22911200cf094c85b92eefe20c1e382744422" + }, + { + "part": "target-details-0005", + "path": "calibration-trees/us/a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887/target-details-0005.json.gz", + "sha256": "54f53ac5cd93367f662f485fb5a29fbf4c0adb5c97ee8448372bd2883443709c", + "rawBytes": 3692745, + "gzipBytes": 134739, + "deflateSha256": "67238ec4a79fbdcaf025178a8db8f2886671003ecb9a304b485f1ec0c056911b" + }, + { + "part": "tier-1", + "path": "calibration-trees/us/a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887/tier-1.json.gz", + "sha256": "e047b9322a142a12ee7fb372825d38dce92df84187d09d51055741b16bab5f34", + "rawBytes": 1925251, + "gzipBytes": 136491, + "deflateSha256": "552a0d63fa2693ebeaf85803265f0887f99539534a83b807e18afbb19732d6f4" + }, + { + "part": "tier-2", + "path": "calibration-trees/us/a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887/tier-2.json.gz", + "sha256": "4df69b52a5f37874a794fe626f49d7553980780168af19fa115fcb4e6b716721", + "rawBytes": 3977571, + "gzipBytes": 184597, + "deflateSha256": "5ff15b7e5b0e92dc518451f987d564e668dbf8adebc0ab723a655b750feb27ee" + }, + { + "part": "tier-3", + "path": "calibration-trees/us/a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887/tier-3.json.gz", + "sha256": "8284ec366d164f2d9fd07d12e63b252e5273835357941371fca4df45aa0fc41c", + "rawBytes": 2787021, + "gzipBytes": 118869, + "deflateSha256": "095e7fdf1ce7d9b7166b9cbcbb3bf57f02f5645d70e4d78aaa504365e8284165" + }, + { + "part": "tier-4", + "path": "calibration-trees/us/a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887/tier-4.json.gz", + "sha256": "da5436b8e262840545c335684c0ba3ef910d6bd6ab68b30587d42dfa0ca8f3ab", + "rawBytes": 235484, + "gzipBytes": 12101, + "deflateSha256": "d12f1e6b1303dfbb43ec00174d98f75db08409b4b1a9b7d9974a8aff08fed11b" + }, + { + "part": "index", + "path": "calibration-trees/us/a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887/index.json.gz", + "sha256": "95b80c3363c2a60d12162f1affd32c4f9baca3424950bfec119892336bf18c3b", + "rawBytes": 277887, + "gzipBytes": 71381, + "deflateSha256": "dcf78f6cd4ab5c59de0c15aba0cca6c4243c522d4adf43fb55222bd3cf9270d0" + } + ], + "readers": { + "programRootSha256": "d779b63b324b1f7780f5de5acbeec16ec60aec692c3d7e75cb11f912fb80c1f7", + "geographyRootSha256": "aceb71e38199712740e1265ab7860f9e515c3310bd01523ea6a145b2802100e1" + } + }, + "uk-release": { + "buildArtifactId": "cf259cfc2b491934f6730d39f2af26980ad9cd712259215476924c626f96f269", + "files": [ + { + "part": "filter-index", + "path": "calibration-trees/uk/cf259cfc2b491934f6730d39f2af26980ad9cd712259215476924c626f96f269/filter-index.json.gz", + "sha256": "d072056bb788ca8264cd7fb35e7fab4e36799577404083c811b9d9d2ca8aad12", + "rawBytes": 712, + "gzipBytes": 335, + "deflateSha256": "8738777f02867e8e946e4196623767f0425585b69d824da049ac7d747c275e47" + }, + { + "part": "target-summary-0001", + "path": "calibration-trees/uk/cf259cfc2b491934f6730d39f2af26980ad9cd712259215476924c626f96f269/target-summary-0001.json.gz", + "sha256": "1189c92b969109fb85982dbab23674ac4f24f2056f9c0b64f782061742c665e2", + "rawBytes": 26240, + "gzipBytes": 1963, + "deflateSha256": "1750e9150a9aee244ae8b8cdcdc3684c59451bab65eaac2c31909afe3dd9e53b" + }, + { + "part": "target-details-0001", + "path": "calibration-trees/uk/cf259cfc2b491934f6730d39f2af26980ad9cd712259215476924c626f96f269/target-details-0001.json.gz", + "sha256": "c8f764a32739e60f9d2c2f7183f09c4fbc21329a5158175df53414ce7afc5682", + "rawBytes": 35154, + "gzipBytes": 2514, + "deflateSha256": "81625364304d5bc5dd61c077e7344133f9ef2078b37378f72d735e97e2040088" + }, + { + "part": "tier-1", + "path": "calibration-trees/uk/cf259cfc2b491934f6730d39f2af26980ad9cd712259215476924c626f96f269/tier-1.json.gz", + "sha256": "3e0762bc4706f535db0a54e4f08857fd3c42230f6490eb5a97607bf6e4b4f63d", + "rawBytes": 12122, + "gzipBytes": 1294, + "deflateSha256": "1b69edc96be45b5e3cab99bcfd862815e2efe2764a699811ddc7f9abc58d7080" + }, + { + "part": "tier-2", + "path": "calibration-trees/uk/cf259cfc2b491934f6730d39f2af26980ad9cd712259215476924c626f96f269/tier-2.json.gz", + "sha256": "c7e8153154fe8aca532d47e60e6f0c30542056b5a12d40521050d824f8ffba7c", + "rawBytes": 12422, + "gzipBytes": 1233, + "deflateSha256": "d144798c42b127e51cb0b2f1a4ec4a5fc525191e4166e9d6ba4a30d1a826880d" + }, + { + "part": "tier-3", + "path": "calibration-trees/uk/cf259cfc2b491934f6730d39f2af26980ad9cd712259215476924c626f96f269/tier-3.json.gz", + "sha256": "98274a322bb7aba17caf61f5d45cf094181b2bdb4711dff6c59bd3d6287f77f1", + "rawBytes": 10299, + "gzipBytes": 981, + "deflateSha256": "c2783702053f63f85e8b9b32b183ed1e3883879c1203c10efd69342b0b306aa7" + }, + { + "part": "index", + "path": "calibration-trees/uk/cf259cfc2b491934f6730d39f2af26980ad9cd712259215476924c626f96f269/index.json.gz", + "sha256": "4eb54beae26438fc9ca829416a785d89eb2d06042d178e877acb8dc0ddc921df", + "rawBytes": 8691, + "gzipBytes": 2171, + "deflateSha256": "73794c40e3b60ea42a41ded5bbf922b10f2c2d90f5e4cd1ee1472ae4205f00f1" + } + ], + "readers": { + "programRootSha256": "96538e6c83d7d8a099e858ce0d400a1d6a24fcd6113f61882b7cc18af4f695b7", + "geographyRootSha256": "5f12106506775ee68ec3d33669dc3f3cf45985770ca15a9e69f72bfe60393598" + } + }, + "uk-release-loss-attribution": { + "buildArtifactId": "b3d7b849dc1a5b5e7a58f10ef5ec165c78c83254ec559c2f3c8fee40dc124702", + "files": [ + { + "part": "filter-index", + "path": "calibration-trees/uk/b3d7b849dc1a5b5e7a58f10ef5ec165c78c83254ec559c2f3c8fee40dc124702/filter-index.json.gz", + "sha256": "af1d80bffc34c54334509323196afa03a6153cd984ff2af5c2339d5e48e8ed51", + "rawBytes": 516, + "gzipBytes": 294, + "deflateSha256": "69d7435a503c2b355afe205fc8258196ca13d0839249f17905583d2ecfb4b33b" + }, + { + "part": "target-summary-0001", + "path": "calibration-trees/uk/b3d7b849dc1a5b5e7a58f10ef5ec165c78c83254ec559c2f3c8fee40dc124702/target-summary-0001.json.gz", + "sha256": "ec03a849ff444317331ab8a3b6999b61559642a06e3c7cabf51d61b953601b92", + "rawBytes": 3394, + "gzipBytes": 861, + "deflateSha256": "08860c52164991d0da4794285a66e771e62ad3ebd3ba0031e2a778d1407d684d" + }, + { + "part": "target-details-0001", + "path": "calibration-trees/uk/b3d7b849dc1a5b5e7a58f10ef5ec165c78c83254ec559c2f3c8fee40dc124702/target-details-0001.json.gz", + "sha256": "f31be395376be010badd5c6c71a815629b5e181a67a8b0529a4d18b2704cf516", + "rawBytes": 4135, + "gzipBytes": 940, + "deflateSha256": "5f7da89297cc8b8632da90b8ac4500106c5c6d6baa90b30d7346e75629d8caa0" + }, + { + "part": "tier-1", + "path": "calibration-trees/uk/b3d7b849dc1a5b5e7a58f10ef5ec165c78c83254ec559c2f3c8fee40dc124702/tier-1.json.gz", + "sha256": "d8d20da0ff764fbadf701243b9b93e257be63fa1a9fd55cd346fa4f27a0bcb88", + "rawBytes": 3695, + "gzipBytes": 614, + "deflateSha256": "7fee5206ccb86351a94f733a2b61a8d621acf430f22d8c6921e7097317e6fd1a" + }, + { + "part": "tier-2", + "path": "calibration-trees/uk/b3d7b849dc1a5b5e7a58f10ef5ec165c78c83254ec559c2f3c8fee40dc124702/tier-2.json.gz", + "sha256": "288f688d1ee5c977f71c2afc48cd7fe9431f297ae07203971f2df14281ca197b", + "rawBytes": 2042, + "gzipBytes": 488, + "deflateSha256": "f85a3fba480e654d1b8d40c1c66980f61d2789eb842ea87647dfb78d99f21a78" + }, + { + "part": "index", + "path": "calibration-trees/uk/b3d7b849dc1a5b5e7a58f10ef5ec165c78c83254ec559c2f3c8fee40dc124702/index.json.gz", + "sha256": "4c4a8659c29663ada1668f126382151be0da548512fa6515b0deafb8a3d51209", + "rawBytes": 6385, + "gzipBytes": 1642, + "deflateSha256": "9ce173c5baf4527e126c0f437a4733563577330b5777d838acac199d94e98f5e" + } + ], + "readers": { + "programRootSha256": "bed713ac6393d42bebfd2d045c994cffce38e7dbdc32f85a0eacbcc0012f28cb", + "geographyRootSha256": "d540f8f9bde0a0f5d95799b7c32a2d9eb30922c34597a9f5f214592f2c06e858" + } + }, + "be-release": { + "buildArtifactId": "3f1dd7eed7c2424998507bbd7c05cb5090febbf1b6af5762dff52bdd56e49b92", + "files": [ + { + "part": "filter-index", + "path": "calibration-trees/be/3f1dd7eed7c2424998507bbd7c05cb5090febbf1b6af5762dff52bdd56e49b92/filter-index.json.gz", + "sha256": "ffa4ddc71fd3a12973639095d03b71f796a10a751eb9a9f6a874610e03d0cb5d", + "rawBytes": 559, + "gzipBytes": 292, + "deflateSha256": "5f02a32cf57111faee1bd28147907b958078dec3c8272fc8213fd3f8b17e7c21" + }, + { + "part": "target-summary-0001", + "path": "calibration-trees/be/3f1dd7eed7c2424998507bbd7c05cb5090febbf1b6af5762dff52bdd56e49b92/target-summary-0001.json.gz", + "sha256": "942b442f47aeeaac57f815dd4686779f09f3a9b211d22c6f4f2701fc4c204149", + "rawBytes": 5493, + "gzipBytes": 1226, + "deflateSha256": "aabb61222ebf9051c3cddf150d03807e96da2b7fbcdb298c8d1d39af51c8ceb1" + }, + { + "part": "target-details-0001", + "path": "calibration-trees/be/3f1dd7eed7c2424998507bbd7c05cb5090febbf1b6af5762dff52bdd56e49b92/target-details-0001.json.gz", + "sha256": "251dc89a046ccc3a87edeb1f821adebe17d2759acf63c08ca942c7c835590400", + "rawBytes": 9943, + "gzipBytes": 2175, + "deflateSha256": "dd5442a83f52c3ac45ed6a5cd7e3fd2b1ebe4ef76b3d2c17901a6f279d24fec3" + }, + { + "part": "tier-1", + "path": "calibration-trees/be/3f1dd7eed7c2424998507bbd7c05cb5090febbf1b6af5762dff52bdd56e49b92/tier-1.json.gz", + "sha256": "eb6a49f6287348cc0fb0f2fb3e9484a8f05002d54b98551ff6c4cb378e063160", + "rawBytes": 6205, + "gzipBytes": 813, + "deflateSha256": "0bd6f1d57380b9fa37c4792072d0891bbffe018c6d519163d9c13f2db4a739bc" + }, + { + "part": "tier-2", + "path": "calibration-trees/be/3f1dd7eed7c2424998507bbd7c05cb5090febbf1b6af5762dff52bdd56e49b92/tier-2.json.gz", + "sha256": "3778c614299c9198db70563b8b6643e5a0be385b28f6c9786ceb33a4cdc97c1c", + "rawBytes": 3575, + "gzipBytes": 741, + "deflateSha256": "1edd02e58cb975731431beca88371f582e02b8f455a30ec314b2ac8b667da25d" + }, + { + "part": "index", + "path": "calibration-trees/be/3f1dd7eed7c2424998507bbd7c05cb5090febbf1b6af5762dff52bdd56e49b92/index.json.gz", + "sha256": "50aec5e9efc2c23dbc3c53c4f87e30e0a759de43de492c7a5305b1b95929a10a", + "rawBytes": 7588, + "gzipBytes": 1847, + "deflateSha256": "549baf405496cd7bdda4610b1144e75d6de550b75fcc39babe1d29248816c96c" + } + ], + "readers": { + "programRootSha256": "0e7cf0a69fb8231677e484e80c1394ed3ebec6949355eb49b31c86e4d57a8072", + "geographyRootSha256": "939d5bf5d74c528b5dfd9fb40e30f4a463456acf85acbbdcb72b6a4f71db33a9" + } + }, + "us-staging": { + "buildArtifactId": "b185a3ee9a2f79006b919073f302c10355a7bb66119bc393f3a2466b93a0c8a2", + "files": [ + { + "part": "filter-index", + "path": "calibration-trees/us/b185a3ee9a2f79006b919073f302c10355a7bb66119bc393f3a2466b93a0c8a2/filter-index.json.gz", + "sha256": "ec362ae4d656d32ab8c93e615861881abaa8a5999bf839976f17b4b58db85623", + "rawBytes": 34254, + "gzipBytes": 5441, + "deflateSha256": "65444bf6458ad47ef3024dd1577ade516e8d9faad68653ff057cd5c85351542f" + }, + { + "part": "target-summary-0001", + "path": "calibration-trees/us/b185a3ee9a2f79006b919073f302c10355a7bb66119bc393f3a2466b93a0c8a2/target-summary-0001.json.gz", + "sha256": "05af411f3a512cc66b0fd30f611085a1c54c9afc20db3ee4336fd7c7d01a00cd", + "rawBytes": 3481970, + "gzipBytes": 145130, + "deflateSha256": "550c15c88f1fb4bada0b15e786bdc8b642ef89e678016ee60da6dbc76d0ac03e" + }, + { + "part": "target-details-0001", + "path": "calibration-trees/us/b185a3ee9a2f79006b919073f302c10355a7bb66119bc393f3a2466b93a0c8a2/target-details-0001.json.gz", + "sha256": "3c586bca8cfc1df8efb3986551bfbb84b58b7c74783a2642ca3beee8c7a610ca", + "rawBytes": 3997974, + "gzipBytes": 175885, + "deflateSha256": "77a9c4c9e16e2c136d8c8622065eda1c95145573c73eef11493c80010eb09ed8" + }, + { + "part": "target-details-0002", + "path": "calibration-trees/us/b185a3ee9a2f79006b919073f302c10355a7bb66119bc393f3a2466b93a0c8a2/target-details-0002.json.gz", + "sha256": "a34b20714ae4a3d08e1ec363d3ec56f6956e26b9fe12b01adb81743eafaf9e15", + "rawBytes": 1347045, + "gzipBytes": 57128, + "deflateSha256": "87055b67b1c390a522e90f1088faa1ff692247ed88c2ede7afa67e5dec7a3f72" + }, + { + "part": "tier-1", + "path": "calibration-trees/us/b185a3ee9a2f79006b919073f302c10355a7bb66119bc393f3a2466b93a0c8a2/tier-1.json.gz", + "sha256": "27566511b431c6100155e11a177a90f8adab792a72c98875c92e41094d2be637", + "rawBytes": 412455, + "gzipBytes": 37843, + "deflateSha256": "87b3e9a0ebe59e6c4cf3f51b2fbc7d84f6ab8247690468a5f11eeed0a3498723" + }, + { + "part": "tier-2", + "path": "calibration-trees/us/b185a3ee9a2f79006b919073f302c10355a7bb66119bc393f3a2466b93a0c8a2/tier-2.json.gz", + "sha256": "bd7a122f9e685bec93f4f094da3b4faf5e8fee2889b31efae92acc0bdbd18864", + "rawBytes": 906593, + "gzipBytes": 57400, + "deflateSha256": "d7233caf7e2f259426394ee9acf8e5c9942bc1ea390ce80ad0ee3327381dae40" + }, + { + "part": "tier-3", + "path": "calibration-trees/us/b185a3ee9a2f79006b919073f302c10355a7bb66119bc393f3a2466b93a0c8a2/tier-3.json.gz", + "sha256": "e85cf5dc46e34f585fa078aa58d001794d08fed1b8b85ea2a68d979433d19c82", + "rawBytes": 1579869, + "gzipBytes": 72062, + "deflateSha256": "6905fb5238046fad7b2eb0d82249d80ca3d6ba967e4b602f567ce925a03fe6fd" + }, + { + "part": "index", + "path": "calibration-trees/us/b185a3ee9a2f79006b919073f302c10355a7bb66119bc393f3a2466b93a0c8a2/index.json.gz", + "sha256": "0d99c1d5527f8ca99030011bd74f31dd5ca1f5a70f606ed28333935ce7790240", + "rawBytes": 108144, + "gzipBytes": 17624, + "deflateSha256": "c947115c296eaf9dbbc44b618aca7478e9969d7d8403812a97b82842b23abc32" + } + ], + "readers": { + "programRootSha256": "258a9ea648962e6ac21d8c0cd43d6e1aa21957c1744cce35ec8ad5476773bc59", + "geographyRootSha256": "f6661f55b7bff607e0c2f93d5da5ce24fc319be67a1194d209d165ac0e201573" + } + }, + "uk-staging": { + "buildArtifactId": "76b4e84866cfe1e91c38eb8028210147c2b5107d5cbbb08d5e251a7f1752ee82", + "files": [ + { + "part": "filter-index", + "path": "calibration-trees/uk/76b4e84866cfe1e91c38eb8028210147c2b5107d5cbbb08d5e251a7f1752ee82/filter-index.json.gz", + "sha256": "646c22a59fe577a5c7ffee5a5b7d0c9cd993b44c6b88d8736e512b487cd16ffc", + "rawBytes": 749, + "gzipBytes": 346, + "deflateSha256": "3ac0dee3c91c6ed48b7c749169e8bb259dd732cb13fefe4c142a6541a43221ec" + }, + { + "part": "target-summary-0001", + "path": "calibration-trees/uk/76b4e84866cfe1e91c38eb8028210147c2b5107d5cbbb08d5e251a7f1752ee82/target-summary-0001.json.gz", + "sha256": "f657c7e06d08f97bfb98041a20fe3847b43452f25293b0a0f683f253972988f7", + "rawBytes": 26859, + "gzipBytes": 2170, + "deflateSha256": "c08450dcf07c6376f2149628dc0c3c2dade7d8a70d480d4bc42711f82edf1e66" + }, + { + "part": "target-details-0001", + "path": "calibration-trees/uk/76b4e84866cfe1e91c38eb8028210147c2b5107d5cbbb08d5e251a7f1752ee82/target-details-0001.json.gz", + "sha256": "78cb2561ce61822e4291387122cc8930b9b9b403ad4b1eab6639b8f594590aab", + "rawBytes": 36359, + "gzipBytes": 2884, + "deflateSha256": "0e09769db8fafe490e55f4af3afb57c71925e91f68b4a69f71c3c1747d24fd81" + }, + { + "part": "tier-1", + "path": "calibration-trees/uk/76b4e84866cfe1e91c38eb8028210147c2b5107d5cbbb08d5e251a7f1752ee82/tier-1.json.gz", + "sha256": "19fb5fbf6165969bb8c48a6c032c77eb17dcf655521e31998f8b4d765286c42c", + "rawBytes": 13679, + "gzipBytes": 1494, + "deflateSha256": "79cd9ebf5adc5046c3da34eedc6d4d91b4ab366a4ef565e3ade8d2818945e94b" + }, + { + "part": "tier-2", + "path": "calibration-trees/uk/76b4e84866cfe1e91c38eb8028210147c2b5107d5cbbb08d5e251a7f1752ee82/tier-2.json.gz", + "sha256": "ffde8c88925f2a6fe24d5eb5be6ca9cc6b37102d1cdef74c2d697c8693ca0270", + "rawBytes": 13566, + "gzipBytes": 1430, + "deflateSha256": "1ca9972e9e6f63ce61232e09bcff204b0f2d5b8d84455bfe5386273d45f45620" + }, + { + "part": "tier-3", + "path": "calibration-trees/uk/76b4e84866cfe1e91c38eb8028210147c2b5107d5cbbb08d5e251a7f1752ee82/tier-3.json.gz", + "sha256": "bb5dc7c9aaeff0427852da9c44abc07ba5781468cadea9f5119d0262eb3a674d", + "rawBytes": 11277, + "gzipBytes": 1068, + "deflateSha256": "0b4debba9f9ba0755def3eb1d19f924e7b6a1f5259ab28a7ad5bc7a212ed106f" + }, + { + "part": "index", + "path": "calibration-trees/uk/76b4e84866cfe1e91c38eb8028210147c2b5107d5cbbb08d5e251a7f1752ee82/index.json.gz", + "sha256": "7ad10415d1da6e693a607f8fd543da2983f8b93bd5f2ef589499f602ea22f93a", + "rawBytes": 9667, + "gzipBytes": 2407, + "deflateSha256": "276306fe5db92d47419db795c64cb54766f9aeae43066b424fee4fce7f07beeb" + } + ], + "readers": { + "programRootSha256": "7d3c7f6931cea0758a8da3828c3a7f5bd6710c2dc0e278384aaad29289ac00d3", + "geographyRootSha256": "b77b8ca27fa64b8d630221a6d60938bee09368110a72c411e6656240efc04886" + } + }, + "uk-staging-staged-bundle": { + "buildArtifactId": "b7df81d8d39b27f48b4b30cbedd00b2ffe5d844bd6dc265ee8481d8f002acba1", + "files": [ + { + "part": "filter-index", + "path": "calibration-trees/uk/b7df81d8d39b27f48b4b30cbedd00b2ffe5d844bd6dc265ee8481d8f002acba1/filter-index.json.gz", + "sha256": "d06efa27f18baa1d7d0aab876b3c728d2496643472a6a82dd5a985243cdfe192", + "rawBytes": 749, + "gzipBytes": 346, + "deflateSha256": "a410a1ccf6f861d55e0cf67509019cd5f760341e71a4c5bc9e3ffbf730be9cdb" + }, + { + "part": "target-summary-0001", + "path": "calibration-trees/uk/b7df81d8d39b27f48b4b30cbedd00b2ffe5d844bd6dc265ee8481d8f002acba1/target-summary-0001.json.gz", + "sha256": "798172011c376e311dc63180c0c4ffa9feb7867067611053029521f2d4fdd768", + "rawBytes": 26859, + "gzipBytes": 2167, + "deflateSha256": "7c80a15e571c8906762cf42e35b6589fbe26b6e56e65ad9dab4cf6b61098cae1" + }, + { + "part": "target-details-0001", + "path": "calibration-trees/uk/b7df81d8d39b27f48b4b30cbedd00b2ffe5d844bd6dc265ee8481d8f002acba1/target-details-0001.json.gz", + "sha256": "6d5981a6d94c7992f4a7ed99c3ad69f0533633995b56b25808207b1a9df9df5c", + "rawBytes": 36359, + "gzipBytes": 2884, + "deflateSha256": "bac7e96363766893e12f2d56c99c4cf45c0d7dea63ae34de5550054eb0190d6c" + }, + { + "part": "tier-1", + "path": "calibration-trees/uk/b7df81d8d39b27f48b4b30cbedd00b2ffe5d844bd6dc265ee8481d8f002acba1/tier-1.json.gz", + "sha256": "a3e16af357d09c971a4eb5442dd08841530c339bab27f1edbdc4d38b4fd4f6b3", + "rawBytes": 13679, + "gzipBytes": 1495, + "deflateSha256": "8da63c3ce0d36cc4209d05ab6f8d343b09462176c3edec817036351358b45cd8" + }, + { + "part": "tier-2", + "path": "calibration-trees/uk/b7df81d8d39b27f48b4b30cbedd00b2ffe5d844bd6dc265ee8481d8f002acba1/tier-2.json.gz", + "sha256": "07031463189db6a520cff9dfc30bb49f83c92f58012cb3cbac90b73c00d32ad4", + "rawBytes": 13566, + "gzipBytes": 1429, + "deflateSha256": "974a744de6e560282b36a131d75d0601176967cae3a2276e1d473ab852fa768f" + }, + { + "part": "tier-3", + "path": "calibration-trees/uk/b7df81d8d39b27f48b4b30cbedd00b2ffe5d844bd6dc265ee8481d8f002acba1/tier-3.json.gz", + "sha256": "17685f2757333c376af40ccbd2891e4a1425e148193ff11be400acba9fe925cc", + "rawBytes": 11277, + "gzipBytes": 1067, + "deflateSha256": "2f5e31e1a4078e6a4738aaff7341785765983592e117ff955a41ffb6e7b6518b" + }, + { + "part": "index", + "path": "calibration-trees/uk/b7df81d8d39b27f48b4b30cbedd00b2ffe5d844bd6dc265ee8481d8f002acba1/index.json.gz", + "sha256": "6c0fcc717808f6ceb2da5204dd667298c9d888450a21062c5effcc089d8b8bce", + "rawBytes": 10036, + "gzipBytes": 2516, + "deflateSha256": "bab7076b574f9df8e8c85e3f44a057653e3c8b46f741a32c3406e02f0f07985e" + } + ], + "readers": { + "programRootSha256": "ad34108f20d1ae4a69e97abd95e02d8e99e43263fdf85ec2905e9c814e0de619", + "geographyRootSha256": "4ab073886f7a6358e91bb4eff6f424f23929c7ecf68277bdaa48b2305db9774f" + } + } + }, + "comparisons": { + "us-comparison": { + "pairArtifactId": "b25614d1c431f393df8173abd42ec832569ab684b15d2a8e821465a65a349deb", + "reported": { + "buildArtifactId": "3a2f004d0c7c67538dbc76c5fc44e8c2c6c923ca1f38eb505da1265d9ed0bcb6", + "files": [ + { + "part": "filter-index", + "path": "calibration-trees/us/3a2f004d0c7c67538dbc76c5fc44e8c2c6c923ca1f38eb505da1265d9ed0bcb6/filter-index.json.gz", + "sha256": "d47845aca5fd532935f7bd1dc9db7b64ce6799bb00c262ffefb78bb1feec26fd", + "rawBytes": 138363, + "gzipBytes": 37586, + "deflateSha256": "7241762e4051ecaec528807ecfce33c392632066330b63e09880e0c2fbf87ddf" + }, + { + "part": "target-summary-0001", + "path": "calibration-trees/us/3a2f004d0c7c67538dbc76c5fc44e8c2c6c923ca1f38eb505da1265d9ed0bcb6/target-summary-0001.json.gz", + "sha256": "dcc88dfc2244cf9292730e56072b469bfc26e1b4a7b6cb1002eb2476e857af28", + "rawBytes": 7997882, + "gzipBytes": 433224, + "deflateSha256": "383d7b09b2c2a034bf57ca4b38cc99ecc2717ef23e64d8c952693db478c20964" + }, + { + "part": "target-summary-0002", + "path": "calibration-trees/us/3a2f004d0c7c67538dbc76c5fc44e8c2c6c923ca1f38eb505da1265d9ed0bcb6/target-summary-0002.json.gz", + "sha256": "a3a7e86f281719991c08a076a15d2bef83c8191e7139ab016b954d45becc545f", + "rawBytes": 5314378, + "gzipBytes": 226692, + "deflateSha256": "1b5c3134bb5546a0c74fd83459c449c59037024649765fdef87f2e6646736ac0" + }, + { + "part": "tier-1", + "path": "calibration-trees/us/3a2f004d0c7c67538dbc76c5fc44e8c2c6c923ca1f38eb505da1265d9ed0bcb6/tier-1.json.gz", + "sha256": "44d202e2d29bd65f28858a97458efee4bf6467ba6a08fbcf1060f1966fdbb375", + "rawBytes": 2599312, + "gzipBytes": 158190, + "deflateSha256": "807cd6aad33f132ad94fe31b51b57bf9b83d8586bb56642d028ee46b041921d1" + }, + { + "part": "tier-2", + "path": "calibration-trees/us/3a2f004d0c7c67538dbc76c5fc44e8c2c6c923ca1f38eb505da1265d9ed0bcb6/tier-2.json.gz", + "sha256": "d8c14feaa83c3b64b1cdc1db4e0ad5c1a20705e9cfb55d68bd1af1222dc9522a", + "rawBytes": 5354347, + "gzipBytes": 226836, + "deflateSha256": "4b797cb03ac9a777fa803793f936c4402c5a75b80bdca3fbee81636c68f43ba3" + }, + { + "part": "tier-3", + "path": "calibration-trees/us/3a2f004d0c7c67538dbc76c5fc44e8c2c6c923ca1f38eb505da1265d9ed0bcb6/tier-3.json.gz", + "sha256": "6d598c253aec09cb149e4480a6e8b3dc9248f797ab1e99a11d691bd70d6d16df", + "rawBytes": 3846319, + "gzipBytes": 149274, + "deflateSha256": "dc6031a2fd3ad7a7127c4a7b7302ffda63addce38d2ec6cf5ab303846badb0bb" + }, + { + "part": "tier-4", + "path": "calibration-trees/us/3a2f004d0c7c67538dbc76c5fc44e8c2c6c923ca1f38eb505da1265d9ed0bcb6/tier-4.json.gz", + "sha256": "f40e82c063b5fb2d1768277210d359925373c0069625a17331823b65e862584e", + "rawBytes": 308154, + "gzipBytes": 12761, + "deflateSha256": "188370eb4c1962ef45309cef98de532bdb148e8dab73cbf6c057eb8f37cebb0b" + }, + { + "part": "index", + "path": "calibration-trees/us/3a2f004d0c7c67538dbc76c5fc44e8c2c6c923ca1f38eb505da1265d9ed0bcb6/index.json.gz", + "sha256": "68f0b33df1cde657216338397852533e2312bc1edc90cb768ee5c9e2fe443ba6", + "rawBytes": 298723, + "gzipBytes": 76095, + "deflateSha256": "e19458452d36d3e7392059f98e9d53b8fe47e1cf9196f5be6aed4e214feacea8" + } + ] + }, + "shared": { + "buildArtifactId": "c68fce91b776c236471e542ce7402f513eae623c6cdd1bdba042d44f71cb828d", + "files": [ + { + "part": "filter-index", + "path": "calibration-trees/us/c68fce91b776c236471e542ce7402f513eae623c6cdd1bdba042d44f71cb828d/filter-index.json.gz", + "sha256": "fb55b20e3969a458edd9d1fac30292971d26f045b37e3d74e240219280b3ef2d", + "rawBytes": 34342, + "gzipBytes": 7505, + "deflateSha256": "c71e4a4dda5cbc9b02955cc078126fcf1218327497af06da0959b7ce78ace0d6" + }, + { + "part": "target-summary-0001", + "path": "calibration-trees/us/c68fce91b776c236471e542ce7402f513eae623c6cdd1bdba042d44f71cb828d/target-summary-0001.json.gz", + "sha256": "4d18b042228fc76279b764810d7792dc0df81a34177dc73015e4a868ff3b732f", + "rawBytes": 3771003, + "gzipBytes": 246320, + "deflateSha256": "31e0cc3f35c081f19968e93d40525e9729be9d7bea14a2608231b127f5bb10f8" + }, + { + "part": "tier-1", + "path": "calibration-trees/us/c68fce91b776c236471e542ce7402f513eae623c6cdd1bdba042d44f71cb828d/tier-1.json.gz", + "sha256": "1e1a6c134ebcfd75be671662da6d3c8f235a4f15fafe7f3f9570f25e52ea7e5c", + "rawBytes": 552600, + "gzipBytes": 40078, + "deflateSha256": "572ec78175cc365a56cff9f440bb589de0a71a078c456e8d6e2ac7c4c27b5413" + }, + { + "part": "tier-2", + "path": "calibration-trees/us/c68fce91b776c236471e542ce7402f513eae623c6cdd1bdba042d44f71cb828d/tier-2.json.gz", + "sha256": "38eefe09571c5d13f5543aad50db9380c47db98150d234e22510f0d2d77817b5", + "rawBytes": 1197416, + "gzipBytes": 58347, + "deflateSha256": "622a4e5f3bdfe10d328a4bd516d1f80df15b2e47daaa1d6d628df05d03e08287" + }, + { + "part": "tier-3", + "path": "calibration-trees/us/c68fce91b776c236471e542ce7402f513eae623c6cdd1bdba042d44f71cb828d/tier-3.json.gz", + "sha256": "05076f3bb6f15df25018538706332658e0fdf0e174b38a5291e2dd7266665130", + "rawBytes": 2108070, + "gzipBytes": 71811, + "deflateSha256": "6c9cfd058f9dbaeab9996689d6777084f2e3bf9e44f2fb46189fe4065c00a318" + }, + { + "part": "index", + "path": "calibration-trees/us/c68fce91b776c236471e542ce7402f513eae623c6cdd1bdba042d44f71cb828d/index.json.gz", + "sha256": "7e22a0727aae3492eeb7663abea959df57ea66c5a19dcce9bff2c2da17802e55", + "rawBytes": 125916, + "gzipBytes": 22273, + "deflateSha256": "ecb285cf59a35d4e75b4190d1bc3fc482dcab64f19c50178e87ae34a1255015b" + } + ] + }, + "firstTargetDetailSha256": "2591b61cf2a4fa2ddf0376f151fc13ae6fffa429127db6ee089bcac5a49ab937" + }, + "uk-comparison": { + "pairArtifactId": "b1678368efa7eaf1a128d0896d5d75f9e13ee6be2b023117048c9e9d5f379a1a", + "reported": { + "buildArtifactId": "0b3f31ebf3dee573063cca681e506199069ad663b659375ea3318a2246a1437a", + "files": [ + { + "part": "filter-index", + "path": "calibration-trees/uk/0b3f31ebf3dee573063cca681e506199069ad663b659375ea3318a2246a1437a/filter-index.json.gz", + "sha256": "37b482e364881ce67af69d6b29241bf9a7c5463b246974646da63696e652eabb", + "rawBytes": 837, + "gzipBytes": 378, + "deflateSha256": "805792dead7ebaafc71d1b08d1f6869d1dbfb8f0b025ebcb7b02fd7ee6ec2f65" + }, + { + "part": "target-summary-0001", + "path": "calibration-trees/uk/0b3f31ebf3dee573063cca681e506199069ad663b659375ea3318a2246a1437a/target-summary-0001.json.gz", + "sha256": "772d76e6f26a24e7f408109b3647ac32f36c2c3236996d74a0997c8d705e316c", + "rawBytes": 27817, + "gzipBytes": 2749, + "deflateSha256": "5f1c32c3fa153f5cec7a9290310285ed1adf357fe1b2535be09b0588c22d14d0" + }, + { + "part": "tier-1", + "path": "calibration-trees/uk/0b3f31ebf3dee573063cca681e506199069ad663b659375ea3318a2246a1437a/tier-1.json.gz", + "sha256": "6a6ea301d3da672fc99c69087c55d56bf45797371fd6bb538d9a9e6db8c5b8db", + "rawBytes": 18499, + "gzipBytes": 1544, + "deflateSha256": "9f65130d3d06c1ba739db380c3827cb46225d80554f9781b22a9d065b7d79ac3" + }, + { + "part": "tier-2", + "path": "calibration-trees/uk/0b3f31ebf3dee573063cca681e506199069ad663b659375ea3318a2246a1437a/tier-2.json.gz", + "sha256": "2042af40f746c2360e3557e18f316f62d82cf695c358edea17ddc9461616289d", + "rawBytes": 17941, + "gzipBytes": 1557, + "deflateSha256": "2578507f4e2c47f21d034f3e87fcf1fbed546aec43304f24f65bb06581b4af1c" + }, + { + "part": "tier-3", + "path": "calibration-trees/uk/0b3f31ebf3dee573063cca681e506199069ad663b659375ea3318a2246a1437a/tier-3.json.gz", + "sha256": "e5e323937aadb0b71c5aa612ae753262dd6b12d1d98367b3ff5599f9362ebf9f", + "rawBytes": 14981, + "gzipBytes": 1157, + "deflateSha256": "0fbb14728476f1538ad589c099ebe888bc6dcec6d6eafcb8b5762c22aa7d90e5" + }, + { + "part": "index", + "path": "calibration-trees/uk/0b3f31ebf3dee573063cca681e506199069ad663b659375ea3318a2246a1437a/index.json.gz", + "sha256": "872af5a7521ada4eece7114039f4a481dbabb3bee87e0a385363d8ac557fbf01", + "rawBytes": 13124, + "gzipBytes": 2880, + "deflateSha256": "8c493df1b1cbce84e854758ececcafe5b8f04b356fc88e56553de4737db4b266" + } + ] + }, + "shared": { + "buildArtifactId": "c762478f4780a61eb4f6023ec89252f5408df5b55de6e19b45d2bc15c3a84470", + "files": [ + { + "part": "filter-index", + "path": "calibration-trees/uk/c762478f4780a61eb4f6023ec89252f5408df5b55de6e19b45d2bc15c3a84470/filter-index.json.gz", + "sha256": "e16994f6877c725a1c45752c940db770bb169e05357d73abd60b2460306daac1", + "rawBytes": 837, + "gzipBytes": 380, + "deflateSha256": "21449af7ed6eac7fc814f0c6510feab180b70088e7bc536e83f6d93dc0d70a57" + }, + { + "part": "target-summary-0001", + "path": "calibration-trees/uk/c762478f4780a61eb4f6023ec89252f5408df5b55de6e19b45d2bc15c3a84470/target-summary-0001.json.gz", + "sha256": "3be692fed30853ff6087aa4152203ca525f8ebaa75ed75c46959391f1311898e", + "rawBytes": 27817, + "gzipBytes": 2750, + "deflateSha256": "aa26cc87038a3669df9839a49d8e5111a4479683243b7bc927230e3b190897f0" + }, + { + "part": "tier-1", + "path": "calibration-trees/uk/c762478f4780a61eb4f6023ec89252f5408df5b55de6e19b45d2bc15c3a84470/tier-1.json.gz", + "sha256": "1699cea53373599ee96291ad9b55112e7794aa07bca979fe97c451f820578605", + "rawBytes": 18506, + "gzipBytes": 1543, + "deflateSha256": "3f3ed3d2c9057f9d5937a442dd805d2db7104f80180e30e8cd5439e8f8bf1e23" + }, + { + "part": "tier-2", + "path": "calibration-trees/uk/c762478f4780a61eb4f6023ec89252f5408df5b55de6e19b45d2bc15c3a84470/tier-2.json.gz", + "sha256": "1e2868e454501fd249bf27875303b0e608d9128ffa363fd141cba7c4fc508d1b", + "rawBytes": 17945, + "gzipBytes": 1554, + "deflateSha256": "19d1da91928c0a7256df11a8d8b121b80ae57191d57ce5401b6712ac4fb98c4b" + }, + { + "part": "tier-3", + "path": "calibration-trees/uk/c762478f4780a61eb4f6023ec89252f5408df5b55de6e19b45d2bc15c3a84470/tier-3.json.gz", + "sha256": "bf6a95a7d4d224ebf4ee00a498e4ad869f32a298d913d656e117462fb18b0c88", + "rawBytes": 14975, + "gzipBytes": 1157, + "deflateSha256": "a9d33fb093fe27f37e11dd9773bf395afaf578acb903187740abc8632636a3d7" + }, + { + "part": "index", + "path": "calibration-trees/uk/c762478f4780a61eb4f6023ec89252f5408df5b55de6e19b45d2bc15c3a84470/index.json.gz", + "sha256": "97ccd606b8d1a51f886d029e1554a4145038d909d671cf4d7f0ddcf18b8d1e5a", + "rawBytes": 13135, + "gzipBytes": 2893, + "deflateSha256": "337e27bc583ec0ae08ebc949ce62aeaf2fe26d6d42864c4b13ea9027c5773f8a" + } + ] + }, + "firstTargetDetailSha256": "1e62c70f8872039fa6ae165c5c5c2fc1959cd6137ce575e984e6b479a54ab038" + } + }, + "manifest": { + "sha256": "cfaf8056091029ca88b4ada65c7e0001c52da76a87e4f1f90c024914f254c045", + "rawBytes": 4560 + } +} diff --git a/frontend/lib/microcosm/test-support/calibration-tree-golden-cases.ts b/frontend/lib/microcosm/test-support/calibration-tree-golden-cases.ts new file mode 100644 index 00000000..0778eea1 --- /dev/null +++ b/frontend/lib/microcosm/test-support/calibration-tree-golden-cases.ts @@ -0,0 +1,945 @@ +// Golden calibration tree cases: representative builder inputs (US and UK, +// releases and staging runs, and comparisons between them) assembled from the +// fixtures the module tests already use, and a record of everything the +// builder writes for them. The record reduces each written part to its path, +// raw JSON digest and size, gzip size and deflate-stream digest, and adds the +// build ids, the manifest bytes, and what the readers return from the +// written bytes. +// +// fixtures/calibration-tree-golden.json is that record as produced by the +// builder before it moved into its own package; the golden tests require the +// builder to reproduce it exactly. Regenerate it only for an intended output +// change, from frontend/: +// +// bun run lib/microcosm/test-support/calibration-tree-golden-cases.ts +import { createHash } from "node:crypto"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import { gunzipSync } from "node:zlib"; + +import { loadCalibrationComparisonSource, loadCalibrationComparisonTargetDetail } from "../calibration-comparison-blob"; +import { buildCalibrationComparisonBundles } from "../calibration-comparison-bundle"; +import { createExplorerState, type ExplorerState } from "../calibration-explorer"; +import { + CALIBRATION_TREE_SCHEMA_VERSION, + calibrationTreePartPath, + calibrationTreeResponseFromBundle, + calibrationTreeTargetsFromSummaries, + parseCalibrationTreeIndex, + parseCalibrationTreePart, + type CalibrationTreeFilterIndexArtifact, + type CalibrationTreeSourceArtifact, + type CalibrationTreeTargetDetailsArtifact, + type CalibrationTreeTargetSummaryArtifact, + type CalibrationTreeTierArtifact, +} from "../calibration-tree-artifact"; +import type { getCalibrationTreeBlob } from "../calibration-tree-blob"; +import { + buildCalibrationTreeBundle, + type CalibrationTreeBundle, + type CalibrationTreeBundleFile, +} from "../calibration-tree-bundle"; +import { + emptyCalibrationTreeManifest, + serializeCalibrationTreeManifest, + withCalibrationTreeManifestEntry, + type CalibrationTreeManifestEntry, +} from "../calibration-tree-manifest"; +import { + COUNTRY_REGISTRY, + type CountryRegistration, + type MicrocosmCountry, +} from "../countries"; +import { + UK_LOCAL_CALIBRATION_DIR_ENV, + buildCalibration, + microcosmRepo, + type Calibration, +} from "../latest-artifact"; +import { resolveStagingCalibrationSource, stagingRepository } from "../staging-artifact"; +import { + TARGET_LOSS_BASIS_HASH_ALGORITHM, + TARGET_LOSS_FORMULA, + targetLossBasisHash, +} from "../target-loss-attribution"; + +type JsonObject = Record; + +const FIXTURES = join(import.meta.dir, "..", "fixtures"); +export const CALIBRATION_TREE_GOLDEN_RECORD_PATH = join(FIXTURES, "calibration-tree-golden.json"); + +// gzipSync writes a fixed ten-byte header: magic, deflate, no flags, zero +// mtime, maximum-compression flag, then the operating-system byte, which +// zlib sets per platform (3 on Linux, 19 on macOS). Everything after it is +// the deflate stream and the CRC-32 and size trailer. +export const GZIP_HEADER_WITHOUT_OS = [0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02]; +const GZIP_HEADER_BYTES = 10; + +export interface GoldenFileRecord { + part: string; + path: string; + sha256: string; + rawBytes: number; + gzipBytes: number; + deflateSha256: string; +} + +export interface GoldenBundleRecord { + buildArtifactId: string; + files: GoldenFileRecord[]; +} + +export interface GoldenSourceRecord extends GoldenBundleRecord { + readers: { + programRootSha256: string; + geographyRootSha256: string; + }; +} + +export interface GoldenComparisonRecord { + pairArtifactId: string; + reported: GoldenBundleRecord; + shared: GoldenBundleRecord; + firstTargetDetailSha256: string; +} + +export const GOLDEN_SOURCE_CASES = [ + "us-release", + "uk-release", + "uk-release-loss-attribution", + "be-release", + "us-staging", + "uk-staging", + "uk-staging-staged-bundle", +] as const; +export type GoldenSourceCaseName = (typeof GOLDEN_SOURCE_CASES)[number]; + +export const GOLDEN_COMPARISON_CASES = ["us-comparison", "uk-comparison"] as const; +export type GoldenComparisonCaseName = (typeof GOLDEN_COMPARISON_CASES)[number]; + +export interface CalibrationTreeGoldenRecord { + schemaVersion: typeof CALIBRATION_TREE_SCHEMA_VERSION; + sources: Record; + comparisons: Record; + manifest: { sha256: string; rawBytes: number }; +} + +export interface GoldenReleaseInput { + country: MicrocosmCountry; + releaseId: string; + hfCommitSha: string; + publishedAt: string; + diagnosticsText: string; + releaseManifestText: string | null; +} + +interface GoldenSourceBuild { + country: MicrocosmCountry; + bundle: CalibrationTreeBundle; + manifestEntry: CalibrationTreeManifestEntry; +} + +function sha256Text(value: string): string { + return createHash("sha256").update(value, "utf8").digest("hex"); +} + +function sha256Bytes(value: Uint8Array): string { + return createHash("sha256").update(value).digest("hex"); +} + +function fixtureText(relativePath: string): string { + return readFileSync(join(FIXTURES, relativePath), "utf8"); +} + +function compressedFixtureText(relativePath: string): string { + return gunzipSync(readFileSync(join(FIXTURES, relativePath))).toString("utf8"); +} + +// --- Inputs --------------------------------------------------------------- + +const US_RELEASE_ID = "populace-us-2024-buildp-sparse-rmloss100-cae8640-20260728T011454Z"; +const UK_RELEASE_ID = "microcosm-uk-2024-25-national-golden"; +const UK_LOSS_ATTRIBUTION_RELEASE_ID = "microcosm-uk-2024-25-loss-attribution-golden"; +const BE_RELEASE_ID = "microcosm-be-2024-national-golden"; + +const US_STAGING_RUN_ID = "us-calibration-golden-20260930T120000Z"; +const UK_STAGING_RUN_ID = "uk-calibration-golden-20261002T231556Z"; +const UK_STAGED_BUNDLE_RUN_ID = "uk-frs-calibration-attempt-20261003T081500Z-golden"; + +const US_STAGING_REVISION = "5c2a9e1d7b3f4a6c8e0d2b4f6a8c0e2d4b6f8a0c"; +const UK_STAGING_REVISION = "8e4b2d6f0a1c3e5a7c9e1b3d5f7a9c1e3b5d7f9a"; +const UK_STAGED_BUNDLE_REVISION = "40439a3f8d241cd361bb693488b42e7258623a00"; + +// Assign each identity-only legacy target a deterministic target and +// estimates, so the tree carries real fit bands and errors. +function withLegacyValues(targets: JsonObject[]): JsonObject[] { + return targets.map((target, targetIndex) => { + const targetValue = 1_000 * (1 + (targetIndex % 97)); + return { + ...target, + target: targetValue, + initial_estimate: targetValue * (1 + ((targetIndex % 31) - 15) / 50), + final_estimate: targetValue * (1 + ((targetIndex % 23) - 11) / 100), + }; + }); +} + +// Attach the weighted target-error attribution Microcosm reports from +// diagnostics schema 6 on: provided weights and scales, each target's capped +// scaled error and loss contribution, and the basis digest the reader +// verifies. Comparisons are built only between builds that report it. +function withReportedLossAttribution(diagnostics: JsonObject): JsonObject { + const cap = 1; + const weighted = (diagnostics.targets as JsonObject[]).map((target, targetIndex) => ({ + ...target, + name: String(target.name), + target: Number(target.target), + final_estimate: Number(target.final_estimate), + target_loss_weight: 1 + (targetIndex % 4), + target_loss_scale: Math.max(Math.abs(Number(target.target)), 1), + })); + const totalWeight = weighted.reduce((sum, target) => sum + target.target_loss_weight, 0); + const targets = weighted.map((target) => { + const cappedError = Math.min( + Math.abs(target.final_estimate - target.target) / target.target_loss_scale, + cap, + ); + const share = target.target_loss_weight / totalWeight; + return { + ...target, + target_loss_weight_share: share, + final_capped_scaled_error: cappedError, + final_loss_contribution: share * cappedError, + }; + }); + return { + ...diagnostics, + final_loss: targets.reduce((sum, target) => sum + target.final_loss_contribution, 0), + diagnostic_warnings: [], + target_loss_basis: { + formula: TARGET_LOSS_FORMULA, + cap, + target_count: targets.length, + total_target_weight: totalWeight, + weight_kind: "provided", + scale_kind: "provided", + hash_algorithm: TARGET_LOSS_BASIS_HASH_ALGORITHM, + sha256: targetLossBasisHash(targets.map((target) => ({ + name: target.name, + target_loss_weight: target.target_loss_weight, + target_loss_scale: target.target_loss_scale, + }))), + }, + targets, + }; +} + +function usLegacyTargets(): JsonObject[] { + const fixture = JSON.parse(compressedFixtureText("us-legacy-target-identities.json.gz")) as { + targets: JsonObject[]; + }; + return withLegacyValues(fixture.targets); +} + +// Schema 6 keeps the legacy target reader and adds reported attribution. +function usReleaseDiagnostics(): JsonObject { + return withReportedLossAttribution({ schema_version: 6, targets: usLegacyTargets() }); +} + +// The four structured UK geographies and two receipts lines used by the +// schema 7 and schema 8 reader tests. +const UK_GEOGRAPHIES = [ + { id: "K02000001", label: "United Kingdom" }, + { id: "K03000001", label: "Great Britain" }, + { id: "E92000001", label: "England" }, + { id: "S92000003", label: "Scotland" }, +]; +const UK_RECEIPT_LINES = [ + { id: "income_tax", label: "Income tax receipts" }, + { id: "national_insurance", label: "National insurance contributions" }, +]; + +function ukHierarchyTarget(options: { + name: string; + provider: { id: string; label: string }; + category: { id: string; label: string }; + geography: { id: string; label: string }; + dimension: { id: string; label: string } | null; + targetLabel: string; + target: number; + initialEstimate: number; + finalEstimate: number; +}): JsonObject { + return { + name: `${options.name}@2025`, + target_name: options.name, + source: `${options.provider.label} citation`, + target: options.target, + initial_estimate: options.initialEstimate, + final_estimate: options.finalEstimate, + hierarchy: { + provider: options.provider, + category: { ...options.category, provider_id: options.provider.id }, + geography: { ...options.geography, level: "country" }, + dimensions: options.dimension + ? [{ + id: `${options.category.id}_line`, + label: "Economic and fiscal outlook line", + value_id: options.dimension.id, + value_label: options.dimension.label, + }] + : [], + target: { id: options.name, label: options.targetLabel }, + }, + }; +} + +function ukHierarchyTargets(): JsonObject[] { + const obr = { id: "obr", label: "Office for Budget Responsibility" }; + const ons = { id: "ons", label: "Office for National Statistics" }; + const receipts = { id: "obr.efo_receipts", label: "Economic and fiscal outlook receipts" }; + const population = { id: "ons.population", label: "Population estimates" }; + const targets: JsonObject[] = []; + UK_GEOGRAPHIES.forEach((geography, geographyIndex) => { + UK_RECEIPT_LINES.forEach((line, lineIndex) => { + const targetValue = 1_000 * (geographyIndex + 1) + 250 * lineIndex; + targets.push(ukHierarchyTarget({ + name: `obr.${line.id}.${geography.id}`, + provider: obr, + category: receipts, + geography, + dimension: line, + targetLabel: `${line.label}, ${geography.label}`, + target: targetValue, + initialEstimate: targetValue * 0.9, + finalEstimate: targetValue * (1 + 0.004 * (geographyIndex - lineIndex)), + })); + }); + const populationValue = 5_000_000 * (geographyIndex + 1); + targets.push(ukHierarchyTarget({ + name: `ons.population.${geography.id}`, + provider: ons, + category: population, + geography, + dimension: null, + targetLabel: `Population, ${geography.label}`, + target: populationValue, + initialEstimate: populationValue * 1.08, + finalEstimate: populationValue * (1 - 0.0000001 * geographyIndex), + })); + }); + return targets; +} + +function ukHierarchyDiagnostics(targets: JsonObject[]): JsonObject { + return withReportedLossAttribution({ schema_version: 8, release_id: UK_RELEASE_ID, targets }); +} + +// The candidate a later staging run would produce: every receipts estimate +// moves, Scotland's population target is dropped and Wales's is added. +function ukStagingTargets(): JsonObject[] { + const moved: JsonObject[] = ukHierarchyTargets() + .filter((target) => target.target_name !== "ons.population.S92000003") + .map((target, targetIndex) => ({ + ...target, + final_estimate: Number(target.final_estimate) * (1 + 0.01 * ((targetIndex % 5) - 2)), + })); + const wales = { id: "W92000004", label: "Wales" }; + moved.push(ukHierarchyTarget({ + name: `ons.population.${wales.id}`, + provider: { id: "ons", label: "Office for National Statistics" }, + category: { id: "ons.population", label: "Population estimates" }, + geography: wales, + dimension: null, + targetLabel: `Population, ${wales.label}`, + target: 3_100_000, + initialEstimate: 3_200_000, + finalEstimate: 3_099_000, + })); + return moved; +} + +// The candidate a later US staging run would produce: the first 1,500 +// release targets, every estimate moved. +function usStagingDiagnostics(): JsonObject { + const targets = usLegacyTargets().slice(0, 1_500).map((target, targetIndex) => ({ + ...target, + final_estimate: Number(target.final_estimate) * (1 + 0.003 * ((targetIndex % 7) - 3)), + })); + return withReportedLossAttribution({ schema_version: 6, targets }); +} + +export function goldenReleaseInputs(): Record< + "us-release" | "uk-release" | "uk-release-loss-attribution" | "be-release", + GoldenReleaseInput +> { + return { + "us-release": { + country: "us", + releaseId: US_RELEASE_ID, + hfCommitSha: "7d3e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e", + publishedAt: "2026-07-28T01:14:54.000Z", + diagnosticsText: JSON.stringify(usReleaseDiagnostics()), + releaseManifestText: null, + }, + "uk-release": { + country: "uk", + releaseId: UK_RELEASE_ID, + hfCommitSha: "f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1", + publishedAt: "2026-10-04T18:57:22.000Z", + diagnosticsText: JSON.stringify(ukHierarchyDiagnostics(ukHierarchyTargets())), + releaseManifestText: JSON.stringify({ country: { code: "uk" } }), + }, + "uk-release-loss-attribution": { + country: "uk", + releaseId: UK_LOSS_ATTRIBUTION_RELEASE_ID, + hfCommitSha: "a9c1e3b5d7f9a1c3e5b7d9f1a3c5e7b9d1f3a5c7", + publishedAt: "2026-10-06T09:30:00.000Z", + diagnosticsText: fixtureText("target-loss-attribution/valid-uniform.json"), + releaseManifestText: null, + }, + "be-release": { + country: "be", + releaseId: BE_RELEASE_ID, + hfCommitSha: "b2d4f6a8c0e2b4d6f8a0c2e4b6d8f0a2c4e6b8d0", + publishedAt: "2026-09-16T12:00:00.000Z", + diagnosticsText: fixtureText("be-release/calibration_diagnostics.json"), + releaseManifestText: fixtureText("be-release/release_manifest.json"), + }, + }; +} + +// --- Builder, as the publisher drives it ----------------------------------- + +function bundleComparison(calibration: Calibration) { + return { + releaseId: calibration.release_id, + calibrationProvenance: calibration.calibration_provenance, + status: calibration.target_loss_attribution.status, + aggregate: calibration.target_loss_attribution.aggregate, + cap: calibration.target_loss_attribution.cap, + basisIdentifier: calibration.target_loss_attribution.basis_identifier, + targetRepresentation: calibration.target_schema.target_representation, + }; +} + +function indexFile(bundle: CalibrationTreeBundle): CalibrationTreeBundleFile { + const file = bundle.files.find((candidate) => candidate.part === "index"); + if (!file) throw new Error("Calibration tree bundle has no index."); + return file; +} + +// Repository overrides and the UK local-run directory change which inputs +// the builder reads; the record uses each country's registered defaults. +function repositoryEnvironmentNames(): string[] { + const names = new Set([UK_LOCAL_CALIBRATION_DIR_ENV]); + for (const registration of Object.values(COUNTRY_REGISTRY) as CountryRegistration[]) { + for (const name of [ + registration.repo_env, + registration.revision_env, + registration.staging?.repo_env, + registration.staging?.revision_env, + ]) { + if (name) names.add(name); + } + } + return [...names]; +} + +export async function withRegisteredRepositories( + operation: () => Promise, +): Promise { + const saved = new Map(repositoryEnvironmentNames().map((name) => [name, process.env[name]])); + for (const name of saved.keys()) delete process.env[name]; + try { + return await operation(); + } finally { + for (const [name, value] of saved) { + if (value == null) delete process.env[name]; + else process.env[name] = value; + } + } +} + +async function withHuggingFaceFiles( + files: Map, + operation: () => Promise, +): Promise { + const originalFetch = globalThis.fetch; + globalThis.fetch = (async (input: string | URL | Request) => { + const body = files.get(String(input)); + return body == null + ? new Response(null, { status: 404 }) + : new Response(body, { headers: { "Content-Type": "application/json" } }); + }) as typeof fetch; + try { + return await operation(); + } finally { + globalThis.fetch = originalFetch; + } +} + +function buildReleaseSource(input: GoldenReleaseInput): GoldenSourceBuild { + const prefix = `releases/${input.releaseId}`; + const diagnostics = { + json: JSON.parse(input.diagnosticsText) as JsonObject, + source: { + path: `${prefix}/calibration_diagnostics.json`, + sha256: sha256Text(input.diagnosticsText), + } satisfies CalibrationTreeSourceArtifact, + }; + const releaseManifest = input.releaseManifestText == null + ? null + : { + json: JSON.parse(input.releaseManifestText) as JsonObject, + source: { + path: `${prefix}/release_manifest.json`, + sha256: sha256Text(input.releaseManifestText), + } satisfies CalibrationTreeSourceArtifact, + }; + const calibration = buildCalibration( + diagnostics.json, + input.releaseId, + null, + {}, + releaseManifest?.json ?? {}, + {}, + input.country, + "huggingface_immutable", + diagnostics.source.sha256, + input.hfCommitSha, + ); + const bundle = buildCalibrationTreeBundle({ + country: input.country, + createdAt: input.publishedAt, + releaseId: input.releaseId, + hfRepo: microcosmRepo(input.country), + hfCommitSha: input.hfCommitSha, + sourceArtifacts: { + calibrationDiagnostics: diagnostics.source, + buildManifest: null, + releaseManifest: releaseManifest?.source ?? null, + demographics: null, + }, + rows: calibration.rows, + calibrationProvenance: calibration.calibration_provenance, + lossAttributionAvailable: calibration.target_loss_attribution.status !== "unavailable", + comparison: bundleComparison(calibration), + }); + const build = bundle.index.build; + const index = indexFile(bundle); + return { + country: input.country, + bundle, + manifestEntry: { + buildArtifactId: build.buildArtifactId, + kind: "release", + sourceId: build.sourceId, + label: build.label, + releaseId: input.releaseId, + stagingRunId: null, + hfRepo: microcosmRepo(input.country), + hfCommitSha: input.hfCommitSha, + treeSchemaVersion: CALIBRATION_TREE_SCHEMA_VERSION, + indexSha256: index.sha256, + indexBytes: index.rawBytes, + createdAt: build.createdAt, + updatedAt: build.createdAt, + }, + }; +} + +async function buildStagingSource(options: { + country: MicrocosmCountry; + runId: string; + revisionSha: string; + files: Map; +}): Promise { + const repository = stagingRepository(options.country); + if (!repository) throw new Error(`Staging is not configured for ${options.country}.`); + const source = await withHuggingFaceFiles(options.files, () => + resolveStagingCalibrationSource(options.runId, 0, options.country, options.revisionSha) + ); + if (!source) throw new Error(`Staging run ${options.runId} has no calibration diagnostics.`); + const { calibration } = source; + const bundle = buildCalibrationTreeBundle({ + country: options.country, + buildKind: "staging", + sourceId: options.runId, + label: source.candidateReleaseId, + createdAt: source.updatedAt, + releaseId: source.candidateReleaseId, + hfRepo: repository.repo, + hfCommitSha: options.revisionSha, + sourceArtifacts: { ...source.sourceArtifacts, demographics: null }, + rows: calibration.rows, + calibrationProvenance: calibration.calibration_provenance, + lossAttributionAvailable: calibration.target_loss_attribution.status !== "unavailable", + comparison: bundleComparison(calibration), + }); + const index = indexFile(bundle); + return { + country: options.country, + bundle, + manifestEntry: { + buildArtifactId: bundle.index.buildArtifactId, + kind: "staging", + sourceId: options.runId, + label: source.candidateReleaseId, + releaseId: null, + stagingRunId: options.runId, + hfRepo: repository.repo, + hfCommitSha: options.revisionSha, + treeSchemaVersion: CALIBRATION_TREE_SCHEMA_VERSION, + indexSha256: index.sha256, + indexBytes: index.rawBytes, + createdAt: source.updatedAt, + updatedAt: source.updatedAt, + }, + }; +} + +function stagingFileUrl(country: MicrocosmCountry, revision: string, path: string): string { + return `https://huggingface.co/datasets/${stagingRepository(country)!.repo}/resolve/${revision}/${path}`; +} + +// A version 1 run: progress names the candidate and the diagnostics sit at +// the legacy run path. +function usVersion1StagingFiles(): Map { + const runId = US_STAGING_RUN_ID; + const url = (path: string) => stagingFileUrl("us", US_STAGING_REVISION, `runs/${runId}/${path}`); + return new Map([ + [url("progress.json"), JSON.stringify({ + schema_version: 1, + run_id: runId, + candidate_release_id: "populace-us-2024-golden-candidate", + updated_at: "2026-09-30T12:00:00+00:00", + })], + [url("run_manifest.json"), JSON.stringify({ schema_version: 1, run_id: runId })], + [url("calibration_diagnostics.json"), JSON.stringify(usStagingDiagnostics())], + ]); +} + +function version2RunManifest( + runId: string, + updatedAt: string, + artifacts: JsonObject[], +): JsonObject { + return { + schema_name: "microcosm.staging.run-manifest", + schema_version: 2, + run_id: runId, + country_code: "GB", + operation_id: "uk_national_calibration", + pipeline: { id: "uk_national_calibration", version: "2026.09" }, + candidate_id: `${runId}-candidate`, + release_id: null, + run_kind: "calibration", + non_release: true, + started_at: "2026-10-02T20:00:00+00:00", + updated_at: updatedAt, + status: "completed", + current_stage: "complete", + sample: { mode: "full" }, + delivery: { + contract_version: 2, + enabled: true, + mode: "local_only", + run_id: runId, + configured_repository: null, + upload_attempts: 0, + upload_successes: 0, + read_back: "not_requested", + last_error_code: null, + opt_out_reason: null, + }, + artifacts, + failure: null, + paths: { + progress: `runs/${runId}/progress.json`, + events: `runs/${runId}/events.ndjson`, + calibration_progress: null, + }, + }; +} + +// A version 2 run that uploaded its diagnostics with its telemetry. +function ukVersion2StagingFiles(): Map { + const runId = UK_STAGING_RUN_ID; + const diagnosticsText = JSON.stringify(ukHierarchyDiagnostics(ukStagingTargets())); + const manifest = version2RunManifest(runId, "2026-10-02T23:15:56+00:00", [{ + logical_name: "calibration_diagnostics", + artifact_kind: "aggregate_diagnostics", + contract_relative_path: "artifacts/calibration_diagnostics.json", + media_type: "application/json", + sha256: sha256Text(diagnosticsText), + classification: "aggregate", + }]); + const url = (path: string) => stagingFileUrl("uk", UK_STAGING_REVISION, `runs/${runId}/${path}`); + return new Map([ + [url("run_manifest.json"), JSON.stringify(manifest)], + [url("artifacts/calibration_diagnostics.json"), diagnosticsText], + ]); +} + +// A version 2 run whose diagnostics travel in its staged dataset bundle in +// the release repository, located by the staged-dataset receipt. +function ukStagedBundleStagingFiles(): Map { + const runId = UK_STAGED_BUNDLE_RUN_ID; + const diagnosticsText = JSON.stringify(ukHierarchyDiagnostics(ukStagingTargets())); + const releaseRepository = COUNTRY_REGISTRY.uk.repo; + const receiptText = JSON.stringify({ + contract_version: 1, + run_id: runId, + status: "uploaded", + mode: "local_and_remote", + repository: releaseRepository, + prefix: `staged/${runId}`, + revision: UK_STAGED_BUNDLE_REVISION, + error_code: null, + opt_out_reason: null, + files: { + "calibration_diagnostics.json": { + bytes: Buffer.byteLength(diagnosticsText, "utf8"), + sha256: sha256Text(diagnosticsText), + }, + "microcosm_uk_2024_25.h5": { bytes: 1, sha256: "0".repeat(64) }, + }, + }); + const manifest = version2RunManifest(runId, "2026-10-03T08:30:00+00:00", [{ + logical_name: "staged_dataset", + artifact_kind: "build_metadata", + contract_relative_path: "artifacts/staged_dataset.json", + media_type: "application/json", + sha256: sha256Text(receiptText), + classification: "non_row_level", + }]); + const url = (path: string) => stagingFileUrl("uk", UK_STAGING_REVISION, `runs/${runId}/${path}`); + return new Map([ + [url("run_manifest.json"), JSON.stringify(manifest)], + [url("artifacts/staged_dataset.json"), receiptText], + [ + `https://huggingface.co/datasets/${releaseRepository}/resolve/${UK_STAGED_BUNDLE_REVISION}/staged/${runId}/calibration_diagnostics.json`, + diagnosticsText, + ], + ]); +} + +const sourceBuilds = new Map>(); + +function buildSourceCase(name: GoldenSourceCaseName): Promise { + return withRegisteredRepositories(async () => { + switch (name) { + case "us-release": + case "uk-release": + case "uk-release-loss-attribution": + case "be-release": + return buildReleaseSource(goldenReleaseInputs()[name]); + case "us-staging": + return buildStagingSource({ + country: "us", + runId: US_STAGING_RUN_ID, + revisionSha: US_STAGING_REVISION, + files: usVersion1StagingFiles(), + }); + case "uk-staging": + return buildStagingSource({ + country: "uk", + runId: UK_STAGING_RUN_ID, + revisionSha: UK_STAGING_REVISION, + files: ukVersion2StagingFiles(), + }); + case "uk-staging-staged-bundle": + return buildStagingSource({ + country: "uk", + runId: UK_STAGED_BUNDLE_RUN_ID, + revisionSha: UK_STAGING_REVISION, + files: ukStagedBundleStagingFiles(), + }); + } + }); +} + +export function goldenSourceBuild(name: GoldenSourceCaseName): Promise { + let build = sourceBuilds.get(name); + if (!build) { + build = buildSourceCase(name); + sourceBuilds.set(name, build); + } + return build; +} + +// --- Record --------------------------------------------------------------- + +function fileRecord(file: CalibrationTreeBundleFile): GoldenFileRecord { + return { + part: file.part, + path: file.path, + sha256: file.sha256, + rawBytes: file.rawBytes, + gzipBytes: file.gzipBytes, + deflateSha256: sha256Bytes(file.compressed.subarray(GZIP_HEADER_BYTES)), + }; +} + +export function bundleRecord(bundle: CalibrationTreeBundle): GoldenBundleRecord { + return { + buildArtifactId: bundle.index.buildArtifactId, + files: bundle.files.map(fileRecord), + }; +} + +// Stored bundle files by Blob path, served the way the Blob client returns them. +type StoredFiles = Map; + +function storeBundle(storedFiles: StoredFiles, bundle: CalibrationTreeBundle): void { + for (const file of bundle.files) storedFiles.set(file.path, file.compressed); +} + +function storedJson(storedFiles: StoredFiles, path: string): unknown { + const compressed = storedFiles.get(path); + if (!compressed) throw new Error(`Stored calibration tree file ${path} is missing.`); + return JSON.parse(gunzipSync(compressed).toString("utf8")); +} + +function storedBlobReader(storedFiles: StoredFiles): typeof getCalibrationTreeBlob { + return (async (options: Parameters[0]) => { + const path = calibrationTreePartPath(options.country, options.buildArtifactId, options.part); + const compressed = storedFiles.get(path); + if (!compressed) return null; + return { + statusCode: 200 as const, + stream: new Blob([new Uint8Array(compressed)]).stream(), + headers: new Headers(), + blob: { + url: `https://blob.example/${path}`, + downloadUrl: `https://blob.example/${path}?download=1`, + pathname: path, + contentDisposition: "inline", + cacheControl: "public, max-age=31536000", + uploadedAt: new Date("2026-10-15T00:00:00.000Z"), + etag: `"${sha256Bytes(compressed)}"`, + contentType: "application/gzip", + size: compressed.byteLength, + }, + }; + }) as typeof getCalibrationTreeBlob; +} + +// The explorer's root responses, read back from the stored bytes the way the +// dashboard loads a published tree. +function readerRecord(bundle: CalibrationTreeBundle): GoldenSourceRecord["readers"] { + const storedFiles: StoredFiles = new Map(); + storeBundle(storedFiles, bundle); + const index = parseCalibrationTreeIndex( + storedJson(storedFiles, calibrationTreePartPath(bundle.index.country, bundle.index.buildArtifactId, "index")), + ); + const part = (descriptor: { part: Parameters[1]; path: string }) => + parseCalibrationTreePart(storedJson(storedFiles, descriptor.path), descriptor.part); + const loaded = { + index, + tiers: index.parts.tiers.map((descriptor) => part(descriptor) as CalibrationTreeTierArtifact), + filterIndex: part(index.parts.filterIndex) as CalibrationTreeFilterIndexArtifact, + targetSummaries: calibrationTreeTargetsFromSummaries( + index, + index.parts.targetSummaries.map( + (descriptor) => part(descriptor) as CalibrationTreeTargetSummaryArtifact, + ), + ), + targetDetailShard: index.parts.targetDetails[0] + ? part(index.parts.targetDetails[0]) as CalibrationTreeTargetDetailsArtifact + : undefined, + }; + const rootResponse = (breakdown: ExplorerState["breakdown"]) => + sha256Text(JSON.stringify( + calibrationTreeResponseFromBundle(loaded, { ...createExplorerState(), breakdown }), + )); + return { + programRootSha256: rootResponse("program"), + geographyRootSha256: rootResponse("geography"), + }; +} + +export async function goldenSourceRecord(name: GoldenSourceCaseName): Promise { + const { bundle } = await goldenSourceBuild(name); + return { ...bundleRecord(bundle), readers: readerRecord(bundle) }; +} + +const COMPARISON_SOURCES: Record< + GoldenComparisonCaseName, + { current: GoldenSourceCaseName; candidate: GoldenSourceCaseName } +> = { + "us-comparison": { current: "us-release", candidate: "us-staging" }, + "uk-comparison": { current: "uk-release", candidate: "uk-staging" }, +}; + +// Comparisons are built the way the dashboard builds them lazily: both +// sources are read back from their stored bytes. +export async function goldenComparisonRecord( + name: GoldenComparisonCaseName, +): Promise { + const [current, candidate] = await Promise.all([ + goldenSourceBuild(COMPARISON_SOURCES[name].current), + goldenSourceBuild(COMPARISON_SOURCES[name].candidate), + ]); + const storedFiles: StoredFiles = new Map(); + storeBundle(storedFiles, current.bundle); + storeBundle(storedFiles, candidate.bundle); + const getBlob = storedBlobReader(storedFiles); + const country = current.country; + const bundles = buildCalibrationComparisonBundles( + await loadCalibrationComparisonSource(country, current.bundle.index.buildArtifactId, getBlob), + await loadCalibrationComparisonSource(country, candidate.bundle.index.buildArtifactId, getBlob), + ); + storeBundle(storedFiles, bundles.reported); + storeBundle(storedFiles, bundles.shared); + const firstTargetDetail = await loadCalibrationComparisonTargetDetail({ + country, + buildArtifactId: bundles.reported.index.buildArtifactId, + targetOrdinal: 0, + getBlob, + }); + return { + pairArtifactId: bundles.pairArtifactId, + reported: bundleRecord(bundles.reported), + shared: bundleRecord(bundles.shared), + firstTargetDetailSha256: sha256Text(JSON.stringify(firstTargetDetail)), + }; +} + +// The manifest the publisher would hold after publishing every recorded +// source, latest release per country first. +export async function goldenManifestRecord(): Promise { + let manifest = emptyCalibrationTreeManifest(); + const latestReleases = new Set(["us-release", "uk-release", "be-release"]); + for (const name of GOLDEN_SOURCE_CASES) { + const { country, manifestEntry } = await goldenSourceBuild(name); + manifest = withCalibrationTreeManifestEntry( + manifest, + country, + manifestEntry, + latestReleases.has(name), + ); + } + const serialized = serializeCalibrationTreeManifest(manifest); + return { sha256: sha256Text(serialized), rawBytes: Buffer.byteLength(serialized, "utf8") }; +} + +export async function recordCalibrationTreeGoldens(): Promise { + const sources = {} as CalibrationTreeGoldenRecord["sources"]; + for (const name of GOLDEN_SOURCE_CASES) sources[name] = await goldenSourceRecord(name); + const comparisons = {} as CalibrationTreeGoldenRecord["comparisons"]; + for (const name of GOLDEN_COMPARISON_CASES) comparisons[name] = await goldenComparisonRecord(name); + return { + schemaVersion: CALIBRATION_TREE_SCHEMA_VERSION, + sources, + comparisons, + manifest: await goldenManifestRecord(), + }; +} + +if (import.meta.main) { + const record = await recordCalibrationTreeGoldens(); + await Bun.write(CALIBRATION_TREE_GOLDEN_RECORD_PATH, `${JSON.stringify(record, null, 2)}\n`); + console.log(`Recorded ${CALIBRATION_TREE_GOLDEN_RECORD_PATH}`); +} diff --git a/frontend/scripts/publish-calibration-tree-golden.test.ts b/frontend/scripts/publish-calibration-tree-golden.test.ts new file mode 100644 index 00000000..b6250c2e --- /dev/null +++ b/frontend/scripts/publish-calibration-tree-golden.test.ts @@ -0,0 +1,176 @@ +import { afterEach, expect, test } from "bun:test"; + +import recordedGoldens from "../lib/microcosm/fixtures/calibration-tree-golden.json"; +import { + emptyCalibrationTreeManifest, + withCalibrationTreeManifestEntry, + type CalibrationTreeManifestEntry, +} from "../lib/microcosm/calibration-tree-manifest"; +import { COUNTRY_REGISTRY } from "../lib/microcosm/countries"; +import { + goldenReleaseInputs, + withRegisteredRepositories, + type CalibrationTreeGoldenRecord, + type GoldenReleaseInput, +} from "../lib/microcosm/test-support/calibration-tree-golden-cases"; +import { + reconcileReleaseBuilds, + type CalibrationManifestStore, +} from "./publish-calibration-tree"; + +// A repair rebuilds a published release whose index went missing and refuses +// any rebuilt index whose digest differs from the manifest entry. Seeding the +// entry with the recorded build id and index digest therefore proves, through +// the publisher's own Hugging Face reads and source hashing, that it rebuilds +// the recorded index byte for byte. +const recorded = recordedGoldens as CalibrationTreeGoldenRecord; +const MAIN_REVISION_SHA = "0123456789abcdef0123456789abcdef01234567"; +// Generous, so the suite also passes on a heavily loaded machine. +const BUILD_TIMEOUT_MS = 300_000; +const originalFetch = globalThis.fetch; + +afterEach(() => { + globalThis.fetch = originalFetch; +}); + +type ReleaseCaseName = keyof ReturnType; + +// The dataset repository as the publisher reads it: one tagged release at +// its commit, which `latest.json` on the main branch also names. +function huggingFaceReleaseRepository(release: GoldenReleaseInput): typeof fetch { + const repo = COUNTRY_REGISTRY[release.country].repo; + const api = `https://huggingface.co/api/datasets/${repo}`; + const resolveUrl = (revision: string, path: string) => + `https://huggingface.co/datasets/${repo}/resolve/${revision}/${path}`; + const prefix = `releases/${release.releaseId}`; + const publishedFiles = new Map([ + ["calibration_diagnostics.json", release.diagnosticsText], + ...(release.releaseManifestText == null + ? [] + : [["release_manifest.json", release.releaseManifestText] as [string, string]]), + ]); + const responses = new Map Response>([ + [ + `${api}/tree/main/releases?recursive=true`, + () => Response.json([...publishedFiles.keys()].map((name) => ({ + type: "file", + path: `${prefix}/${name}`, + }))), + ], + [ + `${api}/refs`, + () => Response.json({ + tags: [{ name: release.releaseId, targetCommit: release.hfCommitSha }], + }), + ], + [`${api}/revision/main`, () => Response.json({ sha: MAIN_REVISION_SHA })], + [`${api}/revision/${release.releaseId}`, () => Response.json({ sha: release.hfCommitSha })], + [ + resolveUrl(MAIN_REVISION_SHA, "latest.json"), + () => Response.json({ release_id: release.releaseId }), + ], + ]); + for (const [name, text] of publishedFiles) { + responses.set(resolveUrl(release.hfCommitSha, `${prefix}/${name}`), () => new Response(text)); + responses.set(resolveUrl("main", `${prefix}/${name}`), () => new Response(text)); + } + return (async (input: string | URL | Request) => { + const url = String(input); + const respond = responses.get(url); + if (respond) return respond(); + if (url.startsWith(resolveUrl(release.hfCommitSha, `${prefix}/`))) { + return new Response(null, { status: 404 }); + } + throw new Error(`Unexpected Hugging Face request ${url}`); + }) as typeof fetch; +} + +function recordedReleaseEntry( + release: GoldenReleaseInput, + name: ReleaseCaseName, +): CalibrationTreeManifestEntry { + const record = recorded.sources[name]; + const index = record.files.find((file) => file.part === "index"); + if (!index) throw new Error(`The ${name} record has no index.`); + return { + buildArtifactId: record.buildArtifactId, + kind: "release", + sourceId: release.releaseId, + label: release.releaseId, + releaseId: release.releaseId, + stagingRunId: null, + hfRepo: COUNTRY_REGISTRY[release.country].repo, + hfCommitSha: release.hfCommitSha, + treeSchemaVersion: 6, + indexSha256: index.sha256, + indexBytes: index.rawBytes, + createdAt: release.publishedAt, + updatedAt: release.publishedAt, + }; +} + +// A published release whose stored index has gone missing. +function storeMissingIndex( + release: GoldenReleaseInput, + entry: CalibrationTreeManifestEntry, +): CalibrationManifestStore { + return { + readManifest: async () => withCalibrationTreeManifestEntry( + emptyCalibrationTreeManifest(), + release.country, + entry, + true, + ), + listBlobs: async () => new Map(), + audit: async () => ({ + complete: false, + repairable: true, + reasons: ["index is missing"], + index: null, + }), + updateManifest: async () => { + throw new Error("A dry run must not write the manifest."); + }, + }; +} + +const RELEASE_DESCRIPTIONS: Record<"us-release" | "uk-release" | "be-release", string> = { + "us-release": "US legacy", + "uk-release": "UK hierarchy", + "be-release": "Belgium", +}; + +for (const [name, description] of Object.entries(RELEASE_DESCRIPTIONS) as Array< + [keyof typeof RELEASE_DESCRIPTIONS, string] +>) { + test( + `a dry-run repair of the recorded ${description} release rebuilds its recorded index`, + async () => { + const release = goldenReleaseInputs()[name]; + const entry = recordedReleaseEntry(release, name); + globalThis.fetch = huggingFaceReleaseRepository(release); + + const report = await withRegisteredRepositories(() => + reconcileReleaseBuilds(release.country, "unused-blob-token", true, storeMissingIndex(release, entry)) + ); + + expect(report.outcomes).toEqual([{ + sourceId: release.releaseId, + status: "repaired", + buildArtifactId: recorded.sources[name].buildArtifactId, + reason: "Dry run; no Blob writes performed.", + }]); + }, + BUILD_TIMEOUT_MS, + ); +} + +test("a repair refuses a rebuilt index that differs from the recorded digest", async () => { + const release = goldenReleaseInputs()["uk-release"]; + const entry = { ...recordedReleaseEntry(release, "uk-release"), indexSha256: "0".repeat(64) }; + globalThis.fetch = huggingFaceReleaseRepository(release); + + await expect(withRegisteredRepositories(() => + reconcileReleaseBuilds(release.country, "unused-blob-token", true, storeMissingIndex(release, entry)) + )).rejects.toThrow("Rebuilt calibration index differs from immutable manifest entry"); +}); From e337d3d8088bf2b3fc6f46a139951318f1beecbb Mon Sep 17 00:00:00 2001 From: Anthony Volk <14987227+anth-volk@users.noreply.github.com> Date: Fri, 16 Oct 2026 00:28:20 +0400 Subject: [PATCH 2/3] Move the calibration tree builder into a workspace package 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 --- AGENTS.md | 2 +- CLAUDE.md | 2 +- README.md | 2 +- docs/calibration-tree-artifacts.md | 23 +- docs/spec-driven-countries.md | 6 +- frontend/app/api/microcosm/builds/route.ts | 2 +- .../app/api/microcosm/builds/run/route.ts | 2 +- frontend/app/api/microcosm/compare/route.ts | 2 +- .../microcosm/comparison-tree/route.test.ts | 2 +- .../api/microcosm/comparison-tree/route.ts | 12 +- .../target-detail/route.test.ts | 2 +- .../comparison-tree/target-detail/route.ts | 6 +- .../app/api/microcosm/cross-dataset/route.ts | 2 +- frontend/app/api/microcosm/releases/route.ts | 2 +- frontend/app/api/microcosm/route.ts | 2 +- .../api/microcosm/staging/compare/route.ts | 6 +- .../app/api/microcosm/staging/run/route.ts | 6 +- .../app/api/microcosm/staging/runs/route.ts | 4 +- .../staging/target-change-tree/route.ts | 8 +- .../staging/target-diagnostics/route.ts | 6 +- .../microcosm/staging/target-tree/route.ts | 6 +- .../api/microcosm/target-diagnostics/route.ts | 2 +- .../microcosm/target-investigation/route.ts | 2 +- .../app/api/microcosm/target-treemap/route.ts | 2 +- .../api/microcosm/tree-manifest/route.test.ts | 2 +- .../app/api/microcosm/tree-manifest/route.ts | 6 +- frontend/app/api/microcosm/tree/route.ts | 8 +- frontend/app/api/microcosm/variable/route.ts | 2 +- frontend/app/microcosm/page.tsx | 2 +- frontend/app/microcosm/targets/page.tsx | 2 +- frontend/bun.lock | 14 + frontend/bunfig.toml | 5 + .../components/layout/country-context.tsx | 2 +- frontend/components/layout/mobile-nav.tsx | 2 +- frontend/components/layout/nav-items.test.ts | 2 +- frontend/components/layout/nav-items.ts | 2 +- frontend/components/layout/nav-sidebar.tsx | 2 +- .../calibration-explorer-filter-menu.tsx | 4 +- .../microcosm/calibration-explorer-map.tsx | 4 +- .../calibration-explorer-view.test.ts | 2 +- .../microcosm/calibration-explorer-view.ts | 8 +- .../microcosm/microcosm-staging-view.tsx | 2 +- .../microcosm/microcosm-target-detail.tsx | 4 +- .../microcosm/microcosm-targets-view.tsx | 2 +- .../microcosm/staging-target-change-map.tsx | 6 +- ...staging-target-detail-presentation.test.ts | 2 +- .../staging-target-detail-presentation.ts | 2 +- frontend/lib/api/hooks/use-microcosm.test.ts | 2 +- frontend/lib/api/hooks/use-microcosm.ts | 12 +- .../lib/cross-dataset/fact-presentation.ts | 4 +- frontend/lib/cross-dataset/source.ts | 2 +- frontend/lib/microcosm/build-monitor.test.ts | 16 +- frontend/lib/microcosm/build-runs.ts | 4 +- .../microcosm/calibration-prefetch.test.ts | 4 +- .../lib/microcosm/calibration-prefetch.ts | 4 +- .../lib/microcosm/calibration-tree-http.ts | 2 +- .../lib/microcosm/calibration-tree-request.ts | 2 +- .../calibration-treemap-layout.test.ts | 2 +- .../microcosm/calibration-treemap-layout.ts | 4 +- frontend/lib/microcosm/countries.ts | 246 +----------------- frontend/lib/microcosm/hf-webhook-config.ts | 2 +- frontend/lib/microcosm/hf-webhook-handler.ts | 2 +- frontend/lib/microcosm/local-build-runs.ts | 2 +- frontend/lib/microcosm/presentation.ts | 2 +- .../lib/microcosm/production-release.test.ts | 2 +- .../reviewed-repository-import.fixture.ts | 2 +- .../reviewed-repository-import.test.ts | 2 +- frontend/lib/microcosm/source-attribution.ts | 2 +- .../lib/microcosm/target-change-tree.test.ts | 6 +- frontend/lib/microcosm/target-change-tree.ts | 8 +- .../target-change-visualization.test.ts | 4 +- .../microcosm/target-change-visualization.ts | 4 +- .../microcosm/target-matching-presentation.ts | 4 +- .../third-country-conformance.test.ts | 6 +- frontend/lib/slack.ts | 2 +- frontend/package.json | 6 +- .../packages/calibration-tree/package.json | 49 ++++ .../src}/calibration-build-comparison.test.ts | 0 .../src}/calibration-build-comparison.ts | 0 .../src}/calibration-comparison-blob.test.ts | 0 .../src}/calibration-comparison-blob.ts | 0 .../calibration-comparison-bundle.test.ts | 0 .../src}/calibration-comparison-bundle.ts | 0 .../src}/calibration-explorer.test.ts | 0 .../src}/calibration-explorer.ts | 0 .../src}/calibration-release-locator.test.ts | 0 .../src}/calibration-release-locator.ts | 0 .../src}/calibration-tree-artifact.test.ts | 0 .../src}/calibration-tree-artifact.ts | 0 .../src}/calibration-tree-blob.test.ts | 0 .../src}/calibration-tree-blob.ts | 0 .../src}/calibration-tree-bundle.ts | 0 .../src}/calibration-tree-golden.test.ts | 0 .../src}/calibration-tree-manifest.test.ts | 0 .../src}/calibration-tree-manifest.ts | 0 .../src}/calibration-tree.test.ts | 0 .../calibration-tree/src}/calibration-tree.ts | 0 .../src}/chronicle-metadata.test.ts | 0 .../src}/chronicle-metadata.ts | 0 .../calibration-tree/src}/countries.test.ts | 0 .../calibration-tree/src/countries.ts | 241 +++++++++++++++++ .../be-release/calibration_diagnostics.json | 0 .../fixtures/be-release/release_manifest.json | 0 .../fixtures/calibration-tree-golden.json | 0 .../fixtures/staging-contract/v1/SHA256SUMS | 0 .../v1/calibration_progress.json | 0 .../staging-contract/v1/events.ndjson | 0 .../staging-contract/v1/latest_staging.json | 0 .../staging-contract/v1/progress.json | 0 .../staging-contract/v1/run_manifest.json | 0 .../fixtures/staging-contract/v1/runs.json | 0 .../fixtures/staging-contract/v2/SHA256SUMS | 0 .../calibration_progress.json | 0 .../uk-calibration-v2-fixture/events.ndjson | 0 .../uk-calibration-v2-fixture/progress.json | 0 .../run_manifest.json | 0 .../runs/uk-spine-v2-fixture/events.ndjson | 0 .../runs/uk-spine-v2-fixture/progress.json | 0 .../uk-spine-v2-fixture/run_manifest.json | 0 .../staging-contract/v2/contract-cases.json | 0 .../runs/uk-failed-v2-fixture/events.ndjson | 0 .../runs/uk-failed-v2-fixture/progress.json | 0 .../uk-failed-v2-fixture/run_manifest.json | 0 .../fixtures/staging-contract/v3/SHA256SUMS | 0 .../runs/uk-blocked-v3-fixture/events.ndjson | 0 .../runs/uk-blocked-v3-fixture/progress.json | 0 .../uk-blocked-v3-fixture/run_manifest.json | 0 .../calibration_progress.json | 0 .../uk-calibration-v3-fixture/events.ndjson | 0 .../uk-calibration-v3-fixture/progress.json | 0 .../run_manifest.json | 0 .../runs/uk-spine-v3-fixture/events.ndjson | 0 .../runs/uk-spine-v3-fixture/progress.json | 0 .../uk-spine-v3-fixture/run_manifest.json | 0 .../staging-contract/v3/contract-cases.json | 0 .../runs/uk-failed-v3-fixture/events.ndjson | 0 .../runs/uk-failed-v3-fixture/progress.json | 0 .../uk-failed-v3-fixture/run_manifest.json | 0 .../malformed-partial.json | 0 .../unavailable-warning.json | 0 .../valid-unequal-custom-scales.json | 0 .../valid-uniform.json | 0 .../us-legacy-target-identities.json.gz | Bin .../src}/hierarchy-target-reader.test.ts | 0 .../src}/hierarchy-target-reader.ts | 0 .../src}/historical-weight-summary.test.ts | 0 .../src}/historical-weight-summary.ts | 0 .../src}/latest-artifact.test.ts | 0 .../calibration-tree/src}/latest-artifact.ts | 2 +- .../src}/legacy-target-reader.ts | 0 .../src}/legacy-target-regression.test.ts | 0 .../src/package-boundary.test.ts | 128 +++++++++ .../src}/program-label.test.ts | 0 .../calibration-tree/src}/program-label.ts | 0 .../calibration-tree/src}/reforms.test.ts | 0 .../calibration-tree/src}/reforms.ts | 0 .../src}/source-label.test.ts | 0 .../calibration-tree/src}/source-label.ts | 2 +- .../src}/source-labels.test.ts | 0 .../calibration-tree/src}/source-labels.ts | 0 .../src}/staging-artifact.test.ts | 0 .../calibration-tree/src}/staging-artifact.ts | 16 +- .../src}/staging-contract.test.ts | 0 .../calibration-tree/src}/staging-contract.ts | 0 .../src}/structured-dimension-reader.test.ts | 0 .../src}/structured-dimension-reader.ts | 0 .../src}/structured-target-reader.ts | 0 .../src}/target-change.test.ts | 0 .../calibration-tree/src}/target-change.ts | 0 .../src}/target-loss-attribution-manifest.ts | 0 .../src}/target-loss-attribution.test.ts | 0 .../src}/target-loss-attribution.ts | 0 .../src}/target-representation.test.ts | 0 .../src}/target-representation.ts | 0 .../src}/target-surface-matcher.test.ts | 0 .../src}/target-surface-matcher.ts | 0 .../calibration-tree-golden-cases.ts | 37 +-- .../calibration-tree/src}/treemap.test.ts | 0 .../packages/calibration-tree/tsconfig.json | 16 ++ .../types/next-fetch-cache.d.ts | 8 + .../calibration-deployment-smoke.test.ts | 2 +- .../scripts/calibration-deployment-smoke.ts | 2 +- .../publish-calibration-tree-golden.test.ts | 8 +- .../scripts/publish-calibration-tree.test.ts | 2 +- frontend/scripts/publish-calibration-tree.ts | 16 +- frontend/tsconfig.json | 2 +- 186 files changed, 679 insertions(+), 410 deletions(-) create mode 100644 frontend/packages/calibration-tree/package.json rename frontend/{lib/microcosm => packages/calibration-tree/src}/calibration-build-comparison.test.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/calibration-build-comparison.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/calibration-comparison-blob.test.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/calibration-comparison-blob.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/calibration-comparison-bundle.test.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/calibration-comparison-bundle.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/calibration-explorer.test.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/calibration-explorer.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/calibration-release-locator.test.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/calibration-release-locator.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/calibration-tree-artifact.test.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/calibration-tree-artifact.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/calibration-tree-blob.test.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/calibration-tree-blob.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/calibration-tree-bundle.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/calibration-tree-golden.test.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/calibration-tree-manifest.test.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/calibration-tree-manifest.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/calibration-tree.test.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/calibration-tree.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/chronicle-metadata.test.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/chronicle-metadata.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/countries.test.ts (100%) create mode 100644 frontend/packages/calibration-tree/src/countries.ts rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/be-release/calibration_diagnostics.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/be-release/release_manifest.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/calibration-tree-golden.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v1/SHA256SUMS (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v1/calibration_progress.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v1/events.ndjson (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v1/latest_staging.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v1/progress.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v1/run_manifest.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v1/runs.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v2/SHA256SUMS (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/calibration_progress.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/events.ndjson (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/progress.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/run_manifest.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v2/completed-spine/runs/uk-spine-v2-fixture/events.ndjson (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v2/completed-spine/runs/uk-spine-v2-fixture/progress.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v2/completed-spine/runs/uk-spine-v2-fixture/run_manifest.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v2/contract-cases.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v2/failed/runs/uk-failed-v2-fixture/events.ndjson (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v2/failed/runs/uk-failed-v2-fixture/progress.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v2/failed/runs/uk-failed-v2-fixture/run_manifest.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v3/SHA256SUMS (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v3/blocked/runs/uk-blocked-v3-fixture/events.ndjson (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v3/blocked/runs/uk-blocked-v3-fixture/progress.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v3/blocked/runs/uk-blocked-v3-fixture/run_manifest.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/calibration_progress.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/events.ndjson (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/progress.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/run_manifest.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v3/completed-spine/runs/uk-spine-v3-fixture/events.ndjson (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v3/completed-spine/runs/uk-spine-v3-fixture/progress.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v3/completed-spine/runs/uk-spine-v3-fixture/run_manifest.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v3/contract-cases.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v3/failed/runs/uk-failed-v3-fixture/events.ndjson (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v3/failed/runs/uk-failed-v3-fixture/progress.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/staging-contract/v3/failed/runs/uk-failed-v3-fixture/run_manifest.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/target-loss-attribution/malformed-partial.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/target-loss-attribution/unavailable-warning.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/target-loss-attribution/valid-unequal-custom-scales.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/target-loss-attribution/valid-uniform.json (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/fixtures/us-legacy-target-identities.json.gz (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/hierarchy-target-reader.test.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/hierarchy-target-reader.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/historical-weight-summary.test.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/historical-weight-summary.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/latest-artifact.test.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/latest-artifact.ts (99%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/legacy-target-reader.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/legacy-target-regression.test.ts (100%) create mode 100644 frontend/packages/calibration-tree/src/package-boundary.test.ts rename frontend/{lib/microcosm => packages/calibration-tree/src}/program-label.test.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/program-label.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/reforms.test.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/reforms.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/source-label.test.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/source-label.ts (61%) rename frontend/{lib => packages/calibration-tree/src}/source-labels.test.ts (100%) rename frontend/{lib => packages/calibration-tree/src}/source-labels.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/staging-artifact.test.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/staging-artifact.ts (98%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/staging-contract.test.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/staging-contract.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/structured-dimension-reader.test.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/structured-dimension-reader.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/structured-target-reader.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/target-change.test.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/target-change.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/target-loss-attribution-manifest.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/target-loss-attribution.test.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/target-loss-attribution.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/target-representation.test.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/target-representation.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/target-surface-matcher.test.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/target-surface-matcher.ts (100%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/test-support/calibration-tree-golden-cases.ts (96%) rename frontend/{lib/microcosm => packages/calibration-tree/src}/treemap.test.ts (100%) create mode 100644 frontend/packages/calibration-tree/tsconfig.json create mode 100644 frontend/packages/calibration-tree/types/next-fetch-cache.d.ts diff --git a/AGENTS.md b/AGENTS.md index a414e40d..de787654 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,4 +7,4 @@ - Before providing a Staging preview URL, request `/api/microcosm/staging/runs` for the selected country and confirm that the JSON response reports the repository as available. Checking only the HTTP status is insufficient because the route can return HTTP 200 with `available: false`. Treat 401, 403, the 404 response Hugging Face may return for an unauthorized private repository, and any `available: false` response as a failed launch. - When investigating a discrepant Microcosm calibration target, follow [the shared investigation workflow](docs/ai/workflows/investigate-microcosm-target.md) and its linked role-specific reviews. - Before starting the application for Cross-dataset work, configure exactly one DIR or URL artifact location for each country you will use, as described in [the Cross-dataset application configuration](docs/cross-dataset-api.md#configure-the-application). -- Adding a Microcosm country is one entry in `frontend/lib/microcosm/countries.ts`; pages are gated by the registration's capabilities and the release artifact's `country` block, never by country code (see [the spec-driven countries note](docs/spec-driven-countries.md)). +- Adding a Microcosm country is one entry in `frontend/packages/calibration-tree/src/countries.ts`; pages are gated by the registration's capabilities and the release artifact's `country` block, never by country code (see [the spec-driven countries note](docs/spec-driven-countries.md)). diff --git a/CLAUDE.md b/CLAUDE.md index e8b7c29a..d2891d09 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,4 +2,4 @@ - When investigating a discrepant Microcosm calibration target, follow [the shared investigation workflow](docs/ai/workflows/investigate-microcosm-target.md) and its linked role-specific reviews. - Before starting the application for Cross-dataset work, configure exactly one DIR or URL artifact location for each country you will use, as described in [the Cross-dataset application configuration](docs/cross-dataset-api.md#configure-the-application). -- Adding a Microcosm country is one entry in `frontend/lib/microcosm/countries.ts`; pages are gated by the registration's capabilities and the release artifact's `country` block, never by country code (see [the spec-driven countries note](docs/spec-driven-countries.md)). +- Adding a Microcosm country is one entry in `frontend/packages/calibration-tree/src/countries.ts`; pages are gated by the registration's capabilities and the release artifact's `country` block, never by country code (see [the spec-driven countries note](docs/spec-driven-countries.md)). diff --git a/README.md b/README.md index bbdf607c..c6fe1fe0 100644 --- a/README.md +++ b/README.md @@ -204,7 +204,7 @@ with `POPULACE_UK_STAGING_HF_REPO` or `POPULACE_UK_STAGING_HF_REVISION`. Never use a `NEXT_PUBLIC_` variable for a private-repository credential. The staging contract fixtures under -`frontend/lib/microcosm/fixtures/staging-contract/` are byte-identical copies +`frontend/packages/calibration-tree/src/fixtures/staging-contract/` are byte-identical copies of Microcosm's canonical producer fixtures. The consumer tests pin each version's `SHA256SUMS` digest and verify every listed file. A contract change must update the canonical Microcosm fixture, its digest manifest, this copy, diff --git a/docs/calibration-tree-artifacts.md b/docs/calibration-tree-artifacts.md index 390f2a59..216571a1 100644 --- a/docs/calibration-tree-artifacts.md +++ b/docs/calibration-tree-artifacts.md @@ -4,6 +4,23 @@ The calibration explorer reads precomputed schema-6 JSON from private Vercel Blob storage. The browser does not download calibration diagnostics or rebuild the hierarchy. Schemas 1 through 5 are unsupported. +## Code + +The builder, the schema-6 types, parsers and serializers, the Blob I/O, and +the release and staging readers the builder reads from live in the +`@policyengine/calibration-tree` workspace package at +`frontend/packages/calibration-tree/`. The dashboard and the publisher import +it through its subpath exports, such as +`@policyengine/calibration-tree/calibration-tree-bundle`. The package imports +only its own modules, Node built-ins and `@vercel/blob`, and its boundary test +enforces that. + +`src/fixtures/calibration-tree-golden.json` records the exact bytes the builder +writes for representative US and UK release and staging inputs, the +comparisons between them, and the manifest. The golden tests require every +change to reproduce it. Regenerate it only for an intended output change, as +`src/test-support/calibration-tree-golden-cases.ts` describes. + ## Required Blob folder schema ```text @@ -355,7 +372,8 @@ fails publication and therefore fails the GitHub Actions job. ## Verification -Run from `frontend/`: +Run from `frontend/`; `bun test` includes the package's tests and +`bun run lint` also typechecks the package: ```sh bun test @@ -363,6 +381,9 @@ bun run lint bun run build ``` +To check the package on its own, run `bun test` and `bun run typecheck` from +`frontend/packages/calibration-tree/`. + Then verify that: 1. The manifest and all parts declare schema 6. diff --git a/docs/spec-driven-countries.md b/docs/spec-driven-countries.md index 7680c713..b285d73c 100644 --- a/docs/spec-driven-countries.md +++ b/docs/spec-driven-countries.md @@ -24,7 +24,7 @@ contract: they are served only by `/microcosm/datasets` from ## Code-owned metadata that remains -`COUNTRY_REGISTRY` in `frontend/lib/microcosm/countries.ts` is the only required +`COUNTRY_REGISTRY` in `frontend/packages/calibration-tree/src/countries.ts` is the only required registration point: country unions and parsers, selector lists, dataset and national-geography labels, public/private link behavior, and page gating read the registration or the artifact `country` block. Overview and target-browser @@ -73,10 +73,10 @@ production tables. ### Country `release_manifest.country` is read by `releaseCountry` in -`frontend/lib/microcosm/latest-artifact.ts` and served as `country` on the +`frontend/packages/calibration-tree/src/latest-artifact.ts` and served as `country` on the overview summary and the target-diagnostics page (client type `MicrocosmArtifactCountry`). Registration lives in -`frontend/lib/microcosm/countries.ts`; adding a country is one entry there. +`frontend/packages/calibration-tree/src/countries.ts`; adding a country is one entry there. ```json { diff --git a/frontend/app/api/microcosm/builds/route.ts b/frontend/app/api/microcosm/builds/route.ts index 5dfbd34d..0e06f6b9 100644 --- a/frontend/app/api/microcosm/builds/route.ts +++ b/frontend/app/api/microcosm/builds/route.ts @@ -2,7 +2,7 @@ import { NextResponse } from "next/server"; import { parseBuildSource } from "@/lib/microcosm/build-source"; import { loadBuildRuns } from "@/lib/microcosm/build-runs"; -import { parseCountry, scrub } from "@/lib/microcosm/latest-artifact"; +import { parseCountry, scrub } from "@policyengine/calibration-tree/latest-artifact"; export const dynamic = "force-dynamic"; export const revalidate = 0; diff --git a/frontend/app/api/microcosm/builds/run/route.ts b/frontend/app/api/microcosm/builds/run/route.ts index d6c1250e..cf80ae7a 100644 --- a/frontend/app/api/microcosm/builds/run/route.ts +++ b/frontend/app/api/microcosm/builds/run/route.ts @@ -2,7 +2,7 @@ import { NextResponse } from "next/server"; import { parseBuildSource } from "@/lib/microcosm/build-source"; import { loadBuildRun } from "@/lib/microcosm/build-runs"; -import { parseCountry, scrub } from "@/lib/microcosm/latest-artifact"; +import { parseCountry, scrub } from "@policyengine/calibration-tree/latest-artifact"; export const dynamic = "force-dynamic"; export const revalidate = 0; diff --git a/frontend/app/api/microcosm/compare/route.ts b/frontend/app/api/microcosm/compare/route.ts index bc6fa558..5083fb65 100644 --- a/frontend/app/api/microcosm/compare/route.ts +++ b/frontend/app/api/microcosm/compare/route.ts @@ -6,7 +6,7 @@ import { loadPointerReleaseId, parseCountry, scrub, -} from "@/lib/microcosm/latest-artifact"; +} from "@policyengine/calibration-tree/latest-artifact"; export const revalidate = 21_600; export const runtime = "nodejs"; diff --git a/frontend/app/api/microcosm/comparison-tree/route.test.ts b/frontend/app/api/microcosm/comparison-tree/route.test.ts index 4cf1fe9c..c0658c2d 100644 --- a/frontend/app/api/microcosm/comparison-tree/route.test.ts +++ b/frontend/app/api/microcosm/comparison-tree/route.test.ts @@ -4,7 +4,7 @@ import { gzipSync } from "node:zlib"; import { calibrationComparisonBuildArtifactId, calibrationComparisonPairArtifactId, -} from "@/lib/microcosm/calibration-comparison-bundle"; +} from "@policyengine/calibration-tree/calibration-comparison-bundle"; import { createCalibrationComparisonTreeHandler, type CalibrationComparisonTreeRouteDependencies, diff --git a/frontend/app/api/microcosm/comparison-tree/route.ts b/frontend/app/api/microcosm/comparison-tree/route.ts index d1692fe1..2001f26d 100644 --- a/frontend/app/api/microcosm/comparison-tree/route.ts +++ b/frontend/app/api/microcosm/comparison-tree/route.ts @@ -1,24 +1,24 @@ import { NextResponse } from "next/server"; import { withBasePath } from "@/lib/base-path"; -import { ensureCalibrationComparison } from "@/lib/microcosm/calibration-comparison-blob"; +import { ensureCalibrationComparison } from "@policyengine/calibration-tree/calibration-comparison-blob"; import { calibrationComparisonBuildArtifactId, calibrationComparisonPairArtifactId, -} from "@/lib/microcosm/calibration-comparison-bundle"; -import { getCalibrationTreeBlob } from "@/lib/microcosm/calibration-tree-blob"; +} from "@policyengine/calibration-tree/calibration-comparison-bundle"; +import { getCalibrationTreeBlob } from "@policyengine/calibration-tree/calibration-tree-blob"; import { immutableCalibrationTreePartResponse } from "@/lib/microcosm/calibration-tree-http"; import { isCalibrationTreePart, isSha256, type CalibrationTreePart, -} from "@/lib/microcosm/calibration-tree-artifact"; +} from "@policyengine/calibration-tree/calibration-tree-artifact"; import { isCountry, selectableCountries, type MicrocosmCountry, -} from "@/lib/microcosm/countries"; -import type { TargetChangeMode } from "@/lib/microcosm/target-change"; +} from "@policyengine/calibration-tree/countries"; +import type { TargetChangeMode } from "@policyengine/calibration-tree/target-change"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/frontend/app/api/microcosm/comparison-tree/target-detail/route.test.ts b/frontend/app/api/microcosm/comparison-tree/target-detail/route.test.ts index 465c6184..37c1e541 100644 --- a/frontend/app/api/microcosm/comparison-tree/target-detail/route.test.ts +++ b/frontend/app/api/microcosm/comparison-tree/target-detail/route.test.ts @@ -1,6 +1,6 @@ import { expect, test } from "bun:test"; -import type { CalibrationTreeComparisonTargetDetailResponse } from "@/lib/microcosm/calibration-tree-artifact"; +import type { CalibrationTreeComparisonTargetDetailResponse } from "@policyengine/calibration-tree/calibration-tree-artifact"; import { createCalibrationComparisonTargetDetailHandler, type CalibrationComparisonTargetDetailRouteDependencies, diff --git a/frontend/app/api/microcosm/comparison-tree/target-detail/route.ts b/frontend/app/api/microcosm/comparison-tree/target-detail/route.ts index a078c0dc..e42b3d11 100644 --- a/frontend/app/api/microcosm/comparison-tree/target-detail/route.ts +++ b/frontend/app/api/microcosm/comparison-tree/target-detail/route.ts @@ -1,12 +1,12 @@ import { NextResponse } from "next/server"; -import { loadCalibrationComparisonTargetDetail } from "@/lib/microcosm/calibration-comparison-blob"; -import { isSha256 } from "@/lib/microcosm/calibration-tree-artifact"; +import { loadCalibrationComparisonTargetDetail } from "@policyengine/calibration-tree/calibration-comparison-blob"; +import { isSha256 } from "@policyengine/calibration-tree/calibration-tree-artifact"; import { isCountry, selectableCountries, type MicrocosmCountry, -} from "@/lib/microcosm/countries"; +} from "@policyengine/calibration-tree/countries"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/frontend/app/api/microcosm/cross-dataset/route.ts b/frontend/app/api/microcosm/cross-dataset/route.ts index 10ec163f..924f10d3 100644 --- a/frontend/app/api/microcosm/cross-dataset/route.ts +++ b/frontend/app/api/microcosm/cross-dataset/route.ts @@ -3,7 +3,7 @@ import { NextResponse } from "next/server"; import { ArtifactError } from "@/lib/cross-dataset/artifact"; import { crossDatasetApiResponse } from "@/lib/cross-dataset/query"; import { configuredCrossDatasetReader } from "@/lib/cross-dataset/source"; -import { parseCountry } from "@/lib/microcosm/latest-artifact"; +import { parseCountry } from "@policyengine/calibration-tree/latest-artifact"; export const revalidate = 300; export const runtime = "nodejs"; diff --git a/frontend/app/api/microcosm/releases/route.ts b/frontend/app/api/microcosm/releases/route.ts index 53336b66..1a1b1c05 100644 --- a/frontend/app/api/microcosm/releases/route.ts +++ b/frontend/app/api/microcosm/releases/route.ts @@ -5,7 +5,7 @@ import { loadReleases, parseCountry, scrub, -} from "@/lib/microcosm/latest-artifact"; +} from "@policyengine/calibration-tree/latest-artifact"; export const revalidate = 60; diff --git a/frontend/app/api/microcosm/route.ts b/frontend/app/api/microcosm/route.ts index e734c9e8..e9401726 100644 --- a/frontend/app/api/microcosm/route.ts +++ b/frontend/app/api/microcosm/route.ts @@ -11,7 +11,7 @@ import { parseCountry, microcosmRepo, scrub, -} from "@/lib/microcosm/latest-artifact"; +} from "@policyengine/calibration-tree/latest-artifact"; export const revalidate = 21_600; export const runtime = "nodejs"; diff --git a/frontend/app/api/microcosm/staging/compare/route.ts b/frontend/app/api/microcosm/staging/compare/route.ts index df8ef2d9..f5ba57a3 100644 --- a/frontend/app/api/microcosm/staging/compare/route.ts +++ b/frontend/app/api/microcosm/staging/compare/route.ts @@ -1,12 +1,12 @@ import { NextResponse } from "next/server"; -import { hasCapability } from "@/lib/microcosm/countries"; +import { hasCapability } from "@policyengine/calibration-tree/countries"; import { loadPointerReleaseId, parseCountry, scrub, -} from "@/lib/microcosm/latest-artifact"; -import { loadStagingComparison } from "@/lib/microcosm/staging-artifact"; +} from "@policyengine/calibration-tree/latest-artifact"; +import { loadStagingComparison } from "@policyengine/calibration-tree/staging-artifact"; export const dynamic = "force-dynamic"; export const revalidate = 0; diff --git a/frontend/app/api/microcosm/staging/run/route.ts b/frontend/app/api/microcosm/staging/run/route.ts index 586e2827..0c01d4cf 100644 --- a/frontend/app/api/microcosm/staging/run/route.ts +++ b/frontend/app/api/microcosm/staging/run/route.ts @@ -1,8 +1,8 @@ import { NextResponse } from "next/server"; -import { hasCapability } from "@/lib/microcosm/countries"; -import { parseCountry, scrub } from "@/lib/microcosm/latest-artifact"; -import { loadStagingRun } from "@/lib/microcosm/staging-artifact"; +import { hasCapability } from "@policyengine/calibration-tree/countries"; +import { parseCountry, scrub } from "@policyengine/calibration-tree/latest-artifact"; +import { loadStagingRun } from "@policyengine/calibration-tree/staging-artifact"; export const dynamic = "force-dynamic"; export const revalidate = 0; diff --git a/frontend/app/api/microcosm/staging/runs/route.ts b/frontend/app/api/microcosm/staging/runs/route.ts index 2a1def49..418fc4db 100644 --- a/frontend/app/api/microcosm/staging/runs/route.ts +++ b/frontend/app/api/microcosm/staging/runs/route.ts @@ -1,7 +1,7 @@ import { NextResponse } from "next/server"; -import { loadStagingRuns } from "@/lib/microcosm/staging-artifact"; -import { parseCountry, scrub } from "@/lib/microcosm/latest-artifact"; +import { loadStagingRuns } from "@policyengine/calibration-tree/staging-artifact"; +import { parseCountry, scrub } from "@policyengine/calibration-tree/latest-artifact"; export const dynamic = "force-dynamic"; export const revalidate = 0; diff --git a/frontend/app/api/microcosm/staging/target-change-tree/route.ts b/frontend/app/api/microcosm/staging/target-change-tree/route.ts index 5dd51949..b4ab3afe 100644 --- a/frontend/app/api/microcosm/staging/target-change-tree/route.ts +++ b/frontend/app/api/microcosm/staging/target-change-tree/route.ts @@ -5,13 +5,13 @@ import { classifyApiError, parseCountry, scrub, -} from "@/lib/microcosm/latest-artifact"; -import { isSha256 } from "@/lib/microcosm/calibration-tree-artifact"; +} from "@policyengine/calibration-tree/latest-artifact"; +import { isSha256 } from "@policyengine/calibration-tree/calibration-tree-artifact"; import { loadStagingTargetChangeDataset, loadStagingTargetChangeDatasetFromBuild, -} from "@/lib/microcosm/staging-artifact"; -import type { TargetChangeMode } from "@/lib/microcosm/target-change"; +} from "@policyengine/calibration-tree/staging-artifact"; +import type { TargetChangeMode } from "@policyengine/calibration-tree/target-change"; import { buildTargetChangeTree } from "@/lib/microcosm/target-change-tree"; export const dynamic = "force-dynamic"; diff --git a/frontend/app/api/microcosm/staging/target-diagnostics/route.ts b/frontend/app/api/microcosm/staging/target-diagnostics/route.ts index 8da6e8b2..80254929 100644 --- a/frontend/app/api/microcosm/staging/target-diagnostics/route.ts +++ b/frontend/app/api/microcosm/staging/target-diagnostics/route.ts @@ -1,8 +1,8 @@ import { NextResponse } from "next/server"; -import { hasCapability } from "@/lib/microcosm/countries"; -import { parseCountry, scrub } from "@/lib/microcosm/latest-artifact"; -import { loadStagingTargetDiagnostics } from "@/lib/microcosm/staging-artifact"; +import { hasCapability } from "@policyengine/calibration-tree/countries"; +import { parseCountry, scrub } from "@policyengine/calibration-tree/latest-artifact"; +import { loadStagingTargetDiagnostics } from "@policyengine/calibration-tree/staging-artifact"; export const dynamic = "force-dynamic"; export const revalidate = 0; diff --git a/frontend/app/api/microcosm/staging/target-tree/route.ts b/frontend/app/api/microcosm/staging/target-tree/route.ts index 4dae4471..748a113c 100644 --- a/frontend/app/api/microcosm/staging/target-tree/route.ts +++ b/frontend/app/api/microcosm/staging/target-tree/route.ts @@ -1,9 +1,9 @@ import { NextResponse } from "next/server"; -import { buildCalibrationTree, type CalibrationTreeTarget } from "@/lib/microcosm/calibration-tree"; +import { buildCalibrationTree, type CalibrationTreeTarget } from "@policyengine/calibration-tree/calibration-tree"; import { calibrationTreeRequestState } from "@/lib/microcosm/calibration-tree-request"; -import { classifyApiError, parseCountry, scrub } from "@/lib/microcosm/latest-artifact"; -import { loadStagingCalibration } from "@/lib/microcosm/staging-artifact"; +import { classifyApiError, parseCountry, scrub } from "@policyengine/calibration-tree/latest-artifact"; +import { loadStagingCalibration } from "@policyengine/calibration-tree/staging-artifact"; export const dynamic = "force-dynamic"; export const revalidate = 0; diff --git a/frontend/app/api/microcosm/target-diagnostics/route.ts b/frontend/app/api/microcosm/target-diagnostics/route.ts index 45b49265..aef1ebda 100644 --- a/frontend/app/api/microcosm/target-diagnostics/route.ts +++ b/frontend/app/api/microcosm/target-diagnostics/route.ts @@ -6,7 +6,7 @@ import { loadRelease, parseCountry, scrub, -} from "@/lib/microcosm/latest-artifact"; +} from "@policyengine/calibration-tree/latest-artifact"; export const revalidate = 21_600; export const runtime = "nodejs"; diff --git a/frontend/app/api/microcosm/target-investigation/route.ts b/frontend/app/api/microcosm/target-investigation/route.ts index f6446f82..3690a728 100644 --- a/frontend/app/api/microcosm/target-investigation/route.ts +++ b/frontend/app/api/microcosm/target-investigation/route.ts @@ -6,7 +6,7 @@ import { loadRelease, parseCountry, scrub, -} from "@/lib/microcosm/latest-artifact"; +} from "@policyengine/calibration-tree/latest-artifact"; export const revalidate = 21_600; diff --git a/frontend/app/api/microcosm/target-treemap/route.ts b/frontend/app/api/microcosm/target-treemap/route.ts index db910387..3e4e0d94 100644 --- a/frontend/app/api/microcosm/target-treemap/route.ts +++ b/frontend/app/api/microcosm/target-treemap/route.ts @@ -6,7 +6,7 @@ import { parseCountry, microcosmTargetTreemap, scrub, -} from "@/lib/microcosm/latest-artifact"; +} from "@policyengine/calibration-tree/latest-artifact"; export const revalidate = 21_600; export const runtime = "nodejs"; diff --git a/frontend/app/api/microcosm/tree-manifest/route.test.ts b/frontend/app/api/microcosm/tree-manifest/route.test.ts index f27ed8aa..99f096d3 100644 --- a/frontend/app/api/microcosm/tree-manifest/route.test.ts +++ b/frontend/app/api/microcosm/tree-manifest/route.test.ts @@ -1,6 +1,6 @@ import { expect, test } from "bun:test"; -import { emptyCalibrationTreeManifest, withCalibrationTreeManifestEntry } from "@/lib/microcosm/calibration-tree-manifest"; +import { emptyCalibrationTreeManifest, withCalibrationTreeManifestEntry } from "@policyengine/calibration-tree/calibration-tree-manifest"; import { createCalibrationTreeManifestHandler } from "./route"; test("manifest API returns one country's current immutable release identity", async () => { diff --git a/frontend/app/api/microcosm/tree-manifest/route.ts b/frontend/app/api/microcosm/tree-manifest/route.ts index bfc79e68..efeb6ff9 100644 --- a/frontend/app/api/microcosm/tree-manifest/route.ts +++ b/frontend/app/api/microcosm/tree-manifest/route.ts @@ -1,11 +1,11 @@ import { NextResponse } from "next/server"; -import { readCalibrationTreeManifest } from "@/lib/microcosm/calibration-tree-blob"; -import { calibrationTreeCountryManifest } from "@/lib/microcosm/calibration-tree-manifest"; +import { readCalibrationTreeManifest } from "@policyengine/calibration-tree/calibration-tree-blob"; +import { calibrationTreeCountryManifest } from "@policyengine/calibration-tree/calibration-tree-manifest"; import { isCountry, selectableCountries, -} from "@/lib/microcosm/countries"; +} from "@policyengine/calibration-tree/countries"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/frontend/app/api/microcosm/tree/route.ts b/frontend/app/api/microcosm/tree/route.ts index 8a6e64a6..02e1d0fc 100644 --- a/frontend/app/api/microcosm/tree/route.ts +++ b/frontend/app/api/microcosm/tree/route.ts @@ -1,19 +1,19 @@ import { NextResponse } from "next/server"; import { withBasePath } from "@/lib/base-path"; -import { getCalibrationTreeBlob } from "@/lib/microcosm/calibration-tree-blob"; +import { getCalibrationTreeBlob } from "@policyengine/calibration-tree/calibration-tree-blob"; import { immutableCalibrationTreePartResponse } from "@/lib/microcosm/calibration-tree-http"; import { isCalibrationTreePart, isSha256, type CalibrationTreePart, -} from "@/lib/microcosm/calibration-tree-artifact"; -import { resolveCalibrationRelease } from "@/lib/microcosm/calibration-release-locator"; +} from "@policyengine/calibration-tree/calibration-tree-artifact"; +import { resolveCalibrationRelease } from "@policyengine/calibration-tree/calibration-release-locator"; import { isCountry, selectableCountries, type MicrocosmCountry, -} from "@/lib/microcosm/countries"; +} from "@policyengine/calibration-tree/countries"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/frontend/app/api/microcosm/variable/route.ts b/frontend/app/api/microcosm/variable/route.ts index b32a43bf..4e10e052 100644 --- a/frontend/app/api/microcosm/variable/route.ts +++ b/frontend/app/api/microcosm/variable/route.ts @@ -4,7 +4,7 @@ import { promisify } from "node:util"; import { NextResponse } from "next/server"; -import { microcosmRepo, scrub } from "@/lib/microcosm/latest-artifact"; +import { microcosmRepo, scrub } from "@policyengine/calibration-tree/latest-artifact"; import { HOSTED_US_RELEASE, reviewedVariableRelease, diff --git a/frontend/app/microcosm/page.tsx b/frontend/app/microcosm/page.tsx index 9314538d..1e8e6245 100644 --- a/frontend/app/microcosm/page.tsx +++ b/frontend/app/microcosm/page.tsx @@ -1,7 +1,7 @@ import { AppShell } from "@/components/layout/app-shell"; import { MicrocosmOverviewView } from "@/components/microcosm/microcosm-overview-view"; import { publishedReleaseSelection } from "@/lib/microcosm/calibration-selection"; -import { parseCountry } from "@/lib/microcosm/countries"; +import { parseCountry } from "@policyengine/calibration-tree/countries"; interface MicrocosmOverviewPageProps { searchParams?: Promise>; diff --git a/frontend/app/microcosm/targets/page.tsx b/frontend/app/microcosm/targets/page.tsx index a3d2e99c..5d46b1a6 100644 --- a/frontend/app/microcosm/targets/page.tsx +++ b/frontend/app/microcosm/targets/page.tsx @@ -1,7 +1,7 @@ import { AppShell } from "@/components/layout/app-shell"; import { MicrocosmTargetsView } from "@/components/microcosm/microcosm-targets-view"; import { publishedReleaseSelection } from "@/lib/microcosm/calibration-selection"; -import { parseCountry } from "@/lib/microcosm/countries"; +import { parseCountry } from "@policyengine/calibration-tree/countries"; interface MicrocosmTargetsPageProps { searchParams?: Promise>; diff --git a/frontend/bun.lock b/frontend/bun.lock index a664d676..b1329105 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -5,6 +5,7 @@ "": { "name": "calibration-diagnostics-frontend", "dependencies": { + "@policyengine/calibration-tree": "workspace:*", "@policyengine/ui-kit": "^0.9.0", "@tanstack/react-query": "^5.84.0", "@tanstack/react-query-devtools": "^5.83.0", @@ -31,6 +32,17 @@ "typescript": "^5.8.0", }, }, + "packages/calibration-tree": { + "name": "@policyengine/calibration-tree", + "version": "0.1.0", + "dependencies": { + "@vercel/blob": "^2.6.1", + }, + "devDependencies": { + "@types/bun": "^1.3.14", + "typescript": "^5.8.0", + }, + }, }, "packages": { "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], @@ -125,6 +137,8 @@ "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.2.6", "", { "os": "win32", "cpu": "x64" }, "sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA=="], + "@policyengine/calibration-tree": ["@policyengine/calibration-tree@workspace:packages/calibration-tree"], + "@policyengine/ui-kit": ["@policyengine/ui-kit@0.9.1", "", { "dependencies": { "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "d3-geo": "^3.1.0", "dayjs": "^1.11.13", "lucide-react": "^0.577.0", "radix-ui": "^1.4.3", "tailwind-merge": "^3.5.0", "tw-animate-css": "^1.4.0" }, "peerDependencies": { "react": ">=19", "react-dom": ">=19", "recharts": ">=2.12" } }, "sha512-wigj/WodytMGqW8n02IKda2uYVxzBE3iDqzRweDJn+4VZQqb02rCf1TBqsM9WGslkPAetavjAoQrBCVnGZQEKQ=="], "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="], diff --git a/frontend/bunfig.toml b/frontend/bunfig.toml index 8755352e..27f7bdaa 100644 --- a/frontend/bunfig.toml +++ b/frontend/bunfig.toml @@ -1,2 +1,7 @@ +[install] +# Keep the hoisted node_modules layout that Next.js and Vercel build against. +# Bun otherwise installs a project with workspaces using its isolated linker. +linker = "hoisted" + [test] preload = ["./test/setup.ts"] diff --git a/frontend/components/layout/country-context.tsx b/frontend/components/layout/country-context.tsx index 4e8d4a4b..868f4fde 100644 --- a/frontend/components/layout/country-context.tsx +++ b/frontend/components/layout/country-context.tsx @@ -13,7 +13,7 @@ import { DEFAULT_COUNTRY, isCountry, type MicrocosmCountry, -} from "@/lib/microcosm/countries"; +} from "@policyengine/calibration-tree/countries"; export type Country = MicrocosmCountry; diff --git a/frontend/components/layout/mobile-nav.tsx b/frontend/components/layout/mobile-nav.tsx index fb432aa7..09feab9d 100644 --- a/frontend/components/layout/mobile-nav.tsx +++ b/frontend/components/layout/mobile-nav.tsx @@ -5,7 +5,7 @@ import { useEffect, useId, useRef, useState } from "react"; import { useCountry } from "@/components/layout/country-context"; import { activeNavItem, navGroupsForCountry } from "@/components/layout/nav-items"; -import { countryRegistration } from "@/lib/microcosm/countries"; +import { countryRegistration } from "@policyengine/calibration-tree/countries"; import { NavSidebar } from "./nav-sidebar"; diff --git a/frontend/components/layout/nav-items.test.ts b/frontend/components/layout/nav-items.test.ts index 57bcc391..e1aef082 100644 --- a/frontend/components/layout/nav-items.test.ts +++ b/frontend/components/layout/nav-items.test.ts @@ -1,6 +1,6 @@ import { expect, test } from "bun:test"; -import { hasCapability, selectableCountries } from "@/lib/microcosm/countries"; +import { hasCapability, selectableCountries } from "@policyengine/calibration-tree/countries"; import { activeNavItem, diff --git a/frontend/components/layout/nav-items.ts b/frontend/components/layout/nav-items.ts index 34b97396..67117a34 100644 --- a/frontend/components/layout/nav-items.ts +++ b/frontend/components/layout/nav-items.ts @@ -2,7 +2,7 @@ import type { Country } from "@/components/layout/country-context"; import { countryCapabilities, type CountryCapability, -} from "@/lib/microcosm/countries"; +} from "@policyengine/calibration-tree/countries"; // A page is shown when the country (or the release artifact, which can narrow // a registration) serves its capability. Pages that run on country-specific diff --git a/frontend/components/layout/nav-sidebar.tsx b/frontend/components/layout/nav-sidebar.tsx index 1e15d76b..acb1b6b0 100644 --- a/frontend/components/layout/nav-sidebar.tsx +++ b/frontend/components/layout/nav-sidebar.tsx @@ -13,7 +13,7 @@ import { import { countryRegistration, selectableCountries, -} from "@/lib/microcosm/countries"; +} from "@policyengine/calibration-tree/countries"; export function NavSidebar({ onNavigate }: { onNavigate?: () => void } = {}) { const pathname = usePathname(); diff --git a/frontend/components/microcosm/calibration-explorer-filter-menu.tsx b/frontend/components/microcosm/calibration-explorer-filter-menu.tsx index 9c381faf..d77f0016 100644 --- a/frontend/components/microcosm/calibration-explorer-filter-menu.tsx +++ b/frontend/components/microcosm/calibration-explorer-filter-menu.tsx @@ -7,8 +7,8 @@ import { type ComparisonFit, type ExplorerFilters, type ExplorerState, -} from "@/lib/microcosm/calibration-explorer"; -import type { CalibrationTreeResponse } from "@/lib/microcosm/calibration-tree"; +} from "@policyengine/calibration-tree/calibration-explorer"; +import type { CalibrationTreeResponse } from "@policyengine/calibration-tree/calibration-tree"; const FIT_BAND_LABELS: Record = { "0_5": "0–5%", diff --git a/frontend/components/microcosm/calibration-explorer-map.tsx b/frontend/components/microcosm/calibration-explorer-map.tsx index 5c50e14b..485251f2 100644 --- a/frontend/components/microcosm/calibration-explorer-map.tsx +++ b/frontend/components/microcosm/calibration-explorer-map.tsx @@ -43,14 +43,14 @@ import { explorerReducer, type ExplorerBreakdown, type ExplorerState, -} from "@/lib/microcosm/calibration-explorer"; +} from "@policyengine/calibration-tree/calibration-explorer"; import { prefetchCalibrationDescendants } from "@/lib/microcosm/calibration-prefetch"; import { type CalibrationTreeGroup, type CalibrationTreeNode, type CalibrationTreeResponse, type CalibrationTreeSizeMode, -} from "@/lib/microcosm/calibration-tree"; +} from "@policyengine/calibration-tree/calibration-tree"; import { condenseCalibrationTreemap, expandedGroupsForNode, diff --git a/frontend/components/microcosm/calibration-explorer-view.test.ts b/frontend/components/microcosm/calibration-explorer-view.test.ts index 8baaecf5..00467077 100644 --- a/frontend/components/microcosm/calibration-explorer-view.test.ts +++ b/frontend/components/microcosm/calibration-explorer-view.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; -import type { ExplorerState } from "../../lib/microcosm/calibration-explorer"; +import type { ExplorerState } from "@policyengine/calibration-tree/calibration-explorer"; import { EXPLORER_MAP_VERTICAL_PADDING, explorerBreadcrumbs, diff --git a/frontend/components/microcosm/calibration-explorer-view.ts b/frontend/components/microcosm/calibration-explorer-view.ts index f45dd8bd..6dc4dfde 100644 --- a/frontend/components/microcosm/calibration-explorer-view.ts +++ b/frontend/components/microcosm/calibration-explorer-view.ts @@ -1,14 +1,14 @@ import type { ExplorerPath, ExplorerState, -} from "@/lib/microcosm/calibration-explorer"; +} from "@policyengine/calibration-tree/calibration-explorer"; import { type CalibrationTreeMetrics, type CalibrationTreeNode, type CalibrationTreeSizeMode, -} from "@/lib/microcosm/calibration-tree"; -import { canonicalLabel, programLabel } from "@/lib/microcosm/program-label"; -import { sourceLabel } from "@/lib/microcosm/source-label"; +} from "@policyengine/calibration-tree/calibration-tree"; +import { canonicalLabel, programLabel } from "@policyengine/calibration-tree/program-label"; +import { sourceLabel } from "@policyengine/calibration-tree/source-label"; export const EXPLORER_MAP_VERTICAL_PADDING = 10; diff --git a/frontend/components/microcosm/microcosm-staging-view.tsx b/frontend/components/microcosm/microcosm-staging-view.tsx index d48a502a..17879880 100644 --- a/frontend/components/microcosm/microcosm-staging-view.tsx +++ b/frontend/components/microcosm/microcosm-staging-view.tsx @@ -39,7 +39,7 @@ import { type MicrocosmStagingRunResponse, type MicrocosmStagingRunSummary, } from "@/lib/api/hooks/use-microcosm"; -import { countryRegistration, hasCapability } from "@/lib/microcosm/countries"; +import { countryRegistration, hasCapability } from "@policyengine/calibration-tree/countries"; import { formatStagingCurrentStatus, formatStagingStatus, diff --git a/frontend/components/microcosm/microcosm-target-detail.tsx b/frontend/components/microcosm/microcosm-target-detail.tsx index 4e2c0f81..06d5d428 100644 --- a/frontend/components/microcosm/microcosm-target-detail.tsx +++ b/frontend/components/microcosm/microcosm-target-detail.tsx @@ -9,8 +9,8 @@ import { type MicrocosmTargetRow, } from "@/lib/api/hooks/use-microcosm"; import { chronicleSourceEntryUrl } from "@/lib/microcosm/chronicle-entry-url"; -import { canonicalLabel } from "@/lib/microcosm/program-label"; -import { sourceLabel } from "@/lib/microcosm/source-label"; +import { canonicalLabel } from "@policyengine/calibration-tree/program-label"; +import { sourceLabel } from "@policyengine/calibration-tree/source-label"; import { usStateName } from "@/lib/microcosm/us-state-names"; interface DisplayDimension { diff --git a/frontend/components/microcosm/microcosm-targets-view.tsx b/frontend/components/microcosm/microcosm-targets-view.tsx index 2f5c353d..cc21740d 100644 --- a/frontend/components/microcosm/microcosm-targets-view.tsx +++ b/frontend/components/microcosm/microcosm-targets-view.tsx @@ -17,7 +17,7 @@ import { PageHeader } from "@/components/shared/page-header"; import { SectionCard } from "@/components/shared/section-card"; import { ToolbarSelect } from "@/components/shared/toolbar-select"; import { MicrocosmTargetDetail } from "@/components/microcosm/microcosm-target-detail"; -import { sourceLabel } from "@/lib/microcosm/source-label"; +import { sourceLabel } from "@policyengine/calibration-tree/source-label"; import { releaseSelectOptions, useMicrocosmReleases, diff --git a/frontend/components/microcosm/staging-target-change-map.tsx b/frontend/components/microcosm/staging-target-change-map.tsx index 942f947e..2e0f5173 100644 --- a/frontend/components/microcosm/staging-target-change-map.tsx +++ b/frontend/components/microcosm/staging-target-change-map.tsx @@ -29,18 +29,18 @@ import { createExplorerFilters, createExplorerState, explorerReducer, -} from "@/lib/microcosm/calibration-explorer"; +} from "@policyengine/calibration-tree/calibration-explorer"; import type { CalibrationTreeGroup, CalibrationTreeMetrics, -} from "@/lib/microcosm/calibration-tree"; +} from "@policyengine/calibration-tree/calibration-tree"; import { condenseCalibrationTreemapByMetric, expandedGroupsForNode, type CalibrationTreemapGroup, type CalibrationTreemapNode, } from "@/lib/microcosm/calibration-treemap-layout"; -import type { TargetChangeMode } from "@/lib/microcosm/target-change"; +import type { TargetChangeMode } from "@policyengine/calibration-tree/target-change"; import type { TargetChangeTreeResponse } from "@/lib/microcosm/target-change-tree"; import { formatTargetChange, diff --git a/frontend/components/microcosm/staging-target-detail-presentation.test.ts b/frontend/components/microcosm/staging-target-detail-presentation.test.ts index c7561ca3..d0a4a1e1 100644 --- a/frontend/components/microcosm/staging-target-detail-presentation.test.ts +++ b/frontend/components/microcosm/staging-target-detail-presentation.test.ts @@ -1,6 +1,6 @@ import { expect, test } from "bun:test"; -import type { TargetChangeRow } from "@/lib/microcosm/target-change"; +import type { TargetChangeRow } from "@policyengine/calibration-tree/target-change"; import { stagingTargetDetailPresentation } from "./staging-target-detail-presentation"; const TARGET_CHANGE: TargetChangeRow = { diff --git a/frontend/components/microcosm/staging-target-detail-presentation.ts b/frontend/components/microcosm/staging-target-detail-presentation.ts index bc1ed857..27a06a37 100644 --- a/frontend/components/microcosm/staging-target-detail-presentation.ts +++ b/frontend/components/microcosm/staging-target-detail-presentation.ts @@ -6,7 +6,7 @@ import { type MicrocosmTargetErrorSummary, } from "@/components/microcosm/microcosm-target-detail"; import type { MicrocosmTargetRow } from "@/lib/api/hooks/use-microcosm"; -import type { TargetChangeRow } from "@/lib/microcosm/target-change"; +import type { TargetChangeRow } from "@policyengine/calibration-tree/target-change"; import { formatTargetChange } from "@/lib/microcosm/target-change-visualization"; export interface StagingTargetDetailPresentation { diff --git a/frontend/lib/api/hooks/use-microcosm.test.ts b/frontend/lib/api/hooks/use-microcosm.test.ts index 69f67702..2b0928a3 100644 --- a/frontend/lib/api/hooks/use-microcosm.test.ts +++ b/frontend/lib/api/hooks/use-microcosm.test.ts @@ -1,6 +1,6 @@ import { expect, test } from "bun:test"; -import { createExplorerState } from "@/lib/microcosm/calibration-explorer"; +import { createExplorerState } from "@policyengine/calibration-tree/calibration-explorer"; import { fetchCalibrationTreePartsConcurrently, diff --git a/frontend/lib/api/hooks/use-microcosm.ts b/frontend/lib/api/hooks/use-microcosm.ts index 46df32fd..675c1227 100644 --- a/frontend/lib/api/hooks/use-microcosm.ts +++ b/frontend/lib/api/hooks/use-microcosm.ts @@ -17,8 +17,8 @@ import { import { withBasePath } from "@/lib/base-path"; import type { BuildRunSource } from "@/lib/microcosm/build-monitor"; import type { BuildRunResponse, BuildRunsResponse } from "@/lib/microcosm/build-runs"; -import type { ExplorerState } from "@/lib/microcosm/calibration-explorer"; -import type { CalibrationTreeResponse } from "@/lib/microcosm/calibration-tree"; +import type { ExplorerState } from "@policyengine/calibration-tree/calibration-explorer"; +import type { CalibrationTreeResponse } from "@policyengine/calibration-tree/calibration-tree"; import { calibrationTreeResponseFromBundle, calibrationTreeTargetDetailSelection, @@ -34,12 +34,12 @@ import { type CalibrationTreeTargetDetailsArtifact, type CalibrationTreeTargetSummaryArtifact, type CalibrationTreeTierArtifact, -} from "@/lib/microcosm/calibration-tree-artifact"; -import type { CalibrationProvenance } from "@/lib/microcosm/target-loss-attribution"; +} from "@policyengine/calibration-tree/calibration-tree-artifact"; +import type { CalibrationProvenance } from "@policyengine/calibration-tree/target-loss-attribution"; import type { TargetChangeMode, TargetChangeRow, -} from "@/lib/microcosm/target-change"; +} from "@policyengine/calibration-tree/target-change"; import type { TargetChangeTreeApiResponse, TargetChangeTreeResponse, @@ -49,7 +49,7 @@ import { hasCapability, type CountryCapability, type RepositoryVisibility, -} from "@/lib/microcosm/countries"; +} from "@policyengine/calibration-tree/countries"; import { apiGet } from "../client"; export interface MicrocosmGates { diff --git a/frontend/lib/cross-dataset/fact-presentation.ts b/frontend/lib/cross-dataset/fact-presentation.ts index 09a1f0b4..cfb07b57 100644 --- a/frontend/lib/cross-dataset/fact-presentation.ts +++ b/frontend/lib/cross-dataset/fact-presentation.ts @@ -4,9 +4,9 @@ import type { SourceSummary, } from "./artifact"; import type { Country } from "@/components/layout/country-context"; -import { DEFAULT_COUNTRY, isCountry } from "@/lib/microcosm/countries"; +import { DEFAULT_COUNTRY, isCountry } from "@policyengine/calibration-tree/countries"; import { sourceDisplayLabel } from "./presentation"; -import { sourceAuthorityLabel } from "../source-labels"; +import { sourceAuthorityLabel } from "@policyengine/calibration-tree/source-labels"; export interface FactCatalogParams { country: Country; diff --git a/frontend/lib/cross-dataset/source.ts b/frontend/lib/cross-dataset/source.ts index a027c738..0c849958 100644 --- a/frontend/lib/cross-dataset/source.ts +++ b/frontend/lib/cross-dataset/source.ts @@ -6,7 +6,7 @@ import { DEFAULT_COUNTRY, countryRegistration, type MicrocosmCountry, -} from "../microcosm/countries"; +} from "@policyengine/calibration-tree/countries"; interface CountryArtifactConfig { countryCode: string; diff --git a/frontend/lib/microcosm/build-monitor.test.ts b/frontend/lib/microcosm/build-monitor.test.ts index f9db4e9c..828c241c 100644 --- a/frontend/lib/microcosm/build-monitor.test.ts +++ b/frontend/lib/microcosm/build-monitor.test.ts @@ -27,7 +27,7 @@ import { parseStagingEvents, parseStagingManifest, parseStagingProgress, -} from "./staging-contract"; +} from "@policyengine/calibration-tree/staging-contract"; const T0 = Date.parse("2026-09-26T10:00:00Z"); const min = (n: number) => n * 60_000; @@ -1001,7 +1001,19 @@ describe("solver passes inside calibration", () => { }); describe("version 3 outcomes", () => { - const FIXTURES = join(import.meta.dir, "fixtures", "staging-contract", "v3"); + // The staging contract fixtures live beside the contract parser in the + // calibration tree package. + const FIXTURES = join( + import.meta.dir, + "..", + "..", + "packages", + "calibration-tree", + "src", + "fixtures", + "staging-contract", + "v3", + ); const fixtureText = (path: string) => readFileSync(join(FIXTURES, path)).toString(); const fixtureJson = (path: string) => JSON.parse(fixtureText(path)) as Record; diff --git a/frontend/lib/microcosm/build-runs.ts b/frontend/lib/microcosm/build-runs.ts index 79d2fd1c..b8812efb 100644 --- a/frontend/lib/microcosm/build-runs.ts +++ b/frontend/lib/microcosm/build-runs.ts @@ -23,7 +23,7 @@ import { import { hasCapability, type MicrocosmCountry, -} from "@/lib/microcosm/countries"; +} from "@policyengine/calibration-tree/countries"; import { discoverLocalRuns, loadLocalRunDocuments, @@ -33,7 +33,7 @@ import { import { loadStagingRuns, loadStagingRunTelemetry, -} from "@/lib/microcosm/staging-artifact"; +} from "@policyengine/calibration-tree/staging-artifact"; import { collectorConfigured, loadCollectorRun, diff --git a/frontend/lib/microcosm/calibration-prefetch.test.ts b/frontend/lib/microcosm/calibration-prefetch.test.ts index 2ac80b17..f9da57b2 100644 --- a/frontend/lib/microcosm/calibration-prefetch.test.ts +++ b/frontend/lib/microcosm/calibration-prefetch.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; -import { createExplorerState, type ExplorerState } from "./calibration-explorer"; +import { createExplorerState, type ExplorerState } from "@policyengine/calibration-tree/calibration-explorer"; import { prefetchCalibrationDescendants, type CalibrationTreeFetcher, @@ -8,7 +8,7 @@ import { import type { CalibrationTreeNode, CalibrationTreeResponse, -} from "./calibration-tree"; +} from "@policyengine/calibration-tree/calibration-tree"; const EMPTY_METRICS = { nTargets: 0, diff --git a/frontend/lib/microcosm/calibration-prefetch.ts b/frontend/lib/microcosm/calibration-prefetch.ts index 2a25d8e2..a52a432b 100644 --- a/frontend/lib/microcosm/calibration-prefetch.ts +++ b/frontend/lib/microcosm/calibration-prefetch.ts @@ -1,8 +1,8 @@ import { nextLevelExplorerStates, type ExplorerState, -} from "./calibration-explorer"; -import type { CalibrationTreeResponse } from "./calibration-tree"; +} from "@policyengine/calibration-tree/calibration-explorer"; +import type { CalibrationTreeResponse } from "@policyengine/calibration-tree/calibration-tree"; export type CalibrationTreeFetcher = ( state: ExplorerState, diff --git a/frontend/lib/microcosm/calibration-tree-http.ts b/frontend/lib/microcosm/calibration-tree-http.ts index ddd36e43..23b31f54 100644 --- a/frontend/lib/microcosm/calibration-tree-http.ts +++ b/frontend/lib/microcosm/calibration-tree-http.ts @@ -1,7 +1,7 @@ import type { GetBlobResult } from "@vercel/blob"; import { NextResponse } from "next/server"; -import { calibrationTreeBlobText } from "./calibration-tree-blob"; +import { calibrationTreeBlobText } from "@policyengine/calibration-tree/calibration-tree-blob"; function acceptsGzip(request: Request): boolean { return (request.headers.get("accept-encoding") ?? "") diff --git a/frontend/lib/microcosm/calibration-tree-request.ts b/frontend/lib/microcosm/calibration-tree-request.ts index fa9c3a99..50dcad86 100644 --- a/frontend/lib/microcosm/calibration-tree-request.ts +++ b/frontend/lib/microcosm/calibration-tree-request.ts @@ -5,7 +5,7 @@ import { type CalibrationStatus, type ComparisonFit, type FitBand, -} from "./calibration-explorer"; +} from "@policyengine/calibration-tree/calibration-explorer"; const CALIBRATION_STATUSES = new Set([ "included", diff --git a/frontend/lib/microcosm/calibration-treemap-layout.test.ts b/frontend/lib/microcosm/calibration-treemap-layout.test.ts index 480d6868..005981be 100644 --- a/frontend/lib/microcosm/calibration-treemap-layout.test.ts +++ b/frontend/lib/microcosm/calibration-treemap-layout.test.ts @@ -4,7 +4,7 @@ import type { CalibrationTreeGroup, CalibrationTreeMetrics, CalibrationTreeNode, -} from "./calibration-tree"; +} from "@policyengine/calibration-tree/calibration-tree"; import { condenseCalibrationTreemap, expandedGroupsForNode, diff --git a/frontend/lib/microcosm/calibration-treemap-layout.ts b/frontend/lib/microcosm/calibration-treemap-layout.ts index c97a8081..b7ef0dd5 100644 --- a/frontend/lib/microcosm/calibration-treemap-layout.ts +++ b/frontend/lib/microcosm/calibration-treemap-layout.ts @@ -1,11 +1,11 @@ -import type { ExplorerNodeSelection } from "./calibration-explorer"; +import type { ExplorerNodeSelection } from "@policyengine/calibration-tree/calibration-explorer"; import type { CalibrationTreeGroup, CalibrationTreeMetrics, CalibrationTreeNode, CalibrationTreeSizeMode, CalibrationTreeTarget, -} from "./calibration-tree"; +} from "@policyengine/calibration-tree/calibration-tree"; // Keep these aligned with the original calibration map. They represent roughly // a 51px square for groups and a 28px square for nodes. diff --git a/frontend/lib/microcosm/countries.ts b/frontend/lib/microcosm/countries.ts index a57253d5..1d8d5144 100644 --- a/frontend/lib/microcosm/countries.ts +++ b/frontend/lib/microcosm/countries.ts @@ -1,241 +1,5 @@ -// Country registry for the Microcosm dashboard: the one place a country is -// registered. Adding a country is one entry here; everything else (selectors, -// navigation, repository resolution, national geography, link behaviour) reads -// the registration or the release artifact's typed `country` block. -// -// Isomorphic: imported by client components and API routes alike, so it must -// not import server-only modules or read `process.env`. Repository resolvers -// accept an explicit environment object from server-side callers. - -export const COUNTRY_CAPABILITIES = [ - "calibration", - "targets", - "compare", - "cross_dataset", - "staging", - "model_coverage", - "pipeline", - "variables", - "external_checks", -] as const; -export type CountryCapability = (typeof COUNTRY_CAPABILITIES)[number]; - -export type RepositoryVisibility = "public" | "private"; - -export type RepositoryEnvironment = Readonly< - Record ->; - -export interface ResolvedRepository { - repo: string; - revision: string; -} - -export interface CountryRegistration { - // Default HF dataset repository and revision (the server may override both - // through the deployment variables named in repo_env / revision_env). - repo: string; - revision: string; - repo_env?: string; - revision_env?: string; - // Country display name ("United States"). - label: string; - // Sidebar dataset line ("Microcosm US"). - dataset_label: string; - // National geography label and id (null when the id is unknown). - geography: string; - geography_id: string | null; - visibility: RepositoryVisibility; - capabilities: readonly CountryCapability[]; - // Optional staging telemetry repository. A staging-capable country must - // declare this instead of inheriting another country's repository. - staging?: { - repo: string; - revision: string; - repo_env?: string; - revision_env?: string; - token_env?: string; - }; - // Other jurisdiction codes a cross-dataset bundle may use for this country. - jurisdiction_aliases?: readonly string[]; - // Conformance-only registration: a valid country that is never listed in - // selectors or alert allowlists. - fixture?: true; -} - -const ALL_CAPABILITIES: readonly CountryCapability[] = COUNTRY_CAPABILITIES; - -const CALIBRATION_CAPABILITIES: readonly CountryCapability[] = [ - "calibration", - "targets", - "compare", - "cross_dataset", -]; - -// Deprecated upstream identifiers: Microcosm's published HF repositories and -// deployment variables still use the former Populace names. -export const COUNTRY_REGISTRY = { - us: { - repo: "policyengine/populace-us", - revision: "main", - repo_env: "POPULACE_HF_REPO", - label: "United States", - dataset_label: "Microcosm US", - geography: "United States", - geography_id: "0100000US", - visibility: "public", - capabilities: ALL_CAPABILITIES, - staging: { - // Deprecated upstream identifier: the US staging publisher still uses - // the former Populace repository and deployment-variable names. - repo: "policyengine/populace-us-staging", - revision: "main", - repo_env: "POPULACE_STAGING_HF_REPO", - revision_env: "POPULACE_STAGING_HF_REVISION", - }, - }, - uk: { - repo: "policyengine/populace-uk-private", - revision: "main", - repo_env: "POPULACE_UK_HF_REPO", - revision_env: "POPULACE_UK_HF_REVISION", - label: "United Kingdom", - dataset_label: "Microcosm UK", - geography: "United Kingdom", - geography_id: null, - visibility: "private", - capabilities: [...CALIBRATION_CAPABILITIES, "staging"], - staging: { - repo: "policyengine/populace-uk-staging", - revision: "main", - repo_env: "POPULACE_UK_STAGING_HF_REPO", - revision_env: "POPULACE_UK_STAGING_HF_REVISION", - token_env: "POPULACE_UK_STAGING_HF_TOKEN", - }, - jurisdiction_aliases: ["GB"], - }, - be: { - repo: "policyengine/populace-be-private", - revision: "main", - repo_env: "POPULACE_BE_HF_REPO", - revision_env: "POPULACE_BE_HF_REVISION", - label: "Belgium", - dataset_label: "Microcosm Belgium", - geography: "Belgium", - geography_id: null, - visibility: "private", - capabilities: CALIBRATION_CAPABILITIES, - }, - // Synthetic repository used only by the third-country conformance fixture. - zz: { - repo: "policyengine/microcosm-zz-fixture", - revision: "main", - label: "Zedland", - dataset_label: "Microcosm ZZ", - geography: "Zedland", - geography_id: null, - visibility: "private", - capabilities: ["calibration", "targets", "compare"], - fixture: true, - }, - // Synthetic non-US staging registration. It verifies that staging artifact - // resolution is country-scoped without exposing an unfinished country in - // selectors or treating its fixture repository as a published dataset. - am: { - repo: "policyengine/microcosm-am-fixture", - revision: "main", - label: "Armenia", - dataset_label: "Microcosm Armenia", - geography: "Armenia", - geography_id: null, - visibility: "private", - capabilities: ["calibration", "targets", "compare", "staging"], - staging: { - repo: "policyengine/microcosm-am-staging-fixture", - revision: "main", - }, - fixture: true, - }, -} satisfies Record; - -export type MicrocosmCountry = keyof typeof COUNTRY_REGISTRY; - -export const DEFAULT_COUNTRY: MicrocosmCountry = "us"; - -const COUNTRY_CODES = Object.keys(COUNTRY_REGISTRY) as MicrocosmCountry[]; - -export function isCountry(value: string | null | undefined): value is MicrocosmCountry { - return value != null && Object.hasOwn(COUNTRY_REGISTRY, value); -} - -export function parseCountry(value: string | null | undefined): MicrocosmCountry { - return isCountry(value) ? value : DEFAULT_COUNTRY; -} - -export function countryRegistration(country: MicrocosmCountry): CountryRegistration { - return COUNTRY_REGISTRY[country]; -} - -// Countries offered in selectors, in registry order; fixtures are excluded. -export function selectableCountries(): MicrocosmCountry[] { - return COUNTRY_CODES.filter((country) => !countryRegistration(country).fixture); -} - -export function countryCapabilities(country: MicrocosmCountry): readonly CountryCapability[] { - return COUNTRY_REGISTRY[country].capabilities; -} - -export function hasCapability(country: MicrocosmCountry, capability: CountryCapability): boolean { - return countryCapabilities(country).includes(capability); -} - -function configuredValue( - environment: RepositoryEnvironment, - variableName: string | undefined, - fallback: string, -): string { - return (variableName ? environment[variableName] : undefined) ?? fallback; -} - -export function resolveRegisteredRepository( - country: MicrocosmCountry, - environment: RepositoryEnvironment, -): ResolvedRepository { - const registration = countryRegistration(country); - return { - repo: configuredValue( - environment, - registration.repo_env, - registration.repo, - ), - revision: configuredValue( - environment, - registration.revision_env, - registration.revision, - ), - }; -} - -export function resolveRegisteredStagingRepository( - country: MicrocosmCountry, - environment: RepositoryEnvironment, -): ResolvedRepository | null { - const registration = countryRegistration(country); - if (!hasCapability(country, "staging") || !registration.staging) return null; - return { - repo: configuredValue( - environment, - registration.staging.repo_env, - registration.staging.repo, - ), - revision: configuredValue( - environment, - registration.staging.revision_env, - registration.staging.revision, - ), - }; -} - -export function isCountryCapability(value: unknown): value is CountryCapability { - return typeof value === "string" && (COUNTRY_CAPABILITIES as readonly string[]).includes(value); -} +// Former location of the country registry, kept as a compatibility +// re-export for code that has not yet moved to the package path. The +// registry lives in @policyengine/calibration-tree/countries; import it from +// there. +export * from "@policyengine/calibration-tree/countries"; diff --git a/frontend/lib/microcosm/hf-webhook-config.ts b/frontend/lib/microcosm/hf-webhook-config.ts index 1a359b0e..60fe8000 100644 --- a/frontend/lib/microcosm/hf-webhook-config.ts +++ b/frontend/lib/microcosm/hf-webhook-config.ts @@ -4,7 +4,7 @@ import { selectableCountries, type MicrocosmCountry, type RepositoryEnvironment, -} from "./countries"; +} from "@policyengine/calibration-tree/countries"; export interface WebhookRepositoryRegistration { country: MicrocosmCountry; diff --git a/frontend/lib/microcosm/hf-webhook-handler.ts b/frontend/lib/microcosm/hf-webhook-handler.ts index d6b552ab..156cbfc7 100644 --- a/frontend/lib/microcosm/hf-webhook-handler.ts +++ b/frontend/lib/microcosm/hf-webhook-handler.ts @@ -4,7 +4,7 @@ import { NextResponse } from "next/server"; import { postReleaseAlert } from "@/lib/slack"; -import type { MicrocosmCountry } from "./countries"; +import type { MicrocosmCountry } from "@policyengine/calibration-tree/countries"; import type { HfWebhookConfig } from "./hf-webhook-config"; interface UpdatedRef { diff --git a/frontend/lib/microcosm/local-build-runs.ts b/frontend/lib/microcosm/local-build-runs.ts index 2a8b412f..86963cf6 100644 --- a/frontend/lib/microcosm/local-build-runs.ts +++ b/frontend/lib/microcosm/local-build-runs.ts @@ -8,7 +8,7 @@ import { parseStagingManifest, parseStagingProgress, validateStagingRunConsistency, -} from "@/lib/microcosm/staging-contract"; +} from "@policyengine/calibration-tree/staging-contract"; // Local build runs: the `runs//` telemetry folders a Microcosm build // writes on the machine it runs on (the US release writes them under diff --git a/frontend/lib/microcosm/presentation.ts b/frontend/lib/microcosm/presentation.ts index d74c78bf..7bdf5e07 100644 --- a/frontend/lib/microcosm/presentation.ts +++ b/frontend/lib/microcosm/presentation.ts @@ -1,4 +1,4 @@ -import type { MicrocosmCountry } from "./countries"; +import type { MicrocosmCountry } from "@policyengine/calibration-tree/countries"; export interface MicrocosmPresentationSlots { overview_intro?: string; diff --git a/frontend/lib/microcosm/production-release.test.ts b/frontend/lib/microcosm/production-release.test.ts index 875617a6..1a59f04c 100644 --- a/frontend/lib/microcosm/production-release.test.ts +++ b/frontend/lib/microcosm/production-release.test.ts @@ -7,7 +7,7 @@ import { import { hfResolveUrl, microcosmRevision, -} from "./latest-artifact"; +} from "@policyengine/calibration-tree/latest-artifact"; test("dashboard discovery follows the repository branch while variable calculation stays pinned", () => { expect(microcosmRevision("us")).toBe("main"); diff --git a/frontend/lib/microcosm/reviewed-repository-import.fixture.ts b/frontend/lib/microcosm/reviewed-repository-import.fixture.ts index f97a8c99..c62cb82b 100644 --- a/frontend/lib/microcosm/reviewed-repository-import.fixture.ts +++ b/frontend/lib/microcosm/reviewed-repository-import.fixture.ts @@ -9,7 +9,7 @@ import { microcosmCountryGeography, microcosmRepo, microcosmRevision, -} from "./latest-artifact"; +} from "@policyengine/calibration-tree/latest-artifact"; import { IncompatibleProductionReleaseError } from "./production-release"; // Collecting page data imports every route, so one country's refused selection diff --git a/frontend/lib/microcosm/reviewed-repository-import.test.ts b/frontend/lib/microcosm/reviewed-repository-import.test.ts index 3148fa89..cac9d6f7 100644 --- a/frontend/lib/microcosm/reviewed-repository-import.test.ts +++ b/frontend/lib/microcosm/reviewed-repository-import.test.ts @@ -1,6 +1,6 @@ import { expect, test } from "bun:test"; -import { COUNTRY_REGISTRY } from "./countries"; +import { COUNTRY_REGISTRY } from "@policyengine/calibration-tree/countries"; // A US-only data-selection conflict must fail US reads only. Asserting it while // building the repository table would throw during module import instead, taking diff --git a/frontend/lib/microcosm/source-attribution.ts b/frontend/lib/microcosm/source-attribution.ts index 2130a80c..d8d9761b 100644 --- a/frontend/lib/microcosm/source-attribution.ts +++ b/frontend/lib/microcosm/source-attribution.ts @@ -2,7 +2,7 @@ import { countryRegistration, type MicrocosmCountry, type RepositoryVisibility, -} from "./countries"; +} from "@policyengine/calibration-tree/countries"; export interface MicrocosmSourceAttribution { label: string; diff --git a/frontend/lib/microcosm/target-change-tree.test.ts b/frontend/lib/microcosm/target-change-tree.test.ts index 1189c9b6..f3cd529f 100644 --- a/frontend/lib/microcosm/target-change-tree.test.ts +++ b/frontend/lib/microcosm/target-change-tree.test.ts @@ -3,9 +3,9 @@ import { describe, expect, test } from "bun:test"; import { createExplorerState, type ExplorerState, -} from "./calibration-explorer"; -import type { Calibration } from "./latest-artifact"; -import { buildTargetChangeDataset } from "./target-change"; +} from "@policyengine/calibration-tree/calibration-explorer"; +import type { Calibration } from "@policyengine/calibration-tree/latest-artifact"; +import { buildTargetChangeDataset } from "@policyengine/calibration-tree/target-change"; import { buildTargetChangeTree } from "./target-change-tree"; interface RowInput { diff --git a/frontend/lib/microcosm/target-change-tree.ts b/frontend/lib/microcosm/target-change-tree.ts index 32c2f437..e8b0c8ef 100644 --- a/frontend/lib/microcosm/target-change-tree.ts +++ b/frontend/lib/microcosm/target-change-tree.ts @@ -1,9 +1,9 @@ -import type { ExplorerState } from "./calibration-explorer"; +import type { ExplorerState } from "@policyengine/calibration-tree/calibration-explorer"; import { buildCalibrationTree, type CalibrationTreeResponse, type CalibrationTreeTarget, -} from "./calibration-tree"; +} from "@policyengine/calibration-tree/calibration-tree"; import { targetChangeForMode, type TargetChangeAttributionSide, @@ -12,8 +12,8 @@ import { type TargetChangeMode, type TargetChangeRow, type TargetChangeSummary, -} from "./target-change"; -import type { TargetMatchingSummary } from "./target-surface-matcher"; +} from "@policyengine/calibration-tree/target-change"; +import type { TargetMatchingSummary } from "@policyengine/calibration-tree/target-surface-matcher"; export interface TargetChangeTreeResponse extends CalibrationTreeResponse { available: boolean; diff --git a/frontend/lib/microcosm/target-change-visualization.test.ts b/frontend/lib/microcosm/target-change-visualization.test.ts index 4033d962..e8bb44ba 100644 --- a/frontend/lib/microcosm/target-change-visualization.test.ts +++ b/frontend/lib/microcosm/target-change-visualization.test.ts @@ -3,8 +3,8 @@ import { describe, expect, test } from "bun:test"; import type { CalibrationTreeGroup, CalibrationTreeMetrics, -} from "./calibration-tree"; -import type { TargetChangeRow } from "./target-change"; +} from "@policyengine/calibration-tree/calibration-tree"; +import type { TargetChangeRow } from "@policyengine/calibration-tree/target-change"; import { formatTargetChange, targetChangeDetailValues, diff --git a/frontend/lib/microcosm/target-change-visualization.ts b/frontend/lib/microcosm/target-change-visualization.ts index 99bd2e20..9899038c 100644 --- a/frontend/lib/microcosm/target-change-visualization.ts +++ b/frontend/lib/microcosm/target-change-visualization.ts @@ -2,12 +2,12 @@ import type { Placed } from "@/lib/treemap/squarify"; import type { CalibrationTreeChangeMetrics, CalibrationTreeGroup, -} from "./calibration-tree"; +} from "@policyengine/calibration-tree/calibration-tree"; import { aggregateCalibrationTreeMetrics } from "./calibration-treemap-layout"; import type { TargetChangeMode, TargetChangeRow, -} from "./target-change"; +} from "@policyengine/calibration-tree/target-change"; export type TargetChangeDirection = "increase" | "reduction"; diff --git a/frontend/lib/microcosm/target-matching-presentation.ts b/frontend/lib/microcosm/target-matching-presentation.ts index 614e3b8e..c0ead2b2 100644 --- a/frontend/lib/microcosm/target-matching-presentation.ts +++ b/frontend/lib/microcosm/target-matching-presentation.ts @@ -1,8 +1,8 @@ -import type { TargetMatchKind } from "./target-surface-matcher"; +import type { TargetMatchKind } from "@policyengine/calibration-tree/target-surface-matcher"; import type { TargetRepresentation, TargetRowRepresentation, -} from "./target-representation"; +} from "@policyengine/calibration-tree/target-representation"; export function targetRepresentationLabel( representation: TargetRepresentation | TargetRowRepresentation, diff --git a/frontend/lib/microcosm/third-country-conformance.test.ts b/frontend/lib/microcosm/third-country-conformance.test.ts index f99a8532..19274d23 100644 --- a/frontend/lib/microcosm/third-country-conformance.test.ts +++ b/frontend/lib/microcosm/third-country-conformance.test.ts @@ -2,8 +2,8 @@ import { describe, expect, test } from "bun:test"; import { isCountry as isClientCountry } from "@/components/layout/country-context"; import { navGroupsForCountry } from "@/components/layout/nav-items"; -import { countryCapabilities, selectableCountries } from "@/lib/microcosm/countries"; -import { sourceAuthorityLabel } from "@/lib/source-labels"; +import { countryCapabilities, selectableCountries } from "@policyengine/calibration-tree/countries"; +import { sourceAuthorityLabel } from "@policyengine/calibration-tree/source-labels"; import diagnosticsFixture from "./fixtures/zz-release/calibration_diagnostics.json"; import releaseManifestFixture from "./fixtures/zz-release/release_manifest.json"; @@ -17,7 +17,7 @@ import { microcosmRevision, microcosmTargetTreemap, parseCountry, -} from "./latest-artifact"; +} from "@policyengine/calibration-tree/latest-artifact"; const COUNTRY = "zz" as const; const RELEASE_ID = "microcosm-zz-2026-conformance"; diff --git a/frontend/lib/slack.ts b/frontend/lib/slack.ts index f0d97e2c..156bef2b 100644 --- a/frontend/lib/slack.ts +++ b/frontend/lib/slack.ts @@ -1,4 +1,4 @@ -import type { MicrocosmCountry } from "@/lib/microcosm/latest-artifact"; +import type { MicrocosmCountry } from "@policyengine/calibration-tree/latest-artifact"; const DASHBOARD_URL = "https://microcosm.institute/calibration/dashboard/microcosm"; diff --git a/frontend/package.json b/frontend/package.json index c76942ef..0de18140 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -3,16 +3,20 @@ "version": "0.1.0", "private": true, "type": "module", + "workspaces": [ + "packages/*" + ], "scripts": { "dev": "node scripts/dev.mjs", "build": "next build", "start": "next start", - "lint": "tsc --noEmit", + "lint": "tsc --noEmit && tsc --noEmit -p packages/calibration-tree", "publish:calibration-tree": "bun run scripts/publish-calibration-tree.ts", "test:dev-port": "node --test scripts/available-port.node-test.mjs", "replay:build-run": "bun run scripts/replay-build-run.ts" }, "dependencies": { + "@policyengine/calibration-tree": "workspace:*", "@policyengine/ui-kit": "^0.9.0", "@tanstack/react-query": "^5.84.0", "@tanstack/react-query-devtools": "^5.83.0", diff --git a/frontend/packages/calibration-tree/package.json b/frontend/packages/calibration-tree/package.json new file mode 100644 index 00000000..ab5fc8fe --- /dev/null +++ b/frontend/packages/calibration-tree/package.json @@ -0,0 +1,49 @@ +{ + "name": "@policyengine/calibration-tree", + "version": "0.1.0", + "description": "Builds, validates, reads and publishes Microcosm calibration tree artifacts (tree schema 6), with the release and staging readers they are built from.", + "private": true, + "type": "module", + "exports": { + "./calibration-build-comparison": "./src/calibration-build-comparison.ts", + "./calibration-comparison-blob": "./src/calibration-comparison-blob.ts", + "./calibration-comparison-bundle": "./src/calibration-comparison-bundle.ts", + "./calibration-explorer": "./src/calibration-explorer.ts", + "./calibration-release-locator": "./src/calibration-release-locator.ts", + "./calibration-tree": "./src/calibration-tree.ts", + "./calibration-tree-artifact": "./src/calibration-tree-artifact.ts", + "./calibration-tree-blob": "./src/calibration-tree-blob.ts", + "./calibration-tree-bundle": "./src/calibration-tree-bundle.ts", + "./calibration-tree-manifest": "./src/calibration-tree-manifest.ts", + "./chronicle-metadata": "./src/chronicle-metadata.ts", + "./countries": "./src/countries.ts", + "./hierarchy-target-reader": "./src/hierarchy-target-reader.ts", + "./historical-weight-summary": "./src/historical-weight-summary.ts", + "./latest-artifact": "./src/latest-artifact.ts", + "./legacy-target-reader": "./src/legacy-target-reader.ts", + "./program-label": "./src/program-label.ts", + "./reforms": "./src/reforms.ts", + "./source-label": "./src/source-label.ts", + "./source-labels": "./src/source-labels.ts", + "./staging-artifact": "./src/staging-artifact.ts", + "./staging-contract": "./src/staging-contract.ts", + "./structured-dimension-reader": "./src/structured-dimension-reader.ts", + "./structured-target-reader": "./src/structured-target-reader.ts", + "./target-change": "./src/target-change.ts", + "./target-loss-attribution": "./src/target-loss-attribution.ts", + "./target-loss-attribution-manifest": "./src/target-loss-attribution-manifest.ts", + "./target-representation": "./src/target-representation.ts", + "./target-surface-matcher": "./src/target-surface-matcher.ts" + }, + "scripts": { + "test": "bun test", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@vercel/blob": "^2.6.1" + }, + "devDependencies": { + "@types/bun": "^1.3.14", + "typescript": "^5.8.0" + } +} diff --git a/frontend/lib/microcosm/calibration-build-comparison.test.ts b/frontend/packages/calibration-tree/src/calibration-build-comparison.test.ts similarity index 100% rename from frontend/lib/microcosm/calibration-build-comparison.test.ts rename to frontend/packages/calibration-tree/src/calibration-build-comparison.test.ts diff --git a/frontend/lib/microcosm/calibration-build-comparison.ts b/frontend/packages/calibration-tree/src/calibration-build-comparison.ts similarity index 100% rename from frontend/lib/microcosm/calibration-build-comparison.ts rename to frontend/packages/calibration-tree/src/calibration-build-comparison.ts diff --git a/frontend/lib/microcosm/calibration-comparison-blob.test.ts b/frontend/packages/calibration-tree/src/calibration-comparison-blob.test.ts similarity index 100% rename from frontend/lib/microcosm/calibration-comparison-blob.test.ts rename to frontend/packages/calibration-tree/src/calibration-comparison-blob.test.ts diff --git a/frontend/lib/microcosm/calibration-comparison-blob.ts b/frontend/packages/calibration-tree/src/calibration-comparison-blob.ts similarity index 100% rename from frontend/lib/microcosm/calibration-comparison-blob.ts rename to frontend/packages/calibration-tree/src/calibration-comparison-blob.ts diff --git a/frontend/lib/microcosm/calibration-comparison-bundle.test.ts b/frontend/packages/calibration-tree/src/calibration-comparison-bundle.test.ts similarity index 100% rename from frontend/lib/microcosm/calibration-comparison-bundle.test.ts rename to frontend/packages/calibration-tree/src/calibration-comparison-bundle.test.ts diff --git a/frontend/lib/microcosm/calibration-comparison-bundle.ts b/frontend/packages/calibration-tree/src/calibration-comparison-bundle.ts similarity index 100% rename from frontend/lib/microcosm/calibration-comparison-bundle.ts rename to frontend/packages/calibration-tree/src/calibration-comparison-bundle.ts diff --git a/frontend/lib/microcosm/calibration-explorer.test.ts b/frontend/packages/calibration-tree/src/calibration-explorer.test.ts similarity index 100% rename from frontend/lib/microcosm/calibration-explorer.test.ts rename to frontend/packages/calibration-tree/src/calibration-explorer.test.ts diff --git a/frontend/lib/microcosm/calibration-explorer.ts b/frontend/packages/calibration-tree/src/calibration-explorer.ts similarity index 100% rename from frontend/lib/microcosm/calibration-explorer.ts rename to frontend/packages/calibration-tree/src/calibration-explorer.ts diff --git a/frontend/lib/microcosm/calibration-release-locator.test.ts b/frontend/packages/calibration-tree/src/calibration-release-locator.test.ts similarity index 100% rename from frontend/lib/microcosm/calibration-release-locator.test.ts rename to frontend/packages/calibration-tree/src/calibration-release-locator.test.ts diff --git a/frontend/lib/microcosm/calibration-release-locator.ts b/frontend/packages/calibration-tree/src/calibration-release-locator.ts similarity index 100% rename from frontend/lib/microcosm/calibration-release-locator.ts rename to frontend/packages/calibration-tree/src/calibration-release-locator.ts diff --git a/frontend/lib/microcosm/calibration-tree-artifact.test.ts b/frontend/packages/calibration-tree/src/calibration-tree-artifact.test.ts similarity index 100% rename from frontend/lib/microcosm/calibration-tree-artifact.test.ts rename to frontend/packages/calibration-tree/src/calibration-tree-artifact.test.ts diff --git a/frontend/lib/microcosm/calibration-tree-artifact.ts b/frontend/packages/calibration-tree/src/calibration-tree-artifact.ts similarity index 100% rename from frontend/lib/microcosm/calibration-tree-artifact.ts rename to frontend/packages/calibration-tree/src/calibration-tree-artifact.ts diff --git a/frontend/lib/microcosm/calibration-tree-blob.test.ts b/frontend/packages/calibration-tree/src/calibration-tree-blob.test.ts similarity index 100% rename from frontend/lib/microcosm/calibration-tree-blob.test.ts rename to frontend/packages/calibration-tree/src/calibration-tree-blob.test.ts diff --git a/frontend/lib/microcosm/calibration-tree-blob.ts b/frontend/packages/calibration-tree/src/calibration-tree-blob.ts similarity index 100% rename from frontend/lib/microcosm/calibration-tree-blob.ts rename to frontend/packages/calibration-tree/src/calibration-tree-blob.ts diff --git a/frontend/lib/microcosm/calibration-tree-bundle.ts b/frontend/packages/calibration-tree/src/calibration-tree-bundle.ts similarity index 100% rename from frontend/lib/microcosm/calibration-tree-bundle.ts rename to frontend/packages/calibration-tree/src/calibration-tree-bundle.ts diff --git a/frontend/lib/microcosm/calibration-tree-golden.test.ts b/frontend/packages/calibration-tree/src/calibration-tree-golden.test.ts similarity index 100% rename from frontend/lib/microcosm/calibration-tree-golden.test.ts rename to frontend/packages/calibration-tree/src/calibration-tree-golden.test.ts diff --git a/frontend/lib/microcosm/calibration-tree-manifest.test.ts b/frontend/packages/calibration-tree/src/calibration-tree-manifest.test.ts similarity index 100% rename from frontend/lib/microcosm/calibration-tree-manifest.test.ts rename to frontend/packages/calibration-tree/src/calibration-tree-manifest.test.ts diff --git a/frontend/lib/microcosm/calibration-tree-manifest.ts b/frontend/packages/calibration-tree/src/calibration-tree-manifest.ts similarity index 100% rename from frontend/lib/microcosm/calibration-tree-manifest.ts rename to frontend/packages/calibration-tree/src/calibration-tree-manifest.ts diff --git a/frontend/lib/microcosm/calibration-tree.test.ts b/frontend/packages/calibration-tree/src/calibration-tree.test.ts similarity index 100% rename from frontend/lib/microcosm/calibration-tree.test.ts rename to frontend/packages/calibration-tree/src/calibration-tree.test.ts diff --git a/frontend/lib/microcosm/calibration-tree.ts b/frontend/packages/calibration-tree/src/calibration-tree.ts similarity index 100% rename from frontend/lib/microcosm/calibration-tree.ts rename to frontend/packages/calibration-tree/src/calibration-tree.ts diff --git a/frontend/lib/microcosm/chronicle-metadata.test.ts b/frontend/packages/calibration-tree/src/chronicle-metadata.test.ts similarity index 100% rename from frontend/lib/microcosm/chronicle-metadata.test.ts rename to frontend/packages/calibration-tree/src/chronicle-metadata.test.ts diff --git a/frontend/lib/microcosm/chronicle-metadata.ts b/frontend/packages/calibration-tree/src/chronicle-metadata.ts similarity index 100% rename from frontend/lib/microcosm/chronicle-metadata.ts rename to frontend/packages/calibration-tree/src/chronicle-metadata.ts diff --git a/frontend/lib/microcosm/countries.test.ts b/frontend/packages/calibration-tree/src/countries.test.ts similarity index 100% rename from frontend/lib/microcosm/countries.test.ts rename to frontend/packages/calibration-tree/src/countries.test.ts diff --git a/frontend/packages/calibration-tree/src/countries.ts b/frontend/packages/calibration-tree/src/countries.ts new file mode 100644 index 00000000..a57253d5 --- /dev/null +++ b/frontend/packages/calibration-tree/src/countries.ts @@ -0,0 +1,241 @@ +// Country registry for the Microcosm dashboard: the one place a country is +// registered. Adding a country is one entry here; everything else (selectors, +// navigation, repository resolution, national geography, link behaviour) reads +// the registration or the release artifact's typed `country` block. +// +// Isomorphic: imported by client components and API routes alike, so it must +// not import server-only modules or read `process.env`. Repository resolvers +// accept an explicit environment object from server-side callers. + +export const COUNTRY_CAPABILITIES = [ + "calibration", + "targets", + "compare", + "cross_dataset", + "staging", + "model_coverage", + "pipeline", + "variables", + "external_checks", +] as const; +export type CountryCapability = (typeof COUNTRY_CAPABILITIES)[number]; + +export type RepositoryVisibility = "public" | "private"; + +export type RepositoryEnvironment = Readonly< + Record +>; + +export interface ResolvedRepository { + repo: string; + revision: string; +} + +export interface CountryRegistration { + // Default HF dataset repository and revision (the server may override both + // through the deployment variables named in repo_env / revision_env). + repo: string; + revision: string; + repo_env?: string; + revision_env?: string; + // Country display name ("United States"). + label: string; + // Sidebar dataset line ("Microcosm US"). + dataset_label: string; + // National geography label and id (null when the id is unknown). + geography: string; + geography_id: string | null; + visibility: RepositoryVisibility; + capabilities: readonly CountryCapability[]; + // Optional staging telemetry repository. A staging-capable country must + // declare this instead of inheriting another country's repository. + staging?: { + repo: string; + revision: string; + repo_env?: string; + revision_env?: string; + token_env?: string; + }; + // Other jurisdiction codes a cross-dataset bundle may use for this country. + jurisdiction_aliases?: readonly string[]; + // Conformance-only registration: a valid country that is never listed in + // selectors or alert allowlists. + fixture?: true; +} + +const ALL_CAPABILITIES: readonly CountryCapability[] = COUNTRY_CAPABILITIES; + +const CALIBRATION_CAPABILITIES: readonly CountryCapability[] = [ + "calibration", + "targets", + "compare", + "cross_dataset", +]; + +// Deprecated upstream identifiers: Microcosm's published HF repositories and +// deployment variables still use the former Populace names. +export const COUNTRY_REGISTRY = { + us: { + repo: "policyengine/populace-us", + revision: "main", + repo_env: "POPULACE_HF_REPO", + label: "United States", + dataset_label: "Microcosm US", + geography: "United States", + geography_id: "0100000US", + visibility: "public", + capabilities: ALL_CAPABILITIES, + staging: { + // Deprecated upstream identifier: the US staging publisher still uses + // the former Populace repository and deployment-variable names. + repo: "policyengine/populace-us-staging", + revision: "main", + repo_env: "POPULACE_STAGING_HF_REPO", + revision_env: "POPULACE_STAGING_HF_REVISION", + }, + }, + uk: { + repo: "policyengine/populace-uk-private", + revision: "main", + repo_env: "POPULACE_UK_HF_REPO", + revision_env: "POPULACE_UK_HF_REVISION", + label: "United Kingdom", + dataset_label: "Microcosm UK", + geography: "United Kingdom", + geography_id: null, + visibility: "private", + capabilities: [...CALIBRATION_CAPABILITIES, "staging"], + staging: { + repo: "policyengine/populace-uk-staging", + revision: "main", + repo_env: "POPULACE_UK_STAGING_HF_REPO", + revision_env: "POPULACE_UK_STAGING_HF_REVISION", + token_env: "POPULACE_UK_STAGING_HF_TOKEN", + }, + jurisdiction_aliases: ["GB"], + }, + be: { + repo: "policyengine/populace-be-private", + revision: "main", + repo_env: "POPULACE_BE_HF_REPO", + revision_env: "POPULACE_BE_HF_REVISION", + label: "Belgium", + dataset_label: "Microcosm Belgium", + geography: "Belgium", + geography_id: null, + visibility: "private", + capabilities: CALIBRATION_CAPABILITIES, + }, + // Synthetic repository used only by the third-country conformance fixture. + zz: { + repo: "policyengine/microcosm-zz-fixture", + revision: "main", + label: "Zedland", + dataset_label: "Microcosm ZZ", + geography: "Zedland", + geography_id: null, + visibility: "private", + capabilities: ["calibration", "targets", "compare"], + fixture: true, + }, + // Synthetic non-US staging registration. It verifies that staging artifact + // resolution is country-scoped without exposing an unfinished country in + // selectors or treating its fixture repository as a published dataset. + am: { + repo: "policyengine/microcosm-am-fixture", + revision: "main", + label: "Armenia", + dataset_label: "Microcosm Armenia", + geography: "Armenia", + geography_id: null, + visibility: "private", + capabilities: ["calibration", "targets", "compare", "staging"], + staging: { + repo: "policyengine/microcosm-am-staging-fixture", + revision: "main", + }, + fixture: true, + }, +} satisfies Record; + +export type MicrocosmCountry = keyof typeof COUNTRY_REGISTRY; + +export const DEFAULT_COUNTRY: MicrocosmCountry = "us"; + +const COUNTRY_CODES = Object.keys(COUNTRY_REGISTRY) as MicrocosmCountry[]; + +export function isCountry(value: string | null | undefined): value is MicrocosmCountry { + return value != null && Object.hasOwn(COUNTRY_REGISTRY, value); +} + +export function parseCountry(value: string | null | undefined): MicrocosmCountry { + return isCountry(value) ? value : DEFAULT_COUNTRY; +} + +export function countryRegistration(country: MicrocosmCountry): CountryRegistration { + return COUNTRY_REGISTRY[country]; +} + +// Countries offered in selectors, in registry order; fixtures are excluded. +export function selectableCountries(): MicrocosmCountry[] { + return COUNTRY_CODES.filter((country) => !countryRegistration(country).fixture); +} + +export function countryCapabilities(country: MicrocosmCountry): readonly CountryCapability[] { + return COUNTRY_REGISTRY[country].capabilities; +} + +export function hasCapability(country: MicrocosmCountry, capability: CountryCapability): boolean { + return countryCapabilities(country).includes(capability); +} + +function configuredValue( + environment: RepositoryEnvironment, + variableName: string | undefined, + fallback: string, +): string { + return (variableName ? environment[variableName] : undefined) ?? fallback; +} + +export function resolveRegisteredRepository( + country: MicrocosmCountry, + environment: RepositoryEnvironment, +): ResolvedRepository { + const registration = countryRegistration(country); + return { + repo: configuredValue( + environment, + registration.repo_env, + registration.repo, + ), + revision: configuredValue( + environment, + registration.revision_env, + registration.revision, + ), + }; +} + +export function resolveRegisteredStagingRepository( + country: MicrocosmCountry, + environment: RepositoryEnvironment, +): ResolvedRepository | null { + const registration = countryRegistration(country); + if (!hasCapability(country, "staging") || !registration.staging) return null; + return { + repo: configuredValue( + environment, + registration.staging.repo_env, + registration.staging.repo, + ), + revision: configuredValue( + environment, + registration.staging.revision_env, + registration.staging.revision, + ), + }; +} + +export function isCountryCapability(value: unknown): value is CountryCapability { + return typeof value === "string" && (COUNTRY_CAPABILITIES as readonly string[]).includes(value); +} diff --git a/frontend/lib/microcosm/fixtures/be-release/calibration_diagnostics.json b/frontend/packages/calibration-tree/src/fixtures/be-release/calibration_diagnostics.json similarity index 100% rename from frontend/lib/microcosm/fixtures/be-release/calibration_diagnostics.json rename to frontend/packages/calibration-tree/src/fixtures/be-release/calibration_diagnostics.json diff --git a/frontend/lib/microcosm/fixtures/be-release/release_manifest.json b/frontend/packages/calibration-tree/src/fixtures/be-release/release_manifest.json similarity index 100% rename from frontend/lib/microcosm/fixtures/be-release/release_manifest.json rename to frontend/packages/calibration-tree/src/fixtures/be-release/release_manifest.json diff --git a/frontend/lib/microcosm/fixtures/calibration-tree-golden.json b/frontend/packages/calibration-tree/src/fixtures/calibration-tree-golden.json similarity index 100% rename from frontend/lib/microcosm/fixtures/calibration-tree-golden.json rename to frontend/packages/calibration-tree/src/fixtures/calibration-tree-golden.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v1/SHA256SUMS b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/SHA256SUMS similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v1/SHA256SUMS rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/SHA256SUMS diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v1/calibration_progress.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/calibration_progress.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v1/calibration_progress.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/calibration_progress.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v1/events.ndjson b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/events.ndjson similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v1/events.ndjson rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/events.ndjson diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v1/latest_staging.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/latest_staging.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v1/latest_staging.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/latest_staging.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v1/progress.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/progress.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v1/progress.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/progress.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v1/run_manifest.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/run_manifest.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v1/run_manifest.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/run_manifest.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v1/runs.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/runs.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v1/runs.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/runs.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v2/SHA256SUMS b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/SHA256SUMS similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v2/SHA256SUMS rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/SHA256SUMS diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/calibration_progress.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/calibration_progress.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/calibration_progress.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/calibration_progress.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/events.ndjson b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/events.ndjson similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/events.ndjson rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/events.ndjson diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/progress.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/progress.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/progress.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/progress.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/run_manifest.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/run_manifest.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/run_manifest.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/run_manifest.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v2/completed-spine/runs/uk-spine-v2-fixture/events.ndjson b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/completed-spine/runs/uk-spine-v2-fixture/events.ndjson similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v2/completed-spine/runs/uk-spine-v2-fixture/events.ndjson rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/completed-spine/runs/uk-spine-v2-fixture/events.ndjson diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v2/completed-spine/runs/uk-spine-v2-fixture/progress.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/completed-spine/runs/uk-spine-v2-fixture/progress.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v2/completed-spine/runs/uk-spine-v2-fixture/progress.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/completed-spine/runs/uk-spine-v2-fixture/progress.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v2/completed-spine/runs/uk-spine-v2-fixture/run_manifest.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/completed-spine/runs/uk-spine-v2-fixture/run_manifest.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v2/completed-spine/runs/uk-spine-v2-fixture/run_manifest.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/completed-spine/runs/uk-spine-v2-fixture/run_manifest.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v2/contract-cases.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/contract-cases.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v2/contract-cases.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/contract-cases.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v2/failed/runs/uk-failed-v2-fixture/events.ndjson b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/failed/runs/uk-failed-v2-fixture/events.ndjson similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v2/failed/runs/uk-failed-v2-fixture/events.ndjson rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/failed/runs/uk-failed-v2-fixture/events.ndjson diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v2/failed/runs/uk-failed-v2-fixture/progress.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/failed/runs/uk-failed-v2-fixture/progress.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v2/failed/runs/uk-failed-v2-fixture/progress.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/failed/runs/uk-failed-v2-fixture/progress.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v2/failed/runs/uk-failed-v2-fixture/run_manifest.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/failed/runs/uk-failed-v2-fixture/run_manifest.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v2/failed/runs/uk-failed-v2-fixture/run_manifest.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/failed/runs/uk-failed-v2-fixture/run_manifest.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/SHA256SUMS b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/SHA256SUMS similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/SHA256SUMS rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/SHA256SUMS diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/blocked/runs/uk-blocked-v3-fixture/events.ndjson b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/blocked/runs/uk-blocked-v3-fixture/events.ndjson similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/blocked/runs/uk-blocked-v3-fixture/events.ndjson rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/blocked/runs/uk-blocked-v3-fixture/events.ndjson diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/blocked/runs/uk-blocked-v3-fixture/progress.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/blocked/runs/uk-blocked-v3-fixture/progress.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/blocked/runs/uk-blocked-v3-fixture/progress.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/blocked/runs/uk-blocked-v3-fixture/progress.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/blocked/runs/uk-blocked-v3-fixture/run_manifest.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/blocked/runs/uk-blocked-v3-fixture/run_manifest.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/blocked/runs/uk-blocked-v3-fixture/run_manifest.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/blocked/runs/uk-blocked-v3-fixture/run_manifest.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/calibration_progress.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/calibration_progress.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/calibration_progress.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/calibration_progress.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/events.ndjson b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/events.ndjson similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/events.ndjson rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/events.ndjson diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/progress.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/progress.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/progress.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/progress.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/run_manifest.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/run_manifest.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/run_manifest.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/run_manifest.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/completed-spine/runs/uk-spine-v3-fixture/events.ndjson b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/completed-spine/runs/uk-spine-v3-fixture/events.ndjson similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/completed-spine/runs/uk-spine-v3-fixture/events.ndjson rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/completed-spine/runs/uk-spine-v3-fixture/events.ndjson diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/completed-spine/runs/uk-spine-v3-fixture/progress.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/completed-spine/runs/uk-spine-v3-fixture/progress.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/completed-spine/runs/uk-spine-v3-fixture/progress.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/completed-spine/runs/uk-spine-v3-fixture/progress.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/completed-spine/runs/uk-spine-v3-fixture/run_manifest.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/completed-spine/runs/uk-spine-v3-fixture/run_manifest.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/completed-spine/runs/uk-spine-v3-fixture/run_manifest.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/completed-spine/runs/uk-spine-v3-fixture/run_manifest.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/contract-cases.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/contract-cases.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/contract-cases.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/contract-cases.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/failed/runs/uk-failed-v3-fixture/events.ndjson b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/failed/runs/uk-failed-v3-fixture/events.ndjson similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/failed/runs/uk-failed-v3-fixture/events.ndjson rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/failed/runs/uk-failed-v3-fixture/events.ndjson diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/failed/runs/uk-failed-v3-fixture/progress.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/failed/runs/uk-failed-v3-fixture/progress.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/failed/runs/uk-failed-v3-fixture/progress.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/failed/runs/uk-failed-v3-fixture/progress.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/failed/runs/uk-failed-v3-fixture/run_manifest.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/failed/runs/uk-failed-v3-fixture/run_manifest.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/failed/runs/uk-failed-v3-fixture/run_manifest.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/failed/runs/uk-failed-v3-fixture/run_manifest.json diff --git a/frontend/lib/microcosm/fixtures/target-loss-attribution/malformed-partial.json b/frontend/packages/calibration-tree/src/fixtures/target-loss-attribution/malformed-partial.json similarity index 100% rename from frontend/lib/microcosm/fixtures/target-loss-attribution/malformed-partial.json rename to frontend/packages/calibration-tree/src/fixtures/target-loss-attribution/malformed-partial.json diff --git a/frontend/lib/microcosm/fixtures/target-loss-attribution/unavailable-warning.json b/frontend/packages/calibration-tree/src/fixtures/target-loss-attribution/unavailable-warning.json similarity index 100% rename from frontend/lib/microcosm/fixtures/target-loss-attribution/unavailable-warning.json rename to frontend/packages/calibration-tree/src/fixtures/target-loss-attribution/unavailable-warning.json diff --git a/frontend/lib/microcosm/fixtures/target-loss-attribution/valid-unequal-custom-scales.json b/frontend/packages/calibration-tree/src/fixtures/target-loss-attribution/valid-unequal-custom-scales.json similarity index 100% rename from frontend/lib/microcosm/fixtures/target-loss-attribution/valid-unequal-custom-scales.json rename to frontend/packages/calibration-tree/src/fixtures/target-loss-attribution/valid-unequal-custom-scales.json diff --git a/frontend/lib/microcosm/fixtures/target-loss-attribution/valid-uniform.json b/frontend/packages/calibration-tree/src/fixtures/target-loss-attribution/valid-uniform.json similarity index 100% rename from frontend/lib/microcosm/fixtures/target-loss-attribution/valid-uniform.json rename to frontend/packages/calibration-tree/src/fixtures/target-loss-attribution/valid-uniform.json diff --git a/frontend/lib/microcosm/fixtures/us-legacy-target-identities.json.gz b/frontend/packages/calibration-tree/src/fixtures/us-legacy-target-identities.json.gz similarity index 100% rename from frontend/lib/microcosm/fixtures/us-legacy-target-identities.json.gz rename to frontend/packages/calibration-tree/src/fixtures/us-legacy-target-identities.json.gz diff --git a/frontend/lib/microcosm/hierarchy-target-reader.test.ts b/frontend/packages/calibration-tree/src/hierarchy-target-reader.test.ts similarity index 100% rename from frontend/lib/microcosm/hierarchy-target-reader.test.ts rename to frontend/packages/calibration-tree/src/hierarchy-target-reader.test.ts diff --git a/frontend/lib/microcosm/hierarchy-target-reader.ts b/frontend/packages/calibration-tree/src/hierarchy-target-reader.ts similarity index 100% rename from frontend/lib/microcosm/hierarchy-target-reader.ts rename to frontend/packages/calibration-tree/src/hierarchy-target-reader.ts diff --git a/frontend/lib/microcosm/historical-weight-summary.test.ts b/frontend/packages/calibration-tree/src/historical-weight-summary.test.ts similarity index 100% rename from frontend/lib/microcosm/historical-weight-summary.test.ts rename to frontend/packages/calibration-tree/src/historical-weight-summary.test.ts diff --git a/frontend/lib/microcosm/historical-weight-summary.ts b/frontend/packages/calibration-tree/src/historical-weight-summary.ts similarity index 100% rename from frontend/lib/microcosm/historical-weight-summary.ts rename to frontend/packages/calibration-tree/src/historical-weight-summary.ts diff --git a/frontend/lib/microcosm/latest-artifact.test.ts b/frontend/packages/calibration-tree/src/latest-artifact.test.ts similarity index 100% rename from frontend/lib/microcosm/latest-artifact.test.ts rename to frontend/packages/calibration-tree/src/latest-artifact.test.ts diff --git a/frontend/lib/microcosm/latest-artifact.ts b/frontend/packages/calibration-tree/src/latest-artifact.ts similarity index 99% rename from frontend/lib/microcosm/latest-artifact.ts rename to frontend/packages/calibration-tree/src/latest-artifact.ts index b0ba929b..692006b4 100644 --- a/frontend/lib/microcosm/latest-artifact.ts +++ b/frontend/packages/calibration-tree/src/latest-artifact.ts @@ -8,7 +8,7 @@ import { basename, join } from "node:path"; import { createHash } from "node:crypto"; -import { sourceAuthorityLabel } from "@/lib/source-labels"; +import { sourceAuthorityLabel } from "./source-labels"; import { normalizeChronicleMetadata } from "./chronicle-metadata"; import { diff --git a/frontend/lib/microcosm/legacy-target-reader.ts b/frontend/packages/calibration-tree/src/legacy-target-reader.ts similarity index 100% rename from frontend/lib/microcosm/legacy-target-reader.ts rename to frontend/packages/calibration-tree/src/legacy-target-reader.ts diff --git a/frontend/lib/microcosm/legacy-target-regression.test.ts b/frontend/packages/calibration-tree/src/legacy-target-regression.test.ts similarity index 100% rename from frontend/lib/microcosm/legacy-target-regression.test.ts rename to frontend/packages/calibration-tree/src/legacy-target-regression.test.ts diff --git a/frontend/packages/calibration-tree/src/package-boundary.test.ts b/frontend/packages/calibration-tree/src/package-boundary.test.ts new file mode 100644 index 00000000..7d6846ac --- /dev/null +++ b/frontend/packages/calibration-tree/src/package-boundary.test.ts @@ -0,0 +1,128 @@ +// The package must stay self-contained so it can leave the dashboard's +// repository: its modules import only each other, Node built-ins and the +// dependencies its manifest declares, never the dashboard's `@/` aliases, +// Next.js or `server-only`. Consumers reach it only through its exports. +import { Glob } from "bun"; +import { expect, test } from "bun:test"; +import { existsSync, readFileSync, statSync } from "node:fs"; +import { builtinModules } from "node:module"; +import { basename, dirname, join, relative, resolve } from "node:path"; + +const PACKAGE_ROOT = resolve(import.meta.dir, ".."); +const SOURCE_ROOT = join(PACKAGE_ROOT, "src"); +// Bun lists its own modules (`bun`, `bun:test`, ...) among the built-ins. +const NODE_BUILTINS = new Set(builtinModules.filter((name) => !name.startsWith("bun"))); + +interface PackageManifest { + name: string; + exports: Record; + dependencies?: Record; + devDependencies?: Record; +} + +function packageManifest(): PackageManifest { + return JSON.parse(readFileSync(join(PACKAGE_ROOT, "package.json"), "utf8")); +} + +function sourceFiles(): string[] { + return [...new Glob("**/*.ts").scanSync({ cwd: SOURCE_ROOT })].sort(); +} + +// Tests and their support code may also use the test runner, the package's +// own entry points and its development dependencies. +function isTestCode(relativePath: string): boolean { + return relativePath.endsWith(".test.ts") || relativePath.startsWith("test-support/"); +} + +function importSpecifiers(source: string): string[] { + const patterns = [ + /(?:^|\n)\s*(?:import|export)\s[^;]*?\sfrom\s+["']([^"']+)["']/g, + /(?:^|\n)\s*import\s+["']([^"']+)["']/g, + /\bimport\(\s*["']([^"']+)["']\s*\)/g, + /\bmock\.module\(\s*["']([^"']+)["']/g, + ]; + return patterns.flatMap((pattern) => + [...source.matchAll(pattern)].map((match) => match[1]) + ); +} + +function packageName(specifier: string): string { + const segments = specifier.split("/"); + return specifier.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0]; +} + +function resolvesInsidePackage(fromFile: string, specifier: string): boolean { + const base = resolve(dirname(fromFile), specifier); + if (relative(PACKAGE_ROOT, base).startsWith("..")) return false; + return [base, `${base}.ts`, join(base, "index.ts")].some( + (candidate) => existsSync(candidate) && statSync(candidate).isFile(), + ); +} + +function importViolation( + relativePath: string, + specifier: string, + manifest: PackageManifest, +): string | null { + const file = join(SOURCE_ROOT, relativePath); + const testCode = isTestCode(relativePath); + if (specifier.startsWith(".")) { + return resolvesInsidePackage(file, specifier) ? null : "does not resolve inside the package"; + } + if (specifier === "bun" || specifier.startsWith("bun:")) { + return testCode ? null : "is test-runner code outside a test"; + } + if (specifier.startsWith("node:")) { + return NODE_BUILTINS.has(specifier.slice("node:".length)) ? null : "is not a Node built-in"; + } + if (NODE_BUILTINS.has(specifier)) return "must use the node: prefix"; + if (specifier.startsWith("@/")) return "is a dashboard path alias"; + if (packageName(specifier) === manifest.name) { + if (!testCode) return "is a self-reference; package modules import each other relatively"; + return `./${specifier.slice(manifest.name.length + 1)}` in manifest.exports + ? null + : "is not a package export"; + } + const declared = { + ...manifest.dependencies, + ...(testCode ? manifest.devDependencies : {}), + }; + return packageName(specifier) in declared ? null : "is not a declared dependency"; +} + +test("package modules import only package files, Node built-ins and declared dependencies", () => { + const manifest = packageManifest(); + const violations = sourceFiles().flatMap((relativePath) => + importSpecifiers(readFileSync(join(SOURCE_ROOT, relativePath), "utf8")).flatMap((specifier) => { + const violation = importViolation(relativePath, specifier, manifest); + return violation ? [`${relativePath}: "${specifier}" ${violation}`] : []; + }) + ); + + expect(violations).toEqual([]); +}); + +test("every package export resolves to a package module", () => { + const manifest = packageManifest(); + const unresolved = Object.entries(manifest.exports).flatMap(([subpath, target]) => { + const file = resolve(PACKAGE_ROOT, target); + const valid = + subpath === `./${basename(target, ".ts")}` && + !relative(SOURCE_ROOT, file).startsWith("..") && + existsSync(file); + return valid ? [] : [`${subpath} -> ${target}`]; + }); + + expect(Object.keys(manifest.exports).length).toBeGreaterThan(0); + expect(unresolved).toEqual([]); +}); + +test("every package module is exported", () => { + const manifest = packageManifest(); + const exportedTargets = new Set(Object.values(manifest.exports)); + const unexported = sourceFiles() + .filter((relativePath) => !isTestCode(relativePath)) + .filter((relativePath) => !exportedTargets.has(`./src/${relativePath}`)); + + expect(unexported).toEqual([]); +}); diff --git a/frontend/lib/microcosm/program-label.test.ts b/frontend/packages/calibration-tree/src/program-label.test.ts similarity index 100% rename from frontend/lib/microcosm/program-label.test.ts rename to frontend/packages/calibration-tree/src/program-label.test.ts diff --git a/frontend/lib/microcosm/program-label.ts b/frontend/packages/calibration-tree/src/program-label.ts similarity index 100% rename from frontend/lib/microcosm/program-label.ts rename to frontend/packages/calibration-tree/src/program-label.ts diff --git a/frontend/lib/microcosm/reforms.test.ts b/frontend/packages/calibration-tree/src/reforms.test.ts similarity index 100% rename from frontend/lib/microcosm/reforms.test.ts rename to frontend/packages/calibration-tree/src/reforms.test.ts diff --git a/frontend/lib/microcosm/reforms.ts b/frontend/packages/calibration-tree/src/reforms.ts similarity index 100% rename from frontend/lib/microcosm/reforms.ts rename to frontend/packages/calibration-tree/src/reforms.ts diff --git a/frontend/lib/microcosm/source-label.test.ts b/frontend/packages/calibration-tree/src/source-label.test.ts similarity index 100% rename from frontend/lib/microcosm/source-label.test.ts rename to frontend/packages/calibration-tree/src/source-label.test.ts diff --git a/frontend/lib/microcosm/source-label.ts b/frontend/packages/calibration-tree/src/source-label.ts similarity index 61% rename from frontend/lib/microcosm/source-label.ts rename to frontend/packages/calibration-tree/src/source-label.ts index 9823a7d5..ebe8d75d 100644 --- a/frontend/lib/microcosm/source-label.ts +++ b/frontend/packages/calibration-tree/src/source-label.ts @@ -1,4 +1,4 @@ -import { sourceAuthorityLabel } from "@/lib/source-labels"; +import { sourceAuthorityLabel } from "./source-labels"; export function sourceLabel(source: string): string { return sourceAuthorityLabel(source); diff --git a/frontend/lib/source-labels.test.ts b/frontend/packages/calibration-tree/src/source-labels.test.ts similarity index 100% rename from frontend/lib/source-labels.test.ts rename to frontend/packages/calibration-tree/src/source-labels.test.ts diff --git a/frontend/lib/source-labels.ts b/frontend/packages/calibration-tree/src/source-labels.ts similarity index 100% rename from frontend/lib/source-labels.ts rename to frontend/packages/calibration-tree/src/source-labels.ts diff --git a/frontend/lib/microcosm/staging-artifact.test.ts b/frontend/packages/calibration-tree/src/staging-artifact.test.ts similarity index 100% rename from frontend/lib/microcosm/staging-artifact.test.ts rename to frontend/packages/calibration-tree/src/staging-artifact.test.ts diff --git a/frontend/lib/microcosm/staging-artifact.ts b/frontend/packages/calibration-tree/src/staging-artifact.ts similarity index 98% rename from frontend/lib/microcosm/staging-artifact.ts rename to frontend/packages/calibration-tree/src/staging-artifact.ts index 84163369..5663eb85 100644 --- a/frontend/lib/microcosm/staging-artifact.ts +++ b/frontend/packages/calibration-tree/src/staging-artifact.ts @@ -13,24 +13,24 @@ import { loadRelease, microcosmCountryGeography, microcosmRepo, -} from "@/lib/microcosm/latest-artifact"; -import type { CalibrationTreeSourceArtifact } from "@/lib/microcosm/calibration-tree-artifact"; +} from "./latest-artifact"; +import type { CalibrationTreeSourceArtifact } from "./calibration-tree-artifact"; import { countryRegistration, resolveRegisteredStagingRepository, type RepositoryEnvironment, type ResolvedRepository, -} from "@/lib/microcosm/countries"; +} from "./countries"; import { type ReformValidation, buildReformValidation, -} from "@/lib/microcosm/reforms"; +} from "./reforms"; import { buildTargetChangeDataset, type TargetChangeDataset, -} from "@/lib/microcosm/target-change"; -import { buildTargetChangeDatasetFromSummaryAndCalibration } from "@/lib/microcosm/calibration-build-comparison"; -import { loadCalibrationComparisonSource } from "@/lib/microcosm/calibration-comparison-blob"; +} from "./target-change"; +import { buildTargetChangeDatasetFromSummaryAndCalibration } from "./calibration-build-comparison"; +import { loadCalibrationComparisonSource } from "./calibration-comparison-blob"; import { IncompatibleStagingDataError, isStructuredStagingDocument, @@ -41,7 +41,7 @@ import { parseStagingRunIndex, validateStagingRunConsistency, type StagingSchemaVersion, -} from "@/lib/microcosm/staging-contract"; +} from "./staging-contract"; type JsonObject = Record; diff --git a/frontend/lib/microcosm/staging-contract.test.ts b/frontend/packages/calibration-tree/src/staging-contract.test.ts similarity index 100% rename from frontend/lib/microcosm/staging-contract.test.ts rename to frontend/packages/calibration-tree/src/staging-contract.test.ts diff --git a/frontend/lib/microcosm/staging-contract.ts b/frontend/packages/calibration-tree/src/staging-contract.ts similarity index 100% rename from frontend/lib/microcosm/staging-contract.ts rename to frontend/packages/calibration-tree/src/staging-contract.ts diff --git a/frontend/lib/microcosm/structured-dimension-reader.test.ts b/frontend/packages/calibration-tree/src/structured-dimension-reader.test.ts similarity index 100% rename from frontend/lib/microcosm/structured-dimension-reader.test.ts rename to frontend/packages/calibration-tree/src/structured-dimension-reader.test.ts diff --git a/frontend/lib/microcosm/structured-dimension-reader.ts b/frontend/packages/calibration-tree/src/structured-dimension-reader.ts similarity index 100% rename from frontend/lib/microcosm/structured-dimension-reader.ts rename to frontend/packages/calibration-tree/src/structured-dimension-reader.ts diff --git a/frontend/lib/microcosm/structured-target-reader.ts b/frontend/packages/calibration-tree/src/structured-target-reader.ts similarity index 100% rename from frontend/lib/microcosm/structured-target-reader.ts rename to frontend/packages/calibration-tree/src/structured-target-reader.ts diff --git a/frontend/lib/microcosm/target-change.test.ts b/frontend/packages/calibration-tree/src/target-change.test.ts similarity index 100% rename from frontend/lib/microcosm/target-change.test.ts rename to frontend/packages/calibration-tree/src/target-change.test.ts diff --git a/frontend/lib/microcosm/target-change.ts b/frontend/packages/calibration-tree/src/target-change.ts similarity index 100% rename from frontend/lib/microcosm/target-change.ts rename to frontend/packages/calibration-tree/src/target-change.ts diff --git a/frontend/lib/microcosm/target-loss-attribution-manifest.ts b/frontend/packages/calibration-tree/src/target-loss-attribution-manifest.ts similarity index 100% rename from frontend/lib/microcosm/target-loss-attribution-manifest.ts rename to frontend/packages/calibration-tree/src/target-loss-attribution-manifest.ts diff --git a/frontend/lib/microcosm/target-loss-attribution.test.ts b/frontend/packages/calibration-tree/src/target-loss-attribution.test.ts similarity index 100% rename from frontend/lib/microcosm/target-loss-attribution.test.ts rename to frontend/packages/calibration-tree/src/target-loss-attribution.test.ts diff --git a/frontend/lib/microcosm/target-loss-attribution.ts b/frontend/packages/calibration-tree/src/target-loss-attribution.ts similarity index 100% rename from frontend/lib/microcosm/target-loss-attribution.ts rename to frontend/packages/calibration-tree/src/target-loss-attribution.ts diff --git a/frontend/lib/microcosm/target-representation.test.ts b/frontend/packages/calibration-tree/src/target-representation.test.ts similarity index 100% rename from frontend/lib/microcosm/target-representation.test.ts rename to frontend/packages/calibration-tree/src/target-representation.test.ts diff --git a/frontend/lib/microcosm/target-representation.ts b/frontend/packages/calibration-tree/src/target-representation.ts similarity index 100% rename from frontend/lib/microcosm/target-representation.ts rename to frontend/packages/calibration-tree/src/target-representation.ts diff --git a/frontend/lib/microcosm/target-surface-matcher.test.ts b/frontend/packages/calibration-tree/src/target-surface-matcher.test.ts similarity index 100% rename from frontend/lib/microcosm/target-surface-matcher.test.ts rename to frontend/packages/calibration-tree/src/target-surface-matcher.test.ts diff --git a/frontend/lib/microcosm/target-surface-matcher.ts b/frontend/packages/calibration-tree/src/target-surface-matcher.ts similarity index 100% rename from frontend/lib/microcosm/target-surface-matcher.ts rename to frontend/packages/calibration-tree/src/target-surface-matcher.ts diff --git a/frontend/lib/microcosm/test-support/calibration-tree-golden-cases.ts b/frontend/packages/calibration-tree/src/test-support/calibration-tree-golden-cases.ts similarity index 96% rename from frontend/lib/microcosm/test-support/calibration-tree-golden-cases.ts rename to frontend/packages/calibration-tree/src/test-support/calibration-tree-golden-cases.ts index 0778eea1..0a7b0b93 100644 --- a/frontend/lib/microcosm/test-support/calibration-tree-golden-cases.ts +++ b/frontend/packages/calibration-tree/src/test-support/calibration-tree-golden-cases.ts @@ -7,19 +7,23 @@ // written bytes. // // fixtures/calibration-tree-golden.json is that record as produced by the -// builder before it moved into its own package; the golden tests require the -// builder to reproduce it exactly. Regenerate it only for an intended output -// change, from frontend/: +// builder before it moved into this package; the golden tests require the +// builder to reproduce it exactly. The cases import the builder through the +// package's public entry points. Regenerate the record only for an intended +// output change, from the package directory: // -// bun run lib/microcosm/test-support/calibration-tree-golden-cases.ts +// bun run src/test-support/calibration-tree-golden-cases.ts import { createHash } from "node:crypto"; import { readFileSync } from "node:fs"; import { join } from "node:path"; import { gunzipSync } from "node:zlib"; -import { loadCalibrationComparisonSource, loadCalibrationComparisonTargetDetail } from "../calibration-comparison-blob"; -import { buildCalibrationComparisonBundles } from "../calibration-comparison-bundle"; -import { createExplorerState, type ExplorerState } from "../calibration-explorer"; +import { + loadCalibrationComparisonSource, + loadCalibrationComparisonTargetDetail, +} from "@policyengine/calibration-tree/calibration-comparison-blob"; +import { buildCalibrationComparisonBundles } from "@policyengine/calibration-tree/calibration-comparison-bundle"; +import { createExplorerState, type ExplorerState } from "@policyengine/calibration-tree/calibration-explorer"; import { CALIBRATION_TREE_SCHEMA_VERSION, calibrationTreePartPath, @@ -32,36 +36,39 @@ import { type CalibrationTreeTargetDetailsArtifact, type CalibrationTreeTargetSummaryArtifact, type CalibrationTreeTierArtifact, -} from "../calibration-tree-artifact"; -import type { getCalibrationTreeBlob } from "../calibration-tree-blob"; +} from "@policyengine/calibration-tree/calibration-tree-artifact"; +import type { getCalibrationTreeBlob } from "@policyengine/calibration-tree/calibration-tree-blob"; import { buildCalibrationTreeBundle, type CalibrationTreeBundle, type CalibrationTreeBundleFile, -} from "../calibration-tree-bundle"; +} from "@policyengine/calibration-tree/calibration-tree-bundle"; import { emptyCalibrationTreeManifest, serializeCalibrationTreeManifest, withCalibrationTreeManifestEntry, type CalibrationTreeManifestEntry, -} from "../calibration-tree-manifest"; +} from "@policyengine/calibration-tree/calibration-tree-manifest"; import { COUNTRY_REGISTRY, type CountryRegistration, type MicrocosmCountry, -} from "../countries"; +} from "@policyengine/calibration-tree/countries"; import { UK_LOCAL_CALIBRATION_DIR_ENV, buildCalibration, microcosmRepo, type Calibration, -} from "../latest-artifact"; -import { resolveStagingCalibrationSource, stagingRepository } from "../staging-artifact"; +} from "@policyengine/calibration-tree/latest-artifact"; +import { + resolveStagingCalibrationSource, + stagingRepository, +} from "@policyengine/calibration-tree/staging-artifact"; import { TARGET_LOSS_BASIS_HASH_ALGORITHM, TARGET_LOSS_FORMULA, targetLossBasisHash, -} from "../target-loss-attribution"; +} from "@policyengine/calibration-tree/target-loss-attribution"; type JsonObject = Record; diff --git a/frontend/lib/microcosm/treemap.test.ts b/frontend/packages/calibration-tree/src/treemap.test.ts similarity index 100% rename from frontend/lib/microcosm/treemap.test.ts rename to frontend/packages/calibration-tree/src/treemap.test.ts diff --git a/frontend/packages/calibration-tree/tsconfig.json b/frontend/packages/calibration-tree/tsconfig.json new file mode 100644 index 00000000..af017ab0 --- /dev/null +++ b/frontend/packages/calibration-tree/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ESNext", "DOM"], + "module": "ESNext", + "moduleResolution": "bundler", + "types": ["bun"], + "strict": true, + "noEmit": true, + "skipLibCheck": true, + "isolatedModules": true, + "esModuleInterop": true, + "resolveJsonModule": true + }, + "include": ["src/**/*.ts", "types/**/*.d.ts"] +} diff --git a/frontend/packages/calibration-tree/types/next-fetch-cache.d.ts b/frontend/packages/calibration-tree/types/next-fetch-cache.d.ts new file mode 100644 index 00000000..77060ce2 --- /dev/null +++ b/frontend/packages/calibration-tree/types/next-fetch-cache.d.ts @@ -0,0 +1,8 @@ +// The dashboard calls the release and staging readers inside Next.js, whose +// fetch reads a `next.revalidate` hint to cache Hugging Face responses; Bun +// ignores the field. Next.js declares it on RequestInit for the dashboard. +// This declaration mirrors Next's NextFetchRequestConfig and is seen only by +// the package's own typecheck, so the package typechecks without Next.js. +interface RequestInit { + next?: { revalidate?: number | false; tags?: string[] } | undefined; +} diff --git a/frontend/scripts/calibration-deployment-smoke.test.ts b/frontend/scripts/calibration-deployment-smoke.test.ts index 35b85aea..86723d4c 100644 --- a/frontend/scripts/calibration-deployment-smoke.test.ts +++ b/frontend/scripts/calibration-deployment-smoke.test.ts @@ -1,7 +1,7 @@ import { expect, spyOn, test } from "bun:test"; import { verifyCalibrationDeployment } from "./calibration-deployment-smoke"; -import { hasCapability, selectableCountries } from "../lib/microcosm/countries"; +import { hasCapability, selectableCountries } from "@policyengine/calibration-tree/countries"; type Fetcher = (url: string | URL, init?: RequestInit) => Promise; const noSleep = async (_milliseconds: number) => {}; diff --git a/frontend/scripts/calibration-deployment-smoke.ts b/frontend/scripts/calibration-deployment-smoke.ts index b303de39..6b8a456f 100644 --- a/frontend/scripts/calibration-deployment-smoke.ts +++ b/frontend/scripts/calibration-deployment-smoke.ts @@ -4,7 +4,7 @@ import { hasCapability, selectableCountries, type MicrocosmCountry, -} from "../lib/microcosm/countries"; +} from "@policyengine/calibration-tree/countries"; type JsonObject = Record; type Fetcher = (url: string | URL, init?: RequestInit) => Promise; diff --git a/frontend/scripts/publish-calibration-tree-golden.test.ts b/frontend/scripts/publish-calibration-tree-golden.test.ts index b6250c2e..659136bd 100644 --- a/frontend/scripts/publish-calibration-tree-golden.test.ts +++ b/frontend/scripts/publish-calibration-tree-golden.test.ts @@ -1,18 +1,18 @@ import { afterEach, expect, test } from "bun:test"; -import recordedGoldens from "../lib/microcosm/fixtures/calibration-tree-golden.json"; +import recordedGoldens from "../packages/calibration-tree/src/fixtures/calibration-tree-golden.json"; import { emptyCalibrationTreeManifest, withCalibrationTreeManifestEntry, type CalibrationTreeManifestEntry, -} from "../lib/microcosm/calibration-tree-manifest"; -import { COUNTRY_REGISTRY } from "../lib/microcosm/countries"; +} from "@policyengine/calibration-tree/calibration-tree-manifest"; +import { COUNTRY_REGISTRY } from "@policyengine/calibration-tree/countries"; import { goldenReleaseInputs, withRegisteredRepositories, type CalibrationTreeGoldenRecord, type GoldenReleaseInput, -} from "../lib/microcosm/test-support/calibration-tree-golden-cases"; +} from "../packages/calibration-tree/src/test-support/calibration-tree-golden-cases"; import { reconcileReleaseBuilds, type CalibrationManifestStore, diff --git a/frontend/scripts/publish-calibration-tree.test.ts b/frontend/scripts/publish-calibration-tree.test.ts index edbaa457..c4994fc1 100644 --- a/frontend/scripts/publish-calibration-tree.test.ts +++ b/frontend/scripts/publish-calibration-tree.test.ts @@ -15,7 +15,7 @@ import { withCalibrationTreeManifestEntry, type CalibrationTreeManifest, type CalibrationTreeManifestEntry, -} from "../lib/microcosm/calibration-tree-manifest"; +} from "@policyengine/calibration-tree/calibration-tree-manifest"; const originalFetch = globalThis.fetch; diff --git a/frontend/scripts/publish-calibration-tree.ts b/frontend/scripts/publish-calibration-tree.ts index 7530e634..903fa1b9 100644 --- a/frontend/scripts/publish-calibration-tree.ts +++ b/frontend/scripts/publish-calibration-tree.ts @@ -1,11 +1,11 @@ import { CALIBRATION_TREE_SCHEMA_VERSION, type CalibrationTreeSourceArtifact, -} from "../lib/microcosm/calibration-tree-artifact"; +} from "@policyengine/calibration-tree/calibration-tree-artifact"; import { buildCalibrationTreeBundle, type CalibrationTreeBundleFile, -} from "../lib/microcosm/calibration-tree-bundle"; +} from "@policyengine/calibration-tree/calibration-tree-bundle"; import { auditCalibrationTreeBuild, listCalibrationTreeBlobs, @@ -13,12 +13,12 @@ import { updateCalibrationTreeManifest, uploadCalibrationTreeBundle, type CalibrationTreeBuildAudit, -} from "../lib/microcosm/calibration-tree-blob"; +} from "@policyengine/calibration-tree/calibration-tree-blob"; import type { ListBlobResultBlob } from "@vercel/blob"; import type { CalibrationTreeManifest, CalibrationTreeManifestEntry, -} from "../lib/microcosm/calibration-tree-manifest"; +} from "@policyengine/calibration-tree/calibration-tree-manifest"; import { createHash } from "node:crypto"; import { CalibrationReleaseNotFoundError, @@ -26,24 +26,24 @@ import { resolveHfReleaseDirectorySha, resolveHfRepositoryCommitPublishedAt, resolveHfRevisionSha, -} from "../lib/microcosm/calibration-release-locator"; +} from "@policyengine/calibration-tree/calibration-release-locator"; import { isCountry, selectableCountries, type MicrocosmCountry, -} from "../lib/microcosm/countries"; +} from "@policyengine/calibration-tree/countries"; import { buildCalibration, loadReleases, microcosmRepo, microcosmRevision, -} from "../lib/microcosm/latest-artifact"; +} from "@policyengine/calibration-tree/latest-artifact"; import { loadStagingRuns, resolveStagingCalibrationSource, resolveStagingRevisionSha, stagingRepository, -} from "../lib/microcosm/staging-artifact"; +} from "@policyengine/calibration-tree/staging-artifact"; type JsonObject = Record; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 3f8f96b4..de2774dc 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -19,5 +19,5 @@ } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts"], - "exclude": ["node_modules"] + "exclude": ["node_modules", "packages"] } From e78b67216bf0a62ebb53a394183b411f78c38909 Mon Sep 17 00:00:00 2001 From: Anthony Volk <14987227+anth-volk@users.noreply.github.com> Date: Fri, 16 Oct 2026 00:35:39 +0400 Subject: [PATCH 3/3] Run the calibration tree publisher from the package 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 --- docs/calibration-tree-artifacts.md | 16 ++++++++-------- frontend/package.json | 2 +- frontend/packages/calibration-tree/package.json | 2 ++ .../src}/publish-calibration-tree-golden.test.ts | 6 +++--- .../src}/publish-calibration-tree.test.ts | 2 +- .../src}/publish-calibration-tree.ts | 16 ++++++++-------- 6 files changed, 23 insertions(+), 21 deletions(-) rename frontend/{scripts => packages/calibration-tree/src}/publish-calibration-tree-golden.test.ts (96%) rename frontend/{scripts => packages/calibration-tree/src}/publish-calibration-tree.test.ts (99%) rename frontend/{scripts => packages/calibration-tree/src}/publish-calibration-tree.ts (98%) diff --git a/docs/calibration-tree-artifacts.md b/docs/calibration-tree-artifacts.md index 216571a1..240245dc 100644 --- a/docs/calibration-tree-artifacts.md +++ b/docs/calibration-tree-artifacts.md @@ -6,14 +6,14 @@ the hierarchy. Schemas 1 through 5 are unsupported. ## Code -The builder, the schema-6 types, parsers and serializers, the Blob I/O, and -the release and staging readers the builder reads from live in the -`@policyengine/calibration-tree` workspace package at -`frontend/packages/calibration-tree/`. The dashboard and the publisher import -it through its subpath exports, such as -`@policyengine/calibration-tree/calibration-tree-bundle`. The package imports -only its own modules, Node built-ins and `@vercel/blob`, and its boundary test -enforces that. +The builder, the schema-6 types, parsers and serializers, the Blob I/O, the +release and staging readers the builder reads from, and the publisher +(`src/publish-calibration-tree.ts`, run by `bun run publish:calibration-tree`) +live in the `@policyengine/calibration-tree` workspace package at +`frontend/packages/calibration-tree/`. The dashboard imports it through its +subpath exports, such as `@policyengine/calibration-tree/calibration-tree-bundle`. +The package imports only its own modules, Node built-ins and `@vercel/blob`, +and its boundary test enforces that. `src/fixtures/calibration-tree-golden.json` records the exact bytes the builder writes for representative US and UK release and staging inputs, the diff --git a/frontend/package.json b/frontend/package.json index 0de18140..faabd39d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,7 +11,7 @@ "build": "next build", "start": "next start", "lint": "tsc --noEmit && tsc --noEmit -p packages/calibration-tree", - "publish:calibration-tree": "bun run scripts/publish-calibration-tree.ts", + "publish:calibration-tree": "bun run packages/calibration-tree/src/publish-calibration-tree.ts", "test:dev-port": "node --test scripts/available-port.node-test.mjs", "replay:build-run": "bun run scripts/replay-build-run.ts" }, diff --git a/frontend/packages/calibration-tree/package.json b/frontend/packages/calibration-tree/package.json index ab5fc8fe..76a6b871 100644 --- a/frontend/packages/calibration-tree/package.json +++ b/frontend/packages/calibration-tree/package.json @@ -22,6 +22,7 @@ "./latest-artifact": "./src/latest-artifact.ts", "./legacy-target-reader": "./src/legacy-target-reader.ts", "./program-label": "./src/program-label.ts", + "./publish-calibration-tree": "./src/publish-calibration-tree.ts", "./reforms": "./src/reforms.ts", "./source-label": "./src/source-label.ts", "./source-labels": "./src/source-labels.ts", @@ -36,6 +37,7 @@ "./target-surface-matcher": "./src/target-surface-matcher.ts" }, "scripts": { + "publish:calibration-tree": "bun run src/publish-calibration-tree.ts", "test": "bun test", "typecheck": "tsc --noEmit" }, diff --git a/frontend/scripts/publish-calibration-tree-golden.test.ts b/frontend/packages/calibration-tree/src/publish-calibration-tree-golden.test.ts similarity index 96% rename from frontend/scripts/publish-calibration-tree-golden.test.ts rename to frontend/packages/calibration-tree/src/publish-calibration-tree-golden.test.ts index 659136bd..8f1cf64d 100644 --- a/frontend/scripts/publish-calibration-tree-golden.test.ts +++ b/frontend/packages/calibration-tree/src/publish-calibration-tree-golden.test.ts @@ -1,6 +1,6 @@ import { afterEach, expect, test } from "bun:test"; -import recordedGoldens from "../packages/calibration-tree/src/fixtures/calibration-tree-golden.json"; +import recordedGoldens from "./fixtures/calibration-tree-golden.json"; import { emptyCalibrationTreeManifest, withCalibrationTreeManifestEntry, @@ -12,11 +12,11 @@ import { withRegisteredRepositories, type CalibrationTreeGoldenRecord, type GoldenReleaseInput, -} from "../packages/calibration-tree/src/test-support/calibration-tree-golden-cases"; +} from "./test-support/calibration-tree-golden-cases"; import { reconcileReleaseBuilds, type CalibrationManifestStore, -} from "./publish-calibration-tree"; +} from "@policyengine/calibration-tree/publish-calibration-tree"; // A repair rebuilds a published release whose index went missing and refuses // any rebuilt index whose digest differs from the manifest entry. Seeding the diff --git a/frontend/scripts/publish-calibration-tree.test.ts b/frontend/packages/calibration-tree/src/publish-calibration-tree.test.ts similarity index 99% rename from frontend/scripts/publish-calibration-tree.test.ts rename to frontend/packages/calibration-tree/src/publish-calibration-tree.test.ts index c4994fc1..ce10d492 100644 --- a/frontend/scripts/publish-calibration-tree.test.ts +++ b/frontend/packages/calibration-tree/src/publish-calibration-tree.test.ts @@ -15,7 +15,7 @@ import { withCalibrationTreeManifestEntry, type CalibrationTreeManifest, type CalibrationTreeManifestEntry, -} from "@policyengine/calibration-tree/calibration-tree-manifest"; +} from "./calibration-tree-manifest"; const originalFetch = globalThis.fetch; diff --git a/frontend/scripts/publish-calibration-tree.ts b/frontend/packages/calibration-tree/src/publish-calibration-tree.ts similarity index 98% rename from frontend/scripts/publish-calibration-tree.ts rename to frontend/packages/calibration-tree/src/publish-calibration-tree.ts index 903fa1b9..30e2d71b 100644 --- a/frontend/scripts/publish-calibration-tree.ts +++ b/frontend/packages/calibration-tree/src/publish-calibration-tree.ts @@ -1,11 +1,11 @@ import { CALIBRATION_TREE_SCHEMA_VERSION, type CalibrationTreeSourceArtifact, -} from "@policyengine/calibration-tree/calibration-tree-artifact"; +} from "./calibration-tree-artifact"; import { buildCalibrationTreeBundle, type CalibrationTreeBundleFile, -} from "@policyengine/calibration-tree/calibration-tree-bundle"; +} from "./calibration-tree-bundle"; import { auditCalibrationTreeBuild, listCalibrationTreeBlobs, @@ -13,12 +13,12 @@ import { updateCalibrationTreeManifest, uploadCalibrationTreeBundle, type CalibrationTreeBuildAudit, -} from "@policyengine/calibration-tree/calibration-tree-blob"; +} from "./calibration-tree-blob"; import type { ListBlobResultBlob } from "@vercel/blob"; import type { CalibrationTreeManifest, CalibrationTreeManifestEntry, -} from "@policyengine/calibration-tree/calibration-tree-manifest"; +} from "./calibration-tree-manifest"; import { createHash } from "node:crypto"; import { CalibrationReleaseNotFoundError, @@ -26,24 +26,24 @@ import { resolveHfReleaseDirectorySha, resolveHfRepositoryCommitPublishedAt, resolveHfRevisionSha, -} from "@policyengine/calibration-tree/calibration-release-locator"; +} from "./calibration-release-locator"; import { isCountry, selectableCountries, type MicrocosmCountry, -} from "@policyengine/calibration-tree/countries"; +} from "./countries"; import { buildCalibration, loadReleases, microcosmRepo, microcosmRevision, -} from "@policyengine/calibration-tree/latest-artifact"; +} from "./latest-artifact"; import { loadStagingRuns, resolveStagingCalibrationSource, resolveStagingRevisionSha, stagingRepository, -} from "@policyengine/calibration-tree/staging-artifact"; +} from "./staging-artifact"; type JsonObject = Record;