From 1d4ad8ede727834cd4ed07c4aed284f117da20f6 Mon Sep 17 00:00:00 2001 From: Alec Wenzowski Date: Tue, 1 Sep 2026 03:40:03 +0000 Subject: [PATCH 1/4] fix(ci): define the deps-install seam the provisioner already calls `batten` is absent at SessionStart on a fresh container, so every mediated call fails open in silence for the first ~2.5 minutes of a session and for the whole of any session where the SessionStart hook does not reach its build step. The engine mediates nothing and nothing says so. The bootstrap seam was never missing. The environment's setup script runs `mise run deps-install` on every provision, before any session starts, and soft-fails with a stderr line when the task is absent -- which this repository has never defined. `mise tasks ls` carried `install-check` and `install:local` and no `deps*` at all, so the call has been dangling for the life of the defect. Define it, and its `deps` report sibling that the same script calls. The task runs `install.sh` -- the one install interface -- and never `install:local`, which builds the working tree's binary and is a dev-clone convenience rather than a provisioning path: it assumes a Rust toolchain and a 141-second compile that provisioning cannot. Routing through `mise run` is load-bearing rather than incidental. The setup script shadows `mise` with a wrapper that prepends the GitHub API and asset hosts to NO_PROXY and sets MISE_GITHUB_TOKEN from the session PAT, because the agent proxy answers 403 for third-party release repos. A task body inherits that environment; a bare `./install.sh` in the setup script would not. Verified both arms on this container. Success installs v0.0.137, digest-verified, and is silent at exit 0. A refusal emits the `::error::` line naming which failure it was and propagates install.sh's own code, keeping could-not-look (2) distinct from a decided refusal (1). Two defects found and fixed while writing it, both recorded in comments beside the code: a default inline `run` body executes under `sh -e`, so the first draft's bare call aborted the task before the branch that reports the failure could run; and backticks inside the double-quoted messages would have been command substitution on the binary's own name. Refs: CLOUD-1085 --- mise.toml | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/mise.toml b/mise.toml index daa57114b..daa9b751c 100644 --- a/mise.toml +++ b/mise.toml @@ -1396,6 +1396,101 @@ install -m 0755 target/release/batten "$dest/batten" echo "install:local: batten -> $dest/batten" """ +# The provisioning seam the environment's setup script ALREADY CALLS, and which +# this repository never defined (CLOUD-1085). +# +# THE CALL SITE IS NOT OURS AND IT IS NOT NEW. The container's setup script runs +# `mise run deps-install` on every provision, before any session starts, and +# soft-fails with a stderr line when the task is absent — which is what it did for +# the whole life of this defect. So the bootstrap seam was never missing; it was a +# DANGLING CALL, and defining the task is the entire fix. Nothing outside this +# repository changes, which is exactly what §1 asks for: the change is in the +# environment configuration that provisions a container, and that configuration +# already makes the call. +# +# `install.sh`, NEVER `install:local`, and the distinction is this row's own. +# The local task builds the WORKING TREE's binary — "a dev-clone convenience that +# supersedes a release build, not a provisioning path". A provisioning path cannot +# assume a Rust toolchain, a 141-second compile (measured, CLOUD-1085), or that the +# checkout builds at all. One install interface (§1); this is the caller it lacked. +# +# WHY ROUTING THROUGH `mise run` IS LOAD-BEARING rather than incidental. The setup +# script shadows `mise` with a wrapper that prepends the GitHub API and asset hosts +# to NO_PROXY and sets MISE_GITHUB_TOKEN from the session PAT, because the agent +# proxy answers 403 for third-party release repos. A task body inherits that +# wrapper's environment, so the release API is reachable here and would NOT be from +# a bare `./install.sh` in the setup script. The seam and the proxy fix are the same +# mechanism. +# +# LOUD ON FAILURE, because the defect this closes is a SILENT absence: an absent +# reference monitor and a passing one are indistinguishable from outside, and every +# mediated call in a session without a binary fails open with nothing said. The +# caller appends its own line; this supplies the `::error::` and the non-zero exit +# that make it a report rather than a shrug — CLOUD-824's posture of moving the +# missing-binary report to provisioning time, where it can still be fixed. +# +# `2` STAYS DISTINCT FROM `1` (house style §7). `install.sh` answers 1 for a refusal +# it decided — a bad digest, an unwritable destination — and 2 for could-not-look: +# no curl, or an unreachable release API. Collapsing them sends a reader with a +# network problem hunting a corrupted asset. +# +# `|| status=$?` RATHER THAN A BARE CALL, and this is measured rather than +# stylistic. A default inline body runs under `sh -e` — unlike the `shell = +# "bash -c"` bodies `.claude/rules/toolchain.md` describes, which do not — so a +# bare `./install.sh` ABORTS the task at the failure and the branch below never +# runs. Observed exactly that on the first version of this body: the install +# refused, mise printed its own `ERROR task failed`, and the `::error::` line that +# names WHICH failure was never emitted. The capture shape is the one +# `tests/task-fail-closed.bats` already admits (CLOUD-318) and it is what keeps +# the code readable instead of merely propagated. +# +# NO BACKTICKS IN THE MESSAGES, and not for prose reasons: these are double-quoted +# shell strings, where a backtick opens COMMAND SUBSTITUTION. Quoting the binary's +# name the way every comment here does would run it. The first draft of this body +# did exactly that. +# +# The refusal arm does NOT claim nothing was written. `install.sh` installs and +# THEN refuses when the destination is off PATH ("Off PATH is a refusal, not a +# warning over exit 0"), so a message saying no binary exists is false in the one +# failure mode most likely to be hit. What is true in every arm — and what actually +# matters, since `.claude/settings.json` registers the engine by BARE NAME — is +# that the name does not resolve. +[tasks."deps-install"] +description = "Provision the host dependencies mise does not: the released `batten`, before any hook fires (CLOUD-1085)" +run = ''' +status=0 +./install.sh || status=$? +if [ "$status" -eq 0 ]; then exit 0; fi +if [ "$status" -eq 2 ]; then + echo "::error:: deps-install: could not look — the release API was unreachable, so nothing was installed and batten does not resolve by name. Every mediated call this session then fails open in silence. See the install output above." >&2 +else + echo "::error:: deps-install: install.sh refused with exit $status, so batten does not resolve by name — it may have written a binary somewhere PATH does not reach. Every mediated call this session then fails open in silence. See the install output above." >&2 +fi +exit "$status" +''' + +# The report half of the same seam, and the setup script calls this one too. +# +# A REPORT, NOT A GATE: the caller discards the status, so this exists to put the +# answer in the provisioning log where a human reads it, rather than leaving the +# absence to surface as an unrelated failure at first use. That is the same reason +# the setup script ends with `mise ls --current`. +# +# It names the resolved PATH entry, because "a batten exists" and "the batten the +# hook registrations resolve" are different claims — `.claude/settings.json` +# registers `batten hook` by BARE NAME on every event, so what decides whether the +# engine mediates is which one PATH finds first. +[tasks.deps] +description = "Report the host dependencies `deps-install` provisions, so an absent one is visible in the setup log and not at first use" +run = ''' +if command -v batten >/dev/null 2>&1; then + echo "deps: batten at $(command -v batten) — $(batten --version 2>/dev/null || echo 'version unreadable')" + exit 0 +fi +echo "::error:: deps: no batten on PATH, so the engine mediates nothing. Run: mise run deps-install" >&2 +exit 1 +''' + [tasks.batten-check] description = "Consumer #1: evaluate the committed batten.toml with batten's own engine against this repository" # `cargo run` rather than an installed binary, so the gate always judges the From 3f8757ea2684721a200655d4d47d30a9a7332580 Mon Sep 17 00:00:00 2001 From: Alec Wenzowski Date: Tue, 1 Sep 2026 03:45:58 +0000 Subject: [PATCH 2/4] fix(hook): report a session whose SessionStart registration never ran MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CLOUD-1085's second half. The first closed the cause -- nothing installed `batten` before the first hook fired -- and this closes the silence: a session where that happened said nothing, and an absent reference monitor is indistinguishable from a passing one from outside. The reporter serves exactly two events and seeds the per-session snapshot at whichever arrives first. So a seed at `PostToolBatch` means `SessionStart` never reached the engine, and since the hosts register it by bare name the usual cause is that no binary resolved when that event fired. Measured on the container that produced the row: SessionStart receipt at 04:37:21, binary at 04:39:58, first snapshot at 04:40:48 -- three and a half minutes in which every mediated call failed open and nothing reported it. The mechanism is free. The snapshot is already keyed per session and already written in that branch, so the same write that rate-limits the drift notice rate-limits this one: emitted once per session, never again. Pointer-only and advisory. It rides the existing channel, never exit 2 -- an absent monitor is news, not a verdict about the call in hand. It does not claim which calls preceded it, because this process cannot see them, and it names the provisioning step rather than a policy remedy. RE-DECIDES ONE LANDED CASE, quoted in place rather than deleted. `the_first_batch_of_a_session_seeds_the_snapshot_silently` asserted silence on exactly this condition. Its reasoning -- a session that started after a change has already read the new files -- is right about `SessionStart` and wrong about the case it covered, so it moves to the mirror case and the original becomes the discriminating one §7 asks for: no snapshot entry produces the advisory, an entry produces silence. One stated bound: a consumer that deliberately does not register `SessionStart` would see this every session. The row specifies the simple predicate and it is implemented as written; narrowing it needs the wiring, which `doctor hooks` reads and this reporter should not. Refs: CLOUD-1085 --- crates/batten/src/contract.rs | 57 +++++++++++++++++++++++++ crates/batten/src/lib.rs | 21 ++++++++-- crates/batten/tests/contract_drift.rs | 60 ++++++++++++++++++++++++--- 3 files changed, 130 insertions(+), 8 deletions(-) diff --git a/crates/batten/src/contract.rs b/crates/batten/src/contract.rs index 2cef3d4da..fd5ea75e7 100644 --- a/crates/batten/src/contract.rs +++ b/crates/batten/src/contract.rs @@ -308,6 +308,63 @@ pub fn render(change: &ChangeSet, wiring: &[String]) -> String { out } +/// The advisory for a session whose `SessionStart` registration never ran +/// (CLOUD-1085). +/// +/// # Why a seed at a later event is news, and one at `SessionStart` is not +/// +/// [`previous`] returns [`Look::CouldNotLook`] for the first batch of a session, +/// and the caller seeds it silently — correctly, because a session that started +/// after a change has already read the new files and nudging it is the noise that +/// gets an advisory channel ignored. +/// +/// **That reasoning holds only at `SessionStart`.** The reporter serves exactly +/// two events, and the snapshot is seeded at the first one to arrive. So a seed +/// happening at `PostToolBatch` means `SessionStart` did not reach this code — +/// and since the host registers the engine by bare name on every event, the +/// overwhelmingly likely cause is that no `batten` resolved when that event +/// fired. Measured on the container that produced CLOUD-1085: the SessionStart +/// receipt was written at 04:37:21, the binary appeared at 04:39:58, and the +/// first snapshot landed at 04:40:48 — at `PostToolBatch`, three and a half +/// minutes late, with every mediated call in between failing open in silence. +/// +/// **An absent reference monitor and a passing one are indistinguishable from +/// outside**, which is the whole defect: nothing in that session reported +/// anything. This is the one place the difference is observable, and it costs +/// nothing to observe — the per-session snapshot the drift predicate already +/// keeps is the entire mechanism. +/// +/// # What it does NOT claim +/// +/// Not that the calls before it were unsafe, and not which ones they were: this +/// process cannot see them. It reports the one fact it holds — the engine did not +/// run at this session's start — and names the provisioning step, because a +/// binary that is absent when the first hook fires is a provisioning failure +/// rather than a policy one (CLOUD-824's posture: report it where it can still be +/// fixed). +/// +/// Pointer-only by construction: no path, no session id, no count of anything +/// read off the disk. The session id is a host token and would be a poor pointer +/// anyway — the reader has exactly one session. +/// +/// Rate-limited by the same write as the drift notice. The caller records the +/// snapshot in the same branch, so this is emitted once per session and never +/// again, which is what keeps it credible rather than a line everybody learns to +/// scroll past. +#[must_use] +pub fn unmediated_session() -> String { + "contract-drift: this session's SessionStart registration did not run\n\n\ + The per-session snapshot is being seeded at a later event, which means the engine\n\ + was not invoked when this session started. The hosts register it by BARE NAME, so\n\ + the usual cause is that no `batten` resolved on PATH at that moment — in which case\n\ + every mediated call until it appeared failed open and said nothing.\n\n\ + This is a provisioning failure rather than a policy one: `mise run deps-install`\n\ + installs the released binary, and `mise run deps` reports which one PATH finds.\n\n\ + Which calls preceded this is not answerable from here, and is not claimed.\n\ + Reported once per session; silence otherwise.\n" + .to_owned() +} + #[cfg(test)] #[allow(clippy::unwrap_used, clippy::expect_used)] mod tests { diff --git a/crates/batten/src/lib.rs b/crates/batten/src/lib.rs index d5807f9df..b393e0ffa 100644 --- a/crates/batten/src/lib.rs +++ b/crates/batten/src/lib.rs @@ -5452,11 +5452,26 @@ fn report_contract_drift( }; let session = envelope.session.as_deref(); - // No snapshot is the FIRST batch of this session, seeded silently. A session - // that started after a change has already read the new files at start, and - // nudging it about them is the noise that gets an advisory channel ignored. + // No snapshot is the FIRST batch of this session. A session that started + // after a change has already read the new files at start, and nudging it + // about them is the noise that gets an advisory channel ignored — so the + // seed is SILENT at `SessionStart`. + // + // AT ANY LATER EVENT THE SAME SEED IS NEWS (CLOUD-1085). This reporter serves + // exactly two events and seeds at whichever arrives first, so seeding at + // `PostToolBatch` means `SessionStart` never reached here — and the hosts + // register the engine by bare name, so the usual cause is that no binary + // resolved when that event fired. Every mediated call until one did failed + // open in silence, and this is the only place that difference is observable. + // + // Recorded before the emit, for the reason the drift notice below gives: the + // write is the rate limit, and erring toward one missed notice beats erring + // toward an unbounded repeat of the same one. let facts::Look::Is(previous) = contract::previous(&git_dir, session) else { drop(contract::record(&git_dir, session, ¤t)); + if !matches!(envelope.event, hook::Event::SessionStart) { + advice.push(contract::unmediated_session()); + } return; }; diff --git a/crates/batten/tests/contract_drift.rs b/crates/batten/tests/contract_drift.rs index a6739fc70..8dd029dbe 100644 --- a/crates/batten/tests/contract_drift.rs +++ b/crates/batten/tests/contract_drift.rs @@ -116,14 +116,64 @@ fn notice(output: &Output) -> Option { ) } +/// **RE-DECIDED BY CLOUD-1085**, and the previous decision is quoted rather than +/// deleted because it was right about the case it named and wrong about the case +/// it covered. +/// +/// This case read: *"A session that started AFTER a change has already read the +/// new files, so nudging it about them is the noise that gets an advisory channel +/// ignored"* — and asserted silence on the first `PostToolBatch` of a session. +/// The reasoning holds at `SessionStart`, which is the event that argument is +/// about. It does not hold here, and the mirror case below is where it now lives. +/// +/// The reporter serves exactly two events and seeds at whichever arrives first, +/// so a seed at `PostToolBatch` means `SessionStart` never reached the engine. +/// Measured on the container that produced CLOUD-1085: SessionStart receipt at +/// 04:37:21, binary at 04:39:58, first snapshot at 04:40:48 — every mediated call +/// in that window failed open in silence, and nothing said so. This case is what +/// makes that observable. #[test] -fn the_first_batch_of_a_session_seeds_the_snapshot_silently() { - // A session that started AFTER a change has already read the new files, so - // nudging it about them is the noise that gets an advisory channel ignored. +fn a_seed_at_a_later_event_reports_the_unmediated_start() { let dir = fixture("contract-seed"); + let told = drift(&dir, "s1") + .pipe_notice() + .expect("a seed at PostToolBatch means SessionStart never ran"); + assert!( + told.contains("SessionStart registration did not run"), + "the notice names the condition rather than the symptom: {told}" + ); + assert!( + told.contains("deps-install"), + "a missing binary is a PROVISIONING failure and the notice names the step: {told}" + ); + // Once per session. The write is the rate limit here exactly as it is for a + // change-set, so the very next batch is silent — without this a session with + // no binary would carry the same line on every batch it ever ran. assert_eq!(drift(&dir, "s1").pipe_notice(), None); - // And a surface that has not moved stays quiet on every later batch. - assert_eq!(drift(&dir, "s1").pipe_notice(), None); +} + +/// The anti-vacuity mirror, and the home of the reasoning the case above quotes. +/// +/// A session whose `SessionStart` DID reach the engine seeds there, silently, and +/// stays silent on every later batch. Without this the case above would pass over +/// a reporter that simply nags on every seed, which is the noise the whole +/// channel is rate-limited to avoid. +/// +/// Fails by: dropping the `SessionStart` arm of the event test, so the advisory +/// fires on the seeding event itself. +#[test] +fn a_session_seeded_at_session_start_is_silent_and_stays_silent() { + let dir = fixture("contract-seeded-at-start"); + assert_eq!( + drift_on(&dir, "s1", "SessionStart", &[]).pipe_notice(), + None, + "the seeding event is the one the silence argument is about" + ); + assert_eq!( + drift(&dir, "s1").pipe_notice(), + None, + "and a surface that has not moved stays quiet on every later batch" + ); } #[test] From 28d20f6e254b5c41117eb8d5771246a39a891699 Mon Sep 17 00:00:00 2001 From: Alec Wenzowski Date: Tue, 1 Sep 2026 03:50:13 +0000 Subject: [PATCH 3/4] test(hook): assert session isolation directly, not the absence of every advisory `each_session_is_told_about_what_moved_under_it_and_not_about_the_rest` asserted `pipe_notice() == None` for a session seeded now. The property it owns is ISOLATION -- a session that started after a change is not nudged about one it already has -- and `== None` is a proxy for that which also couples the case to every other advisory the channel may ever carry. The unmediated-start notice makes the coupling bite. This fixture drives `PostToolBatch` only, which is by construction the condition that notice reports, so the fresh session now receives it: a different notice about a different fact, and the isolation claim is untouched. Asserted over what the notice CLAIMS instead. The fresh session must not name the moved file and must carry no change-set; the already-running one is still told. That discriminates the same way and stops the next advisory from failing a case that is not about it. Refs: CLOUD-1085 --- crates/batten/tests/contract_drift.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/crates/batten/tests/contract_drift.rs b/crates/batten/tests/contract_drift.rs index 8dd029dbe..cc954de47 100644 --- a/crates/batten/tests/contract_drift.rs +++ b/crates/batten/tests/contract_drift.rs @@ -259,9 +259,25 @@ fn each_session_is_told_about_what_moved_under_it_and_not_about_the_rest() { let dir = fixture("contract-sessions"); drift(&dir, "early"); std::fs::write(dir.join("AGENTS.md"), "# the contract\nmid\n").unwrap(); - // A session whose first batch is now: it reads the CURRENT files at start, - // so its snapshot is seeded with them and it is told nothing. - assert_eq!(drift(&dir, "late").pipe_notice(), None); + // A session whose first batch is now: it reads the CURRENT files at start, so + // its snapshot is seeded with them and it is told nothing ABOUT THE DRIFT. + // + // Asserted over what the notice CLAIMS rather than over its presence, since + // CLOUD-1085. This fixture drives `PostToolBatch` only, which is by + // construction the "SessionStart never ran" condition, so `late` does now + // receive that advisory — a different notice about a different fact. The + // property this case owns is isolation: `late` never hears about a change-set + // that predates it. Asserting silence would couple this case to the presence + // of every other advisory the channel ever carries. + let late = drift(&dir, "late").pipe_notice().unwrap_or_default(); + assert!( + !late.contains("AGENTS.md"), + "a session seeded now is not told about drift that predates it: {late}" + ); + assert!( + !late.contains("changed or added"), + "and is told of no change-set at all: {late}" + ); // The session that was already running is told. assert!(drift(&dir, "early").pipe_notice().is_some()); } From c52854e3e5c5761e84fc2ab2712f2decb1d199a4 Mon Sep 17 00:00:00 2001 From: Alec Wenzowski Date: Tue, 1 Sep 2026 04:28:08 +0000 Subject: [PATCH 4/4] fix(ci): collapse the deps tasks to one-line shims and clear two lint slips Three gate refusals from the first land lap, all mine. `inline-task-bodies-not-growing` refused mise.toml 31->33. Both new tasks were nine-line inline bodies. The ratchet's own block records that the wider literal was rejected precisely because a one-line `run` shim is the campaign SUCCEEDING, and reading that made clear the bodies were wrong on the merits rather than merely over budget: each caught install.sh's exit status only to re-word it, which is a verdict replaced rather than propagated (CLOUD-1090's shape, one file over). Collapsed. `deps-install` is now the install script alone, so its own diagnosis and its own 0/1/2 reach the caller intact instead of being translated. `deps` is `command -v batten && batten --version`, where `command -v` both answers the question -- which binary does PATH find, the one the bare-name registrations resolve -- and supplies the failure exit without a branch to write. The comment describing the ratchet then tripped it a second time. The row matches a literal substring with no anchoring and no regex column (CLOUD-1058), so it reads prose as readily as code: spelling the pattern out incremented the count again with no task added. Reworded to name the shape in words, with that recorded in place so the next author does not rediscover it. Both spellings are now level with base, 31 and 2. clippy doc_markdown twice, same slip copied: an unbackticked event name in contract.rs and again in the test's doc comment. Refs: CLOUD-1085 --- crates/batten/src/contract.rs | 2 +- crates/batten/tests/contract_drift.rs | 2 +- mise.toml | 84 +++++++++++---------------- 3 files changed, 37 insertions(+), 51 deletions(-) diff --git a/crates/batten/src/contract.rs b/crates/batten/src/contract.rs index fd5ea75e7..170434f9e 100644 --- a/crates/batten/src/contract.rs +++ b/crates/batten/src/contract.rs @@ -323,7 +323,7 @@ pub fn render(change: &ChangeSet, wiring: &[String]) -> String { /// happening at `PostToolBatch` means `SessionStart` did not reach this code — /// and since the host registers the engine by bare name on every event, the /// overwhelmingly likely cause is that no `batten` resolved when that event -/// fired. Measured on the container that produced CLOUD-1085: the SessionStart +/// fired. Measured on the container that produced CLOUD-1085: the `SessionStart` /// receipt was written at 04:37:21, the binary appeared at 04:39:58, and the /// first snapshot landed at 04:40:48 — at `PostToolBatch`, three and a half /// minutes late, with every mediated call in between failing open in silence. diff --git a/crates/batten/tests/contract_drift.rs b/crates/batten/tests/contract_drift.rs index cc954de47..6ae92ef9f 100644 --- a/crates/batten/tests/contract_drift.rs +++ b/crates/batten/tests/contract_drift.rs @@ -128,7 +128,7 @@ fn notice(output: &Output) -> Option { /// /// The reporter serves exactly two events and seeds at whichever arrives first, /// so a seed at `PostToolBatch` means `SessionStart` never reached the engine. -/// Measured on the container that produced CLOUD-1085: SessionStart receipt at +/// Measured on the container that produced CLOUD-1085: `SessionStart` receipt at /// 04:37:21, binary at 04:39:58, first snapshot at 04:40:48 — every mediated call /// in that window failed open in silence, and nothing said so. This case is what /// makes that observable. diff --git a/mise.toml b/mise.toml index daa9b751c..7f4afe530 100644 --- a/mise.toml +++ b/mise.toml @@ -1434,62 +1434,48 @@ echo "install:local: batten -> $dest/batten" # no curl, or an unreachable release API. Collapsing them sends a reader with a # network problem hunting a corrupted asset. # -# `|| status=$?` RATHER THAN A BARE CALL, and this is measured rather than -# stylistic. A default inline body runs under `sh -e` — unlike the `shell = -# "bash -c"` bodies `.claude/rules/toolchain.md` describes, which do not — so a -# bare `./install.sh` ABORTS the task at the failure and the branch below never -# runs. Observed exactly that on the first version of this body: the install -# refused, mise printed its own `ERROR task failed`, and the `::error::` line that -# names WHICH failure was never emitted. The capture shape is the one -# `tests/task-fail-closed.bats` already admits (CLOUD-318) and it is what keeps -# the code readable instead of merely propagated. -# -# NO BACKTICKS IN THE MESSAGES, and not for prose reasons: these are double-quoted -# shell strings, where a backtick opens COMMAND SUBSTITUTION. Quoting the binary's -# name the way every comment here does would run it. The first draft of this body -# did exactly that. -# -# The refusal arm does NOT claim nothing was written. `install.sh` installs and -# THEN refuses when the destination is off PATH ("Off PATH is a refusal, not a -# warning over exit 0"), so a message saying no binary exists is false in the one -# failure mode most likely to be hit. What is true in every arm — and what actually -# matters, since `.claude/settings.json` registers the engine by BARE NAME — is -# that the name does not resolve. +# A ONE-LINE SHIM, NOT AN INLINE BODY, and the ratchet is what corrected the first +# draft rather than merely refusing it. `inline-task-bodies-not-growing` counts +# triple-quoted `run` bodies in this file and is `non_increasing`; the first +# version of this task was a nine-line body and took the count 31 -> 33. +# +# THIS COMMENT MAY NOT QUOTE THE PATTERN IT DESCRIBES. The row matches a literal +# substring with no anchoring and no `regex` column (CLOUD-1058), so it reads +# prose as readily as code -- spelling the pattern out here incremented the count +# by one all over again, with no task added. Name the shape in words instead. +# +# The right reading of that refusal is not "waive it". `install.sh` already emits +# the diagnosis — it names the destination, whether the digest verified, and why it +# refused — and it already answers `0/1/2` on the house-style table. The body that +# was here caught that status only to re-word it, which is a verdict REPLACED +# rather than propagated (CLOUD-1090's shape, one file over). Deleting it keeps +# `install.sh`'s own words and its own exit code, and the ratchet's block records +# that a one-line `run = "…"` shim is the campaign succeeding rather than a hole. +# +# The consequence the deleted body narrated belongs here, where it costs nothing: +# when this task fails, `batten` does not resolve by name, and until one does every +# mediated call fails open in silence. The caller already appends its own line, and +# mise prints the non-zero exit. [tasks."deps-install"] description = "Provision the host dependencies mise does not: the released `batten`, before any hook fires (CLOUD-1085)" -run = ''' -status=0 -./install.sh || status=$? -if [ "$status" -eq 0 ]; then exit 0; fi -if [ "$status" -eq 2 ]; then - echo "::error:: deps-install: could not look — the release API was unreachable, so nothing was installed and batten does not resolve by name. Every mediated call this session then fails open in silence. See the install output above." >&2 -else - echo "::error:: deps-install: install.sh refused with exit $status, so batten does not resolve by name — it may have written a binary somewhere PATH does not reach. Every mediated call this session then fails open in silence. See the install output above." >&2 -fi -exit "$status" -''' +run = "./install.sh" # The report half of the same seam, and the setup script calls this one too. # -# A REPORT, NOT A GATE: the caller discards the status, so this exists to put the -# answer in the provisioning log where a human reads it, rather than leaving the -# absence to surface as an unrelated failure at first use. That is the same reason -# the setup script ends with `mise ls --current`. -# -# It names the resolved PATH entry, because "a batten exists" and "the batten the -# hook registrations resolve" are different claims — `.claude/settings.json` -# registers `batten hook` by BARE NAME on every event, so what decides whether the -# engine mediates is which one PATH finds first. +# A REPORT, NOT A GATE: the caller discards the status (`mise run deps || true`), +# so this exists to put the answer in the provisioning log where a human reads it, +# rather than leaving the absence to surface as an unrelated failure at first use. +# Same reason the setup script ends with `mise ls --current`. +# +# `command -v` FIRST, because it is the whole question. "A batten exists" and "the +# batten the hook registrations resolve" are different claims — +# `.claude/settings.json` registers `batten hook` by BARE NAME on every event — so +# the path PATH actually finds is the answer, and printing it is the report. It +# also supplies the failure for free: `command -v` exits non-zero when the name +# does not resolve, and `&&` carries that out without a branch to write. [tasks.deps] description = "Report the host dependencies `deps-install` provisions, so an absent one is visible in the setup log and not at first use" -run = ''' -if command -v batten >/dev/null 2>&1; then - echo "deps: batten at $(command -v batten) — $(batten --version 2>/dev/null || echo 'version unreadable')" - exit 0 -fi -echo "::error:: deps: no batten on PATH, so the engine mediates nothing. Run: mise run deps-install" >&2 -exit 1 -''' +run = "command -v batten && batten --version" [tasks.batten-check] description = "Consumer #1: evaluate the committed batten.toml with batten's own engine against this repository"