Skip to content

fix(ci): CLOUD-1672 pin mise to the version the tree declares, and CLOUD-1477 warm the x64 ci- family - #914

Merged
wenzowski merged 24 commits into
mainfrom
claude/glacial-ci-regression-d9qtr3
Sep 9, 2026
Merged

wenzowski merged 24 commits into
mainfrom
claude/glacial-ci-regression-d9qtr3

Conversation

@wenzowski

@wenzowski wenzowski commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Closes CLOUD-1672. Closes CLOUD-1477.
DO-NOT-CLOSE CLOUD-1461
DO-NOT-CLOSE CLOUD-1398
DO-NOT-CLOSE CLOUD-1620

Two rows on one branch, plus one gate repaired in passing. CLOUD-1672 is the urgent one — it is a live outage fix, and nothing in the repository could land while it held.

On the declined keys. The lease is contended, so land serialises by stacking this branch on the queue ahead of it — this head currently carries #905 (CLOUD-1398) and #912 (CLOUD-1620) below its own ten commits. Those PRs close their own rows; this one carries them so the stack can land in order, and declines them explicitly rather than stranding them. CLOUD-1461 is landed and Done already; the section below says what this branch repaired in its gate.


CLOUD-1672 — the toolchain CI installs was not the one the tree pins

batten.toml pins mise to 2026.9.1 by per-platform URL and digest. jdx/mise-action is digest-pinned at 3c2e0cf8, but that pins the action — it resolves which mise to install on its own, at job start. Two authorities on one version, and nothing compared them.

On 2026-09-08 the action resolved v2026.9.3, whose linux-x64 asset 404s:

curl -fsSL .../v2026.9.3/mise-v2026.9.3-linux-x64.tar.zst
curl: (22) The requested URL returned error: 404   ×5 retries

All 8 jobs of release-artifacts run 34206034815 died at that step inside 11 seconds. v0.0.152 published carrying batten.schema.json and none of its seven binaries. ci.yml run 34220313465 failed the same way in 53s, so nothing could land — the landing lease was contended across the whole fleet for the same reason.

The [[provision]] row's own comment already named the harm one layer down: "a runner that updated itself under a container would make two sessions on one commit run different toolchains, which is the disagreement [[provision]]'s pinning exists to remove." The pin was committed and correct throughout; it simply did not reach the layer where the install happened.

The fix, both halves. All 35 mise-action steps across 24 workflows declare version: 2026.9.1. And because a rule without a runnable gate is half a change, action-version-matches-the-pin now refuses drift: job pin other where a declared version disagrees with the provision row, job pin missing where a step declares none and hands the choice to the installer.

Three properties worth calling out:

  • Equality against the committed row, never a literal. A module carrying 2026.9.1 would be a third authority and would go stale the first time the pin moved.
  • Both documents are found by shape — one carrying jobs, one carrying provision — never by filename. A preset ships to every consumer, so rule 1 forbids naming their paths; the enabling row's sources is where this consumer says which files those are.
  • No provision row for the tool means the rule abstains, not refuses. A project that pins nothing has nothing to disagree with.

Engine half: a manifest declares a scope per module. Manifest.scope said a preset decides exactly one surface — true of every preset that existed, false once a subject has something to say about both. mise is that subject: reproducing a task's argv is a question about a call; the toolchain a workflow installs is a question about the tree. Two constraints assumed the old shape and both now key on the pair: the load-time scope check (decides(scope), naming the scopes the preset does decide), and the duplicate-registration check, which read the second enabling row as dead config when it decides a disjoint surface.


CLOUD-1477 — batten-check read a cache family nothing filled on its architecture

batten-check read ci- on x64 while its only warm writer ran on arm64. rust-cache composes runnerOS-runnerArch at config.ts:93, inside the restore prefix assigned at :133 — two entries that can never touch. Not a partial hit: no hit, ever, at 683s the critical path of the workflow.

CLOUD-1453 shipped the eviction half (cache-sweep.yml, v0.0.142), but eviction was not the cause: across jobs 100903936005 and 101280975479 a swept store moved this job 683s → 600s, 12%, which is pressure lifting rather than a restore landing. ci.yml:601-618 declined the fix on a 10 GiB ceiling the sweep had already removed, and named two alternatives that are both closed.

So cache-warm-linux-x64 writes ci- on x64, batten-check becomes save-if: false like every other pull-request consumer, and read-family-has-a-warm-writer refuses a read-only consumer whose family nothing fills on its architecture — scoped to pure consumers deliberately, since cross-, semver-, perf- and friends write their own entries on purpose.

One cost, stated rather than buried: the release workflow serialises (cancel-in-progress: false), and the first run after merge compiles the x64 entry cold, so that one run stretches and the next release queues behind it. Self-correcting, and the price of the fix.


CLOUD-1461's gate refused an operation its own remedy does not offer

Not this PR's subject, and not closing that row — it is landed and Done. But rebase-not-hand-stepped was wrongly refusing, which is a defect rather than an answer, and repairing it is what let this branch reach a landable shape at all.

The row bans a hand-driven landing lap and separates it from the rebase it must leave alone by asking whether the line names origin/main. git rebase --onto <newbase> <upstream> <branch> names it too, and is not a lap: it replays a range somewhere else, dropping commits from the bottom, which land has no notion of and cannot perform. A shape row declares no [[verdict]] class and this one carries no bypass_env, so there was no sanctioned route to it whatsoever. Measured here: this branch arrived carrying four commits it did not author that closing-key-check required be dropped, and the refusal's own advice named land, which would have replayed all twelve.

The discriminator has to be a flag, which is the one thing pattern cannot see — the operand matcher drops flags before comparing, so the only predicate over a flag a row could previously write was "must be present". unless_contains is contains in the other polarity: shape only, since a column that switches a deny off has no business on a precondition row where the same bytes would silently stop demanding a receipt; and matched against the same line, which is load-bearing in the permissive direction — matched against the segment, an echo --onto on line one would exempt a genuine lap on line two. the_exemption_does_not_reach_another_lines_lap is that arm.

The row's reason also still told every reader it warns rather than denies, which the comment block above it had already recorded as false in every clause. Groomed as rule-predicate-changed on CLOUD-1477, since the narrowing is a corrected false deny: no call the row exists to catch is now permitted, and every lap spelling stays denied.


Verification

  • mise run policy-test: 64 bundles, 814 passed.
  • Preset tiers 39/39; ci-cache-declared engine tier 17/17; land_hand_stepping 11/11.
  • The real-tree arm, which is the one that counts. Both test tiers supply their own pin document, so neither can show that this consumer's sources glob actually delivers batten.toml — and without it the module abstains by design and reports clean forever. Driven over this checkout instead: clean as committed, and moving one workflow's version produced .github/workflows/scorecard.yml:53 action-version-matches-the-pin, exit 2, pointer-only. Restored with git checkout --; the diff is unchanged. a_drifted_version_in_this_repository_is_refused is that proof as a standing case.
  • The gate repair's own end-to-end proof is the rebase itself: git rebase --onto origin/main <upstream> <branch> ran unrefused where it had been denied.
  • mise run verify has returned fast-forward-green on every head this branch has had tonight; every failed lap was the landing lease, spending zero CI matrices.

Overrides

protected-mutation refuses writes to .github/workflows/**, policy/** and batten.toml. Every one went through batten override request/spend; the Admits-* blocks are commit trailers, each answering the class's declared precondition, what is lost, and the rejected route.

A gate hole found while doing that, filed rather than absorbed: a sed -i against batten.toml was not refused where the Edit tool on the same path was. sed is in the [[verb]] table and policy explain prints that it is covered, so this is the engine's call resolution rather than the config — CLOUD-1664, §1 on hook.rs. It was reported in the admission and not relied on.

Follow-ups filed

row what
CLOUD-1664 sed -i writes a protected path unrefused
CLOUD-1665 queue wait is 38% of a run, now larger than any build term
CLOUD-1666 five cache families with no trunk warm writer
CLOUD-1667 perf's 574s cause, named in CLOUD-1453's title and never established
CLOUD-1668 mcp call renders a failed write as {} and exits 0
CLOUD-1673 an admission is issued per subject path, so one change cost 24 identical pairs

CLOUD-1440 already owns the stale timeout budgets.

@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

Warning

Review limit reached

Next included review available in 47 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 28a55883-dcd2-4142-9afc-cb0fe3be107b

📥 Commits

Reviewing files that changed from the base of the PR and between 8c64199 and 2ce05b8.

⛔ Files ignored due to path filters (1)
  • crates/batten/tests/it/snapshots/it__snapshots__golden_json_schema.snap is excluded by !**/*.snap
📒 Files selected for processing (34)
  • .github/workflows/branch-hygiene.yml
  • .github/workflows/ci.yml
  • .github/workflows/commit-lint.yml
  • .github/workflows/rust.yml
  • .github/workflows/zizmor.yml
  • AGENTS.md
  • batten.toml
  • completions/batten.bash
  • completions/batten.fish
  • completions/batten.zsh
  • crates/batten/src/cli.rs
  • crates/batten/src/config.rs
  • crates/batten/src/doctor.rs
  • crates/batten/src/hook.rs
  • crates/batten/src/lib.rs
  • crates/batten/src/policy.rs
  • crates/batten/src/policy/presets/mise/action-version-matches-the-pin.rego
  • crates/batten/src/preset.rs
  • crates/batten/src/rules.rs
  • crates/batten/src/spec.rs
  • crates/batten/src/surface.rs
  • crates/batten/tests/it/land_hand_stepping.rs
  • crates/batten/tests/it/main.rs
  • crates/batten/tests/it/mise_preset.rs
  • crates/batten/tests/it/pointer_only.rs
  • crates/batten/tests/it/preset_manifest.rs
  • crates/batten/tests/it/session_provisioning.rs
  • crates/batten/tests/it/startup.rs
  • install.sh
  • man/batten-doctor-gate.1
  • man/batten-doctor.1
  • mise.toml
  • schema/batten.local.schema.json
  • schema/batten.schema.json

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 5c121c29-3ba6-4819-b0b6-fb596fc01527

📥 Commits

Reviewing files that changed from the base of the PR and between 414d026 and 8c64199.

📒 Files selected for processing (27)
  • .github/workflows/arch-probe.yml
  • .github/workflows/auto-bot-land.yml
  • .github/workflows/auto-release-land.yml
  • .github/workflows/branch-hygiene.yml
  • .github/workflows/ci-drift.yml
  • .github/workflows/ci.yml
  • .github/workflows/commit-lint.yml
  • .github/workflows/coverage.yml
  • .github/workflows/fast-forward.yml
  • .github/workflows/fuzz.yml
  • .github/workflows/hook-latency-drift.yml
  • .github/workflows/land-divergence.yml
  • .github/workflows/lock-currency.yml
  • .github/workflows/mutant.yml
  • .github/workflows/nonverdict-rate.yml
  • .github/workflows/perf.yml
  • .github/workflows/release-artifacts.yml
  • .github/workflows/release-assets.yml
  • .github/workflows/release-plz.yml
  • .github/workflows/rust.yml
  • .github/workflows/scorecard.yml
  • .github/workflows/timeout-drift.yml
  • .github/workflows/zizmor-advisories.yml
  • .github/workflows/zizmor.yml
  • batten.toml
  • crates/batten/tests/it/ci_cache_declared.rs
  • policy/ci-cache-declared.rego

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The workflows pin mise to 2026.9.1. The CI configuration adds an x64 cache-warming job and makes batten-check read the warmed cache without saving entries. The cache policy adds the job read empty verdict for read-only consumers without an architecture-matched warm writer. Integration and policy tests cover architecture mismatches, missing writers, and writer exclusions.

Priority: ⬆️ High — Prioritize the CI and release workflow changes because mise failures were blocking artifact jobs and producing releases with zero binaries, while the x64 cache fix affects recurring build duration.

Severity of issue fixed: High

Merge Risk: ⚪ Minimal · up to 8c641

The cache-warming, policy, and workflow-version changes are consistent at the current head, with no actionable merge-blocking issue identified.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 1 files. (26 skipped: 2…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The description directly explains the cache-family fix, workflow-wide mise pinning, related policy changes, verification, and follow-up issues.
Title check ✅ Passed The title clearly identifies both primary changes: pinning mise to the declared version and warming the x64 ci- cache family.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/glacial-ci-regression-d9qtr3

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

wenzowski added a commit that referenced this pull request Sep 8, 2026
…declares

`batten.toml:352-357` pins mise to 2026.9.1 by per-platform URL and digest.
`jdx/mise-action` cannot see it. The `uses:` digest pins the ACTION; which MISE
that action installs is a separate resolution, and unset it takes whatever is
newest when the job starts. So the tree declares one toolchain and CI runs
another, with nothing comparing them.

Measured 2026-09-08, latest resolved to v2026.9.3 whose linux-x64 asset 404s:

  curl -fsSL .../v2026.9.3/mise-v2026.9.3-linux-x64.tar.zst
  curl: (22) The requested URL returned error: 404      x5 retries

Every job in every workflow died at that step inside 11 seconds.
`release-artifacts` run 34206034815 lost all 8 jobs in 56s and v0.0.152
published carrying `batten.schema.json` and none of its seven target binaries;
`ci.yml` run 34220313465 lost 5 jobs in 53s, so nothing could land at all.

THE PROVISION ROW ALREADY NAMED THIS HARM, one layer down: "a runner that
updated itself under a container would make two sessions on one commit run
different toolchains, which is the disagreement `[[provision]]`'s pinning
exists to remove." That is exactly what happened, and the pin written to
prevent it does not reach the layer where it occurred.

This carries the pin only. The gate half of CLOUD-1672 -- a predicate refusing
a `mise-action` step whose declared version differs from the `[[provision]]`
row's, so the two cannot drift again -- is not in this commit, and the
remaining workflows are not yet pinned. Both follow on this branch.

Pinned here: `ci.yml` (4 steps), `release-artifacts.yml` (3),
`release-plz.yml` (4), `auto-bot-land.yml` (1).

Refs: CLOUD-1672, CLOUD-1574, CLOUD-1455

Admits: 14a7813f18d11c452bb5851acf9aad131e3f63a3e7c0ede84f7ca15db7325b7e
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/ci.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: d6ff1328442bc15a7f1017cdb28a0190b0c3575b6a85e67a75752d80dbe6f9bc
Admits-answer-lost: CI stays down. Every job in this workflow dies at `jdx/mise-action` in ~11s because the action resolves mise latest (v2026.9.3) whose linux-x64 asset 404s, while `batten.toml` pins 2026.9.1. Nothing in the repository can land until the action installs the version the tree declares.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 5a69f466467c58937d55e1c7d5b79e3259b2d190cac2e69e9d4a695d3f734106
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-artifacts.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: v0.0.152 published with zero binaries and the next release does the same. All 8 jobs of run 34206034815 died at `jdx/mise-action` in 11s because the action resolves mise latest (v2026.9.3), whose linux-x64 asset 404s, while `batten.toml` pins 2026.9.1.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 168f6f87f302b1fd73d8e77e621eae3c7f5e3d5752d7ee1f7c61fcc2eb1e76b6
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-plz.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: 4690dbacf228821c8ab51e1aee4bf72d157a3bda3edeb61d491cb9a426c38239
Admits-answer-lost: The release lane keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins, so the tree declares one toolchain and the release runs another — the drift that published v0.0.152 with zero binaries.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e320b15216a7f81f8b17b231e5f62f128ca8b373499501bcc3fdbbf7d8ecf45f
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-plz.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: 168f6f87f302b1fd73d8e77e621eae3c7f5e3d5752d7ee1f7c61fcc2eb1e76b6
Admits-answer-lost: The one job that actually cuts the release stays unpinned while the three cache-warm jobs beside it are pinned — the worst of both, since the file would read as fixed while the step that publishes the tag still installs whatever mise is newest.
Admits-answer-precondition: CLOUD-1672, second write to this file. The three steps carrying a `with:` block were pinned by the previous admission; the fourth (the `release-plz` job's own step, line 338) declares no `with:` at all, so it needs a different insertion and a separate write. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name; the mutating-verb table covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — the file was re-read to find that this fourth step carries no `with:` block.

Admits: 8a95229ff41806a9b5a05b08a17e49af42dfe91abdcf79f76bd1a625bb686797
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/auto-bot-land.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: .github/workflows/auto-bot-land.yml keeps installing whatever mise is newest at job start rather than the 2026.9.1 that `batten.toml` pins. Measured 2026-09-08: latest resolved to v2026.9.3, whose linux-x64 asset 404s, and every job in every workflow died at this step inside 11 seconds. A workflow left unpinned reintroduces the outage on its own next run.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.
wenzowski added a commit that referenced this pull request Sep 8, 2026
…lows

Continues the CLOUD-1672 pin across seven more workflows: `rust.yml` (4 steps),
`commit-lint.yml`, `fast-forward.yml`, `perf.yml`, `coverage.yml`, `zizmor.yml`
and `fuzz.yml`.

Three of these are load-bearing beyond their own run. `commit-lint` is a
required check, so an unpinned step there reds every pull request.
`fast-forward` is the landing path itself. `perf` records the series from a
clock, so an unpinned toolchain silently changes what the series measures
rather than failing.

Thirteen workflows remain, and the gate half of CLOUD-1672 -- a predicate
refusing a `mise-action` step whose declared version differs from the
`[[provision]]` row's -- is still to come on this branch.

Refs: CLOUD-1672

Admits: 8efa23f5401b475d58c607f3ae8e2adf60dbca5cd2df506ffb00546de908fcb6
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/rust.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 71b4de9ae1a0041ffa6098d40137230060304c3db9d59a92f0fb951848ce293f
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/commit-lint.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. `commit-lint` is a required check, so an unpinned step here reds every pull request on its own next run.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: b2ce26a31ad7115c22e0a454348b17fd5de11435bde11478b92e86dab28e560e
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/fast-forward.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This is the landing path itself. It keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins; measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. Unpinned, the fast-forward that lands every branch fails on its own next run.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: ed675dc23520c7be2878c592f48d6a67b2c5a7e4b61a81be4e372ee08abd32b6
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/perf.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. It also records the perf series from a clock, so an unpinned toolchain silently changes what the series measures.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e6bdca0e483274fb95b00b750830540c12fb431e14186b8ddf90c7ef5f161cc3
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/coverage.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e328c82bb610d6dfab5c7457f7f754ea003496c4f464488a7529465336a3a3f5
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/zizmor.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 32cdb5543b38da755a3b2e116dffa060cbe7e321802749e2b6a0c3c66c6d8465
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/fuzz.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.
@wenzowski
wenzowski force-pushed the claude/glacial-ci-regression-d9qtr3 branch from 38dff57 to 8c64199 Compare September 8, 2026 15:37
wenzowski added a commit that referenced this pull request Sep 8, 2026
Completes the CLOUD-1672 pin. Every `jdx/mise-action` step in
`.github/workflows/**` now declares `version: 2026.9.1`, matching
`batten.toml`'s `[[provision]]` row; `git grep -L` reports no workflow
carrying a `mise-action` step without it.

Three of these are pinned for a reason beyond their own install, and each
says so at the step rather than here:

- `lock-currency` is the clock that BUMPS pins, so unpinned it is the one job
  a bad upstream release stops -- the job that would have surfaced this.
- `arch-probe` compares two runner architectures whose validity rests on the
  arms differing only in `runs-on`; an unpinned mise lets them install
  different toolchains and destroys the comparison silently rather than
  failing it.
- `hook-latency-drift` and `nonverdict-rate` record series on a clock, where
  a drifting toolchain changes what is measured rather than reddening.

Still to come on this branch: the gate half of CLOUD-1672 -- a predicate
refusing a `mise-action` step whose declared version differs from the
`[[provision]]` row's, so the two authorities cannot drift apart again.

Refs: CLOUD-1672

Admits: 53fe1b9a28fb798ee6a9fe6ec34dbd51ff974ca27d8923e89ad919695f31cb5f
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-assets.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow publishes release assets and keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. Unpinned, it reintroduces the outage that published v0.0.152 with no binaries.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 9127e19ea5c4a7b38d85eab3f54dc5572b0c0611be0ce158f0d57f2b5d40e536
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/auto-release-land.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow lands the release PR and keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. Unpinned, the release lane stays broken at its landing step.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 54cc2b5a7371515470c55b5911e812cad54824759c7e04b83b394f4795f0d7c7
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/mutant.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 53c89ed482c8e3b6e2262eefd30c407714eeea6beb70a300c0e8bc376f2ffb84
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/lock-currency.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This is the clock that bumps pinned tools, so an unpinned mise here means the job that MOVES pins cannot run at all. Measured 2026-09-08: the action resolved v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s — including the one that would otherwise have surfaced the bad upstream release.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: c36024aa70f3e151f72e152f48965917416625d2bd16216b9abf8848a9501e70
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/ci-drift.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 523d9f0aa526c4181f9d9d9999c2d6a5031be9496707953e17c4be535d211f24
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/timeout-drift.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 75b185566d9fd6163a2549832a838e1ada9326be61d72c59723faedb42707aef
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/land-divergence.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 4649eab6a0922340d2be1ce0e47a8ffd507b47740ae55f85453dc6af35d2bfad
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/hook-latency-drift.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. It also measures the gate's own latency on a clock, so an unpinned toolchain silently changes what the series measures rather than failing.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: d2fb5c29b070b9f91cbdb94a9608bcf2e0cf9963b49c675e44216fbe85391a7e
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/nonverdict-rate.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. It records a rate on a clock, so an unpinned toolchain silently changes what the series measures rather than failing.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 14c4bf8e70e719ee233f6ea75a26e6c39ad884427ec74509fdc8765bcb2e133c
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/scorecard.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 614a8ab2953de8ff590164a654d0c667b821895e4c92af1273e92116e10aa28d
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/zizmor-advisories.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e74e0af2f0245d98a75d0ef01619e24b90929e67fbeb016e9d9fa42689a7d6c9
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/branch-hygiene.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 0d5560b6a87c0ed0a7917002a7eedb5634d010b4200eb65cc46bb866516c8381
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/arch-probe.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This is the matched arm-versus-x64 probe, whose whole validity rests on both arms differing ONLY in `runs-on`. An unpinned mise means the two arms can install different toolchains, which silently destroys the comparison rather than failing it — and the last unpinned resolution, v2026.9.3, 404s on linux-x64 so the probe cannot run at all.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.
@wenzowski wenzowski changed the title fix(ci): CLOUD-1477 — give the x64 ci- family a warm writer, and gate the orphaning fix(ci): CLOUD-1672 pin mise to the version the tree declares, and CLOUD-1477 warm the x64 ci- family Sep 8, 2026
@wenzowski
wenzowski force-pushed the claude/glacial-ci-regression-d9qtr3 branch from 8c64199 to 52ec48b Compare September 8, 2026 20:32
wenzowski added a commit that referenced this pull request Sep 8, 2026
…declares

`batten.toml:352-357` pins mise to 2026.9.1 by per-platform URL and digest.
`jdx/mise-action` cannot see it. The `uses:` digest pins the ACTION; which MISE
that action installs is a separate resolution, and unset it takes whatever is
newest when the job starts. So the tree declares one toolchain and CI runs
another, with nothing comparing them.

Measured 2026-09-08, latest resolved to v2026.9.3 whose linux-x64 asset 404s:

  curl -fsSL .../v2026.9.3/mise-v2026.9.3-linux-x64.tar.zst
  curl: (22) The requested URL returned error: 404      x5 retries

Every job in every workflow died at that step inside 11 seconds.
`release-artifacts` run 34206034815 lost all 8 jobs in 56s and v0.0.152
published carrying `batten.schema.json` and none of its seven target binaries;
`ci.yml` run 34220313465 lost 5 jobs in 53s, so nothing could land at all.

THE PROVISION ROW ALREADY NAMED THIS HARM, one layer down: "a runner that
updated itself under a container would make two sessions on one commit run
different toolchains, which is the disagreement `[[provision]]`'s pinning
exists to remove." That is exactly what happened, and the pin written to
prevent it does not reach the layer where it occurred.

This carries the pin only. The gate half of CLOUD-1672 -- a predicate refusing
a `mise-action` step whose declared version differs from the `[[provision]]`
row's, so the two cannot drift again -- is not in this commit, and the
remaining workflows are not yet pinned. Both follow on this branch.

Pinned here: `ci.yml` (4 steps), `release-artifacts.yml` (3),
`release-plz.yml` (4), `auto-bot-land.yml` (1).

Refs: CLOUD-1672, CLOUD-1574, CLOUD-1455

Admits: 14a7813f18d11c452bb5851acf9aad131e3f63a3e7c0ede84f7ca15db7325b7e
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/ci.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: d6ff1328442bc15a7f1017cdb28a0190b0c3575b6a85e67a75752d80dbe6f9bc
Admits-answer-lost: CI stays down. Every job in this workflow dies at `jdx/mise-action` in ~11s because the action resolves mise latest (v2026.9.3) whose linux-x64 asset 404s, while `batten.toml` pins 2026.9.1. Nothing in the repository can land until the action installs the version the tree declares.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 5a69f466467c58937d55e1c7d5b79e3259b2d190cac2e69e9d4a695d3f734106
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-artifacts.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: v0.0.152 published with zero binaries and the next release does the same. All 8 jobs of run 34206034815 died at `jdx/mise-action` in 11s because the action resolves mise latest (v2026.9.3), whose linux-x64 asset 404s, while `batten.toml` pins 2026.9.1.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 168f6f87f302b1fd73d8e77e621eae3c7f5e3d5752d7ee1f7c61fcc2eb1e76b6
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-plz.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: 4690dbacf228821c8ab51e1aee4bf72d157a3bda3edeb61d491cb9a426c38239
Admits-answer-lost: The release lane keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins, so the tree declares one toolchain and the release runs another — the drift that published v0.0.152 with zero binaries.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e320b15216a7f81f8b17b231e5f62f128ca8b373499501bcc3fdbbf7d8ecf45f
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-plz.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: 168f6f87f302b1fd73d8e77e621eae3c7f5e3d5752d7ee1f7c61fcc2eb1e76b6
Admits-answer-lost: The one job that actually cuts the release stays unpinned while the three cache-warm jobs beside it are pinned — the worst of both, since the file would read as fixed while the step that publishes the tag still installs whatever mise is newest.
Admits-answer-precondition: CLOUD-1672, second write to this file. The three steps carrying a `with:` block were pinned by the previous admission; the fourth (the `release-plz` job's own step, line 338) declares no `with:` at all, so it needs a different insertion and a separate write. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name; the mutating-verb table covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — the file was re-read to find that this fourth step carries no `with:` block.

Admits: 8a95229ff41806a9b5a05b08a17e49af42dfe91abdcf79f76bd1a625bb686797
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/auto-bot-land.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: .github/workflows/auto-bot-land.yml keeps installing whatever mise is newest at job start rather than the 2026.9.1 that `batten.toml` pins. Measured 2026-09-08: latest resolved to v2026.9.3, whose linux-x64 asset 404s, and every job in every workflow died at this step inside 11 seconds. A workflow left unpinned reintroduces the outage on its own next run.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.
wenzowski added a commit that referenced this pull request Sep 8, 2026
…lows

Continues the CLOUD-1672 pin across seven more workflows: `rust.yml` (4 steps),
`commit-lint.yml`, `fast-forward.yml`, `perf.yml`, `coverage.yml`, `zizmor.yml`
and `fuzz.yml`.

Three of these are load-bearing beyond their own run. `commit-lint` is a
required check, so an unpinned step there reds every pull request.
`fast-forward` is the landing path itself. `perf` records the series from a
clock, so an unpinned toolchain silently changes what the series measures
rather than failing.

Thirteen workflows remain, and the gate half of CLOUD-1672 -- a predicate
refusing a `mise-action` step whose declared version differs from the
`[[provision]]` row's -- is still to come on this branch.

Refs: CLOUD-1672

Admits: 8efa23f5401b475d58c607f3ae8e2adf60dbca5cd2df506ffb00546de908fcb6
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/rust.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 71b4de9ae1a0041ffa6098d40137230060304c3db9d59a92f0fb951848ce293f
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/commit-lint.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. `commit-lint` is a required check, so an unpinned step here reds every pull request on its own next run.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: b2ce26a31ad7115c22e0a454348b17fd5de11435bde11478b92e86dab28e560e
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/fast-forward.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This is the landing path itself. It keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins; measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. Unpinned, the fast-forward that lands every branch fails on its own next run.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: ed675dc23520c7be2878c592f48d6a67b2c5a7e4b61a81be4e372ee08abd32b6
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/perf.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. It also records the perf series from a clock, so an unpinned toolchain silently changes what the series measures.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e6bdca0e483274fb95b00b750830540c12fb431e14186b8ddf90c7ef5f161cc3
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/coverage.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e328c82bb610d6dfab5c7457f7f754ea003496c4f464488a7529465336a3a3f5
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/zizmor.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 32cdb5543b38da755a3b2e116dffa060cbe7e321802749e2b6a0c3c66c6d8465
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/fuzz.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.
wenzowski added a commit that referenced this pull request Sep 8, 2026
Completes the CLOUD-1672 pin. Every `jdx/mise-action` step in
`.github/workflows/**` now declares `version: 2026.9.1`, matching
`batten.toml`'s `[[provision]]` row; `git grep -L` reports no workflow
carrying a `mise-action` step without it.

Three of these are pinned for a reason beyond their own install, and each
says so at the step rather than here:

- `lock-currency` is the clock that BUMPS pins, so unpinned it is the one job
  a bad upstream release stops -- the job that would have surfaced this.
- `arch-probe` compares two runner architectures whose validity rests on the
  arms differing only in `runs-on`; an unpinned mise lets them install
  different toolchains and destroys the comparison silently rather than
  failing it.
- `hook-latency-drift` and `nonverdict-rate` record series on a clock, where
  a drifting toolchain changes what is measured rather than reddening.

Still to come on this branch: the gate half of CLOUD-1672 -- a predicate
refusing a `mise-action` step whose declared version differs from the
`[[provision]]` row's, so the two authorities cannot drift apart again.

Refs: CLOUD-1672

Admits: 53fe1b9a28fb798ee6a9fe6ec34dbd51ff974ca27d8923e89ad919695f31cb5f
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-assets.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow publishes release assets and keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. Unpinned, it reintroduces the outage that published v0.0.152 with no binaries.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 9127e19ea5c4a7b38d85eab3f54dc5572b0c0611be0ce158f0d57f2b5d40e536
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/auto-release-land.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow lands the release PR and keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. Unpinned, the release lane stays broken at its landing step.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 54cc2b5a7371515470c55b5911e812cad54824759c7e04b83b394f4795f0d7c7
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/mutant.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 53c89ed482c8e3b6e2262eefd30c407714eeea6beb70a300c0e8bc376f2ffb84
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/lock-currency.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This is the clock that bumps pinned tools, so an unpinned mise here means the job that MOVES pins cannot run at all. Measured 2026-09-08: the action resolved v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s — including the one that would otherwise have surfaced the bad upstream release.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: c36024aa70f3e151f72e152f48965917416625d2bd16216b9abf8848a9501e70
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/ci-drift.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 523d9f0aa526c4181f9d9d9999c2d6a5031be9496707953e17c4be535d211f24
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/timeout-drift.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 75b185566d9fd6163a2549832a838e1ada9326be61d72c59723faedb42707aef
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/land-divergence.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 4649eab6a0922340d2be1ce0e47a8ffd507b47740ae55f85453dc6af35d2bfad
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/hook-latency-drift.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. It also measures the gate's own latency on a clock, so an unpinned toolchain silently changes what the series measures rather than failing.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: d2fb5c29b070b9f91cbdb94a9608bcf2e0cf9963b49c675e44216fbe85391a7e
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/nonverdict-rate.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. It records a rate on a clock, so an unpinned toolchain silently changes what the series measures rather than failing.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 14c4bf8e70e719ee233f6ea75a26e6c39ad884427ec74509fdc8765bcb2e133c
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/scorecard.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 614a8ab2953de8ff590164a654d0c667b821895e4c92af1273e92116e10aa28d
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/zizmor-advisories.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e74e0af2f0245d98a75d0ef01619e24b90929e67fbeb016e9d9fa42689a7d6c9
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/branch-hygiene.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 0d5560b6a87c0ed0a7917002a7eedb5634d010b4200eb65cc46bb866516c8381
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/arch-probe.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This is the matched arm-versus-x64 probe, whose whole validity rests on both arms differing ONLY in `runs-on`. An unpinned mise means the two arms can install different toolchains, which silently destroys the comparison rather than failing it — and the last unpinned resolution, v2026.9.3, 404s on linux-x64 so the probe cannot run at all.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.
wenzowski added a commit that referenced this pull request Sep 8, 2026
The pin half of CLOUD-1672 landed across 24 workflows and nothing held it
there. This is the mechanism, so the rule is not prose (non-negotiable 2).

`action-version-matches-the-pin` refuses a workflow step installing the
toolchain at a version the tree does not pin: `job pin other` where a declared
version disagrees with the `[[provision]]` row, `job pin missing` where the step
declares none and hands the choice to the installer. The comparison is against
the committed row and never a literal -- a module carrying `2026.9.1` would be a
THIRD authority and would go stale the first time the pin moved.

Both documents are found by SHAPE, never by filename: a document carrying
`jobs`, a document carrying `provision`. A preset ships to every consumer, so
rule 1 forbids naming their paths; the enabling row's `sources` is where this
consumer says which of its files those are.

No provision row for the tool means the row ABSTAINS rather than refuses. A
project that pins nothing has nothing to disagree with, and refusing there would
be a verdict about their configuration instead of about drift between two of
their own statements.

THE ENGINE HALF: A MANIFEST DECLARES A SCOPE PER MODULE.

`Manifest.scope` said a preset decides exactly one surface -- true of every
preset that existed when it was written, false the moment a subject has
something to say about both. `mise` is that subject: whether an agent reproduces
a task's argv is a question about a CALL, whether the toolchain a workflow
installs matches the pin is a question about the TREE. They are one preset
because they are one concern, and a consumer should not have to know the engine
once could not express that.

Two constraints assumed the old shape and both now key on the pair:

- the load-time scope check compared one manifest scope for equality; it asks
  `decides(scope)` now, and names the scopes the preset does decide.
- the duplicate-registration check keyed on the source name, so the second row
  enabling one preset at the other scope read as dead config. It is not: each
  row compiles only the modules at its own scope, so they share a name and
  decide disjoint surfaces.

Selecting modules by scope is load-bearing rather than tidy. A tree module
compiled onto the call surface reads keys the engine never builds there, Rego
reads undefined as "does not hold", and the result is a dead gate byte-identical
to a clean tree. `check_tree_paths_are_emittable` catches that today; this keeps
the case from arising rather than relying on a downstream guard to notice.

VERIFIED, AND THE REAL-TREE ARM IS THE ONE THAT COUNTS. `policy test` 64
bundles, 814 passed; the four preset tiers 39/39. Both of those supply their own
pin document, so neither can show that this consumer's `sources` glob actually
delivers `batten.toml` -- and if it did not, the pin would be absent, the module
would abstain by design, and the gate would report clean forever. So it was
driven over this checkout: clean as committed, and moving one workflow's version
produced `.github/workflows/scorecard.yml:53 action-version-matches-the-pin`,
exit 2, pointer-only. The file was restored with `git checkout --` and the diff
is unchanged. `a_drifted_version_in_this_repository_is_refused` is that same
proof as a standing case.

Refs: CLOUD-1672, CLOUD-1181, CLOUD-404

Admits: 48d1eec18e1480b0a3f1f941763ee6705ff0eadb5140db2bb02a5fbc101081c5
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: batten.toml
Admits-anchor: call:8c6419950ad9ffd05f57f842005d1004860e85da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: e1aaff7ef76c6a51d8d71c951a03bfdfec14b5cbaebe9bb738612f15bcda99f3
Admits-answer-lost: The gate half of CLOUD-1672 does not run. The pin is landed across all 24 workflows but nothing holds it there, so the next architecture move, Renovate bump or hand edit silently reintroduces the drift that published v0.0.152 with zero binaries. A rule without its mechanism is half a change by this repository's own non-negotiable rule 2.
Admits-answer-precondition: CLOUD-1672 gate half. The redirect for `batten.toml` is "change it in a pull request" with `config-lint` as the check before it lands — a statement about how the change must LAND, not a surface that can express it: only a direct write can add a `[[rule]]` row. The row enables the `mise` preset at tree scope with the `sources` globs the predicate reads; without it the module ships and decides nothing, which is the dead-gate shape the whole row exists to close. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — the `ci-hygiene` row was read as the shape to copy, since it is the existing tree-scoped preset row carrying workflow `sources` and `line_sources`.

Admits: 6e8cd01a0028a1c5510ed46c8c6539d3976ffc6486db86b606b140723b76865f
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/scorecard.yml
Admits-anchor: call:8c6419950ad9ffd05f57f842005d1004860e85da
Admits-epoch: 3b025bfdaaedf9f46a55c4b09fba627a7d599c231956e6730c43863d886b4d05
Admits-author: alec@wenzowski.com
Admits-prev: 14c4bf8e70e719ee233f6ea75a26e6c39ad884427ec74509fdc8765bcb2e133c
Admits-answer-lost: The gate ships unproven on the surface that matters. The load-time and compiled tiers both supply their own fixture documents, so neither can show that this consumer's `sources` glob actually delivers `batten.toml` to the module — and if it does not, the pin document is absent, the module abstains BY DESIGN, and the gate reports clean forever over a tree it never compared. That is the exact dead-gate shape this row exists to close, reachable only through the row's own configuration.
Admits-answer-precondition: A VERIFICATION WRITE, reverted in the same turn. CLOUD-1672's new gate reports clean over this tree, and clean is byte-identical to a dead gate — so one workflow's `version:` is being changed to a wrong value to confirm the predicate names it, then changed straight back. Only a direct write can do that; no read surface can make a gate fire. Nothing of it reaches a commit: the file is restored with `git checkout --` before the tree is staged, and the diff will show it unchanged.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s new `mise-preset-tree` row and its `sources` list were read, and reading them is what identified the glob as the one thing no fixture can verify.
wenzowski added a commit that referenced this pull request Sep 8, 2026
…declares

`batten.toml:352-357` pins mise to 2026.9.1 by per-platform URL and digest.
`jdx/mise-action` cannot see it. The `uses:` digest pins the ACTION; which MISE
that action installs is a separate resolution, and unset it takes whatever is
newest when the job starts. So the tree declares one toolchain and CI runs
another, with nothing comparing them.

Measured 2026-09-08, latest resolved to v2026.9.3 whose linux-x64 asset 404s:

  curl -fsSL .../v2026.9.3/mise-v2026.9.3-linux-x64.tar.zst
  curl: (22) The requested URL returned error: 404      x5 retries

Every job in every workflow died at that step inside 11 seconds.
`release-artifacts` run 34206034815 lost all 8 jobs in 56s and v0.0.152
published carrying `batten.schema.json` and none of its seven target binaries;
`ci.yml` run 34220313465 lost 5 jobs in 53s, so nothing could land at all.

THE PROVISION ROW ALREADY NAMED THIS HARM, one layer down: "a runner that
updated itself under a container would make two sessions on one commit run
different toolchains, which is the disagreement `[[provision]]`'s pinning
exists to remove." That is exactly what happened, and the pin written to
prevent it does not reach the layer where it occurred.

This carries the pin only. The gate half of CLOUD-1672 -- a predicate refusing
a `mise-action` step whose declared version differs from the `[[provision]]`
row's, so the two cannot drift again -- is not in this commit, and the
remaining workflows are not yet pinned. Both follow on this branch.

Pinned here: `ci.yml` (4 steps), `release-artifacts.yml` (3),
`release-plz.yml` (4), `auto-bot-land.yml` (1).

Refs: CLOUD-1672, CLOUD-1574, CLOUD-1455

Admits: 14a7813f18d11c452bb5851acf9aad131e3f63a3e7c0ede84f7ca15db7325b7e
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/ci.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: d6ff1328442bc15a7f1017cdb28a0190b0c3575b6a85e67a75752d80dbe6f9bc
Admits-answer-lost: CI stays down. Every job in this workflow dies at `jdx/mise-action` in ~11s because the action resolves mise latest (v2026.9.3) whose linux-x64 asset 404s, while `batten.toml` pins 2026.9.1. Nothing in the repository can land until the action installs the version the tree declares.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 5a69f466467c58937d55e1c7d5b79e3259b2d190cac2e69e9d4a695d3f734106
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-artifacts.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: v0.0.152 published with zero binaries and the next release does the same. All 8 jobs of run 34206034815 died at `jdx/mise-action` in 11s because the action resolves mise latest (v2026.9.3), whose linux-x64 asset 404s, while `batten.toml` pins 2026.9.1.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 168f6f87f302b1fd73d8e77e621eae3c7f5e3d5752d7ee1f7c61fcc2eb1e76b6
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-plz.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: 4690dbacf228821c8ab51e1aee4bf72d157a3bda3edeb61d491cb9a426c38239
Admits-answer-lost: The release lane keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins, so the tree declares one toolchain and the release runs another — the drift that published v0.0.152 with zero binaries.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e320b15216a7f81f8b17b231e5f62f128ca8b373499501bcc3fdbbf7d8ecf45f
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-plz.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: 168f6f87f302b1fd73d8e77e621eae3c7f5e3d5752d7ee1f7c61fcc2eb1e76b6
Admits-answer-lost: The one job that actually cuts the release stays unpinned while the three cache-warm jobs beside it are pinned — the worst of both, since the file would read as fixed while the step that publishes the tag still installs whatever mise is newest.
Admits-answer-precondition: CLOUD-1672, second write to this file. The three steps carrying a `with:` block were pinned by the previous admission; the fourth (the `release-plz` job's own step, line 338) declares no `with:` at all, so it needs a different insertion and a separate write. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name; the mutating-verb table covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — the file was re-read to find that this fourth step carries no `with:` block.

Admits: 8a95229ff41806a9b5a05b08a17e49af42dfe91abdcf79f76bd1a625bb686797
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/auto-bot-land.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: .github/workflows/auto-bot-land.yml keeps installing whatever mise is newest at job start rather than the 2026.9.1 that `batten.toml` pins. Measured 2026-09-08: latest resolved to v2026.9.3, whose linux-x64 asset 404s, and every job in every workflow died at this step inside 11 seconds. A workflow left unpinned reintroduces the outage on its own next run.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.
wenzowski added a commit that referenced this pull request Sep 8, 2026
…lows

Continues the CLOUD-1672 pin across seven more workflows: `rust.yml` (4 steps),
`commit-lint.yml`, `fast-forward.yml`, `perf.yml`, `coverage.yml`, `zizmor.yml`
and `fuzz.yml`.

Three of these are load-bearing beyond their own run. `commit-lint` is a
required check, so an unpinned step there reds every pull request.
`fast-forward` is the landing path itself. `perf` records the series from a
clock, so an unpinned toolchain silently changes what the series measures
rather than failing.

Thirteen workflows remain, and the gate half of CLOUD-1672 -- a predicate
refusing a `mise-action` step whose declared version differs from the
`[[provision]]` row's -- is still to come on this branch.

Refs: CLOUD-1672

Admits: 8efa23f5401b475d58c607f3ae8e2adf60dbca5cd2df506ffb00546de908fcb6
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/rust.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 71b4de9ae1a0041ffa6098d40137230060304c3db9d59a92f0fb951848ce293f
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/commit-lint.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. `commit-lint` is a required check, so an unpinned step here reds every pull request on its own next run.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: b2ce26a31ad7115c22e0a454348b17fd5de11435bde11478b92e86dab28e560e
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/fast-forward.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This is the landing path itself. It keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins; measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. Unpinned, the fast-forward that lands every branch fails on its own next run.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: ed675dc23520c7be2878c592f48d6a67b2c5a7e4b61a81be4e372ee08abd32b6
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/perf.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. It also records the perf series from a clock, so an unpinned toolchain silently changes what the series measures.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e6bdca0e483274fb95b00b750830540c12fb431e14186b8ddf90c7ef5f161cc3
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/coverage.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e328c82bb610d6dfab5c7457f7f754ea003496c4f464488a7529465336a3a3f5
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/zizmor.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 32cdb5543b38da755a3b2e116dffa060cbe7e321802749e2b6a0c3c66c6d8465
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/fuzz.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.
@wenzowski
wenzowski force-pushed the claude/glacial-ci-regression-d9qtr3 branch from 52ec48b to 10dbdff Compare September 8, 2026 21:21
wenzowski added a commit that referenced this pull request Sep 8, 2026
Completes the CLOUD-1672 pin. Every `jdx/mise-action` step in
`.github/workflows/**` now declares `version: 2026.9.1`, matching
`batten.toml`'s `[[provision]]` row; `git grep -L` reports no workflow
carrying a `mise-action` step without it.

Three of these are pinned for a reason beyond their own install, and each
says so at the step rather than here:

- `lock-currency` is the clock that BUMPS pins, so unpinned it is the one job
  a bad upstream release stops -- the job that would have surfaced this.
- `arch-probe` compares two runner architectures whose validity rests on the
  arms differing only in `runs-on`; an unpinned mise lets them install
  different toolchains and destroys the comparison silently rather than
  failing it.
- `hook-latency-drift` and `nonverdict-rate` record series on a clock, where
  a drifting toolchain changes what is measured rather than reddening.

Still to come on this branch: the gate half of CLOUD-1672 -- a predicate
refusing a `mise-action` step whose declared version differs from the
`[[provision]]` row's, so the two authorities cannot drift apart again.

Refs: CLOUD-1672

Admits: 53fe1b9a28fb798ee6a9fe6ec34dbd51ff974ca27d8923e89ad919695f31cb5f
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-assets.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow publishes release assets and keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. Unpinned, it reintroduces the outage that published v0.0.152 with no binaries.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 9127e19ea5c4a7b38d85eab3f54dc5572b0c0611be0ce158f0d57f2b5d40e536
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/auto-release-land.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow lands the release PR and keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. Unpinned, the release lane stays broken at its landing step.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 54cc2b5a7371515470c55b5911e812cad54824759c7e04b83b394f4795f0d7c7
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/mutant.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 53c89ed482c8e3b6e2262eefd30c407714eeea6beb70a300c0e8bc376f2ffb84
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/lock-currency.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This is the clock that bumps pinned tools, so an unpinned mise here means the job that MOVES pins cannot run at all. Measured 2026-09-08: the action resolved v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s — including the one that would otherwise have surfaced the bad upstream release.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: c36024aa70f3e151f72e152f48965917416625d2bd16216b9abf8848a9501e70
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/ci-drift.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 523d9f0aa526c4181f9d9d9999c2d6a5031be9496707953e17c4be535d211f24
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/timeout-drift.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 75b185566d9fd6163a2549832a838e1ada9326be61d72c59723faedb42707aef
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/land-divergence.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 4649eab6a0922340d2be1ce0e47a8ffd507b47740ae55f85453dc6af35d2bfad
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/hook-latency-drift.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. It also measures the gate's own latency on a clock, so an unpinned toolchain silently changes what the series measures rather than failing.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: d2fb5c29b070b9f91cbdb94a9608bcf2e0cf9963b49c675e44216fbe85391a7e
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/nonverdict-rate.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. It records a rate on a clock, so an unpinned toolchain silently changes what the series measures rather than failing.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 14c4bf8e70e719ee233f6ea75a26e6c39ad884427ec74509fdc8765bcb2e133c
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/scorecard.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 614a8ab2953de8ff590164a654d0c667b821895e4c92af1273e92116e10aa28d
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/zizmor-advisories.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e74e0af2f0245d98a75d0ef01619e24b90929e67fbeb016e9d9fa42689a7d6c9
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/branch-hygiene.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 0d5560b6a87c0ed0a7917002a7eedb5634d010b4200eb65cc46bb866516c8381
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/arch-probe.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This is the matched arm-versus-x64 probe, whose whole validity rests on both arms differing ONLY in `runs-on`. An unpinned mise means the two arms can install different toolchains, which silently destroys the comparison rather than failing it — and the last unpinned resolution, v2026.9.3, 404s on linux-x64 so the probe cannot run at all.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.
wenzowski added a commit that referenced this pull request Sep 8, 2026
The pin half of CLOUD-1672 landed across 24 workflows and nothing held it
there. This is the mechanism, so the rule is not prose (non-negotiable 2).

`action-version-matches-the-pin` refuses a workflow step installing the
toolchain at a version the tree does not pin: `job pin other` where a declared
version disagrees with the `[[provision]]` row, `job pin missing` where the step
declares none and hands the choice to the installer. The comparison is against
the committed row and never a literal -- a module carrying `2026.9.1` would be a
THIRD authority and would go stale the first time the pin moved.

Both documents are found by SHAPE, never by filename: a document carrying
`jobs`, a document carrying `provision`. A preset ships to every consumer, so
rule 1 forbids naming their paths; the enabling row's `sources` is where this
consumer says which of its files those are.

No provision row for the tool means the row ABSTAINS rather than refuses. A
project that pins nothing has nothing to disagree with, and refusing there would
be a verdict about their configuration instead of about drift between two of
their own statements.

THE ENGINE HALF: A MANIFEST DECLARES A SCOPE PER MODULE.

`Manifest.scope` said a preset decides exactly one surface -- true of every
preset that existed when it was written, false the moment a subject has
something to say about both. `mise` is that subject: whether an agent reproduces
a task's argv is a question about a CALL, whether the toolchain a workflow
installs matches the pin is a question about the TREE. They are one preset
because they are one concern, and a consumer should not have to know the engine
once could not express that.

Two constraints assumed the old shape and both now key on the pair:

- the load-time scope check compared one manifest scope for equality; it asks
  `decides(scope)` now, and names the scopes the preset does decide.
- the duplicate-registration check keyed on the source name, so the second row
  enabling one preset at the other scope read as dead config. It is not: each
  row compiles only the modules at its own scope, so they share a name and
  decide disjoint surfaces.

Selecting modules by scope is load-bearing rather than tidy. A tree module
compiled onto the call surface reads keys the engine never builds there, Rego
reads undefined as "does not hold", and the result is a dead gate byte-identical
to a clean tree. `check_tree_paths_are_emittable` catches that today; this keeps
the case from arising rather than relying on a downstream guard to notice.

VERIFIED, AND THE REAL-TREE ARM IS THE ONE THAT COUNTS. `policy test` 64
bundles, 814 passed; the four preset tiers 39/39. Both of those supply their own
pin document, so neither can show that this consumer's `sources` glob actually
delivers `batten.toml` -- and if it did not, the pin would be absent, the module
would abstain by design, and the gate would report clean forever. So it was
driven over this checkout: clean as committed, and moving one workflow's version
produced `.github/workflows/scorecard.yml:53 action-version-matches-the-pin`,
exit 2, pointer-only. The file was restored with `git checkout --` and the diff
is unchanged. `a_drifted_version_in_this_repository_is_refused` is that same
proof as a standing case.

Refs: CLOUD-1672, CLOUD-1181, CLOUD-404

Admits: 48d1eec18e1480b0a3f1f941763ee6705ff0eadb5140db2bb02a5fbc101081c5
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: batten.toml
Admits-anchor: call:8c6419950ad9ffd05f57f842005d1004860e85da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: e1aaff7ef76c6a51d8d71c951a03bfdfec14b5cbaebe9bb738612f15bcda99f3
Admits-answer-lost: The gate half of CLOUD-1672 does not run. The pin is landed across all 24 workflows but nothing holds it there, so the next architecture move, Renovate bump or hand edit silently reintroduces the drift that published v0.0.152 with zero binaries. A rule without its mechanism is half a change by this repository's own non-negotiable rule 2.
Admits-answer-precondition: CLOUD-1672 gate half. The redirect for `batten.toml` is "change it in a pull request" with `config-lint` as the check before it lands — a statement about how the change must LAND, not a surface that can express it: only a direct write can add a `[[rule]]` row. The row enables the `mise` preset at tree scope with the `sources` globs the predicate reads; without it the module ships and decides nothing, which is the dead-gate shape the whole row exists to close. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — the `ci-hygiene` row was read as the shape to copy, since it is the existing tree-scoped preset row carrying workflow `sources` and `line_sources`.

Admits: 6e8cd01a0028a1c5510ed46c8c6539d3976ffc6486db86b606b140723b76865f
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/scorecard.yml
Admits-anchor: call:8c6419950ad9ffd05f57f842005d1004860e85da
Admits-epoch: 3b025bfdaaedf9f46a55c4b09fba627a7d599c231956e6730c43863d886b4d05
Admits-author: alec@wenzowski.com
Admits-prev: 14c4bf8e70e719ee233f6ea75a26e6c39ad884427ec74509fdc8765bcb2e133c
Admits-answer-lost: The gate ships unproven on the surface that matters. The load-time and compiled tiers both supply their own fixture documents, so neither can show that this consumer's `sources` glob actually delivers `batten.toml` to the module — and if it does not, the pin document is absent, the module abstains BY DESIGN, and the gate reports clean forever over a tree it never compared. That is the exact dead-gate shape this row exists to close, reachable only through the row's own configuration.
Admits-answer-precondition: A VERIFICATION WRITE, reverted in the same turn. CLOUD-1672's new gate reports clean over this tree, and clean is byte-identical to a dead gate — so one workflow's `version:` is being changed to a wrong value to confirm the predicate names it, then changed straight back. Only a direct write can do that; no read surface can make a gate fire. Nothing of it reaches a commit: the file is restored with `git checkout --` before the tree is staged, and the diff will show it unchanged.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s new `mise-preset-tree` row and its `sources` list were read, and reading them is what identified the glob as the one thing no fixture can verify.
wenzowski added a commit that referenced this pull request Sep 8, 2026
…declares

`batten.toml:352-357` pins mise to 2026.9.1 by per-platform URL and digest.
`jdx/mise-action` cannot see it. The `uses:` digest pins the ACTION; which MISE
that action installs is a separate resolution, and unset it takes whatever is
newest when the job starts. So the tree declares one toolchain and CI runs
another, with nothing comparing them.

Measured 2026-09-08, latest resolved to v2026.9.3 whose linux-x64 asset 404s:

  curl -fsSL .../v2026.9.3/mise-v2026.9.3-linux-x64.tar.zst
  curl: (22) The requested URL returned error: 404      x5 retries

Every job in every workflow died at that step inside 11 seconds.
`release-artifacts` run 34206034815 lost all 8 jobs in 56s and v0.0.152
published carrying `batten.schema.json` and none of its seven target binaries;
`ci.yml` run 34220313465 lost 5 jobs in 53s, so nothing could land at all.

THE PROVISION ROW ALREADY NAMED THIS HARM, one layer down: "a runner that
updated itself under a container would make two sessions on one commit run
different toolchains, which is the disagreement `[[provision]]`'s pinning
exists to remove." That is exactly what happened, and the pin written to
prevent it does not reach the layer where it occurred.

This carries the pin only. The gate half of CLOUD-1672 -- a predicate refusing
a `mise-action` step whose declared version differs from the `[[provision]]`
row's, so the two cannot drift again -- is not in this commit, and the
remaining workflows are not yet pinned. Both follow on this branch.

Pinned here: `ci.yml` (4 steps), `release-artifacts.yml` (3),
`release-plz.yml` (4), `auto-bot-land.yml` (1).

Refs: CLOUD-1672, CLOUD-1574, CLOUD-1455

Admits: 14a7813f18d11c452bb5851acf9aad131e3f63a3e7c0ede84f7ca15db7325b7e
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/ci.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: d6ff1328442bc15a7f1017cdb28a0190b0c3575b6a85e67a75752d80dbe6f9bc
Admits-answer-lost: CI stays down. Every job in this workflow dies at `jdx/mise-action` in ~11s because the action resolves mise latest (v2026.9.3) whose linux-x64 asset 404s, while `batten.toml` pins 2026.9.1. Nothing in the repository can land until the action installs the version the tree declares.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 5a69f466467c58937d55e1c7d5b79e3259b2d190cac2e69e9d4a695d3f734106
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-artifacts.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: v0.0.152 published with zero binaries and the next release does the same. All 8 jobs of run 34206034815 died at `jdx/mise-action` in 11s because the action resolves mise latest (v2026.9.3), whose linux-x64 asset 404s, while `batten.toml` pins 2026.9.1.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 168f6f87f302b1fd73d8e77e621eae3c7f5e3d5752d7ee1f7c61fcc2eb1e76b6
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-plz.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: 4690dbacf228821c8ab51e1aee4bf72d157a3bda3edeb61d491cb9a426c38239
Admits-answer-lost: The release lane keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins, so the tree declares one toolchain and the release runs another — the drift that published v0.0.152 with zero binaries.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e320b15216a7f81f8b17b231e5f62f128ca8b373499501bcc3fdbbf7d8ecf45f
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-plz.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: 168f6f87f302b1fd73d8e77e621eae3c7f5e3d5752d7ee1f7c61fcc2eb1e76b6
Admits-answer-lost: The one job that actually cuts the release stays unpinned while the three cache-warm jobs beside it are pinned — the worst of both, since the file would read as fixed while the step that publishes the tag still installs whatever mise is newest.
Admits-answer-precondition: CLOUD-1672, second write to this file. The three steps carrying a `with:` block were pinned by the previous admission; the fourth (the `release-plz` job's own step, line 338) declares no `with:` at all, so it needs a different insertion and a separate write. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name; the mutating-verb table covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — the file was re-read to find that this fourth step carries no `with:` block.

Admits: 8a95229ff41806a9b5a05b08a17e49af42dfe91abdcf79f76bd1a625bb686797
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/auto-bot-land.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: .github/workflows/auto-bot-land.yml keeps installing whatever mise is newest at job start rather than the 2026.9.1 that `batten.toml` pins. Measured 2026-09-08: latest resolved to v2026.9.3, whose linux-x64 asset 404s, and every job in every workflow died at this step inside 11 seconds. A workflow left unpinned reintroduces the outage on its own next run.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.
wenzowski added a commit that referenced this pull request Sep 8, 2026
…lows

Continues the CLOUD-1672 pin across seven more workflows: `rust.yml` (4 steps),
`commit-lint.yml`, `fast-forward.yml`, `perf.yml`, `coverage.yml`, `zizmor.yml`
and `fuzz.yml`.

Three of these are load-bearing beyond their own run. `commit-lint` is a
required check, so an unpinned step there reds every pull request.
`fast-forward` is the landing path itself. `perf` records the series from a
clock, so an unpinned toolchain silently changes what the series measures
rather than failing.

Thirteen workflows remain, and the gate half of CLOUD-1672 -- a predicate
refusing a `mise-action` step whose declared version differs from the
`[[provision]]` row's -- is still to come on this branch.

Refs: CLOUD-1672

Admits: 8efa23f5401b475d58c607f3ae8e2adf60dbca5cd2df506ffb00546de908fcb6
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/rust.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 71b4de9ae1a0041ffa6098d40137230060304c3db9d59a92f0fb951848ce293f
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/commit-lint.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. `commit-lint` is a required check, so an unpinned step here reds every pull request on its own next run.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: b2ce26a31ad7115c22e0a454348b17fd5de11435bde11478b92e86dab28e560e
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/fast-forward.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This is the landing path itself. It keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins; measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. Unpinned, the fast-forward that lands every branch fails on its own next run.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: ed675dc23520c7be2878c592f48d6a67b2c5a7e4b61a81be4e372ee08abd32b6
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/perf.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. It also records the perf series from a clock, so an unpinned toolchain silently changes what the series measures.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e6bdca0e483274fb95b00b750830540c12fb431e14186b8ddf90c7ef5f161cc3
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/coverage.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e328c82bb610d6dfab5c7457f7f754ea003496c4f464488a7529465336a3a3f5
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/zizmor.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 32cdb5543b38da755a3b2e116dffa060cbe7e321802749e2b6a0c3c66c6d8465
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/fuzz.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.
wenzowski added a commit that referenced this pull request Sep 8, 2026
Completes the CLOUD-1672 pin. Every `jdx/mise-action` step in
`.github/workflows/**` now declares `version: 2026.9.1`, matching
`batten.toml`'s `[[provision]]` row; `git grep -L` reports no workflow
carrying a `mise-action` step without it.

Three of these are pinned for a reason beyond their own install, and each
says so at the step rather than here:

- `lock-currency` is the clock that BUMPS pins, so unpinned it is the one job
  a bad upstream release stops -- the job that would have surfaced this.
- `arch-probe` compares two runner architectures whose validity rests on the
  arms differing only in `runs-on`; an unpinned mise lets them install
  different toolchains and destroys the comparison silently rather than
  failing it.
- `hook-latency-drift` and `nonverdict-rate` record series on a clock, where
  a drifting toolchain changes what is measured rather than reddening.

Still to come on this branch: the gate half of CLOUD-1672 -- a predicate
refusing a `mise-action` step whose declared version differs from the
`[[provision]]` row's, so the two authorities cannot drift apart again.

Refs: CLOUD-1672

Admits: 53fe1b9a28fb798ee6a9fe6ec34dbd51ff974ca27d8923e89ad919695f31cb5f
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-assets.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow publishes release assets and keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. Unpinned, it reintroduces the outage that published v0.0.152 with no binaries.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 9127e19ea5c4a7b38d85eab3f54dc5572b0c0611be0ce158f0d57f2b5d40e536
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/auto-release-land.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow lands the release PR and keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. Unpinned, the release lane stays broken at its landing step.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 54cc2b5a7371515470c55b5911e812cad54824759c7e04b83b394f4795f0d7c7
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/mutant.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 53c89ed482c8e3b6e2262eefd30c407714eeea6beb70a300c0e8bc376f2ffb84
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/lock-currency.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This is the clock that bumps pinned tools, so an unpinned mise here means the job that MOVES pins cannot run at all. Measured 2026-09-08: the action resolved v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s — including the one that would otherwise have surfaced the bad upstream release.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: c36024aa70f3e151f72e152f48965917416625d2bd16216b9abf8848a9501e70
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/ci-drift.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 523d9f0aa526c4181f9d9d9999c2d6a5031be9496707953e17c4be535d211f24
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/timeout-drift.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 75b185566d9fd6163a2549832a838e1ada9326be61d72c59723faedb42707aef
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/land-divergence.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 4649eab6a0922340d2be1ce0e47a8ffd507b47740ae55f85453dc6af35d2bfad
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/hook-latency-drift.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. It also measures the gate's own latency on a clock, so an unpinned toolchain silently changes what the series measures rather than failing.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: d2fb5c29b070b9f91cbdb94a9608bcf2e0cf9963b49c675e44216fbe85391a7e
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/nonverdict-rate.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. It records a rate on a clock, so an unpinned toolchain silently changes what the series measures rather than failing.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 14c4bf8e70e719ee233f6ea75a26e6c39ad884427ec74509fdc8765bcb2e133c
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/scorecard.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 614a8ab2953de8ff590164a654d0c667b821895e4c92af1273e92116e10aa28d
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/zizmor-advisories.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e74e0af2f0245d98a75d0ef01619e24b90929e67fbeb016e9d9fa42689a7d6c9
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/branch-hygiene.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 0d5560b6a87c0ed0a7917002a7eedb5634d010b4200eb65cc46bb866516c8381
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/arch-probe.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This is the matched arm-versus-x64 probe, whose whole validity rests on both arms differing ONLY in `runs-on`. An unpinned mise means the two arms can install different toolchains, which silently destroys the comparison rather than failing it — and the last unpinned resolution, v2026.9.3, 404s on linux-x64 so the probe cannot run at all.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.
wenzowski added a commit that referenced this pull request Sep 8, 2026
The pin half of CLOUD-1672 landed across 24 workflows and nothing held it
there. This is the mechanism, so the rule is not prose (non-negotiable 2).

`action-version-matches-the-pin` refuses a workflow step installing the
toolchain at a version the tree does not pin: `job pin other` where a declared
version disagrees with the `[[provision]]` row, `job pin missing` where the step
declares none and hands the choice to the installer. The comparison is against
the committed row and never a literal -- a module carrying `2026.9.1` would be a
THIRD authority and would go stale the first time the pin moved.

Both documents are found by SHAPE, never by filename: a document carrying
`jobs`, a document carrying `provision`. A preset ships to every consumer, so
rule 1 forbids naming their paths; the enabling row's `sources` is where this
consumer says which of its files those are.

No provision row for the tool means the row ABSTAINS rather than refuses. A
project that pins nothing has nothing to disagree with, and refusing there would
be a verdict about their configuration instead of about drift between two of
their own statements.

THE ENGINE HALF: A MANIFEST DECLARES A SCOPE PER MODULE.

`Manifest.scope` said a preset decides exactly one surface -- true of every
preset that existed when it was written, false the moment a subject has
something to say about both. `mise` is that subject: whether an agent reproduces
a task's argv is a question about a CALL, whether the toolchain a workflow
installs matches the pin is a question about the TREE. They are one preset
because they are one concern, and a consumer should not have to know the engine
once could not express that.

Two constraints assumed the old shape and both now key on the pair:

- the load-time scope check compared one manifest scope for equality; it asks
  `decides(scope)` now, and names the scopes the preset does decide.
- the duplicate-registration check keyed on the source name, so the second row
  enabling one preset at the other scope read as dead config. It is not: each
  row compiles only the modules at its own scope, so they share a name and
  decide disjoint surfaces.

Selecting modules by scope is load-bearing rather than tidy. A tree module
compiled onto the call surface reads keys the engine never builds there, Rego
reads undefined as "does not hold", and the result is a dead gate byte-identical
to a clean tree. `check_tree_paths_are_emittable` catches that today; this keeps
the case from arising rather than relying on a downstream guard to notice.

VERIFIED, AND THE REAL-TREE ARM IS THE ONE THAT COUNTS. `policy test` 64
bundles, 814 passed; the four preset tiers 39/39. Both of those supply their own
pin document, so neither can show that this consumer's `sources` glob actually
delivers `batten.toml` -- and if it did not, the pin would be absent, the module
would abstain by design, and the gate would report clean forever. So it was
driven over this checkout: clean as committed, and moving one workflow's version
produced `.github/workflows/scorecard.yml:53 action-version-matches-the-pin`,
exit 2, pointer-only. The file was restored with `git checkout --` and the diff
is unchanged. `a_drifted_version_in_this_repository_is_refused` is that same
proof as a standing case.

Refs: CLOUD-1672, CLOUD-1181, CLOUD-404

Admits: 48d1eec18e1480b0a3f1f941763ee6705ff0eadb5140db2bb02a5fbc101081c5
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: batten.toml
Admits-anchor: call:8c6419950ad9ffd05f57f842005d1004860e85da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: e1aaff7ef76c6a51d8d71c951a03bfdfec14b5cbaebe9bb738612f15bcda99f3
Admits-answer-lost: The gate half of CLOUD-1672 does not run. The pin is landed across all 24 workflows but nothing holds it there, so the next architecture move, Renovate bump or hand edit silently reintroduces the drift that published v0.0.152 with zero binaries. A rule without its mechanism is half a change by this repository's own non-negotiable rule 2.
Admits-answer-precondition: CLOUD-1672 gate half. The redirect for `batten.toml` is "change it in a pull request" with `config-lint` as the check before it lands — a statement about how the change must LAND, not a surface that can express it: only a direct write can add a `[[rule]]` row. The row enables the `mise` preset at tree scope with the `sources` globs the predicate reads; without it the module ships and decides nothing, which is the dead-gate shape the whole row exists to close. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — the `ci-hygiene` row was read as the shape to copy, since it is the existing tree-scoped preset row carrying workflow `sources` and `line_sources`.

Admits: 6e8cd01a0028a1c5510ed46c8c6539d3976ffc6486db86b606b140723b76865f
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/scorecard.yml
Admits-anchor: call:8c6419950ad9ffd05f57f842005d1004860e85da
Admits-epoch: 3b025bfdaaedf9f46a55c4b09fba627a7d599c231956e6730c43863d886b4d05
Admits-author: alec@wenzowski.com
Admits-prev: 14c4bf8e70e719ee233f6ea75a26e6c39ad884427ec74509fdc8765bcb2e133c
Admits-answer-lost: The gate ships unproven on the surface that matters. The load-time and compiled tiers both supply their own fixture documents, so neither can show that this consumer's `sources` glob actually delivers `batten.toml` to the module — and if it does not, the pin document is absent, the module abstains BY DESIGN, and the gate reports clean forever over a tree it never compared. That is the exact dead-gate shape this row exists to close, reachable only through the row's own configuration.
Admits-answer-precondition: A VERIFICATION WRITE, reverted in the same turn. CLOUD-1672's new gate reports clean over this tree, and clean is byte-identical to a dead gate — so one workflow's `version:` is being changed to a wrong value to confirm the predicate names it, then changed straight back. Only a direct write can do that; no read surface can make a gate fire. Nothing of it reaches a commit: the file is restored with `git checkout --` before the tree is staged, and the diff will show it unchanged.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s new `mise-preset-tree` row and its `sources` list were read, and reading them is what identified the glob as the one thing no fixture can verify.
@wenzowski
wenzowski force-pushed the claude/glacial-ci-regression-d9qtr3 branch from 10dbdff to 267bd13 Compare September 8, 2026 22:28
`batten doctor` reported six checks and none of them asked whether a commit in
this clone runs the gate. In the container this repository provisions for
itself, those came apart: `git commit` ran neither `pre-commit` nor `commit-msg`,
so every commit bypassed the gate while `batten startup` reported every row
green. The session-start advisory's "every declared repair has already run this
session; what is listed is what it did not fix" was TRUE and useless — the hooks
were never in the declared set at all.

`mise-tasks/doctor.sh` did see it and emitted two `::error::` lines, which is the
CLOUD-1454 shape one layer up: a reporter is not a gate. Worse, its remedy named
`.claude/hooks/session-start.sh`, a program 7d18858 deleted, so the refusal was
right and its instruction could not be followed — an agent reading it top to
bottom gets `No such file or directory` and has to re-read the sentence to find
the half that works. Both remedies now name `mise run session:git-hooks`, and a
case asserts the named task and hook body resolve in the tree, because prose
cannot hold that and a case over the tracked file can.

`diagnose_commit_gate` is the predicate, and it is one predicate with two
callers: the row the bare report pushes, and the `doctor commit-gate` sub-verb.
A second implementation of it would be the defect this change repairs, one layer
along — `doctor.sh` and the committed authority disagreeing about what an
installed gate is.

THE SUB-VERB IS NOT A DUPLICATE OF THE ROW, and `the_bare_diagnosis_is_unchanged_by_the_sub_verb`
is where that has to be argued. The axis that case defends is `Mediator`'s: bare
`doctor` answers a property of the COMMIT, a sub-verb answers a property of the
WORLD. `doctor mediator` is excluded because install recency is a container fact
that would make a commit gate answer on it. Whether THIS clone's commit path runs
the gate is neither — it is a property of the checkout, byte-stable across
machines, the same class as `git-repo`. The sub-verb exists for an unrelated
reason: a `[[startup]]` row decides on an exit status and cannot select one line
out of a report, so it needs a command answering this question alone.

THE COMMON DIR, NEVER THE PER-WORKTREE ONE. `git::git_dir` is per-worktree and is
right for receipts and HEAD; hooks are not per-worktree, and git resolves
`hooks/<name>` against the common dir — so a linked worktree checked the other
way would report the gate missing while every commit in it runs the gate
correctly. `core.hooksPath` outranks both, resolved across every scope exactly as
git resolves it: a repository that redirects its hooks has hooks, and a probe
ignoring the key would send its owner to install a second copy somewhere git
never reads.

STATS, NEVER EXECUTES, and follows the symlink deliberately. Running the hook to
see whether it works is what `doctor.sh` does behind a probe variable; reaching
user-supplied code from a `read` verb on the derived allowlist is CLOUD-170's
actual invariant. Following the link is required rather than incidental — the
installer makes these symlinks into the tree precisely so the checked-in body
stays the one authority, and a check refusing to follow one would fail the shape
it certifies. The executable bit is asked because it is what git itself asks: a
present, non-executable hook is one git skips silently, which a file-existence
probe reports as healthy.

Pointer-only. The subjects are the hook NAMES — git's own vocabulary — and never
the directory they were looked for in: that path is absolute and per-machine,
which would defeat byte-stability and put the layout of somebody's disk in a
diagnostic that promises not to carry one. Could-not-look passes, this module's
posture: a directory whose hooks path cannot be resolved is one `git-repo` has
already failed on, and double-counting it would redden a checkout for a read that
failed elsewhere.

Refs: CLOUD-1398
The engine-side check landed in the previous commit and nothing asked it. This
is the half that makes it a gate rather than a reporter — `batten startup` now
carries a sixth row, and `--repair` installs the hooks a fresh clone is missing.

Measured on this container before the row existed: `batten startup` reported
five rows green while `git commit` ran neither `pre-commit` nor `commit-msg`.
The session-start advisory's "every declared repair has already run this session;
what is listed is what it did not fix" was true and useless, because the hooks
were never in the declared set at all.

NOT `hk install`, and that is the row's §8 answered by measurement rather than
preference. On this container `hk` resolves only through the pin — `mise exec --
hk --version` answers 1.56.1 while doctor's bare-PATH probe reports
`program-not-on-path hk` and the pin record is absent. `hk install` generates a
hook whose body calls `hk` BARE, so the hook it installs makes every `git commit`
fail with `hk: not found`: a repair that reads as installed and breaks the thing
it installed. `session:git-hooks` is the symlink-based form that works and is
already the session-start step, so this row adds an ASSERTION rather than a
second installer.

THE SUB-VERB RATHER THAN BARE `doctor`, which is `host-dependencies-present`'s
trap approached from the other side. A row decides on an exit status and cannot
select one line out of a report, so `check = ["batten", "doctor"]` would fail
here whenever any unrelated declared program was unreachable — the state this
very container is in, on the `hk` reading above — and would then fire a git-hook
repair that cannot fix that, reporting `repair-failed` forever over a gate that
is installed.

The repair writes under `$GIT_DIR/hooks`, outside the worktree and so outside
`protected`; the `repair` key in the committed authority is the authorisation to
run it.

Verified on this clone with the new binary installed: `batten doctor commit-gate`
reports `commit-gate ok` and `batten startup` reports six rows, none failed. On a
clone with no hooks the sub-verb reports `commit-gate failed commit-hook-missing
commit-msg pre-commit` at exit 1 and the row reports `failed not-provisioned`.

Refs: CLOUD-1398
Admits: 520e162f8cc4f9bc5202f66af1d7b60ebdbcd5ded6055a13c8d59ed8c7732584
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: batten.toml
Admits-anchor: call:12a12ca9677cc60befac3409f65d4b6ddb44e448
Admits-epoch: 34a3ca72f9f6da3b52df73c607444383dade972800f4d78cdf198bbede317b2e
Admits-author: alec@wenzowski.com
Admits-prev: 05ef30b5f6748b6aaf3d352dbe1c58268a1577c5b58e6abf898c802635b84d6f
Admits-answer-lost: CLOUD-1398 cannot be implemented at all. Its Ready block's §1 names the `[[startup]]` table in batten.toml as the authority for the declared precondition, and the engine-side `doctor commit-gate` check without a row asking it is precisely the reporter-that-is-not-a-gate defect the row was filed to close (the CLOUD-1454 class). The concrete cost is that `git commit` in a fresh clone keeps bypassing pre-commit and commit-msg while `batten startup` reports every row green, which is the measured state of this container.
Admits-answer-precondition: The class names `mise run config-lint` and `batten config` as the surface, and neither can ADD a row: batten has no verb that writes its own committed authority, deliberately, so a `[[startup]]` row can only arrive as a direct edit to batten.toml. The write is one a reviewer sees in the diff it lands in — it is on branch claude/cloud-1398-doctor-commit-gate, off origin/main, and lands through a draft PR that `land` readies only after `verify` (which runs config-lint) is green.
Admits-answer-rejected-route: Both. `config read first` is not a route to this outcome: I did read the config first — the five existing rows, the `protected` list and the `[[redirect]]` table are what this change is written against — but reading cannot add a row, so it is a precondition I satisfied rather than an alternative I could take instead. `patch run first` does not apply either: it addresses changing an EXISTING declaration, and this is an addition of a sixth row that no patch anchor exists for. Neither route weakens anything: this edit only ADDS a gate, it removes and loosens nothing, which is the opposite direction from the maximal-weakening case the protected list exists for.
Three corrections to the previous two commits, each made by a gate rather than by
argument. The engine-side check and its `[[startup]]` row stand; where the check
is ASKED changed, the verb was renamed, and the shell task is left alone.

THE ROW CAME OUT OF THE BARE REPORT, and the suite is what said so.
`container-health` renders `diagnose` at session start, so a `commit-gate` row
there made every checkout with no git hooks announce itself as unhealthy —
measured, it reddened
`contract_drift::a_session_seeded_at_session_start_is_silent_and_stays_silent`
over a fixture that has no hooks and wants none. The argument for putting it
there was that a clone's commit path is a property of the CHECKOUT rather than of
the world, byte-stable across machines, the same class as `git-repo`. That is
right about the predicate and wrong about the report: batten requires git hooks
of nobody, so WHETHER a commit path should run a gate is the consumer's
judgement, and minting it in `crates/batten` is non-negotiable rule 1's
violation. The predicate stays in the engine where a caller asks for it; the
judgement lives in this repository's own `[[startup]]` row.

`doctor gate` RATHER THAN `doctor commit-gate`, on a constraint measured rather
than reasoned. A man page is committed as the hyphen-joined command path, and
`surface.rs`'s suite maps that filename back by replacing EVERY hyphen — so
`batten-doctor-commit-gate.1` reads back as the command `doctor commit gate`,
renders nothing, and takes three cases down at once. No verb on this surface has
ever carried an internal hyphen; the reason is now written at the declaration and
beside the row, so the next author does not rediscover it.

THE SHELL TASK IS LEFT ALONE, and CLOUD-1398's own body is wrong about why it
could be edited. It claims the remedy-string fix "is exactly the class
`only_drops_a_retired_reference` and `drops_a_retired_name` already admit".
Measured, that arm requires every removed line to name a path THIS SAME DELTA
deleted, and this delta deletes nothing — `.claude/hooks/session-start.sh` went
in 7d18858. So `shell-rule-retired` refuses the edit and the two landable shapes
are retire it whole or leave it alone. Both governed files are reverted, and the
case that asserted the remedy is re-aimed at the half this change owns: the
`[[startup]]` row's own `repair` argv, which must name a task the manifest
declares and a hook body present in the tree. The stale `::error::` string
survives; it needs a retirement, which is not this row's shape.

Three obligations a new verb owes here, each found by its own gate: the derived
read-only allowlist and the emitted row set (both sorted, both committed), a
declared pointer-only disposition, and the generated man page and completions.

AND ONE DEFECT IN THE NEW CASE ITSELF. Its fixture row spawned bare `batten`, so
`startup` resolved it on PATH and the case graded the container's INSTALL
currency rather than this tree — it passed while the installed copy happened to
carry the verb and went red the moment the verb was renamed here. Pinned to
`CARGO_BIN_EXE_batten`. That is CLOUD-1650's subject arriving inside this suite,
which is worth recording rather than quietly fixing.

Refs: CLOUD-1398
Admits: 075493a65c80d64b2e6be85464946f2de0323a507426be70bf3f45427fd885ef
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: batten.toml
Admits-anchor: call:30f7730dec1c3a06f1caf172617dcdba08c3a24c
Admits-epoch: 4326c29555225ffac55fc1d3db17442d15d8697c4d6659204ee9c5553a08d068
Admits-author: alec@wenzowski.com
Admits-prev: 520e162f8cc4f9bc5202f66af1d7b60ebdbcd5ded6055a13c8d59ed8c7732584
Admits-answer-lost: CLOUD-1398 cannot be implemented at all. Its Ready block's §1 names the `[[startup]]` table in batten.toml as the authority for the declared precondition, and the engine-side `doctor commit-gate` check without a row asking it is precisely the reporter-that-is-not-a-gate defect the row was filed to close (the CLOUD-1454 class). The concrete cost is that `git commit` in a fresh clone keeps bypassing pre-commit and commit-msg while `batten startup` reports every row green, which is the measured state of this container.
Admits-answer-precondition: The class names `mise run config-lint` and `batten config` as the surface, and neither can ADD a row: batten has no verb that writes its own committed authority, deliberately, so a `[[startup]]` row can only arrive as a direct edit to batten.toml. The write is one a reviewer sees in the diff it lands in — it is on branch claude/cloud-1398-doctor-commit-gate, off origin/main, and lands through a draft PR that `land` readies only after `verify` (which runs config-lint) is green.
Admits-answer-rejected-route: Both. `config read first` is not a route to this outcome: I did read the config first — the five existing rows, the `protected` list and the `[[redirect]]` table are what this change is written against — but reading cannot add a row, so it is a precondition I satisfied rather than an alternative I could take instead. `patch run first` does not apply either: it addresses changing an EXISTING declaration, and this is an addition of a sixth row that no patch anchor exists for. Neither route weakens anything: this edit only ADDS a gate, it removes and loosens nothing, which is the opposite direction from the maximal-weakening case the protected list exists for.
The previous commit claimed to revert `mise-tasks/doctor.sh` and did not.
`git checkout -- <path>` restores from HEAD, and HEAD already carried the edit,
so the revert restored the edited bytes and the PR kept a change
`shell-retirement` refuses. Reverted against `origin/main` this time, which is
the comparison that was actually meant.

The finding channel did not catch it either: `shell-rule-retired` read 0 while
the file still differed from the base. What surfaced it was a review bot listing
the file among the PR diff, which is worth recording — the store lagged the tree,
and the tree wins.

So the stale `::error::` remedy naming `.claude/hooks/session-start.sh` survives
on main, and `tests/doctor.bats` still asserts it, which keeps the two
consistent. Fixing it needs a retirement rather than an edit; the reason is on
CLOUD-1398.

Refs: CLOUD-1398
… it is too

The `windows` job reddened on 9891539 and nothing local could have caught it:
`verify` type-checks the Windows triple through `cross-check` but runs the suite
on this host, so a case whose PREMISE is unix-only passes here and fails there.

`a_present_but_unrunnable_hook_reads_as_missing` asserted that a mode-0644 hook
reads as missing. On Windows there is no executable bit for git to consult and it
runs any hook file it finds, so `is_runnable_hook` answering `true` for a present
file is CORRECT rather than a gap — the predicate tracks what git will actually
do on each platform, which is the whole point of asking about the bit at all on
the platform that has one. What was wrong is a case asserting the unix reading
everywhere. Measured: `left: "commit-gate ok"` against
`right: "commit-gate failed commit-hook-missing pre-commit"`.

The two sibling cases stay UNGATED deliberately, and that asymmetry is the
statement: they turn on a hook being ABSENT, which a `git init` produces on both
platforms, so their premise holds everywhere. Both passed on the same Windows
runner that failed this one, which is what makes the split a reading rather than
a guess.

Refs: CLOUD-1398
…se away

The previous commit's `#[cfg(unix)]` was itself the defect this repository has a
gate for, and `platform-gated-test-added` refused it by name. Its rationale is
exactly right: narrowing a case to one platform turns a red leg green while
leaving the other contract UNSTATED and one arm never compiled on the host that
authors it, so the next edit to that arm is discovered by CI rather than locally.

`cfg!` in the body is the remedy the rule's own comment names. Both arms compile
on every target, and the Windows expectation is written down: with no executable
bit for git to consult, a present hook file IS a live hook, so the row is
honestly satisfied and `commit-gate ok` is the right answer there. The unix arm
keeps the reading that matters on a platform with a mode bit — a 0644 hook is one
git will skip, and a probe that only stats for existence calls that healthy.

Both readings are correct; that is the substance rather than a workaround.
`is_runnable_hook` tracks what git will actually do on each platform, and the
case now asserts both instead of asserting one and hiding the other.

Refs: CLOUD-1398
`CommandDecl` gained a required `exits` field on `main` while this branch was in
flight, so the rebase left the `doctor gate` row uncompilable — the cost of a
long-lived branch against a trunk this active, and the compiler caught it rather
than a reviewer.

`EXITS_STANDARD`, whose content is the code it OMITS: `Violation` is unreachable
here, inheriting the promise bare `doctor` makes. A mediating harness reads `2`
as a deny, and "this clone has no commit hooks" is not "policy says no".

Refs: CLOUD-1398
…d base

Reverts dd6b75695178049d04a28a3c7a433fff63b18601.

The `exits` field it declared does not exist on `main`. `land` had rebased this
branch onto a base BORROWED from another branch's unlanded work — its own output
says so, "this tree is SPECULATIVE — it carries 0f2f85c borrowed from
0f2f85c..." — so the required-field compile error that prompted it came from
that speculative base rather than from the trunk.

The commit was therefore correct against a tree that does not exist yet and wrong
against the one this branch lands on: `git show origin/main:crates/batten/src/surface.rs`
carries no `exits:` at all, and with the branch back on real `main` the row
stopped compiling for the opposite reason it was written.

The speculative warning is load-bearing rather than noise, which is the lesson
worth keeping: a fix authored against a borrowed base is a fix for somebody
else's branch, and it fails in the direction that looks like progress — the
compiler was satisfied at the moment of writing. If `CommandDecl` does gain
`exits`, this row wants `EXITS_STANDARD`, whose content is the code it omits:
`Violation` is unreachable, because a mediating harness reads `2` as a deny and
"this clone has no commit hooks" is not "policy says no". That belongs in the
change that adds the field, not here.

Refs: CLOUD-1398
`command -v batten` asks whether a binary is on PATH. The failure that
matters here is a binary that IS on PATH and cannot read this tree — the
release predating a `batten.toml` key, which this repository reaches
constantly because keys land between releases.

Measured on this branch's own landing lap: the installed 0.0.151 refused
`[lease]` with "the config declares a key this build predates". A
presence-only guard sends the task to that binary and fails; `&&` sends
it to the build.

This restores what the unconditional `cargo run` gave for free and what
`target-prune` has always done — try the installed binary, fall back to
a build when it cannot do the job. The previous revision claimed to copy
that shape and copied only half of it.

Refs: CLOUD-1620
Self-correction. The previous commit raised `LAND_LOCK_MAX_WAITS` and
taught AGENTS.md about exit codes 4 and 5. Both described
`mise-tasks/land.sh`, which CLOUD-843 has already retired onto the
engine — it is absent from `origin/main`, and nothing under
`crates/batten/src` reads that variable. So the knob was dead config
the moment it was written, and the contract named exit codes that no
longer exist: `exit.rs` is the house-style table, 0/1/2/3, with no 4 or 5.

The error was reading the tree as it stood at session start and acting on
it after a rebase had moved `main` 129 commits forward underneath. The
same stale-reading class as the defect this branch opened on.

What survives is the property rather than the numbers, because the
numbers have now moved once: a stop that spent nothing is not a failing
branch. The engine already says so in its own refusal — "A saturated
fleet is not a failing branch — run this again" — which is the imperative
the shell task's lease exit never carried, so the fix this branch was
going to file against CLOUD-843 largely arrived with it.

AGENTS.md keeps the correction that mattered: the loop has no wall clock
but does have counted stops, where it previously claimed "no cap" and
"three things only" while six further stops existed.

Refs: CLOUD-1620
wenzowski added a commit that referenced this pull request Sep 9, 2026
…declares

`batten.toml:352-357` pins mise to 2026.9.1 by per-platform URL and digest.
`jdx/mise-action` cannot see it. The `uses:` digest pins the ACTION; which MISE
that action installs is a separate resolution, and unset it takes whatever is
newest when the job starts. So the tree declares one toolchain and CI runs
another, with nothing comparing them.

Measured 2026-09-08, latest resolved to v2026.9.3 whose linux-x64 asset 404s:

  curl -fsSL .../v2026.9.3/mise-v2026.9.3-linux-x64.tar.zst
  curl: (22) The requested URL returned error: 404      x5 retries

Every job in every workflow died at that step inside 11 seconds.
`release-artifacts` run 34206034815 lost all 8 jobs in 56s and v0.0.152
published carrying `batten.schema.json` and none of its seven target binaries;
`ci.yml` run 34220313465 lost 5 jobs in 53s, so nothing could land at all.

THE PROVISION ROW ALREADY NAMED THIS HARM, one layer down: "a runner that
updated itself under a container would make two sessions on one commit run
different toolchains, which is the disagreement `[[provision]]`'s pinning
exists to remove." That is exactly what happened, and the pin written to
prevent it does not reach the layer where it occurred.

This carries the pin only. The gate half of CLOUD-1672 -- a predicate refusing
a `mise-action` step whose declared version differs from the `[[provision]]`
row's, so the two cannot drift again -- is not in this commit, and the
remaining workflows are not yet pinned. Both follow on this branch.

Pinned here: `ci.yml` (4 steps), `release-artifacts.yml` (3),
`release-plz.yml` (4), `auto-bot-land.yml` (1).

Refs: CLOUD-1672, CLOUD-1574, CLOUD-1455

Admits: 14a7813f18d11c452bb5851acf9aad131e3f63a3e7c0ede84f7ca15db7325b7e
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/ci.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: d6ff1328442bc15a7f1017cdb28a0190b0c3575b6a85e67a75752d80dbe6f9bc
Admits-answer-lost: CI stays down. Every job in this workflow dies at `jdx/mise-action` in ~11s because the action resolves mise latest (v2026.9.3) whose linux-x64 asset 404s, while `batten.toml` pins 2026.9.1. Nothing in the repository can land until the action installs the version the tree declares.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 5a69f466467c58937d55e1c7d5b79e3259b2d190cac2e69e9d4a695d3f734106
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-artifacts.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: v0.0.152 published with zero binaries and the next release does the same. All 8 jobs of run 34206034815 died at `jdx/mise-action` in 11s because the action resolves mise latest (v2026.9.3), whose linux-x64 asset 404s, while `batten.toml` pins 2026.9.1.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 168f6f87f302b1fd73d8e77e621eae3c7f5e3d5752d7ee1f7c61fcc2eb1e76b6
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-plz.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: 4690dbacf228821c8ab51e1aee4bf72d157a3bda3edeb61d491cb9a426c38239
Admits-answer-lost: The release lane keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins, so the tree declares one toolchain and the release runs another — the drift that published v0.0.152 with zero binaries.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e320b15216a7f81f8b17b231e5f62f128ca8b373499501bcc3fdbbf7d8ecf45f
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-plz.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: 168f6f87f302b1fd73d8e77e621eae3c7f5e3d5752d7ee1f7c61fcc2eb1e76b6
Admits-answer-lost: The one job that actually cuts the release stays unpinned while the three cache-warm jobs beside it are pinned — the worst of both, since the file would read as fixed while the step that publishes the tag still installs whatever mise is newest.
Admits-answer-precondition: CLOUD-1672, second write to this file. The three steps carrying a `with:` block were pinned by the previous admission; the fourth (the `release-plz` job's own step, line 338) declares no `with:` at all, so it needs a different insertion and a separate write. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name; the mutating-verb table covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — the file was re-read to find that this fourth step carries no `with:` block.

Admits: 8a95229ff41806a9b5a05b08a17e49af42dfe91abdcf79f76bd1a625bb686797
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/auto-bot-land.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: .github/workflows/auto-bot-land.yml keeps installing whatever mise is newest at job start rather than the 2026.9.1 that `batten.toml` pins. Measured 2026-09-08: latest resolved to v2026.9.3, whose linux-x64 asset 404s, and every job in every workflow died at this step inside 11 seconds. A workflow left unpinned reintroduces the outage on its own next run.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.
wenzowski added a commit that referenced this pull request Sep 9, 2026
…lows

Continues the CLOUD-1672 pin across seven more workflows: `rust.yml` (4 steps),
`commit-lint.yml`, `fast-forward.yml`, `perf.yml`, `coverage.yml`, `zizmor.yml`
and `fuzz.yml`.

Three of these are load-bearing beyond their own run. `commit-lint` is a
required check, so an unpinned step there reds every pull request.
`fast-forward` is the landing path itself. `perf` records the series from a
clock, so an unpinned toolchain silently changes what the series measures
rather than failing.

Thirteen workflows remain, and the gate half of CLOUD-1672 -- a predicate
refusing a `mise-action` step whose declared version differs from the
`[[provision]]` row's -- is still to come on this branch.

Refs: CLOUD-1672

Admits: 8efa23f5401b475d58c607f3ae8e2adf60dbca5cd2df506ffb00546de908fcb6
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/rust.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 71b4de9ae1a0041ffa6098d40137230060304c3db9d59a92f0fb951848ce293f
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/commit-lint.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. `commit-lint` is a required check, so an unpinned step here reds every pull request on its own next run.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: b2ce26a31ad7115c22e0a454348b17fd5de11435bde11478b92e86dab28e560e
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/fast-forward.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This is the landing path itself. It keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins; measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. Unpinned, the fast-forward that lands every branch fails on its own next run.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: ed675dc23520c7be2878c592f48d6a67b2c5a7e4b61a81be4e372ee08abd32b6
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/perf.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. It also records the perf series from a clock, so an unpinned toolchain silently changes what the series measures.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e6bdca0e483274fb95b00b750830540c12fb431e14186b8ddf90c7ef5f161cc3
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/coverage.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e328c82bb610d6dfab5c7457f7f754ea003496c4f464488a7529465336a3a3f5
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/zizmor.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 32cdb5543b38da755a3b2e116dffa060cbe7e321802749e2b6a0c3c66c6d8465
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/fuzz.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.
wenzowski added a commit that referenced this pull request Sep 9, 2026
Completes the CLOUD-1672 pin. Every `jdx/mise-action` step in
`.github/workflows/**` now declares `version: 2026.9.1`, matching
`batten.toml`'s `[[provision]]` row; `git grep -L` reports no workflow
carrying a `mise-action` step without it.

Three of these are pinned for a reason beyond their own install, and each
says so at the step rather than here:

- `lock-currency` is the clock that BUMPS pins, so unpinned it is the one job
  a bad upstream release stops -- the job that would have surfaced this.
- `arch-probe` compares two runner architectures whose validity rests on the
  arms differing only in `runs-on`; an unpinned mise lets them install
  different toolchains and destroys the comparison silently rather than
  failing it.
- `hook-latency-drift` and `nonverdict-rate` record series on a clock, where
  a drifting toolchain changes what is measured rather than reddening.

Still to come on this branch: the gate half of CLOUD-1672 -- a predicate
refusing a `mise-action` step whose declared version differs from the
`[[provision]]` row's, so the two authorities cannot drift apart again.

Refs: CLOUD-1672

Admits: 53fe1b9a28fb798ee6a9fe6ec34dbd51ff974ca27d8923e89ad919695f31cb5f
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-assets.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow publishes release assets and keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. Unpinned, it reintroduces the outage that published v0.0.152 with no binaries.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 9127e19ea5c4a7b38d85eab3f54dc5572b0c0611be0ce158f0d57f2b5d40e536
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/auto-release-land.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow lands the release PR and keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. Unpinned, the release lane stays broken at its landing step.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 54cc2b5a7371515470c55b5911e812cad54824759c7e04b83b394f4795f0d7c7
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/mutant.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 53c89ed482c8e3b6e2262eefd30c407714eeea6beb70a300c0e8bc376f2ffb84
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/lock-currency.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This is the clock that bumps pinned tools, so an unpinned mise here means the job that MOVES pins cannot run at all. Measured 2026-09-08: the action resolved v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s — including the one that would otherwise have surfaced the bad upstream release.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: c36024aa70f3e151f72e152f48965917416625d2bd16216b9abf8848a9501e70
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/ci-drift.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 523d9f0aa526c4181f9d9d9999c2d6a5031be9496707953e17c4be535d211f24
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/timeout-drift.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 75b185566d9fd6163a2549832a838e1ada9326be61d72c59723faedb42707aef
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/land-divergence.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 4649eab6a0922340d2be1ce0e47a8ffd507b47740ae55f85453dc6af35d2bfad
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/hook-latency-drift.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. It also measures the gate's own latency on a clock, so an unpinned toolchain silently changes what the series measures rather than failing.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: d2fb5c29b070b9f91cbdb94a9608bcf2e0cf9963b49c675e44216fbe85391a7e
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/nonverdict-rate.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. It records a rate on a clock, so an unpinned toolchain silently changes what the series measures rather than failing.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 14c4bf8e70e719ee233f6ea75a26e6c39ad884427ec74509fdc8765bcb2e133c
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/scorecard.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 614a8ab2953de8ff590164a654d0c667b821895e4c92af1273e92116e10aa28d
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/zizmor-advisories.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e74e0af2f0245d98a75d0ef01619e24b90929e67fbeb016e9d9fa42689a7d6c9
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/branch-hygiene.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 0d5560b6a87c0ed0a7917002a7eedb5634d010b4200eb65cc46bb866516c8381
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/arch-probe.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This is the matched arm-versus-x64 probe, whose whole validity rests on both arms differing ONLY in `runs-on`. An unpinned mise means the two arms can install different toolchains, which silently destroys the comparison rather than failing it — and the last unpinned resolution, v2026.9.3, 404s on linux-x64 so the probe cannot run at all.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.
@wenzowski
wenzowski force-pushed the claude/glacial-ci-regression-d9qtr3 branch from 267bd13 to f4e190d Compare September 9, 2026 00:38
wenzowski added a commit that referenced this pull request Sep 9, 2026
The pin half of CLOUD-1672 landed across 24 workflows and nothing held it
there. This is the mechanism, so the rule is not prose (non-negotiable 2).

`action-version-matches-the-pin` refuses a workflow step installing the
toolchain at a version the tree does not pin: `job pin other` where a declared
version disagrees with the `[[provision]]` row, `job pin missing` where the step
declares none and hands the choice to the installer. The comparison is against
the committed row and never a literal -- a module carrying `2026.9.1` would be a
THIRD authority and would go stale the first time the pin moved.

Both documents are found by SHAPE, never by filename: a document carrying
`jobs`, a document carrying `provision`. A preset ships to every consumer, so
rule 1 forbids naming their paths; the enabling row's `sources` is where this
consumer says which of its files those are.

No provision row for the tool means the row ABSTAINS rather than refuses. A
project that pins nothing has nothing to disagree with, and refusing there would
be a verdict about their configuration instead of about drift between two of
their own statements.

THE ENGINE HALF: A MANIFEST DECLARES A SCOPE PER MODULE.

`Manifest.scope` said a preset decides exactly one surface -- true of every
preset that existed when it was written, false the moment a subject has
something to say about both. `mise` is that subject: whether an agent reproduces
a task's argv is a question about a CALL, whether the toolchain a workflow
installs matches the pin is a question about the TREE. They are one preset
because they are one concern, and a consumer should not have to know the engine
once could not express that.

Two constraints assumed the old shape and both now key on the pair:

- the load-time scope check compared one manifest scope for equality; it asks
  `decides(scope)` now, and names the scopes the preset does decide.
- the duplicate-registration check keyed on the source name, so the second row
  enabling one preset at the other scope read as dead config. It is not: each
  row compiles only the modules at its own scope, so they share a name and
  decide disjoint surfaces.

Selecting modules by scope is load-bearing rather than tidy. A tree module
compiled onto the call surface reads keys the engine never builds there, Rego
reads undefined as "does not hold", and the result is a dead gate byte-identical
to a clean tree. `check_tree_paths_are_emittable` catches that today; this keeps
the case from arising rather than relying on a downstream guard to notice.

VERIFIED, AND THE REAL-TREE ARM IS THE ONE THAT COUNTS. `policy test` 64
bundles, 814 passed; the four preset tiers 39/39. Both of those supply their own
pin document, so neither can show that this consumer's `sources` glob actually
delivers `batten.toml` -- and if it did not, the pin would be absent, the module
would abstain by design, and the gate would report clean forever. So it was
driven over this checkout: clean as committed, and moving one workflow's version
produced `.github/workflows/scorecard.yml:53 action-version-matches-the-pin`,
exit 2, pointer-only. The file was restored with `git checkout --` and the diff
is unchanged. `a_drifted_version_in_this_repository_is_refused` is that same
proof as a standing case.

Refs: CLOUD-1672, CLOUD-1181, CLOUD-404

Admits: 48d1eec18e1480b0a3f1f941763ee6705ff0eadb5140db2bb02a5fbc101081c5
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: batten.toml
Admits-anchor: call:8c6419950ad9ffd05f57f842005d1004860e85da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: e1aaff7ef76c6a51d8d71c951a03bfdfec14b5cbaebe9bb738612f15bcda99f3
Admits-answer-lost: The gate half of CLOUD-1672 does not run. The pin is landed across all 24 workflows but nothing holds it there, so the next architecture move, Renovate bump or hand edit silently reintroduces the drift that published v0.0.152 with zero binaries. A rule without its mechanism is half a change by this repository's own non-negotiable rule 2.
Admits-answer-precondition: CLOUD-1672 gate half. The redirect for `batten.toml` is "change it in a pull request" with `config-lint` as the check before it lands — a statement about how the change must LAND, not a surface that can express it: only a direct write can add a `[[rule]]` row. The row enables the `mise` preset at tree scope with the `sources` globs the predicate reads; without it the module ships and decides nothing, which is the dead-gate shape the whole row exists to close. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — the `ci-hygiene` row was read as the shape to copy, since it is the existing tree-scoped preset row carrying workflow `sources` and `line_sources`.

Admits: 6e8cd01a0028a1c5510ed46c8c6539d3976ffc6486db86b606b140723b76865f
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/scorecard.yml
Admits-anchor: call:8c6419950ad9ffd05f57f842005d1004860e85da
Admits-epoch: 3b025bfdaaedf9f46a55c4b09fba627a7d599c231956e6730c43863d886b4d05
Admits-author: alec@wenzowski.com
Admits-prev: 14c4bf8e70e719ee233f6ea75a26e6c39ad884427ec74509fdc8765bcb2e133c
Admits-answer-lost: The gate ships unproven on the surface that matters. The load-time and compiled tiers both supply their own fixture documents, so neither can show that this consumer's `sources` glob actually delivers `batten.toml` to the module — and if it does not, the pin document is absent, the module abstains BY DESIGN, and the gate reports clean forever over a tree it never compared. That is the exact dead-gate shape this row exists to close, reachable only through the row's own configuration.
Admits-answer-precondition: A VERIFICATION WRITE, reverted in the same turn. CLOUD-1672's new gate reports clean over this tree, and clean is byte-identical to a dead gate — so one workflow's `version:` is being changed to a wrong value to confirm the predicate names it, then changed straight back. Only a direct write can do that; no read surface can make a gate fire. Nothing of it reaches a commit: the file is restored with `git checkout --` before the tree is staged, and the diff will show it unchanged.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s new `mise-preset-tree` row and its `sources` list were read, and reading them is what identified the glob as the one thing no fixture can verify.
wenzowski added a commit that referenced this pull request Sep 9, 2026
…declares

`batten.toml:352-357` pins mise to 2026.9.1 by per-platform URL and digest.
`jdx/mise-action` cannot see it. The `uses:` digest pins the ACTION; which MISE
that action installs is a separate resolution, and unset it takes whatever is
newest when the job starts. So the tree declares one toolchain and CI runs
another, with nothing comparing them.

Measured 2026-09-08, latest resolved to v2026.9.3 whose linux-x64 asset 404s:

  curl -fsSL .../v2026.9.3/mise-v2026.9.3-linux-x64.tar.zst
  curl: (22) The requested URL returned error: 404      x5 retries

Every job in every workflow died at that step inside 11 seconds.
`release-artifacts` run 34206034815 lost all 8 jobs in 56s and v0.0.152
published carrying `batten.schema.json` and none of its seven target binaries;
`ci.yml` run 34220313465 lost 5 jobs in 53s, so nothing could land at all.

THE PROVISION ROW ALREADY NAMED THIS HARM, one layer down: "a runner that
updated itself under a container would make two sessions on one commit run
different toolchains, which is the disagreement `[[provision]]`'s pinning
exists to remove." That is exactly what happened, and the pin written to
prevent it does not reach the layer where it occurred.

This carries the pin only. The gate half of CLOUD-1672 -- a predicate refusing
a `mise-action` step whose declared version differs from the `[[provision]]`
row's, so the two cannot drift again -- is not in this commit, and the
remaining workflows are not yet pinned. Both follow on this branch.

Pinned here: `ci.yml` (4 steps), `release-artifacts.yml` (3),
`release-plz.yml` (4), `auto-bot-land.yml` (1).

Refs: CLOUD-1672, CLOUD-1574, CLOUD-1455

Admits: 14a7813f18d11c452bb5851acf9aad131e3f63a3e7c0ede84f7ca15db7325b7e
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/ci.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: d6ff1328442bc15a7f1017cdb28a0190b0c3575b6a85e67a75752d80dbe6f9bc
Admits-answer-lost: CI stays down. Every job in this workflow dies at `jdx/mise-action` in ~11s because the action resolves mise latest (v2026.9.3) whose linux-x64 asset 404s, while `batten.toml` pins 2026.9.1. Nothing in the repository can land until the action installs the version the tree declares.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 5a69f466467c58937d55e1c7d5b79e3259b2d190cac2e69e9d4a695d3f734106
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-artifacts.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: v0.0.152 published with zero binaries and the next release does the same. All 8 jobs of run 34206034815 died at `jdx/mise-action` in 11s because the action resolves mise latest (v2026.9.3), whose linux-x64 asset 404s, while `batten.toml` pins 2026.9.1.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 168f6f87f302b1fd73d8e77e621eae3c7f5e3d5752d7ee1f7c61fcc2eb1e76b6
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-plz.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: 4690dbacf228821c8ab51e1aee4bf72d157a3bda3edeb61d491cb9a426c38239
Admits-answer-lost: The release lane keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins, so the tree declares one toolchain and the release runs another — the drift that published v0.0.152 with zero binaries.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e320b15216a7f81f8b17b231e5f62f128ca8b373499501bcc3fdbbf7d8ecf45f
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-plz.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: 168f6f87f302b1fd73d8e77e621eae3c7f5e3d5752d7ee1f7c61fcc2eb1e76b6
Admits-answer-lost: The one job that actually cuts the release stays unpinned while the three cache-warm jobs beside it are pinned — the worst of both, since the file would read as fixed while the step that publishes the tag still installs whatever mise is newest.
Admits-answer-precondition: CLOUD-1672, second write to this file. The three steps carrying a `with:` block were pinned by the previous admission; the fourth (the `release-plz` job's own step, line 338) declares no `with:` at all, so it needs a different insertion and a separate write. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name; the mutating-verb table covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — the file was re-read to find that this fourth step carries no `with:` block.

Admits: 8a95229ff41806a9b5a05b08a17e49af42dfe91abdcf79f76bd1a625bb686797
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/auto-bot-land.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: .github/workflows/auto-bot-land.yml keeps installing whatever mise is newest at job start rather than the 2026.9.1 that `batten.toml` pins. Measured 2026-09-08: latest resolved to v2026.9.3, whose linux-x64 asset 404s, and every job in every workflow died at this step inside 11 seconds. A workflow left unpinned reintroduces the outage on its own next run.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.
wenzowski added a commit that referenced this pull request Sep 9, 2026
…lows

Continues the CLOUD-1672 pin across seven more workflows: `rust.yml` (4 steps),
`commit-lint.yml`, `fast-forward.yml`, `perf.yml`, `coverage.yml`, `zizmor.yml`
and `fuzz.yml`.

Three of these are load-bearing beyond their own run. `commit-lint` is a
required check, so an unpinned step there reds every pull request.
`fast-forward` is the landing path itself. `perf` records the series from a
clock, so an unpinned toolchain silently changes what the series measures
rather than failing.

Thirteen workflows remain, and the gate half of CLOUD-1672 -- a predicate
refusing a `mise-action` step whose declared version differs from the
`[[provision]]` row's -- is still to come on this branch.

Refs: CLOUD-1672

Admits: 8efa23f5401b475d58c607f3ae8e2adf60dbca5cd2df506ffb00546de908fcb6
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/rust.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 71b4de9ae1a0041ffa6098d40137230060304c3db9d59a92f0fb951848ce293f
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/commit-lint.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. `commit-lint` is a required check, so an unpinned step here reds every pull request on its own next run.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: b2ce26a31ad7115c22e0a454348b17fd5de11435bde11478b92e86dab28e560e
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/fast-forward.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This is the landing path itself. It keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins; measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. Unpinned, the fast-forward that lands every branch fails on its own next run.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: ed675dc23520c7be2878c592f48d6a67b2c5a7e4b61a81be4e372ee08abd32b6
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/perf.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. It also records the perf series from a clock, so an unpinned toolchain silently changes what the series measures.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e6bdca0e483274fb95b00b750830540c12fb431e14186b8ddf90c7ef5f161cc3
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/coverage.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e328c82bb610d6dfab5c7457f7f754ea003496c4f464488a7529465336a3a3f5
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/zizmor.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 32cdb5543b38da755a3b2e116dffa060cbe7e321802749e2b6a0c3c66c6d8465
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/fuzz.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.
wenzowski added a commit that referenced this pull request Sep 9, 2026
Completes the CLOUD-1672 pin. Every `jdx/mise-action` step in
`.github/workflows/**` now declares `version: 2026.9.1`, matching
`batten.toml`'s `[[provision]]` row; `git grep -L` reports no workflow
carrying a `mise-action` step without it.

Three of these are pinned for a reason beyond their own install, and each
says so at the step rather than here:

- `lock-currency` is the clock that BUMPS pins, so unpinned it is the one job
  a bad upstream release stops -- the job that would have surfaced this.
- `arch-probe` compares two runner architectures whose validity rests on the
  arms differing only in `runs-on`; an unpinned mise lets them install
  different toolchains and destroys the comparison silently rather than
  failing it.
- `hook-latency-drift` and `nonverdict-rate` record series on a clock, where
  a drifting toolchain changes what is measured rather than reddening.

Still to come on this branch: the gate half of CLOUD-1672 -- a predicate
refusing a `mise-action` step whose declared version differs from the
`[[provision]]` row's, so the two authorities cannot drift apart again.

Refs: CLOUD-1672

Admits: 53fe1b9a28fb798ee6a9fe6ec34dbd51ff974ca27d8923e89ad919695f31cb5f
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-assets.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow publishes release assets and keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. Unpinned, it reintroduces the outage that published v0.0.152 with no binaries.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 9127e19ea5c4a7b38d85eab3f54dc5572b0c0611be0ce158f0d57f2b5d40e536
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/auto-release-land.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow lands the release PR and keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. Unpinned, the release lane stays broken at its landing step.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 54cc2b5a7371515470c55b5911e812cad54824759c7e04b83b394f4795f0d7c7
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/mutant.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 53c89ed482c8e3b6e2262eefd30c407714eeea6beb70a300c0e8bc376f2ffb84
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/lock-currency.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This is the clock that bumps pinned tools, so an unpinned mise here means the job that MOVES pins cannot run at all. Measured 2026-09-08: the action resolved v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s — including the one that would otherwise have surfaced the bad upstream release.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: c36024aa70f3e151f72e152f48965917416625d2bd16216b9abf8848a9501e70
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/ci-drift.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 523d9f0aa526c4181f9d9d9999c2d6a5031be9496707953e17c4be535d211f24
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/timeout-drift.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 75b185566d9fd6163a2549832a838e1ada9326be61d72c59723faedb42707aef
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/land-divergence.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 4649eab6a0922340d2be1ce0e47a8ffd507b47740ae55f85453dc6af35d2bfad
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/hook-latency-drift.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. It also measures the gate's own latency on a clock, so an unpinned toolchain silently changes what the series measures rather than failing.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: d2fb5c29b070b9f91cbdb94a9608bcf2e0cf9963b49c675e44216fbe85391a7e
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/nonverdict-rate.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. It records a rate on a clock, so an unpinned toolchain silently changes what the series measures rather than failing.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 14c4bf8e70e719ee233f6ea75a26e6c39ad884427ec74509fdc8765bcb2e133c
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/scorecard.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 614a8ab2953de8ff590164a654d0c667b821895e4c92af1273e92116e10aa28d
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/zizmor-advisories.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e74e0af2f0245d98a75d0ef01619e24b90929e67fbeb016e9d9fa42689a7d6c9
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/branch-hygiene.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 0d5560b6a87c0ed0a7917002a7eedb5634d010b4200eb65cc46bb866516c8381
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/arch-probe.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This is the matched arm-versus-x64 probe, whose whole validity rests on both arms differing ONLY in `runs-on`. An unpinned mise means the two arms can install different toolchains, which silently destroys the comparison rather than failing it — and the last unpinned resolution, v2026.9.3, 404s on linux-x64 so the probe cannot run at all.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.
wenzowski added a commit that referenced this pull request Sep 9, 2026
The pin half of CLOUD-1672 landed across 24 workflows and nothing held it
there. This is the mechanism, so the rule is not prose (non-negotiable 2).

`action-version-matches-the-pin` refuses a workflow step installing the
toolchain at a version the tree does not pin: `job pin other` where a declared
version disagrees with the `[[provision]]` row, `job pin missing` where the step
declares none and hands the choice to the installer. The comparison is against
the committed row and never a literal -- a module carrying `2026.9.1` would be a
THIRD authority and would go stale the first time the pin moved.

Both documents are found by SHAPE, never by filename: a document carrying
`jobs`, a document carrying `provision`. A preset ships to every consumer, so
rule 1 forbids naming their paths; the enabling row's `sources` is where this
consumer says which of its files those are.

No provision row for the tool means the row ABSTAINS rather than refuses. A
project that pins nothing has nothing to disagree with, and refusing there would
be a verdict about their configuration instead of about drift between two of
their own statements.

THE ENGINE HALF: A MANIFEST DECLARES A SCOPE PER MODULE.

`Manifest.scope` said a preset decides exactly one surface -- true of every
preset that existed when it was written, false the moment a subject has
something to say about both. `mise` is that subject: whether an agent reproduces
a task's argv is a question about a CALL, whether the toolchain a workflow
installs matches the pin is a question about the TREE. They are one preset
because they are one concern, and a consumer should not have to know the engine
once could not express that.

Two constraints assumed the old shape and both now key on the pair:

- the load-time scope check compared one manifest scope for equality; it asks
  `decides(scope)` now, and names the scopes the preset does decide.
- the duplicate-registration check keyed on the source name, so the second row
  enabling one preset at the other scope read as dead config. It is not: each
  row compiles only the modules at its own scope, so they share a name and
  decide disjoint surfaces.

Selecting modules by scope is load-bearing rather than tidy. A tree module
compiled onto the call surface reads keys the engine never builds there, Rego
reads undefined as "does not hold", and the result is a dead gate byte-identical
to a clean tree. `check_tree_paths_are_emittable` catches that today; this keeps
the case from arising rather than relying on a downstream guard to notice.

VERIFIED, AND THE REAL-TREE ARM IS THE ONE THAT COUNTS. `policy test` 64
bundles, 814 passed; the four preset tiers 39/39. Both of those supply their own
pin document, so neither can show that this consumer's `sources` glob actually
delivers `batten.toml` -- and if it did not, the pin would be absent, the module
would abstain by design, and the gate would report clean forever. So it was
driven over this checkout: clean as committed, and moving one workflow's version
produced `.github/workflows/scorecard.yml:53 action-version-matches-the-pin`,
exit 2, pointer-only. The file was restored with `git checkout --` and the diff
is unchanged. `a_drifted_version_in_this_repository_is_refused` is that same
proof as a standing case.

Refs: CLOUD-1672, CLOUD-1181, CLOUD-404

Admits: 48d1eec18e1480b0a3f1f941763ee6705ff0eadb5140db2bb02a5fbc101081c5
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: batten.toml
Admits-anchor: call:8c6419950ad9ffd05f57f842005d1004860e85da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: e1aaff7ef76c6a51d8d71c951a03bfdfec14b5cbaebe9bb738612f15bcda99f3
Admits-answer-lost: The gate half of CLOUD-1672 does not run. The pin is landed across all 24 workflows but nothing holds it there, so the next architecture move, Renovate bump or hand edit silently reintroduces the drift that published v0.0.152 with zero binaries. A rule without its mechanism is half a change by this repository's own non-negotiable rule 2.
Admits-answer-precondition: CLOUD-1672 gate half. The redirect for `batten.toml` is "change it in a pull request" with `config-lint` as the check before it lands — a statement about how the change must LAND, not a surface that can express it: only a direct write can add a `[[rule]]` row. The row enables the `mise` preset at tree scope with the `sources` globs the predicate reads; without it the module ships and decides nothing, which is the dead-gate shape the whole row exists to close. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — the `ci-hygiene` row was read as the shape to copy, since it is the existing tree-scoped preset row carrying workflow `sources` and `line_sources`.

Admits: 6e8cd01a0028a1c5510ed46c8c6539d3976ffc6486db86b606b140723b76865f
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/scorecard.yml
Admits-anchor: call:8c6419950ad9ffd05f57f842005d1004860e85da
Admits-epoch: 3b025bfdaaedf9f46a55c4b09fba627a7d599c231956e6730c43863d886b4d05
Admits-author: alec@wenzowski.com
Admits-prev: 14c4bf8e70e719ee233f6ea75a26e6c39ad884427ec74509fdc8765bcb2e133c
Admits-answer-lost: The gate ships unproven on the surface that matters. The load-time and compiled tiers both supply their own fixture documents, so neither can show that this consumer's `sources` glob actually delivers `batten.toml` to the module — and if it does not, the pin document is absent, the module abstains BY DESIGN, and the gate reports clean forever over a tree it never compared. That is the exact dead-gate shape this row exists to close, reachable only through the row's own configuration.
Admits-answer-precondition: A VERIFICATION WRITE, reverted in the same turn. CLOUD-1672's new gate reports clean over this tree, and clean is byte-identical to a dead gate — so one workflow's `version:` is being changed to a wrong value to confirm the predicate names it, then changed straight back. Only a direct write can do that; no read surface can make a gate fire. Nothing of it reaches a commit: the file is restored with `git checkout --` before the tree is staged, and the diff will show it unchanged.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s new `mise-preset-tree` row and its `sources` list were read, and reading them is what identified the glob as the one thing no fixture can verify.
@wenzowski
wenzowski force-pushed the claude/glacial-ci-regression-d9qtr3 branch from f4e190d to 96352c8 Compare September 9, 2026 00:44
`batten-check` reads the `ci-` cache family on x64 while its only warm writer,
`cache-warm-linux`, runs on arm64. rust-cache composes `runnerOS-runnerArch`
into the key at `config.ts:93`, inside the prefix assigned to `restoreKey` at
`:133`, so those are two entries that can never touch: the job restored nothing
and built cold on every run, at 683s the critical path of the whole workflow.

CLOUD-1453 landed the eviction half and is released in v0.0.142, but eviction
was not the cause. Measured across jobs 100903936005 and 101280975479, a swept
store moved this job 683s -> 600s: 12%, which is eviction pressure lifting
rather than a restore landing.

`ci.yml` declined the fix on a ceiling that no longer exists ("10.72 GiB over a
10 GiB ceiling"), naming two alternatives that are both closed -- `cache-sweep`
took the store to 3.16 GiB, and CLOUD-1431 is Done having landed a gate over
the release-target/provision-platform pairing rather than an arm64 scanner, so
the job still cannot move. So the second writer is what lands:

- `cache-warm-linux-x64` writes `ci-` on x64 from the trunk, mirroring
  `cache-warm-linux`'s guarded-compile shape.
- `batten-check` becomes `save-if: false` like every other pull-request
  consumer of the family; its per-lap multi-gigabyte write into a 10 GiB store
  bought only the next lap of its own PR, which a warm entry now serves.
- the stale comment at `ci.yml:601-618` is corrected rather than left for the
  next reader to re-derive the wrong trade from.

Rules ship with their mechanism, so the class is gated rather than just fixed.
`ci-cache-declared`'s three predicates are all about a family's WRITERS, which
is why the orphaning survived CLOUD-1410's own review: the reader kept a valid
`shared-key`, the module kept a warm writer, and the two were on different
machines. `read-family-has-a-warm-writer` is the fourth -- a pull-request job
that is read-only against a family nothing fills on its architecture is
refused, raising the new `job read empty` verdict.

Scoped to pure consumers (`save-if: false`) deliberately. Every rust-cache step
restores, so judging every reader would reach `cross-`, `semver-`,
`${{ matrix.target }}`, `coverage-`, `fuzz-` and `perf-` -- families this
repository writes from the pull request on purpose because they have no trunk
writer, which `ci.yml` records as a deferred follow-up. Those still get their
own entry on a later lap; a `save-if: false` job gets nothing, ever. That is
the same bound rule 3 draws when it excludes the scheduled writers from
`warmed`.

Verified: `mise run policy-test` clean, and the engine tier 17/17 including
`this_repository_is_clean_today` over the committed workflows.

Recorded rather than absorbed: a `sed -i` against `batten.toml` in this session
was NOT refused by the protected-path gate, which is a gap in the
mutating-verb table's coverage of that verb. It was reported in the admission
below and not relied on; the declared route was taken instead.

Refs: CLOUD-1477, CLOUD-1453, CLOUD-1431, CLOUD-1410

Admits: 4690dbacf228821c8ab51e1aee4bf72d157a3bda3edeb61d491cb9a426c38239
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-plz.yml
Admits-anchor: call:7fe35efd0b28cf086de2086028764a0a8eb7cf9b
Admits-epoch: ed0cc67ec403a5849f97cbe66586e656ab21f7b6d2015725b2d51a2a2c6f5b91
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: CLOUD-1477 cannot be implemented at all. `batten-check` keeps paying a cold ~600s build on every CI lap as the critical path of the whole workflow, because the `ci-` family has no warm writer on x64; and the fourth predicate that would make the orphaning a finding rather than a silent green cannot be added, so the next architecture move reintroduces it unseen.
Admits-answer-precondition: The redirect this class names for `.github/workflows/**` is "change it in a pull request", which is a statement about how the change must LAND rather than an alternative surface that can express it: no tool other than a direct write can add a job to a workflow file. The write is one a reviewer will see in the diff it lands in — it goes to branch claude/glacial-ci-regression-d9qtr3 as a draft PR, reviewed before merge, and CLOUD-1477 is a refined row whose §1 and §3 mandate edits to these exact paths.
Admits-answer-rejected-route: `patch run first` does not apply: the protected-path gate is the intersection of the protected paths with the mutating-verb table, and that table already refuses `>`, `tee`, `sed`, `cp`, `install` and `git` over these same paths. Routing the identical bytes through a patch program reaches the identical refusal under a different program name, which is laundering rather than a second route. `config read first` was TAKEN, not rejected — batten.toml's protected and redirect tables and `batten policy explain protected-mutation` were read before this request, and reading them is what established that no expressing surface exists.

Admits: d6ff1328442bc15a7f1017cdb28a0190b0c3575b6a85e67a75752d80dbe6f9bc
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/ci.yml
Admits-anchor: call:7fe35efd0b28cf086de2086028764a0a8eb7cf9b
Admits-epoch: ed0cc67ec403a5849f97cbe66586e656ab21f7b6d2015725b2d51a2a2c6f5b91
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: The `batten-check` job keeps writing a multi-gigabyte `ci-` entry per lap that no other pull request can read, and `main` keeps carrying a comment that declines the fix on a 10 GiB ceiling `cache-sweep.yml` already removed — which is how the next reader re-derives the wrong trade, and is the defect CLOUD-1477 was filed on.
Admits-answer-precondition: The redirect this class names for `.github/workflows/**` is "change it in a pull request", which is a statement about how the change must LAND rather than an alternative surface that can express it: no tool other than a direct write can add `save-if: false` to a step or correct a job's comment. The write is one a reviewer will see in the diff it lands in — branch claude/glacial-ci-regression-d9qtr3, draft PR, reviewed before merge. CLOUD-1477 §3 names this file's lines 601-618 as the thing to correct.
Admits-answer-rejected-route: `patch run first` does not apply: the protected-path gate is the intersection of the protected paths with the mutating-verb table, and that table already refuses `>`, `tee`, `sed`, `cp`, `install` and `git` over these same paths, so routing the identical bytes through a patch program reaches the identical refusal under a different program name. `config read first` was TAKEN, not rejected — batten.toml's protected and redirect tables and `batten policy explain protected-mutation` were read first, and that is what established no expressing surface exists.

Admits: e1aaff7ef76c6a51d8d71c951a03bfdfec14b5cbaebe9bb738612f15bcda99f3
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: batten.toml
Admits-anchor: call:7fe35efd0b28cf086de2086028764a0a8eb7cf9b
Admits-epoch: ee766a19306de59b2c367442e936067d33bd4fae08bdbbc4cc0a061144665d8d
Admits-author: alec@wenzowski.com
Admits-prev: a607863046171bb3f3d1d7909d8eb2b29f17214df9248c89d76a25c8a29db340
Admits-answer-lost: The row keeps prose written for a word it no longer uses, so the registry's gloss and its id disagree — and `job read empty` would ship describing itself as "nothing warms" while the vocabulary defines the condition as "declared and holding nothing". That is the drift the controlled vocabulary exists to prevent, in the row that introduces the word.
Admits-answer-precondition: The redirect this class names for `batten.toml` is "change it in a pull request", with `mise run config-lint` as the check before it lands — which states how the change must LAND rather than naming a surface that can express it: no tool other than a direct write can edit a `[[verdict]]` row's gloss. This is the same `[[verdict]]` addition the previous admission on this subject covered, corrected after `batten policy test` refused the token: `unwarmed` is not in the declared `[[vocabulary.condition]]` list, so the row is being renamed to `job read empty` and its prose brought into line with that word's gloss, "declared and holding nothing". Branch claude/glacial-ci-regression-d9qtr3, draft PR, reviewed before merge.
Admits-answer-rejected-route: `patch run first` does not apply: the protected-path gate is the intersection of the protected paths with the mutating-verb table, which already refuses `>`, `tee`, `sed`, `cp`, `install` and `git` over this path, so a patch program reaches the identical refusal under a different name. Recorded rather than absorbed: a `sed -i` earlier in this session mutated this file WITHOUT being refused, which is a gap in that table's coverage of the verb — it is being reported rather than spent, and this request is the declared route taken in its place. `config read first` was TAKEN — the vocabulary tables at batten.toml:9124-9257 were read before this request, and reading them is what produced the corrected token.

Admits: cee2adcf16471bed1304548e3122e430942c321d2eb748b1a130cd2d39292e28
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: policy/ci-cache-declared.rego
Admits-anchor: call:7fe35efd0b28cf086de2086028764a0a8eb7cf9b
Admits-epoch: 81c654046fb5b5547138aa9ab501ee774f5c6294be621104fd4c4e271a31a309
Admits-author: alec@wenzowski.com
Admits-prev: ea7b294412f1a37b4ed1d26406aca2aaa661363e55c8bfe4c15a58b6f2c34661
Admits-answer-lost: The module does not load. A module raising a token no `[[verdict]]` row declares is refused at load time, and the registry now declares `job read empty` — so leaving the module raising `job read unwarmed` takes the whole of `ci-cache-declared` down, all four predicates, not just the new one.
Admits-answer-precondition: The redirect this class names for `policy/**` is "change it in a pull request", which states how the change must LAND rather than naming a surface that can express it: no tool other than a direct write can change a verdict token inside a Rego module. This is the same predicate the previous admission on this subject covered, corrected after `batten policy test` refused `job read unwarmed` — `unwarmed` is not in the declared `[[vocabulary.condition]]` list, and the registry row has been renamed to `job read empty`, so the module must raise the same token or fail to load. Branch claude/glacial-ci-regression-d9qtr3, draft PR, reviewed before merge.
Admits-answer-rejected-route: `patch run first` does not apply: the protected-path gate is the intersection of the protected paths with the mutating-verb table, which already refuses `>`, `tee`, `sed`, `cp`, `install` and `git` over these paths, so a patch program reaches the identical refusal under a different name. `config read first` was TAKEN — the vocabulary tables at batten.toml:9124-9257 were read before this request, and that reading is what produced the corrected token.
…declares

`batten.toml:352-357` pins mise to 2026.9.1 by per-platform URL and digest.
`jdx/mise-action` cannot see it. The `uses:` digest pins the ACTION; which MISE
that action installs is a separate resolution, and unset it takes whatever is
newest when the job starts. So the tree declares one toolchain and CI runs
another, with nothing comparing them.

Measured 2026-09-08, latest resolved to v2026.9.3 whose linux-x64 asset 404s:

  curl -fsSL .../v2026.9.3/mise-v2026.9.3-linux-x64.tar.zst
  curl: (22) The requested URL returned error: 404      x5 retries

Every job in every workflow died at that step inside 11 seconds.
`release-artifacts` run 34206034815 lost all 8 jobs in 56s and v0.0.152
published carrying `batten.schema.json` and none of its seven target binaries;
`ci.yml` run 34220313465 lost 5 jobs in 53s, so nothing could land at all.

THE PROVISION ROW ALREADY NAMED THIS HARM, one layer down: "a runner that
updated itself under a container would make two sessions on one commit run
different toolchains, which is the disagreement `[[provision]]`'s pinning
exists to remove." That is exactly what happened, and the pin written to
prevent it does not reach the layer where it occurred.

This carries the pin only. The gate half of CLOUD-1672 -- a predicate refusing
a `mise-action` step whose declared version differs from the `[[provision]]`
row's, so the two cannot drift again -- is not in this commit, and the
remaining workflows are not yet pinned. Both follow on this branch.

Pinned here: `ci.yml` (4 steps), `release-artifacts.yml` (3),
`release-plz.yml` (4), `auto-bot-land.yml` (1).

Refs: CLOUD-1672, CLOUD-1574, CLOUD-1455

Admits: 14a7813f18d11c452bb5851acf9aad131e3f63a3e7c0ede84f7ca15db7325b7e
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/ci.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: d6ff1328442bc15a7f1017cdb28a0190b0c3575b6a85e67a75752d80dbe6f9bc
Admits-answer-lost: CI stays down. Every job in this workflow dies at `jdx/mise-action` in ~11s because the action resolves mise latest (v2026.9.3) whose linux-x64 asset 404s, while `batten.toml` pins 2026.9.1. Nothing in the repository can land until the action installs the version the tree declares.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 5a69f466467c58937d55e1c7d5b79e3259b2d190cac2e69e9d4a695d3f734106
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-artifacts.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: v0.0.152 published with zero binaries and the next release does the same. All 8 jobs of run 34206034815 died at `jdx/mise-action` in 11s because the action resolves mise latest (v2026.9.3), whose linux-x64 asset 404s, while `batten.toml` pins 2026.9.1.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 168f6f87f302b1fd73d8e77e621eae3c7f5e3d5752d7ee1f7c61fcc2eb1e76b6
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-plz.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: 4690dbacf228821c8ab51e1aee4bf72d157a3bda3edeb61d491cb9a426c38239
Admits-answer-lost: The release lane keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins, so the tree declares one toolchain and the release runs another — the drift that published v0.0.152 with zero binaries.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e320b15216a7f81f8b17b231e5f62f128ca8b373499501bcc3fdbbf7d8ecf45f
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-plz.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: 168f6f87f302b1fd73d8e77e621eae3c7f5e3d5752d7ee1f7c61fcc2eb1e76b6
Admits-answer-lost: The one job that actually cuts the release stays unpinned while the three cache-warm jobs beside it are pinned — the worst of both, since the file would read as fixed while the step that publishes the tag still installs whatever mise is newest.
Admits-answer-precondition: CLOUD-1672, second write to this file. The three steps carrying a `with:` block were pinned by the previous admission; the fourth (the `release-plz` job's own step, line 338) declares no `with:` at all, so it needs a different insertion and a separate write. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name; the mutating-verb table covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — the file was re-read to find that this fourth step carries no `with:` block.

Admits: 8a95229ff41806a9b5a05b08a17e49af42dfe91abdcf79f76bd1a625bb686797
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/auto-bot-land.yml
Admits-anchor: call:7fc52c56faed1bc3b4407c796505462a81307d86
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: .github/workflows/auto-bot-land.yml keeps installing whatever mise is newest at job start rather than the 2026.9.1 that `batten.toml` pins. Measured 2026-09-08: latest resolved to v2026.9.3, whose linux-x64 asset 404s, and every job in every workflow died at this step inside 11 seconds. A workflow left unpinned reintroduces the outage on its own next run.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.
…lows

Continues the CLOUD-1672 pin across seven more workflows: `rust.yml` (4 steps),
`commit-lint.yml`, `fast-forward.yml`, `perf.yml`, `coverage.yml`, `zizmor.yml`
and `fuzz.yml`.

Three of these are load-bearing beyond their own run. `commit-lint` is a
required check, so an unpinned step there reds every pull request.
`fast-forward` is the landing path itself. `perf` records the series from a
clock, so an unpinned toolchain silently changes what the series measures
rather than failing.

Thirteen workflows remain, and the gate half of CLOUD-1672 -- a predicate
refusing a `mise-action` step whose declared version differs from the
`[[provision]]` row's -- is still to come on this branch.

Refs: CLOUD-1672

Admits: 8efa23f5401b475d58c607f3ae8e2adf60dbca5cd2df506ffb00546de908fcb6
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/rust.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 71b4de9ae1a0041ffa6098d40137230060304c3db9d59a92f0fb951848ce293f
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/commit-lint.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. `commit-lint` is a required check, so an unpinned step here reds every pull request on its own next run.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: b2ce26a31ad7115c22e0a454348b17fd5de11435bde11478b92e86dab28e560e
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/fast-forward.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This is the landing path itself. It keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins; measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. Unpinned, the fast-forward that lands every branch fails on its own next run.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: ed675dc23520c7be2878c592f48d6a67b2c5a7e4b61a81be4e372ee08abd32b6
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/perf.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. It also records the perf series from a clock, so an unpinned toolchain silently changes what the series measures.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e6bdca0e483274fb95b00b750830540c12fb431e14186b8ddf90c7ef5f161cc3
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/coverage.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e328c82bb610d6dfab5c7457f7f754ea003496c4f464488a7529465336a3a3f5
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/zizmor.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 32cdb5543b38da755a3b2e116dffa060cbe7e321802749e2b6a0c3c66c6d8465
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/fuzz.yml
Admits-anchor: call:f937fa2d0a92f95600ef96474885e1fa44b5ad1a
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.
Completes the CLOUD-1672 pin. Every `jdx/mise-action` step in
`.github/workflows/**` now declares `version: 2026.9.1`, matching
`batten.toml`'s `[[provision]]` row; `git grep -L` reports no workflow
carrying a `mise-action` step without it.

Three of these are pinned for a reason beyond their own install, and each
says so at the step rather than here:

- `lock-currency` is the clock that BUMPS pins, so unpinned it is the one job
  a bad upstream release stops -- the job that would have surfaced this.
- `arch-probe` compares two runner architectures whose validity rests on the
  arms differing only in `runs-on`; an unpinned mise lets them install
  different toolchains and destroys the comparison silently rather than
  failing it.
- `hook-latency-drift` and `nonverdict-rate` record series on a clock, where
  a drifting toolchain changes what is measured rather than reddening.

Still to come on this branch: the gate half of CLOUD-1672 -- a predicate
refusing a `mise-action` step whose declared version differs from the
`[[provision]]` row's, so the two authorities cannot drift apart again.

Refs: CLOUD-1672

Admits: 53fe1b9a28fb798ee6a9fe6ec34dbd51ff974ca27d8923e89ad919695f31cb5f
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/release-assets.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow publishes release assets and keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. Unpinned, it reintroduces the outage that published v0.0.152 with no binaries.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 9127e19ea5c4a7b38d85eab3f54dc5572b0c0611be0ce158f0d57f2b5d40e536
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/auto-release-land.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow lands the release PR and keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. Unpinned, the release lane stays broken at its landing step.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 54cc2b5a7371515470c55b5911e812cad54824759c7e04b83b394f4795f0d7c7
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/mutant.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 53c89ed482c8e3b6e2262eefd30c407714eeea6beb70a300c0e8bc376f2ffb84
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/lock-currency.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This is the clock that bumps pinned tools, so an unpinned mise here means the job that MOVES pins cannot run at all. Measured 2026-09-08: the action resolved v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s — including the one that would otherwise have surfaced the bad upstream release.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: c36024aa70f3e151f72e152f48965917416625d2bd16216b9abf8848a9501e70
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/ci-drift.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 523d9f0aa526c4181f9d9d9999c2d6a5031be9496707953e17c4be535d211f24
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/timeout-drift.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 75b185566d9fd6163a2549832a838e1ada9326be61d72c59723faedb42707aef
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/land-divergence.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 4649eab6a0922340d2be1ce0e47a8ffd507b47740ae55f85453dc6af35d2bfad
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/hook-latency-drift.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. It also measures the gate's own latency on a clock, so an unpinned toolchain silently changes what the series measures rather than failing.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: d2fb5c29b070b9f91cbdb94a9608bcf2e0cf9963b49c675e44216fbe85391a7e
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/nonverdict-rate.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. It records a rate on a clock, so an unpinned toolchain silently changes what the series measures rather than failing.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. This step declares no `with:` block, so the write adds one. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 14c4bf8e70e719ee233f6ea75a26e6c39ad884427ec74509fdc8765bcb2e133c
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/scorecard.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 614a8ab2953de8ff590164a654d0c667b821895e4c92af1273e92116e10aa28d
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/zizmor-advisories.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: e74e0af2f0245d98a75d0ef01619e24b90929e67fbeb016e9d9fa42689a7d6c9
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/branch-hygiene.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This workflow keeps installing whatever mise is newest at job start rather than the 2026.9.1 `batten.toml` pins. Measured 2026-09-08 that was v2026.9.3, whose linux-x64 asset 404s, and every job died at this step in 11s. A workflow left unpinned reintroduces the outage on its own next run, and the gate this row also lands will refuse it.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.

Admits: 0d5560b6a87c0ed0a7917002a7eedb5634d010b4200eb65cc46bb866516c8381
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/arch-probe.yml
Admits-anchor: call:a134f824acba494f16226b89be4869ab292bd3da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This is the matched arm-versus-x64 probe, whose whole validity rests on both arms differing ONLY in `runs-on`. An unpinned mise means the two arms can install different toolchains, which silently destroys the comparison rather than failing it — and the last unpinned resolution, v2026.9.3, 404s on linux-x64 so the probe cannot run at all.
Admits-answer-precondition: CLOUD-1672. The redirect for `.github/workflows/**` is "change it in a pull request", which says how the change must LAND, not a surface that can express it: only a direct write can add a `version:` input to a `mise-action` step. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s `[[provision]]` row was read and is where the 2026.9.1 pin comes from.
The pin half of CLOUD-1672 landed across 24 workflows and nothing held it
there. This is the mechanism, so the rule is not prose (non-negotiable 2).

`action-version-matches-the-pin` refuses a workflow step installing the
toolchain at a version the tree does not pin: `job pin other` where a declared
version disagrees with the `[[provision]]` row, `job pin missing` where the step
declares none and hands the choice to the installer. The comparison is against
the committed row and never a literal -- a module carrying `2026.9.1` would be a
THIRD authority and would go stale the first time the pin moved.

Both documents are found by SHAPE, never by filename: a document carrying
`jobs`, a document carrying `provision`. A preset ships to every consumer, so
rule 1 forbids naming their paths; the enabling row's `sources` is where this
consumer says which of its files those are.

No provision row for the tool means the row ABSTAINS rather than refuses. A
project that pins nothing has nothing to disagree with, and refusing there would
be a verdict about their configuration instead of about drift between two of
their own statements.

THE ENGINE HALF: A MANIFEST DECLARES A SCOPE PER MODULE.

`Manifest.scope` said a preset decides exactly one surface -- true of every
preset that existed when it was written, false the moment a subject has
something to say about both. `mise` is that subject: whether an agent reproduces
a task's argv is a question about a CALL, whether the toolchain a workflow
installs matches the pin is a question about the TREE. They are one preset
because they are one concern, and a consumer should not have to know the engine
once could not express that.

Two constraints assumed the old shape and both now key on the pair:

- the load-time scope check compared one manifest scope for equality; it asks
  `decides(scope)` now, and names the scopes the preset does decide.
- the duplicate-registration check keyed on the source name, so the second row
  enabling one preset at the other scope read as dead config. It is not: each
  row compiles only the modules at its own scope, so they share a name and
  decide disjoint surfaces.

Selecting modules by scope is load-bearing rather than tidy. A tree module
compiled onto the call surface reads keys the engine never builds there, Rego
reads undefined as "does not hold", and the result is a dead gate byte-identical
to a clean tree. `check_tree_paths_are_emittable` catches that today; this keeps
the case from arising rather than relying on a downstream guard to notice.

VERIFIED, AND THE REAL-TREE ARM IS THE ONE THAT COUNTS. `policy test` 64
bundles, 814 passed; the four preset tiers 39/39. Both of those supply their own
pin document, so neither can show that this consumer's `sources` glob actually
delivers `batten.toml` -- and if it did not, the pin would be absent, the module
would abstain by design, and the gate would report clean forever. So it was
driven over this checkout: clean as committed, and moving one workflow's version
produced `.github/workflows/scorecard.yml:53 action-version-matches-the-pin`,
exit 2, pointer-only. The file was restored with `git checkout --` and the diff
is unchanged. `a_drifted_version_in_this_repository_is_refused` is that same
proof as a standing case.

Refs: CLOUD-1672, CLOUD-1181, CLOUD-404

Admits: 48d1eec18e1480b0a3f1f941763ee6705ff0eadb5140db2bb02a5fbc101081c5
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: batten.toml
Admits-anchor: call:8c6419950ad9ffd05f57f842005d1004860e85da
Admits-epoch: 549ff209e932fdcaedf859d520794afa6a1a3125e363212214b1d9c737338b7e
Admits-author: alec@wenzowski.com
Admits-prev: e1aaff7ef76c6a51d8d71c951a03bfdfec14b5cbaebe9bb738612f15bcda99f3
Admits-answer-lost: The gate half of CLOUD-1672 does not run. The pin is landed across all 24 workflows but nothing holds it there, so the next architecture move, Renovate bump or hand edit silently reintroduces the drift that published v0.0.152 with zero binaries. A rule without its mechanism is half a change by this repository's own non-negotiable rule 2.
Admits-answer-precondition: CLOUD-1672 gate half. The redirect for `batten.toml` is "change it in a pull request" with `config-lint` as the check before it lands — a statement about how the change must LAND, not a surface that can express it: only a direct write can add a `[[rule]]` row. The row enables the `mise` preset at tree scope with the `sources` globs the predicate reads; without it the module ships and decides nothing, which is the dead-gate shape the whole row exists to close. Branch claude/glacial-ci-regression-d9qtr3, draft PR #914, reviewed before merge.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — the `ci-hygiene` row was read as the shape to copy, since it is the existing tree-scoped preset row carrying workflow `sources` and `line_sources`.

Admits: 6e8cd01a0028a1c5510ed46c8c6539d3976ffc6486db86b606b140723b76865f
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: .github/workflows/scorecard.yml
Admits-anchor: call:8c6419950ad9ffd05f57f842005d1004860e85da
Admits-epoch: 3b025bfdaaedf9f46a55c4b09fba627a7d599c231956e6730c43863d886b4d05
Admits-author: alec@wenzowski.com
Admits-prev: 14c4bf8e70e719ee233f6ea75a26e6c39ad884427ec74509fdc8765bcb2e133c
Admits-answer-lost: The gate ships unproven on the surface that matters. The load-time and compiled tiers both supply their own fixture documents, so neither can show that this consumer's `sources` glob actually delivers `batten.toml` to the module — and if it does not, the pin document is absent, the module abstains BY DESIGN, and the gate reports clean forever over a tree it never compared. That is the exact dead-gate shape this row exists to close, reachable only through the row's own configuration.
Admits-answer-precondition: A VERIFICATION WRITE, reverted in the same turn. CLOUD-1672's new gate reports clean over this tree, and clean is byte-identical to a dead gate — so one workflow's `version:` is being changed to a wrong value to confirm the predicate names it, then changed straight back. Only a direct write can do that; no read surface can make a gate fire. Nothing of it reaches a commit: the file is restored with `git checkout --` before the tree is staged, and the diff will show it unchanged.
Admits-answer-rejected-route: `patch run first` reaches the same refusal under another program name: the mutating-verb table already covers `>`, `tee`, `sed`, `cp`, `install` and `git` over this path. `config read first` was TAKEN — `batten.toml`'s new `mise-preset-tree` row and its `sources` list were read, and reading them is what identified the glob as the one thing no fixture can verify.
The header opened on "the surface it decides" and named scope the manifest's
load-bearing omission, both written when a manifest carried exactly one. The
mechanism moved onto each module in the previous commit and the prose did not,
which is the same stale-comment-beside-a-changed-mechanism defect CLOUD-1477 was
filed on -- caught here in this branch's own diff rather than by the next reader.

The CLOUD-1181 paragraph is kept rather than rewritten: why a preset needed to
declare a scope at all is still exactly right, and it is the reason the field
survives in a different shape rather than being dropped.

WHY THIS COMMIT CARRIES THE BREAK. `semver check` reports
`struct_pub_field_missing` against this branch: `Manifest.scope` is gone, and a
removed public field is a break however non-exhaustive the struct is. The break
is in the PREVIOUS commit, whose message cannot carry the footer without
rewriting it -- so the declaration rides here, which is the pattern `9ea4da6a`
already followed for a waiver landed two commits after it was written.

Refs: CLOUD-1672

BREAKING CHANGE: `preset::Manifest` loses its `scope` field and `modules` becomes
`&[PresetModule]` rather than `&[(&str, &str)]`. A consumer reading either must
move to `Manifest::decides`, `Manifest::scopes` or `Manifest::modules_at`, which
answer the same questions per module. The field could not stay: it asserted that
a preset decides exactly one surface, which is false for any preset whose subject
spans a call and the tree.
`every_class_a_preset_raises_is_declared_by_its_own_manifest` and its mirror
destructured `manifest.modules` as `(pointer, source)`, which stopped compiling
when a module became a struct carrying its own scope.

FOUND BY `cross-check`, NOT BY THE CHECK THAT SHOULD HAVE. `cargo check -p batten`
does not build test targets, so the in-crate census compiled nowhere until the
Windows leg type-checked `lib test` -- a verify lap spent on something
`--all-targets` answers in six seconds. The two cases are the manifest's own
anti-vacuity pair, so a silent break here is a preset shipping a class with no
gloss, or declaring one nothing raises.

Verified: `cargo check -p batten --all-targets` clean, and `mise run cross-check`
green on x86_64-pc-windows-gnu, which is the gate that refused.

Refs: CLOUD-1672
`batten-check` refused the new module under this repository's own
`no-appeal-to-authority` row, at the literal and at both fixtures. The rule is
right and the module was wrong: a bare repository name is indistinguishable from
citing a vendor as an authority, and the row's exemption recognises the forms a
coordinate actually takes -- a step's `uses:` key, or a forty-character digest.

So the value is written the way a workflow writes it and the parts are derived,
which is the shape `mise-action-floor` already uses for the same reason and
which also means the action name and its pin cannot drift apart inside the file.
The fixtures carry full digests rather than truncated ones, which is both what
the exemption recognises and what a real workflow contains.

The digest is illustrative and nothing compares it: pinning the ACTION is a
separate question from pinning the mise it installs, and conflating the two is
what let CLOUD-1672's defect exist at all.

Verified: `policy test` 64 bundles, 814 passed -- unchanged, so deriving the
name did not move any predicate -- and `batten check --rule
no-appeal-to-authority` exits clean over the tree.

Refs: CLOUD-1672
`rebase-not-hand-stepped` separates the lap it bans from the rebase it must
leave alone by asking whether the line names `origin/main`. But
`git rebase --onto <newbase> <upstream> <branch>` names it too and is not a lap
at all: it replays a RANGE somewhere else, dropping commits from the bottom,
which the landing loop has no notion of and cannot perform. So the row refused
an operation its own remedy does not offer -- and a `shape` row declares no
`[[verdict]]` class and this one carries no `bypass_env`, so there was no
sanctioned route to it whatsoever.

MEASURED: a branch carrying four commits that `closing-key-check` required be
dropped could not drop them, and the refusal's advice named `land`, which would
have replayed all twelve.

THE DISCRIMINATOR HAS TO BE A FLAG, which is the one thing `pattern` cannot see:
the operand matcher drops flags before comparing, so the only predicate over a
flag a row could previously write was "must be present". `unless_contains` is
`contains` in the other polarity, `shape` only -- a column that switches a deny
off has no business on a precondition row, where the same bytes would silently
stop DEMANDING a receipt. It is matched against the same LINE as `contains`, and
that is load-bearing in the permissive direction: matched against the segment,
an `echo --onto` on line one would exempt a genuine lap on line two.

The row's `reason` also still told every reader it warns rather than denies,
which the comment block above it had already recorded as false in every clause.

Verified: `land_hand_stepping` carries both directions --
`a_range_move_onto_main_is_not_this_rows_business` and
`the_exemption_does_not_reach_another_lines_lap` -- judged against the committed
`batten.toml` over the compiled binary, with the existing deny arms unchanged so
the exemption cannot widen into the lap itself. 11 passed.

Weakens: rebase-not-hand-stepped no longer refuses a `git rebase --onto` naming
`origin/main`. The narrowing is a corrected false deny rather than a relaxation:
the exempted command is a history edit the landing loop cannot perform, so no
call this row exists to catch is now permitted. The deny arms for every lap
spelling are unchanged and `the_exemption_does_not_reach_another_lines_lap`
pins that the exemption cannot be borrowed from a neighbouring line.

Refs: CLOUD-1461

Admits: e95ab54d2a1069aeef8fabd76c18613979c1aefd6a2e28930ddd85e3546265f7
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: batten.toml
Admits-anchor: call:52ec48bc576c1c1aa70126081654573118c23855
Admits-epoch: cfc384e2524c60c47a938159b26cdfafcea35e08bfdebdddbeeb2a90c84c1162
Admits-author: alec@wenzowski.com
Admits-prev: 48d1eec18e1480b0a3f1f941763ee6705ff0eadb5140db2bb02a5fbc101081c5
Admits-answer-lost: The row keeps refusing `git rebase --onto origin/main <upstream> <branch>` — a range move the landing loop cannot perform — with no [[verdict]] class to override and no bypass_env. A wrongly refusing gate is a defect, not an answer; leaving it means the branch cannot drop the four commits `closing-key-check` requires be dropped, and the reason string keeps telling every reader the row warns when it denies.
Admits-answer-precondition: No surface writes a [[rule]] column. `batten config` reads the table and never edits it, so adding `unless_contains` to `rebase-not-hand-stepped` and correcting its stale `reason` prose can only be a direct write to batten.toml. It is a fifteen-line diff a reviewer sees whole in the PR.
Admits-answer-rejected-route: config read first — rejected because it is a read: it prints the table and returns, and there is no `config set` or `config patch` for a rule column. patch run first — rejected because no patch surface exists for batten.toml; the only patch route is the write itself.
Admits: 7b50267372a83706050e0f3c0537978e5fc258d6c6bfffbc2cf6a73de7587b4b
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: batten.toml
Admits-anchor: call:52ec48bc576c1c1aa70126081654573118c23855
Admits-epoch: 25b643b9be1ee81860f72805ef8554a00e5c0b1f5d5b1e8b3ea86233e88bd405
Admits-author: alec@wenzowski.com
Admits-prev: e95ab54d2a1069aeef8fabd76c18613979c1aefd6a2e28930ddd85e3546265f7
Admits-answer-lost: The `unless_contains` column lands with its reasoning nowhere — the block would still read as though `contains` were the whole predicate, and the next reader tightening this row would delete the exemption as unexplained. The block already carries a recorded correction for exactly this failure (a comment overstating its own gate); leaving it silent repeats it in the other direction.
Admits-answer-precondition: Second write of the same change: the comment block above `rebase-not-hand-stepped` records why each of its columns is what it is, and a new column landing with no entry there is the drift that block exists to prevent. No surface edits a config comment; only a direct write can. It is in the same reviewed diff as the row itself.
Admits-answer-rejected-route: config read first — rejected: it prints the table and returns, and it does not read comments at all, let alone write them. patch run first — rejected: there is no patch surface over batten.toml; the write is the only route.
`every_rule_column_carries_a_fact_verdict` reads the `Rule` struct's own source
and requires every column to say what it declares. The new exemption column said
nothing, which is the silence that arm exists to refuse -- an unclassified column
is declarable on a mediated row and read by nothing.

It is not fact-bearing for `contains`'s reason and in the other polarity: it is a
condition over the command line another column already declared, not a new fact
about the tree.

FOUND BY `verify`, ONE LAP LATE. The arm lives in `crates/batten/src/rules.rs`'s
own test module, so `cargo check -p batten --all-targets` type-checks it and does
not run it -- the same gap between checking and running that cost a lap two
commits ago, seen from the other side.

Refs: CLOUD-1461

Weakens: rule-predicate-changed rule[rebase-not-hand-stepped].unless_contains
@wenzowski
wenzowski force-pushed the claude/glacial-ci-regression-d9qtr3 branch from 96352c8 to 2ce05b8 Compare September 9, 2026 02:33
@wenzowski
wenzowski marked this pull request as ready for review September 9, 2026 02:33
@sonarqubecloud

sonarqubecloud Bot commented Sep 9, 2026

Copy link
Copy Markdown

❌ The last analysis has failed.

See analysis details on SonarQube Cloud

@wenzowski

Copy link
Copy Markdown
Contributor Author

/fast-forward

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Refusing to fast-forward #914 at 2ce05b82613d61d70c15bcc6d19b392debfc9903: a required check has not answered yet. Branch protection requires only final, which fans in over 4 of the 20 names in CI_REQUIRED_CHECKS — so a green final is not a green roster (CLOUD-1570). Wait for the matrix, or let mise run land drive it.

@wenzowski
wenzowski merged commit 2ce05b8 into main Sep 9, 2026
23 of 24 checks passed
@wenzowski
wenzowski deleted the claude/glacial-ci-regression-d9qtr3 branch September 9, 2026 04:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant