SRE-805: Validate mise lockfile in CI#9018
Conversation
Add a 'Mise lockfile' job to the Lint workflow that catches broken, stale, or wrong-architecture .config/mise/mise.lock entries: - .github/scripts/validate-mise-lock.py runs offline checks: config.toml pins match locked versions, required platforms are covered, no platform entry points at an asset for a different OS/arch, and no two platforms share a non-universal asset. - 'mise install --locked --dry-run' natively fails on tools missing from the lockfile or lacking pre-resolved URLs for the platform. - A real 'mise install --locked' of URL-locked tools downloads and sha256-verifies the locked artifacts on linux-x64 and linux-arm64. The job skips gracefully while mise.lock is absent on main, and the known wrong-arch yq macos-arm64 entry produced by 'mise lock' upstream is allowlisted as a warning with a tracking comment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EcBiQCfeizMQJubtqXkiMs
|
Upstream issue draft for jdx/mise — this session can only write to hashintel repos, so filing it needs someone with access. Ready to copy-paste at https://github.com/jdx/mise/issues/new: Title: mise version: 2026.7.0 Repro: In a project with Observed: the [tools.yq."platforms.macos-arm64"]
checksum = "sha256:616b0a0f6a5b79d746f05a169c2b9bb40dee00c605ef165b9a1c1681bba738ac"
url = "https://github.com/mikefarah/yq/releases/download/v4.53.2/yq_darwin_amd64"
url_api = "https://api.github.com/repos/mikefarah/yq/releases/assets/398368453"
provenance = "cosign"
[tools.yq."platforms.macos-x64"]
checksum = "sha256:616b0a0f6a5b79d746f05a169c2b9bb40dee00c605ef165b9a1c1681bba738ac"
url = "https://github.com/mikefarah/yq/releases/download/v4.53.2/yq_darwin_amd64"
url_api = "https://api.github.com/repos/mikefarah/yq/releases/assets/398368453"
provenance = "cosign"Expected: All other tools in the same config (aqua and core backends: cargo-binstall, just, protoc, sentry-cli, taplo, uv, node, java) resolve per-platform correctly in the same run — only yq is affected. Reproduced twice by independent Found while locking tools in hashintel/hash PR #9000. |
Per review: no Python validator for the lockfile. The mise-lock job now relies solely on mise itself: - 'mise install --locked --dry-run' fails on tools missing from the lockfile or lacking pre-resolved URLs for the current platform. - 'mise install --locked <url-locked tools>' downloads the locked URLs and verifies sha256 checksums and sizes natively. The wrong-arch asset class (e.g. the upstream yq macos-arm64 bug) is intentionally no longer checked in CI. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EcBiQCfeizMQJubtqXkiMs
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #9018 +/- ##
=======================================
Coverage 59.73% 59.73%
=======================================
Files 1371 1371
Lines 135461 135461
Branches 6066 6066
=======================================
+ Hits 80911 80912 +1
+ Misses 53618 53617 -1
Partials 932 932 Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Benchmark results
|
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 2002 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 1002 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: high, policies: 3314 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: medium, policies: 1527 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 2078 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 1033 | Flame Graph |
policy_resolution_medium
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 102 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 52 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: high, policies: 269 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: medium, policies: 108 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 133 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 63 | Flame Graph |
policy_resolution_none
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 2 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 2 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 8 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 3 | Flame Graph |
policy_resolution_small
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 52 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 26 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: high, policies: 94 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: medium, policies: 27 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 66 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 29 | Flame Graph |
read_scaling_complete
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_id;one_depth | 1 entities | Flame Graph | |
| entity_by_id;one_depth | 10 entities | Flame Graph | |
| entity_by_id;one_depth | 25 entities | Flame Graph | |
| entity_by_id;one_depth | 5 entities | Flame Graph | |
| entity_by_id;one_depth | 50 entities | Flame Graph | |
| entity_by_id;two_depth | 1 entities | Flame Graph | |
| entity_by_id;two_depth | 10 entities | Flame Graph | |
| entity_by_id;two_depth | 25 entities | Flame Graph | |
| entity_by_id;two_depth | 5 entities | Flame Graph | |
| entity_by_id;two_depth | 50 entities | Flame Graph | |
| entity_by_id;zero_depth | 1 entities | Flame Graph | |
| entity_by_id;zero_depth | 10 entities | Flame Graph | |
| entity_by_id;zero_depth | 25 entities | Flame Graph | |
| entity_by_id;zero_depth | 5 entities | Flame Graph | |
| entity_by_id;zero_depth | 50 entities | Flame Graph |
read_scaling_linkless
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_id | 1 entities | Flame Graph | |
| entity_by_id | 10 entities | Flame Graph | |
| entity_by_id | 100 entities | Flame Graph | |
| entity_by_id | 1000 entities | Flame Graph | |
| entity_by_id | 10000 entities | Flame Graph |
representative_read_entity
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/block/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/book/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/building/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/organization/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/page/v/2
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/person/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/playlist/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/song/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/uk-address/v/1
|
Flame Graph |
representative_read_entity_type
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| get_entity_type_by_id | Account ID: bf5a9ef5-dc3b-43cf-a291-6210c0321eba
|
Flame Graph |
representative_read_multiple_entities
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_property | traversal_paths=0 | 0 | |
| entity_by_property | traversal_paths=255 | 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true | |
| link_by_source_by_property | traversal_paths=0 | 0 | |
| link_by_source_by_property | traversal_paths=255 | 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true |
scenarios
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| full_test | query-limited | Flame Graph | |
| full_test | query-unlimited | Flame Graph | |
| linked_queries | query-limited | Flame Graph | |
| linked_queries | query-unlimited | Flame Graph |
Requested by Tim Diekmann · Slack thread
🌟 What is the purpose of this PR?
Before: a bad
.config/mise/mise.lock— stale versions after aconfig.tomlpin bump, missing platform entries, or a corrupt/tampered locked artifact — is only caught by a human reading the diff, or at install time on the affected platform. CI never exercises the lockfile directly; the Docker builds only hit it implicitly and late.After: the Lint workflow fails fast (well under 2 minutes) with mise's own error messages when the lockfile is stale, incomplete for the platform, or fails checksum verification — on both linux-x64 and linux-arm64 runners.
How — mise-native validation only, no custom scripting:
mise install --locked --dry-run— mise fails if any configured tool (including ones not pinned inconfig.toml, e.g. rust fromrust-toolchain.toml) is missing from the lockfile, or lacks a pre-resolved URL for the current platform. No downloads.mise install --locked cargo-binstall java just node protoc sentry-cli taplo uv yq— actually downloads the locked URLs and mise verifies the sha256 checksums and sizes. These are the tools carryingplatforms.*entries in the lockfile; cargo/npm/pipx tools install through their own package managers and have no locked URLs to verify.Skip behavior:
mise.lockdoes not exist onmainuntil #9000 merges, so every validation step is gated on the file existing and the job passes as a no-op until then. This PR is green both before and after #9000 lands.Deliberately out of scope: the wrong-arch asset class (e.g.
mise lockemitting theyq_darwin_amd64asset under themacos-arm64key, as reproduced in #9000) is not checked — it is an upstreammise lockbug (draft issue text is on this PR), the lockfile it produces is internally consistent, and catching it cross-platform would require exactly the kind of custom lockfile parsing we decided against. macOS entries are validated for presence (layer 1) but not downloaded.🔗 Related links
misetool installation against transient upstream failures #9000 (introducesmise.lock)🚫 Blocked by
misetool installation against transient upstream failures #9000.🔍 What does this change?
mise-lockjob to.github/workflows/lint.yml, matrixed overubuntu-24.04andubuntu-24.04-arm(same runners asdeploy.yml), using the repo-pinnedjdx/mise-action.passedgate job (acceptingsuccess|skipped).Pre-Merge Checklist 🚀
🚢 Has this modified a publishable library?
This PR:
📜 Does this require a change to the docs?
The changes in this PR:
🕸️ Does this require a change to the Turbo Graph?
The changes in this PR:
mise.lock); a tool missing from the list is still validated by the dry-run step, just not downloaded.config.ci.toml/config.dev.tomltools are not validated: mise keeps per-environment lockfiles (mise.ci.locketc.), which don't exist yet.🐾 Next steps
mise lockwrong-arch bug (yqmacos-arm64) is fixed.mise.ci.lock) and extending validation to it.🛡 What tests cover this?
--dry-run, and install-time sha256/size verification).❓ How to test this?
Mise lockfilejobs skip (no lockfile onmainyet).misetool installation against transient upstream failures #9000 onto this branch: the jobs run, resolve every tool from the lockfile, and download + checksum-verify the URL-locked tools.mise.lock, or bump aconfig.tomlpin without re-locking: the job fails with mise's own error message.