Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .serena/memories/toolchain-and-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,32 @@ bypass. `.claude/rules/toolchain.md` is where that binds; read it there rather
than here, because a second copy of the governed-path predicate is exactly the
drift these notes exist to avoid (CLOUD-1132).

**AND THE CASE THAT ACTUALLY ARISES IS THE ONE NEITHER SHAPE NAMES: you find a
BUG in a governed program.** "Leave it alone" is the shape for a file that needs
no change. It is not an answer to one that does — and read as though it were, it
turns every defect in this layer into a permanent one, which is precisely
backwards, since the layer is where the defects are. A needed fix in a governed
`mise-tasks/*.sh` is a **RETIREMENT ROW**: land the predicate as a
`policy/*.rego` module plus a `crates/batten/tests/**/*.rs` tier, delete the
program and its suite with one `conserves` arm per path, and drop it from
`$MUTANT_GATES`. Never a block, and never a filed-and-deferred note that leaves
the bug standing.

**Measured, three times in one session (PR #794).** `mise-tasks/replay.sh` and
`mise-tasks/config-lint.sh` were each reported as carrying an unlandable fix,
with "the file is governed" given as the reason — and in the `config-lint` case
the defect was real, verified, and was the thing that had just admitted that
same PR's own config smell. Each was a retirement whose §1 had been written in
the wrong shape, which is the sentence `.claude/rules/toolchain.md` already
spends a paragraph on. **The tell is reaching for the word _unlandable_ about a
path this campaign exists to retire**: the campaign IS the route, so a governed
path is the one place a fix is guaranteed to have one.

Scoping it is usually smaller than it looks — ask what the shell actually still
owns. `config-lint`'s smell detection is already `batten config lint`; only the
admission layer (a claim receipt plus a `Weakens:` trailer) is bash, and the
engine already owns hash-bound admissions in `batten override request`/`spend`.

The `*-check` decision halves — `graph-check`, `landed-check`, `ready-lint`,
`verified` — use **exit 1 = violation, exit 2 = could not read the input**.
Batten's contract (house-style §7, CLOUD-226) is the exact opposite: **1 =
Expand Down
24 changes: 24 additions & 0 deletions .serena/memories/workflow/board-states.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,30 @@ performed; whether it produces the property is the question the edit does not as
Symmetric with Done: the Done _gate_ ("landed on `main` by fast-forward, CI
green") is a predicate; the `Done` _column_ is where issues land once it holds.

1b. **An In Review row is LANDED, NOT OCCUPIED — so its open work is pullable,
not somebody else's.** Item 2 below says In Review means already merged; this
is the inference a reader takes from it and gets backwards. A row sitting In
Review with attachments looks exactly like a row somebody is actively
working, and the attachments are the strongest-looking evidence — they are
evidence of work **already landed**, which is the opposite of a competitor.

So a row In Review whose body declares a still-open acceptance bullet ("still
open, and the reason this row is not Done") is **available work**. Pull it:
`claim-check`, board move, branch, as for any other row. The duplicate-claim
fear the entry further down records is about the pull-time window, where a
competitor has published nothing — In Review is the state where a competitor
has published everything, which is why it cannot be that case.

Measured 2026-09-01: CLOUD-1218 was declined as "owned, actively — racing it
is CLOUD-230's defect", citing its four attached PRs. All four had landed;
`crates/batten/src/prune.rs` was ungoverned; the row's own last bullet named
the remaining work. Nothing was blocking anything.

The neighbouring rule is real and is about **content, not availability**: item
0's table says an In Review body is a RECORD, so a wrong design there is fixed
by a new row rather than a rewrite. That constrains what you may edit in the
body. It says nothing about whether you may do the work the body asks for.

2. **In Review means already merged — this is trunk-based.** Per
https://trunkbaseddevelopment.com/ we review _after_ merge, before release, to
avoid large divergence on `main`. Unreviewed code paths are kept out of
Expand Down
38 changes: 38 additions & 0 deletions crates/batten/tests/it/shell_retirement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,44 @@ fn a_port_naming_a_live_governed_subject_is_refused() {
);
}

/// THE THIRD `ported` REFUSAL, and the one that shipped with no mutation over
/// it. A subject that DIED is not a surviving one: that is a plain retirement,
/// which `carried` already spells with less, and admitting the same event under
/// both markers lets the ledger record it in two vocabularies.
///
/// Its absence from THIS file is what the missing `#MUTANT` row was hiding, and
/// the gap was two-deep rather than one. The load-time tier pinned the predicate
/// and `ratchet.rs` pinned the case granularity, but neither is the suite
/// `#MUTANT-SUITE` names — so a mutation of this arm had nothing here to redden
/// and could only ever have been declared as a survivor (CLOUD-1302).
///
/// `tests/helpers.bash` is deliberately the dying subject. It is neither a
/// `.bats` nor under `mise-tasks/`, so `governed_when_deleted` does not select
/// it and its own deletion owes no arm — which leaves this arm the only one that
/// can fire, and is what makes the mutation discriminate rather than survive
/// behind a conjunct some other arm already excludes.
#[test]
fn a_port_over_a_subject_that_died_is_refused() {
let root = repo(
"ported-subject-died",
&[
("tests/old-gate.bats", IMMORTAL_SUITE),
("tests/helpers.bash", "helpers\n"),
],
&Head {
written: &[(
"crates/batten/tests/old_gate.rs",
&ledger_ported("tests/old-gate.bats", "tests/helpers.bash"),
)],
removed: &["tests/old-gate.bats", "tests/helpers.bash"],
},
);
assert!(
!findings(&root).is_empty(),
"a port whose subject died is a retirement, and `carried` is its spelling"
);
}

/// The truncation arm, over the engine — and it is the shape that forced the
/// clause rather than one imagined for it.
///
Expand Down
1 change: 1 addition & 0 deletions policy/shell-retirement.rego
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
# withdrawn here, so the field has to name a case that exists, and both do.
#MUTANT port-subject-unnamed-unchecked|s@count(ported_subjects(path)) == 0@false@|a_port_naming_no_subject_is_refused
#MUTANT port-subject-governed-unchecked|s@governed_when_deleted(subject)@false@|a_port_naming_a_live_governed_subject_is_refused
#MUTANT port-subject-retired-unchecked|s@^\tsubject in @\tfalse #@|a_port_over_a_subject_that_died_is_refused
# THE CASE EXERCISES THE INVOCATION CLAUSE, NOT THE PATH ONE. Its caller binds
# `$(dirname "$0")/old-gate.sh` to a variable and repoints at `mise run
# old-gate`, which is CLOUD-1219's decomposition — so a mutation of
Expand Down
25 changes: 17 additions & 8 deletions policy/suite-subject-retirable.rego
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ declared[path] := parts if {
# table would conclude the other four are cheap ports nobody got to, which is the
# opposite of true for every one of them.
#
# * COST (13 rows, 11.4s total against a 667.4s corpus). Portable today — each
# * COST (13 rows, 11.4s total). Portable today — each
# would owe a ledger block and a Rust port spawning whatever its subject is,
# for a sub-second yield. Unported, NOT unportable, and a row that wants one
# only has to be worth writing.
Expand All @@ -155,14 +155,23 @@ declared[path] := parts if {
# something the campaign retires, and porting a suite away from one is the
# claim `named_and_alive` already refuses under the other four markers. No
# amount of willingness moves these; each owes its own row.
# * ITS OWN ROW (`session-start`, 97.8s), below.
# * ITS OWN ROW (`session-start`), below.
#
# ONE IS NOT ABOUT COST AND HAS ITS OWN ROW. `session-start` is the largest suite
# here by two orders of magnitude, and porting it would move its seconds rather
# than remove them — the cost is `mise install` in the two cases that opt back in
# through `real_install_or_skip`, not the bats harness. Its remedy is warming the
# toolchain once in `[tasks."test:bats"]`, which is CLOUD-1273's and is not a
# retirement at all. `.claude/hooks/session-start.sh` was never governed for edits
# THE SECONDS ABOVE ARE READ FROM `bench/suites/RESULTS.md` AND NOTHING HOLDS THEM
# THERE. They are stated because the classification is about relative cost, and a
# corpus DENOMINATOR is deliberately not: it moves every time a suite retires, so
# restating one here is a number that goes stale silently (CLOUD-770's rule).
# The first version of this table carried one, and it was wrong within a week.
#
# ONE IS NOT ABOUT COST AND HAS ITS OWN ROW, and its cost is the one number no
# reading of this tree settles. `session-start`'s seconds are `mise install` in
# the two cases that opt back in through `real_install_or_skip`, not the bats
# harness — so it is the corpus's second-largest suite on a COLD container and
# unremarkable on a warm one, and the generated table reports whichever the run
# that produced it had. Porting it would move those seconds rather than remove
# them. Its remedy is warming the toolchain once in `[tasks."test:bats"]`, which
# is CLOUD-1273's and is not a retirement at all; that row carries both readings
# and is where a number for this suite belongs. `.claude/hooks/session-start.sh` was never governed for edits
# OR for deletion either, so filing it under an immortal subject sent every pass at
# it toward a retirement it never needed.
#
Expand Down
Loading