Repository navigation
Conversation
A release manifest in Microcosm's side-by-side layout names its release and build as strings, and lives at releases/<release>/<build>/. Parse it: the string build becomes DataBuildInfo with only build_id set, so release_build_id names the build and two builds of one release differ, and dumps write it back as the string. Such a manifest may leave its own files' revision to certification. Manifests whose build is an object, with or without cut_tag, are read as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A build published side by side has no Hugging Face tag, so the certificate pins it by its folder. fetch_release_manifest now requires a manifest that names its release and build to be read from releases/<release>/<build>/release_manifest.json, and pins the build's own files at releases/<release>/<build>/<file> and at the manifest URI's revision: the publisher writes a build's files and manifest in one commit and never changes them, while an artifact's own revision field cannot name that commit. Artifacts from other repositories keep their own pins; a file of the build outside its folder is refused. The bundle's datasets, region templates, certified dataset URI and the reachability checks all use the pinned paths, and the build names the certificate. Manifests published before the layout are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The import-time compatibility check reads the remote release manifest from the bundle's pinned folder. For a build in its own release folder, get_data_release_manifest now requires the manifest to name that folder's release and build, and pins the build's own files inside the folder at the bundle's revision, as certification does. A mismatch raises ReleaseBuildFolderError, which is not an unavailable manifest, so the bundled certificate never covers it. Manifests published before the layout keep the existing revision rewrite. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main now holds #615's final code (merged as 2591af3, head 521ab47), which this branch was built on at an earlier head (0f0d5cd). Merged, not rebased, so #683's commits keep their hashes. No conflicts: certification.py and data-certification.md merged automatically, and the diff against main is still only #683's own five files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A build published in its own release folder is certified only while the repository's releases.json at main lists it and has not withdrawn it. The certification helper now serves releases.json by URL, so these tests can set its contents; the earlier tests keep their behaviour. Red at this commit: 5 of the 6 new tests fail (the record is never read, so a withdrawn, unlisted or missing record raises nothing); the tagged release test passes, since tagged releases have no record to read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fetch_release_manifest now reads the repository's releases.json at main for a build published in its own release folder, and refuses the build when the record is missing or unreadable, has another schema version, does not list the build, or marks it withdrawn. The record is read at main rather than at the URI's revision, because a build is withdrawn after the commit that published it. The primary and the regional manifest both go through this check. Tagged releases are unchanged and read no record. Only certification reads the record. Whether an installed bundle should refuse a build withdrawn after it was certified is not decided here. Green at this commit: 33 passed in tests/test_release_build_folders.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The release writer publishes only bare file names, directly inside releases/<release>/<build>/. A path that starts with the folder and then leaves it through "..", a nested path, a dot segment or a backslash must be refused, at certification and at import. HTTP clients remove dot segments from URLs, so "<folder>/../<other build>/<file>" would otherwise fetch another build's file under this build's certificate. Red at this commit: all 7 new tests fail (nothing is raised). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
read_build_folder checked a build's own files with a string prefix, so "<folder>/../<other build>/<file>" passed, and HTTP clients then fetched the other build's file. An own file's path must now be a bare file name, alone or after "<folder>/": one segment, not "." or "..", with no slash or backslash, which is what the release writer publishes (microcosm_emitter_interface.identifiers.bare_file_name). Certification and the import-time check share this rule. Green at this commit: 40 passed in tests/test_release_build_folders.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The certification guide, the certification module's docstring and the changelog fragment now say that a build in its own folder is certified only while releases.json at main lists it and has not withdrawn it, that its own files must be bare file names directly inside the folder, and that import does not re-read releases.json. Documentation only, so no test step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… accepts The release writer's parser (microcosm-emitter#24, ReleasesRecord.parse with check_record) refuses a releases.json with missing or unknown keys, a wrong schema_version, malformed release or build entries, unsafe release or build names, a default naming an unlisted release, a current naming an unlisted or withdrawn build, or more than 4 MiB. Certification should refuse the same records, even when the build being certified is listed and not withdrawn. Adds 24 refused records, one oversized record, and 3 records the writer accepts (no default, a release without builds, a withdrawn build that is not current), which certification must keep accepting. The helper can now serve releases.json as raw bytes. The record fixture now makes the national release's current build its newest listed, non-withdrawn build. Before, the withdrawn-build and unlisted-build tests used records whose current named the withdrawn or unlisted build, which the writer refuses for that reason alone. Red: 22 failed, 46 passed (68 in the file). All 22 fail with "DID NOT RAISE CertificationError". Three refused records (missing schema_version, schema_version 2, missing releases) were already refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
check_build_listed now parses the whole record with the release writer's rules before it looks up the build, so .py no longer certifies from a record that the writer and its other readers refuse. The rules repeat those of microcosm-emitter's ReleasesRecord.parse and check_record (microcosm_emitter_interface.releases, microcosm-emitter#24 at 35481d4d): exact keys at every level, schema_version 1, safe release and build names, valid roles, a non-empty published_at and a boolean withdrawn on every build, a default that is null or a listed release, a current that is null or a listed build that is not withdrawn, and at most 4 MiB. .py does not depend on that package; a comment names the writer as the source of the rules. Also corrects the certification guide: a build withdrawn after it was certified keeps loading in every installed .py release whose bundle pins it, and certifying again changes only the .py releases published afterwards. That is a documentation correction, so it needs no red step. Green: 68 passed (the test file is unchanged since the previous commit). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
.py repeats the release writer's parser for releases.json instead of importing it. These cases pin the rules that are easiest to lose in a later edit: schema_version must be the integer 1 (not true or 1.0), and a record that is not JSON, not text, or nested too deeply to decode is refused like any other record the writer refuses. They pass already; the fake response now decodes text the way requests does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Certification raises it when releases.json is a record the release writer refuses, does not list the build, or marks it withdrawn, as well as for a manifest or file outside its build folder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #682
Base:
main. #615 merged intomainon 2026-10-20 (2591af3e), somainwas merged into this branch (36c3d8d3, a merge, not a rebase) and the base changed fromuk-553-area-routing-toolingtomain. Do not merge before both of these:releases.json.Until a manifest in the new layout exists, nothing here changes behaviour: every manifest published today has
buildas an object and is read exactly as before.Why
Microcosm is moving to a layout where:
releases/<release>/<build>/, and never changed;releases.json(microcosm-emitter#24,microcosm_emitter_interface.releases) records every published build of each release, which one is current, and which are withdrawn;#615 names a build by
build.cut_tag, which new builds don't have, and it assumesbuildis an object. A new manifest names itsreleaseandbuildas strings, so without this PR it fails to parse.What changes
Reading the manifest (
provenance/manifest.py)DataReleaseManifest.releaseis new. A manifest with stringreleaseandbuildis read withbuildasDataBuildInfo(build_id=<build>). Both must match the publisher's identifier rule,^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$. Dumps writebuildback as the string, so merges round-trip it.release_build_idis unchanged: such a build has nocut_tag, so it returns the build name, and two builds of one release never match.buildwithoutrelease, orreleasewith an objectbuild, is refused.build_folderreturnsreleases/<release>/<build>, orNonefor older manifests.DataReleaseArtifact.revisionmay be absent only in a build-folder manifest. Older manifests still need it on every artifact.Certifying (
provenance/certification.py, through the sharedread_build_folder)releases/<release>/<build>/release_manifest.json; a manifest read from any other path is refused.releases.jsonatmainmust list the build and must not mark it withdrawn, or certification refuses it. A local-area release passed with--regional-manifest-uriis checked the same way. The record is read atmain, not at the URI's revision, because a build is withdrawn after the commit that published it. Which build iscurrentdoesn't matter: a certificate pins one build.ReleasesRecord.parsewithcheck_record, microcosm-emitter#24, matched at35481d4d). .py repeats those rules without depending on the emitter interface package: exact keys at every level,schema_version1, safe release and build names, valid roles, a non-emptypublished_atand a booleanwithdrawnon every build, adefaultthat is null or a listed release, acurrentthat is null or a listed build that isn't withdrawn, and at most 4 MiB. So .py never certifies from a record the writer and its other readers refuse.releases/<release>/<build>/<file>and at the URI's revision.pathmust be a bare file name, alone or after the folder, which is what the release writer publishes. A nested path, a.or..segment, or a backslash is refused. HTTP clients remove dot segments, so<folder>/../<other build>/<file>would otherwise fetch another build's file.revisionfield can't name that commit.mainresolves to the same files, because the folder never changes.datasets, the defaultnationalregion template, a local-area release routed with--regional-manifest-uri, its certified dataset URI, and the reachability checks.data_releases.uk.version, the certificate'sdata_build_idand the changelog fragment name. These record the build name, not<release>/<build>: the bundle already pins the release throughrelease_manifest_path, and a slash in the name would put the fragment in a subfolder.Import-time check
get_data_release_manifestrequires the remote manifest to name the release and build of the bundle's pinnedrelease_manifest_path. It then pins the build's files the same way certification does, with the same file-name rule.ReleaseBuildFolderError, aValueError. It is deliberately not aDataReleaseManifestUnavailableError, so the offline fallback to the bundled certificate never covers it.releases.json. A build withdrawn after it was certified keeps loading in every installed .py release whose bundle pins it; certifying again changes only the .py releases published afterwards. Whether an installed bundle should refuse it is not decided here.Docs and changelog:
docs/engineering/skills/data-certification.mdhas a new section, "Builds in their own release folders". The fragment ischangelog.d/682.added.md.Assumed until H2c defines the manifest
releaseandbuildare strings equal to the folder's names. emitter#24'scheck_repositoryenforces exactly this. Readers tell the layouts apart by whetherbuildis a string.artifactsentry withkind,path,repo_idandsha256.pathis a bare file name, as the emitter writes it, or that name afterreleases/<release>/<build>/.revisionmay be dropped. Arole(for exampledatasetorcalibration_diagnostics) may be present; this PR doesn't use it.default_datasets,dataset_role,is_defaultandcompatible_model_packages/compatible_core_packageskeep their current meaning. Manifest-declaredmetadata.region_datasetstemplates are copied as they are, not repinned; Microcosm writes none today.Open for H2c: today's
buildobject also carriesbuilt_at,built_with_model_package(version, git SHA, data build fingerprint) andbuilt_with_core_package. Withbuilda string, these have no place yet, so a build-folder manifest records none of them. As a result:built_with_model_packageandmatching_data_build_fingerprintbases can't apply;compatible_model_packages/compatible_core_packagesor a qualified pair;H2c needs to say where these details go. This PR doesn't invent a place for them.
Tests
All new tests are in
tests/test_release_build_folders.pyand use fixtures only, with no network. Each was written first and run before the change that makes it pass.9a5286c2Read a build published in its own release folder158b5aeeCertify a build from its own release folder0320e5ccRefuse at import a manifest that names another build than its folder36c3d8d3Mergemain(with #615's final code)edc5356bred /ea01e115green: certify a build only whilereleases.jsonlists it and has not withdrawn itDID NOT RAISE CertificationError, one becausereleases.jsonwas never requested), 1 passed (tagged releases read no record); 33 in the fileef297cb6red /62419805green: accept only bare file names directly inside a build's folderDID NOT RAISE); 40 in the file6b72ec01Document thereleases.jsonand file-name checks58d43c6fred /90301909green: certify only from areleases.jsonrecord the release writer acceptsDID NOT RAISE CertificationError), 6 passed (3 records already refused: missingschema_version,schema_version2, missingreleases; 3 records the writer accepts and certification must keep accepting); 68 in the file. The record fixture now keepscurrenton a listed, non-withdrawn build, so the existing withdrawn and unlisted tests use records the writer accepts.90301909also corrects the guide's sentence on withdrawn builds at importFollowing the agreed local-testing rule, only the new test file was run locally. #615's test files (
test_certify_data_release.py,test_release_manifests.py,test_qualified_pair_certification.py) are left to CI. They use manifests whosebuildis an object, which never readreleases.json.ruff format --checkandruff checkpass on the changed files.mypyon the two changed modules reports only errors that predate this PR: missingrequestsstubs, and threeno-any-returnerrors in functions this PR doesn't touch.The
releases.jsonrecords in the new tests were also run, outside this repository, through the writer's ownReleasesRecord.parseat microcosm-emitter#24's35481d4d. It refuses every record the tests expect certification to refuse and accepts the others, and .py's check gives the same result for all 30. The writer also accepts every record the fixture builds for the existing withdrawn and unlisted tests.Review round 2026-10-22
7a47f9a(its head after a rebase;35481d4dabove no longer exists). Thereleases.jsonrules in_read_releases_recordmatch the writer'sReleasesRecord.parseandcheck_recordexactly: the exact keys at every level, the integerschema_version1, the release and build name pattern, the role pattern,published_atandwithdrawntypes,currentanddefault, the 4 MiB limit, and unreadable or too deeply nested JSON. The writer's rules have not changed since 20 October. The file-name rule matchesbare_file_name, and the manifest identity check matchescheck_repository.89c2cffadds tests for the rules most likely to drift:schema_versiontrueor1.0, and a record that is truncated JSON, not text, or nested too deeply. They pass without a code change; the fake response now decodes text the wayrequestsdoes. 73 tests in the file pass.72347a4corrects theReleaseBuildFolderErrordocstring, which now also coversreleases.jsonrefusals.ruff format --checkandruff checkpass;mypyon the two changed modules reports only the twono-any-returnerrors that predate this PR.🤖 Generated with Claude Code