Skip to content

fix(hook): recover an intercepted read, and repair three mechanisms that enforced nothing (CLOUD-1147) - #814

Merged
wenzowski merged 5 commits into
mainfrom
claude/policy-module-mutations-rm4hz1
Sep 1, 2026
Merged

fix(hook): recover an intercepted read, and repair three mechanisms that enforced nothing (CLOUD-1147)#814
wenzowski merged 5 commits into
mainfrom
claude/policy-module-mutations-rm4hz1

Conversation

@wenzowski

@wenzowski wenzowski commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Closes CLOUD-1147.

DO-NOT-CLOSE CLOUD-1141 CLOUD-1210 — both are served by commits here and neither is completed by them. CLOUD-1141's residue is pinned, not fixed, and now has its own row (CLOUD-1304). CLOUD-1210's restructure landed; this repairs three defects it left behind.

Five fixes, one class: a mechanism that reads as coverage and enforces nothing. That is the class CLOUD-1267 was opened to remove; four of these were found while landing it, and the fifth is the row that landing punted.

1. target_consolidation.rs never compiled — 3 cases silently absent

crates/batten/tests/it/main.rs declares 150 modules. Exactly one file under it/ had no mod line, so it never compiled and its three cases never ran. An undeclared .rs beside a target is not a rustc error, and policy/test-targets.rego refuses only a new top-level target, so nothing caught it.

The file matters more than the count. Its own doc:

> That claim is load-bearing: if it were false, CLOUD-1210 would be trading isolation for build speed, which is a trade nobody agreed to. A citation is not a mechanism… so the property ships as a case rather than as a sentence in a commit message.

main.rs's header cites it by name as the mechanism behind that claim. It shipped as a sentence. a_grouped_test_starts_from_a_fresh_process_state, a_sibling_in_the_same_target_does_not_see_that_state and the_binary_under_test_is_still_addressable_from_the_grouped_target now run and pass.

Declaring it alone would leave the cause live, so it ships with a gate (rule 2): a case in test_targets.rs asserting the group's files and its mod lines agree, both directions — either alone is satisfied by an empty group — plus a floor on the declared count so the pair cannot pass over the wrong directory. Over the live tree, for mediated_verbs.rs's reason: a fixture would assert about a main.rs the case wrote itself. mod common; resolves to a directory, which the naive spelling reports as missing.

Shown able to fail: with the mod line removed the case is red, naming ["target_consolidation"].

2 & 3. Two tasks naming targets the restructure removed

Autodiscovery now yields exactly it and policy_modules.

test:hook-profile filtered binary(hook_profile), now the module it::hook_profile. Its four siblings were repointed and this one was missed — it is also the only one of the five with neither a description nor the BATTEN_TEST_SCRATCH_LANE its siblings set. Nothing invokes it, so it was dead rather than breaking. Now selects 8 cases.

snapshots was broken twice on one line, and this half was contributor-facing. crates/batten/tests/snapshots no longer exists, so find exited non-zero and the && the task's own comment relies on short-circuited the accept; --test snapshots is not a target either. Because [tasks.fix] runs under set -eu, mise run fix aborted here and never reached hk fix --all — the repo's advertised fix command, and the one snapshots.rs's own failure message tells you to run. It now completes.

The repair is a filter, not a bare target swap, and the comment now says why. Its existing note explains the narrow target on isolation grounds: cargo test runs a target's cases as threads in one process, and document_read_count asserts a per-process count. That reasoning is stronger after the grouping — cargo test --test it would run ~150 modules in one process and trip exactly that assertion, so the obvious repair reaches the defect the bound exists to avoid.

4. The heredoc interpreter residue had no case

mediated_verbs.rs pins python3 -c "open('p','w')" as an asserted-allowed known gap. The heredoc form had nothing — the CLOUD-418 shape one layer out.

They lose for different reasons, and the second is structural. -c loses because the path is inside a quoted word, so a wider scan could reach it (tried, reverted, refused ordinary mentions). python3 - <<'PY' loses earlier: heredoc bodies are dropped before any predicate exists (CLOUD-723), so the words are ["python3", "-", "<<'PY'"] and the operand list holds no path at all. No operand-based check can reach it however wide.

A pin, not a fix. CLOUD-1141's commit names this shape as deliberately open, and its body says closing it "needs the prospective content as a fact rather than a string to grep, which is its own row". That row did not exist; it does now, refined, as CLOUD-1304, and the pin is what flips when it lands. Measured 2026-09-01: a python3 heredoc wrote a registered policy module unrefused.

5. CLOUD-1147 — the mint starvation

A host may refuse a large tool result, write the bytes to a file, and substitute a notice naming it. The notice is prose, so payload_in cannot parse it, record_mints returns early, and no issue-read receipt is written — silently, by design. an-update-owes-a-recent-read then refuses and names recency, so the agent re-reads, is intercepted identically, and is refused again. The stated remedy is the operation that fails, and it fails because the row is large. Three rows reached that state.

The row's premise was wrong, and the probe is what settled it. CLOUD-1147 says the adjacent capture spine "has nothing to recover either". Measured on the live host:

result_is_null false — the early return is not what stops the mint
result type string — so the JSON parse fails and the mint skips
the path named inside it present, and that file exists, holding the full payload

The bytes survive interception. Nothing looked. So this recovers rather than compromises, and CLOUD-691's forgery objection does not reach it: the receipt attests exactly the bytes the server sent, not a field subset standing in for them.

Bounds, each load-bearing: spilled_path is pure and does no IO, keyed on the notice's shape rather than one host's wording, so a host phrasing it differently recovers nothing and falls back to the ordinary no-mint path — the direction a miss must fail in. Only a path a host placed in a result it substituted is followed, never one a caller supplied. Read once, only after the ordinary decode already failed. Downstream is unchanged: the recovered value goes through payload_in and a mint's requires like any other.

The negatives earned their place during the work. The recovery was first wired into write_records by mistake. It compiled, and all three negatives still passed — only the positive case caught it. A suite of negatives alone would have shipped a no-op.

Shown able to fail: with the or_else removed, the positive case is red.

batten.toml's prose is corrected in the same change, under two declared admissions: it stated twice that the receipt is minted by mise run issue-read-check, a task retired out of the tree — so a reader hitting this refusal was sent to a program that cannot be run.

Disclosure

Three protected-path writes went through the declared override request/spend route, since the hatch no longer opens this class: one for policy/shell-retirement.rego (a rebase conflict resolution), two for batten.toml (comment prose only — no rule, pattern, verb or verdict touched). The Admits: blocks travel in the commits.

Verification

  • test:cargo 3704/3704.
  • Both new gates shown able to fail.
  • mise run fix completes; mise run snapshots accepts; test:hook-profile selects a non-zero set.

Not in this PR

  • The fact-model change that would actually close the heredoc class — CLOUD-1304 owns it, refined, with §3 left explicitly undecided and settled by a replay rather than by prose.
  • graph-check's stale #MUTANT row — shell-rule-retired admits two shapes and a field repair is neither; CLOUD-1061 owns it.
  • CLOUD-1296's own fix, CLOUD-1265's producer, CLOUD-934's preset gap.

@linear-code

linear-code Bot commented Sep 1, 2026

Copy link
Copy Markdown
CLOUD-1147 A row that outgrows the harness result limit can never be updated again: the read is intercepted, so `issue-read` mints nothing and `an-update-owes-a-recent-read` refuses forever

Why

an-update-owes-a-recent-read refuses a save_issue unless an issue-read.<key> receipt for that key was minted within 300s. The receipt is minted from the get_issue result, as it passes through the hook.

Measured 2026-08-29 on CLOUD-1128, the dispatch record for this campaign. Its body had grown to ~58k characters across three appends. get_issue returned it at 57k–66k characters, which exceeds the harness's own tool-result display limit, so the harness intercepted the result before the hook saw a payload. No receipt minted. The on-disk issue-read.CLOUD-1128 receipt stayed frozen at 1788021194 — roughly 100 minutes stale — while two reads seconds before the write minted nothing at all.

The row is now permanently un-updatable through the gated path.

The trap is that the refusal's own remedy is unsatisfiable

The denial says: re-read the row. Re-reading is exactly the operation that fails, and it fails because the row is large. Every retry produces the same interception and the same absent receipt. The remedy is not merely unhelpful — it is unsatisfiable precisely for the rows that hit the condition, which is the shape CLOUD-1057 was closed for on the branch-receipt side. This is the same class on the issue-read side, reached by a different route.

The failure is also silent in the wrong direction. The read appears to succeed from the agent's side (it gets a summary or a truncation notice, not an error), so the missing receipt is discovered only at the write, one step too late, and reads as a recency problem rather than a size one. Nothing in the denial mentions size.

A SECOND MEASURED INSTANCE, and the bypass this row rules out is not even reachable — 2026-08-31

Read across from CLOUD-1266, which was filed as a
duplicate of this row and marked as one. Two findings there are not in this body:

1. BATTEN_HOOK_BYPASS=1 cannot be spent on the surface that refuses

The section below argues a bypass should not be reached for. Measured, it cannot be:
it is an environment variable on the hook process, and a save_issue is an MCP tool call,
not a shell command. A caller has no way to set an env var for one MCP call. The documented
escape exists only on the Bash surface.

So a gate whose stated bypass lives on a different surface than the call it refuses has, in**
*practice, no bypass at all — and that is what turns this from a recoverable refusal into a
permanent one. The row's "why a bypass is not the answer" reasoning still stands on its
merits; what changes is that the option it declines was never on the table, so §2's "stated

*route" has to be a real route rather than an implied hatch.

2. The instance: CLOUD-1151, two attempts, read seconds old both times

  • get_issue CLOUD-1151 includeRelations:trueresult (70,805 characters) exceeds maximum allowed tokens, spilled to a file.
  • save_issue CLOUD-1151 immediately after → Refused by an-update-owes-a-recent-read: the issue-read receipt is older than the 300s this row allows.
  • Read, then wrote again with no intervening tool call. Same refusal. Two attempts.

This is the second instance after CLOUD-1128, on a different row, two days
later — so the condition is recurring rather than one row's accident.

CLOUD-1151 is this row's named beneficiary, and it is carrying a live wrong verdict

Naming it matters because the cost is not abstract. CLOUD-1151 is the
campaign's wave owner, and the correction it cannot receive is a refutation of a claim an**
*implementer would act on: its Bundle A block frames tests/land-lock.bats's 157s of sleep
as waste, when CLOUD-448 and CLOUD-450 are Done and
those waits are the repairtests/land-lock.bats:57-60: "a 60s wait that fails

*silently is a worse report than the 6s one it replaced, not a better one." It also carries a
lifecycle clause missing two of the five steps.

The correction was landed on CLOUD-1170 instead, which is the class owner, so
the finding is not lost — but the row an implementer is dispatched from still carries the
inverted framing and cannot be fixed until this row lands.

So §2's acceptance gains a second named row: CLOUD-1128 and CLOUD-1151 are
updatable again, or each records why it is not.

A THIRD INSTANCE, and it REFUTES this row's own recovery premise — 2026-09-01

Reproduced deliberately, on 5a9924b6, to settle a design question rather than by accident.

get_issue CLOUD-1151 includeRelations:true intercepted at 71,501 characters
issue-read.CLOUD-1151 receipt afterwards none minted — starvation confirmed
the payload on disk 71,849 bytes of complete, valid JSON, opening {"id":"CLOUD-1151",…
the substituted result a plain-text notice that names the path to those bytes

The bytes survive interception. The harness writes the full result to a file and names
that file in the notice it substitutes; the notice is recorded verbatim as toolUseResult.
Because it is plain text rather than JSON, facts::payload_in fails to parse it, returns
None, and record_mints skips — silently, by its own documented design.

So the mint does not fail because the data is gone. It fails because batten reads only
envelope.result and never the file that result points at. Both fields
[[mint]] issue-read requires — id and updatedAt — are present in the spilled file.

What this changes for §2. The row's stated fork — a field-subset receipt, or a route that
does not read the body — was written on the premise that the body is unavailable. It is
available. A receipt minted from the recovered payload covers what the server actually
returned, so CLOUD-691's forgery objection does not reach it: nothing is being attested that
was not read. The field-subset compromise may therefore be unnecessary.

SETTLED BY PROBE — the envelope carries the notice, and the path with it

The open question was whether the hook's PostToolUse envelope receives that notice or
null; both give the identical observable, so it was probed rather than reasoned about. A
temporary write in record_mints, ahead of the is_null early return, dumping what arrived:

field value
tool mcp__Linear__get_issue
result_is_null false — the early return is not what stops the mint
result type string — so payload_in's JSON parse fails and record_mints skips
the path named inside it present, and that file exists, holding the full payload

So recovery is reachable. The hook is handed a pointer to the complete result and does
not follow it: envelope.result is a plain string whose text contains an absolute path to a
file holding exactly the bytes the server returned.

That dissolves the fork §2 was written around. A receipt minted from the recovered file
covers what the server actually returned, so the field-subset compromise is unnecessary and
CLOUD-691's forgery objection does not apply — nothing is attested that was not read.

Two bounds any fix must keep, because this is a path arriving through a tool result:
follow only a path the HARNESS named in its own notice, never one a caller supplied, and read
it once. A recovered payload stays subject to requires, so a spilled file lacking id or
updatedAt mints nothing, exactly as today.

Why a bypass is not the answer

BATTEN_HOOK_BYPASS=1 would clear it, and reaching for that was the wrong instinct once already in this campaign. A gate that refuses for a reason it cannot state, on a condition its remedy cannot clear, is a defect, not a decision to override.

Adjacent rows, and why this is not one of them

  • CLOUD-1121 (In Progress) — the capture spine recovering a payload off disk. Directly adjacent. This row used to say "if the harness intercepts the result, the spine has nothing to recover either", and that is measured false — see the third instance below. 1121 is about not paying twice; this is about not minting at all.
  • CLOUD-685 — gating the size of a payload entering context. That row is about cost. This one is about correctness: a large payload does not merely cost, it voids the receipt.
  • CLOUD-1122save_issue echoing the stored body back. The write-side mirror of the same unbudgeted echo.

Refinement — Ready (mint from what the read actually returned, and say size when size is the cause)

Refinement gate: Definition of Ready & Done. This body carries only specializations.

  • Authority boundary (§1). The mint path for issue-read.<key> in crates/batten/src/, and an-update-owes-a-recent-read's denial prose in its [[verdict]] row. No governed mise-tasks/ program and no tests/**/*.bats file is edited or added.
  • Computable predicate (§2). Two halves, and both are needed. (a) A get_issue call whose result the host truncated or intercepted must be distinguishable from one that returned a body — the could-not-look reading, not silence. (b) A refusal whose cause is an absent-because-intercepted receipt must name that cause and a route, rather than repeating "re-read the row".
  • The conservative direction is stated, because it is the whole design question (§2). A receipt records what was seen. If the read was truncated, the agent did not see the row, so minting a full receipt from a truncated read is forgery and is refused — that is CLOUD-691's recorded class. So the fix is not "mint anyway". It is either a receipt over a declared field subset that does not include the body (the shape CLOUD-1024 already established: a receipt minted from the declared field set alone authorises the update), or a stated route that does not require reading the body at all.
  • Deliberately not in scope (§2). Raising or removing the harness result limit, which is not ours. Capping issue body size. Changing the 300s window. Retiring an-update-owes-a-recent-read.
  • Effect (§3). The probe is a read; the mint is the existing receipt write, unchanged in effect class.
  • Output and exit (§5). Pointer-only: the denial names the key, the receipt's age or its absence, and the cause — never a byte of the body it could not read. Exit follows the one 0/1/2/3 table; a could-not-look reading is exit 3, not a false 2.
  • Commit / bump (§6). Type fix(hook), and the bump is patch.
  • Test obligation (§7). Over the compiled binary in crates/batten/tests/. Shown able to fail per CLOUD-418: a get_issue result carrying an interception marker must not mint a body-covering receipt, and a save_issue following one must be refused with the size cause named rather than the recency cause. The anti-vacuity case is the mirror: an ordinary read still mints, and an ordinary stale receipt still yields the ordinary recency refusal, so this is not a blanket amnesty. Replayed: the recorded CLOUD-1128 interception is the fixture.
  • Blockers (§8). None. relatedTo CLOUD-1121, CLOUD-685, CLOUD-1122.

Acceptance

  • A save_issue on a row too large to read back is either admitted through a stated route, or refused with the size cause named and that route printed — never refused with a remedy that cannot be performed.
  • No receipt covering a body is minted from a read that did not return one.
  • CLOUD-1128 and CLOUD-1151 are updatable again, or each records why it is not and what
    to do with it. CLOUD-1151 is the sharper case: it is the campaign's wave owner and the
    correction it cannot receive refutes a claim an implementer would act on.
  • Ordinary reads and ordinary stale-receipt refusals are unchanged, asserted.

Found while grooming Stage 2: two get_issue calls seconds before a write minted nothing, and the refusal named a 100-minute-old receipt as the reason.

CLOUD-1141 The protected-path gate enumerates shell write verbs, so any interpreter writes a protected file unrefused — `python3 -c` edited `batten.toml` repeatedly in one session

Reopened 2026-08-29 and being fixed on the CLOUD-1131 branch. Cancelled as a duplicate of CLOUD-442 when filed-over-own-diff refused it — correctly, since it names files that branch had open. The decision was then to FIX rather than defer, so the row is live again and its PR closes it. The measurements and the two residues are on CLOUD-442's thread as well.

What the fix is: the enumeration is INVERTED, not extended. [[verb]] keeps naming mutations; a new committed-authority protected_readers names programs that only read. An operand that is a protected path refuses unless the program is known — in [[verb]] at all, or declared a reader. Forgetting a reader is a visible false refusal; forgetting a writer is no longer a silent hole.

Two shapes stay open and are asserted as open, because a suite that looked complete over them would be the CLOUD-418 defect: python3 -c "open('p','w')" puts the path inside one quoted word, and python3 - <<'PY' puts it in a heredoc body the segment projection drops by design (CLOUD-723). The wider word-fragment scan that catches the first was tried and REVERTED — it refused a for loop that merely mentioned the path, and a guard that refuses ordinary mentions is one people switch off. Closing either needs the prospective content as a fact rather than a string to grep, which is its own row.

Why

protected crossed with the [[verb]] table is the gate that replaced memory-guard (CLOUD-442). The verb table is an ENUMERATION of shell write shapes — redirects, tee, mv/cp/rm, an in-place stream edit, a version-control move or remove — so it decides by naming the program. A program it does not name writes the same bytes to the same protected path and is allowed.

Measured 2026-08-29 over the shipped binary against this repository's committed config, one protected path (batten.toml), five spellings of "write to it":

command verdict
echo x >> batten.toml deny (exit 2)
sed -i s/a/b/ batten.toml deny (exit 2)
tee batten.toml deny (exit 2)
python3 -c "open('batten.toml','w')" allowed, exit 0
perl -pi -e s/a/b/ batten.toml allowed, exit 0

This is not hypothetical and was not found by reading. It was found because an agent used it by accident: while building CLOUD-1131 this session edited batten.toml four times with python3 - &lt;&lt;'PY' … PY heredocs and was never refused, in the same session where an Edit tool call to a policy module was correctly denied. The gate was silent for the file it exists to protect while loudly correct about a file one directory away.

Why the deny half looks healthy

Nothing is misconfigured. protected names the three globs it should, and the Edit/Write tool half works — CLOUD-1133 fixed the absolute-path miss that had made it silent, and crates/batten/tests/mediated_verbs.rs covers the corpus. The hole is in the COMMAND half's shape: an allowlist-by-omission, where the safe set is "every program nobody enumerated".

.serena/memories/** and .github/workflows/** are exposed identically. The memory tree is the sharper case, because memory-guard was retired INTO this gate on the claim that the verb table covered its last five write shapes — and it covers them only for the programs it names.

The obvious fix is the wrong one

Adding python, perl, ruby, node, awk and busybox to the table extends the enumeration by six and leaves it an enumeration. The next interpreter is unrefused, and the table now implies a completeness it does not have — worse than the current state, which at least fails visibly the moment somebody probes it.


Refinement — Ready

Refinement gate: Definition of Ready & Done. This body carries only specializations.

  • Source of truth (§1). batten.toml's [[verb]] table and the protected set stay the one authority on what is protected and what mutates. What changes is how a call is CLASSIFIED against them, in crates/batten/src/hook.rs, beside the existing segment reading. No second protected list.
  • Computable predicate (§2). A mediated command whose argv names a protected path in a position the boundary cannot prove is a read is refused, rather than allowed because its program is unlisted. The direction is the point: unknown must fail closed on a protected path, where today it fails open. Decidable from input.call.segments and programs, both already projected — no new fact class and no file is opened.
  • What must NOT change (§2). Reads stay allowed. cat, grep, sed -n 1p and every other read of a protected path are the ordinary case, and a gate that refused them would be switched off within a day — which is how this class of guard dies. The narrowing therefore has to distinguish "names the path" from "writes it" for programs whose argv grammar nobody has encoded, and the honest answer for a genuinely unknown program may be to refuse and offer the hatch rather than to guess.
  • Explicitly not the lever (§2). Extending the [[verb]] enumeration with the interpreters measured above. It closes these two instances and leaves the shape, which is what makes the current state a defect rather than a gap.
  • Effect (§3). read. Classification over an argv the engine already parses.
  • Output and exit contract (§5). Unchanged: the existing V-PROTECTED-MUTATION class, its redirect and the 0/1/2/3 table. Pointer-only — the path and the verb, never the content.
  • Commit / bump (§6). fix(hook)patch until 0.1.0. Behaviour-changing in the raise-only direction house style §8 permits: calls that pass today start being refused, and none that fail today start passing.
  • Test obligation (§7). Over the compiled binary through batten hook, shown able to fail per CLOUD-418. The discriminating pair is the measurement above — one protected path, a named verb and an unnamed interpreter, asserted TOGETHER, because either alone passes against a gate that answers the same way for everything. Plus the direction a careless fix breaks: a read of a protected path by any program stays allowed, and an unprotected path stays allowed for every program. crates/batten/tests/mediated_verbs.rs is where the write-shaped corpus already lives.
  • Blockers (§8). None. relatedTo CLOUD-442 (which retired memory-guard into this gate on the coverage claim this refutes), CLOUD-1133 (the tool-write half of the same gate, whose absolute-path miss was the mirror defect), CLOUD-96 (the gate itself).

Acceptance

  • A write to a protected path via an interpreter the verb table does not name is refused, measured over the shipped binary.
  • A read of a protected path is still allowed, for a named program and an unnamed one alike.
  • An unprotected path is untouched for every program.
  • The remedy is not a longer list of program names, and the acceptance says so: adding one more interpreter to the table must not make these tests pass.

Found while building CLOUD-1131, by noticing that four of this session's own batten.toml edits went through a gate that had just correctly refused an Edit to a policy module.


Generated by Claude Code

CLOUD-1210 118 integration test targets link 14.1 GB of near-identical binaries, and the retirement campaign adds one per retired gate

Why

crates/batten/tests/*.rs is 118 tracked files, and crates/batten/Cargo.toml declares no [[test]] entries — so cargo autodiscovers one test target per file and links 118 separate binaries, each against the whole closure (gix, regorus, syn, clap, jsonschema, hyper/rustls).

Measured, this container, 2026-08-30:

  • target/debug/deps holds 122 extension-less artifacts totalling 14.1 GB, averaging ~116 MB each; the directory is 17 GB.
  • Partial rebuild (119 crates against a warm remainder): **309s wall, 142.0s nextest **Summary. A true cold build was never measured and this number must not be quoted as one.
  • Warm, nothing changed: 231s wall, 127.0s Summary, 5.9s freshness check — leaving 97.7s, 42% of the loop, unattributed. That residue is NOT this row's to claim: see the withdrawal below.

An earlier revision of this row quoted a 1376s cold arm and "~90% of it compile and link". Both were derived by guessing when the run started and ended rather than reading it, and both are withdrawn — which is CLOUD-1208's point arriving on its own sibling.

crates/batten/src/prune.rs:262-269 already records this artifact set — 114 binaries, 13411.9 MB, 86.8% of deps, measured 2026-08-20 — but reads it as a disk problem (CLOUD-766). It is the same fact, and nobody has priced it as latency. CLOUD-1158 is a third reading of the same count, as a moving basis under [prune]'s floors.

Prior art, and the mechanism here is adopted rather than invented. matklad, Delete Cargo Integration Tests, is the canonical statement of this defect: "rustc needs to repeatedly re-link the library crate with each of the integration tests", producing "a significant compilation time blow up for tests". Its recommended layout for a large codebase is exactly tests/it/main.rs plus one module per former file — the §3 below. Cargo's own repository did this refactor and measured it: the time to compile the test suite fell 3x and the size of on-disk artifacts fell 5x. A 5x artifact reduction against this tree's 14.1 GB is the same order as the byte problem CLOUD-766 is open on, arriving from the cause rather than the symptom.

A SECOND SAVING THIS ROW CLAIMED AND HAS NOW WITHDRAWN. An earlier revision said the 97.7s warm residue was nextest's per-binary list phase and therefore fell with the target count — making it this row's second measured win. Falsified 2026-08-30. A zero-match filter run (-E 'test(zzz_no_such_test_zzz_)') pays the freshness check AND the full enumeration and then runs nothing: Starting 0 tests across 119 binaries (3167 tests skipped), total wall 1.75s. The list phase is 56x smaller than the residue it was supposed to explain, so consolidating targets does not recover it and this row must not be justified by it. What the residue actually is remains unknown — CLOUD-1208's subject, arriving on its sibling for the second time.

What survives is the prior art, and it is enough on its own: 3x off test compile time and 5x off on-disk artifacts, measured by Cargo's own repository. This row stands on that and on the 14.1 GB census, not on a residue nobody can attribute.

A third saving the prior art names, stated as a possibility rather than a claim. Splitting tests across binaries means a shared expensive fixture is built once per binary rather than once; consolidated, a OnceLock/LazyLock setup is paid once. This tree materialises scratch git repositories per fixture through real git subprocesses (tests/common/mod.rs::git_in), so the cost is live — but whether any of it is actually shareable is a question for the migration, and this row does not price it.

Root cause, and why it gets worse on its own. The convention is one integration target per contract surface, which is locally correct and has no stopping condition — the same shape CLOUD-365 records for case counts. CLOUD-843's retirement campaign adds a target per retired gate, with ~147 shell suites still standing, so the count is monotone in the campaign's own progress. prune.rs's comment records the count moving 110 → 114 → 118 across three readings in ten days.

TWO RETIREMENTS ARE IN FLIGHT AGAINST THIS FILE DOMAIN, so the interaction is live rather than anticipated: CLOUD-1145 (derived-check) and CLOUD-1161 (ci-local-parity), both In Progress on 2026-08-30, and each mandated to add a top-level crates/batten/tests/*.rs. Whoever lands this row rebases across them and re-homes their files into a group as a mod — a mechanical move, not a content conflict, and not something to engineer around.

A THIRD READING, measured 2026-08-31: on this container the loop does not close

The existing readings price this as latency (compile time) and as bytes (deps at
14.1 GB). Both are true and both understate it. Measured across four consecutive
mise run land attempts in one session:

  • **One **verify lap consumes 25403 MB, reported by target-prune's own
    lap-close line: "the lap opened on 43fc2414 consumed 25403MB".
  • The container's writable allowance leaves ~24 GB free after a full purge of
    target/debug/deps + target/tmp.
  • So a lap consumes more than the machine can hold, and target-prune closes it
    below its own warm floor every time: free 5680MB, floor 7264MB.

The consequence is not slowness, it is that verify can run at most once per**
**manual purge and then wedges. Three of the four land attempts in that session
died at target-prune before reaching CI, and each recovery was a hand-rm of
target/debug/deps — which CLOUD-1218 records as inverting the ratchet, so the
remedy is itself a defect.

target-prune's refusal also names the wrong remedy, which is what sent the
first recovery in the wrong direction: it says "Free space outside ./target, or*
*start a fresh session" while 17 of the 20 GB were **inside **./target, in
deps, in artifacts its warm-basis logic declines to reclaim. Following the
advice as written leads to deleting cross-compile roots — measured earlier the
same session at 2.3 GB freed and 8.3 GB spent rebuilding them on the next lap.

This is the same 14.1 GB artifact set the row already counts; what is new is that
it is now larger than the headroom, so the failure mode changed from "slow" to
"cannot complete". Raising to High on that basis: a contributor on this container
cannot run the repository's own required gate twice without hand-purging, which is
the condition CLOUD-1218 exists to prevent.

REOPENED 2026-09-01 — this row reached Done without being built

It was closed on 2026-08-31 and none of §3 exists. The evidence is in the row's own metadata and in the tree:

  • startedAt: null. The row went Backlog → Todo → Backlog → Todo → Done, never In Progress.
  • No PR attachment. Its sibling CLOUD-1211 carries PR perf: the gate tail 84.4s→37.2s and the Rust suite 100.2s→48.6s, each measured against a null #775; this row carries nothing.
  • Checked against origin/main (a673667), not a stale checkout: 144 top-level crates/batten/tests/*.rs, zero [[test]] entries in crates/batten/Cargo.toml, and no crates/batten/tests/*/main.rs. The grouping §3 specifies was never done.
  • **The ratchet was never written. **batten.toml carries tests-not-deleted and bats-tests-not-deleted, both token counts of #[test] occurrences; no policy/*.rego module refuses an added top-level crates/batten/tests/*.rs.

It was a sweep, not a decision. Six rows carry a completedAt inside a 1.7-second window on 2026-08-31: CLOUD-699 (14:19:59.748), CLOUD-1211 (14:19:59.865), CLOUD-1210 (14:20:00.294), CLOUD-592 (14:20:00.588), CLOUD-1208 (14:20:00.756), CLOUD-1216 (14:20:01.463). No human sets six statuses in 1.7s.

The count grew while this was being re-groomed

In the 8 commits between one container's clone (1a79d8a) and origin/main (a673667), top-level test targets went 142 → 144: commit_admission.rs and mediated_admission.rs added, none deleted. That is this row's own "the campaign adds one per retired gate" observed live, over hours rather than the ten days prune.rs recorded — and it is a stronger argument for the ratchet than the 110 → 114 → 118 series the row was filed on.

§8 is discharged, and the measurement it was blocked for now exists

CLOUD-1208 is Done, so the blocker is cleared. Measured 2026-09-01, this container, 4 cores, cargo nextest run --workspace:

phase cold after touching one src/*.rs no-op
build 277s 48.0s 0.4s
run 139s 92.6s 92.6s

One test target rebuilds and links in ~1.0s (touched tests/walker.rs alone: 1.03s cargo). So the 48s decomposes as ~10s lib compile plus 144 targets × ~1.0s at 4-wide ≈ 36s of linking — the link step is up to ~75% of every rebuild, and a rebuild is what a developer pays per edit.

Read the 75% as an upper bound, not a point estimate. It was measured with touch, which changes mtime with identical content: cargo fingerprints on mtime so all 144 targets relink either way (the ~36s stands), but incremental codegen has nothing new to do, so the lib-compile half is understated and a real edit shifts the ratio toward compile. The paired before/after this row's acceptance requires must use a real edit, not a touch.

What this measurement does NOT support, stated because the row has withdrawn two over-claims already: it says nothing about the 92.6s run phase, which is separately measured at 4.01x parallel efficiency on 4 cores and has no scheduling headroom left. Consolidation is a build-time win only, exactly as §2 already says.

Refinement — Ready

  • **Source of truth (§1). **git ls-tree -r --name-only <ref> -- crates/batten/tests/, filtered on ^crates/batten/tests/[^/]+\.rs$, against the artifact census in target/debug/deps. NOT git ls-files 'crates/batten/tests/*.rs', which this clause specified until 2026-09-01: git's default pathspec * matches /, so that form also matches crates/batten/tests/common/mod.rs — a file common/mod.rs:19-21 states is deliberately not a target — and reports one target too many. prune.rs:262-269 is the prior reading and the one this row must not contradict silently.

  • Mechanism (§3). Group the files under crates/batten/tests/<group>/ with a main.rs declaring one mod per former file. Cargo compiles a tests/ subdirectory as a single target exactly when it holds a main.rs — a fact tests/common/mod.rs:19-21 already states and relies on, which is why common/ is not itself a target today. Grouping is mechanical and case-preserving.

  • The ratchet that stops it re-growing (§3), and it counts TARGETS rather than FILES — the distinction is load-bearing. A [[ratchet]] row on the count of cargo test targets, direction non-increasing. ratchet is an existing rule kind (CLOUD-55) and tests-not-deleted is the precedent. Without a ratchet the campaign restores the count one retirement at a time and this row buys a one-off saving instead of a property.

    A FILE-count ratchet would refuse CLOUD-843**'s entire campaign, which is why the spelling is not free. **.claude/rules/toolchain.md's two-shapes rule requires a retirement to land its predicate as a policy/*.rego module **plus a **crates/batten/tests/*.rs tier, and .claude/rules/policy-modules.md states that tier "is crates/batten/tests/*.rs and never a .bats". So every retirement adds a top-level test file by mandate — which is exactly why prune.rs recorded the count moving 110 → 114 → 118 across three readings in ten days. A ratchet over top-level FILES would fire on the next correctly-executed retirement, and the campaign would have to switch it off: the shape a gate does not survive.

    Counting targets removes the collision by construction — a retirement's tier lands as a mod inside an existing group, the target count does not move, and the ratchet never sees it. That is matklad's recommended layout anyway, so the campaign adopts nothing it was not already heading for. But the convention has to be written down when this lands, or the next retirement author creates a new top-level file and trips a gate nobody warned them about.

  • The assertion that makes this safe (§3) — upstream-documented AND asserted here. nextest's design doc states it plainly: "A key factor distinguishing nextest from cargo test is that nextest runs each test in a separate process", giving memory isolation, state isolation and independent termination (why-process-per-test). So consolidation changes the link count and nothing a case can observe. The claim is load-bearing — if it were false this row would be trading isolation for speed — so it ships as a case pinning that a test's visible environment does not depend on its target, rather than resting on the citation alone.

  • What consolidation does NOT buy, stated so it is not over-claimed (§2). It does not reduce the number of test PROCESSES: nextest already spawns one per case, and 3167 of those remain. It does not recover the 97.7s residue — measured, the list phase is 1.75s. The saving is in link count and artifact bytes, and in compile time by the prior art's measurement. Nothing else.

  • Deliberately not in scope (§2). Deleting or merging any test case; tests-not-deleted must stay green across the whole change. Also out of scope: the [prune] floors, which read the same count and are CLOUD-1158's — but that row's basis moves when this lands, so it is named here rather than left to discover it.

  • Output (§7). Counts and target names.

Test obligation

tests-not-deleted green across the change; the target-count ratchet fails on a re-added top-level file; and the isolation case above fails if a grouped test can see something a standalone one cannot.

Commit / bump (§6): test(harness) — no bump. Not breaking for the consumer surface or the library surface: no shipped verb, no public item and no config key moves; the change is entirely under tests/.

Blockers (§8): blocked by CLOUD-1208, because the saving must be measured before and after rather than claimed — the prior art's 3x/5x is a measurement of Cargo's tree and not of this one, and the residue this row once claimed has been falsified. relatedTo CLOUD-766 (same artifacts, disk reading), CLOUD-1158 (same count, as a prune basis), CLOUD-843 (the campaign that grows it), CLOUD-365, CLOUD-352.

The mechanism, RESOLVED (2026-08-30) — this row's last unrefined clause.

What was undecided: §3 names kind = "ratchet", and that kind cannot say this. Its fields are glob, pattern, direction, base, severity, and its own doc defines it as "the total occurrences of pattern across files matching glob" — a token count inside files. Every committed row is that shape: tests-not-deleted counts #[test], bash-surface-not-growing counts #MISE description=. A cargo test-target count is a property of the directory structure plus Cargo's autodiscovery, and no glob+pattern pair computes it.

The answer is a tree-scoped policy module, and the fact it needs already exists. input.tree["base-delta"] is declared in schema/policy-input.schema.json with added, edited, deleted, base-lines, base-date and code-changed; policy/filed-here.rego already reads it. So the predicate is:

refuse when base-delta.added contains a path matching crates/batten/tests/*.rs at the top level — one segment past tests/.

That is the non-increasing target count stated structurally, and it resolves each problem the open question named. No token count — it asks about paths, which is what a target count is a property of. No base-rev position to take — the base delta is the comparison, so the hard half of the ratchet spelling disappears rather than being solved. No spawn — a command row was the other candidate and CLOUD-843's campaign is retiring exactly that shape.

Why the file spelling is sound here when a [[ratchet]] file count was not. Cargo autodiscovery makes top-level tests/*.rs and test targets 1:1, and a file inside a group directory (tests/<group>/foo.rs) is not a target — so "no new top-level crates/batten/tests/*.rs" is "the target count does not grow", exactly. The earlier objection was to a [[ratchet]] counting files, which fires on any added file including a group mod; this fires only on an added TOP-LEVEL one, which is the thing that mints a target. A retirement landing its tier as a mod inside a group is invisible to it, which is the property that keeps CLOUD-843's campaign running.

One trap for whoever writes it. .claude/rules/policy-modules.md's list of input.tree.* keys does not name base-delta (or symbols), though the generated schema declares both and filed-here.rego reads the first. That file states rules-drift holds its lists to the schemas, so the omission is either a drift the gate is not catching or a list that needs updating — worth resolving in the same change, because an author who checks the documented list will conclude the fact does not exist and reach for a worse mechanism. That is exactly the silent-dead-gate class that file is otherwise about.

Acceptance

  • The target count falls and the ratchet holds it.
  • The delta is quoted from CLOUD-1208's harness against its measured null, not from a hand timing.
  • No case is deleted, and the isolation assertion exists and is shown able to fail.

Review in Linear

@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 34 minutes.

Check out review usage here.

View limit details

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

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Free

Run ID: 786b271f-f191-425e-bd05-1f6321b437e3

📥 Commits

Reviewing files that changed from the base of the PR and between aad25b1 and 9ece058.

📒 Files selected for processing (8)
  • batten.toml
  • crates/batten/src/facts.rs
  • crates/batten/src/lib.rs
  • crates/batten/tests/it/board_receipts.rs
  • crates/batten/tests/it/main.rs
  • crates/batten/tests/it/mediated_verbs.rs
  • crates/batten/tests/it/test_targets.rs
  • mise.toml

Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Essentials by visiting https://app.coderabbit.ai/settings/billing.

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

@wenzowski
wenzowski marked this pull request as ready for review September 1, 2026 19:52
@wenzowski
wenzowski force-pushed the claude/policy-module-mutations-rm4hz1 branch from 055c00b to 6bec24f Compare September 1, 2026 19:52
@wenzowski
wenzowski marked this pull request as draft September 1, 2026 20:12
…d gate it

CLOUD-1210 grouped 144 test targets into one and asserted the property that
makes the grouping safe — nextest gives each case its own process, so
consolidation changes the link count and nothing a test can observe — in
`crates/batten/tests/it/target_consolidation.rs`.

That file has no `mod` line in `it/main.rs`, so it has never compiled and its
three cases have never run. `main.rs`'s own header cites it by name as the
mechanism behind that claim, and the file's doc says the claim "is load-bearing…
so the property ships as a case rather than as a sentence in a commit message".
It shipped as a sentence.

Nothing caught it and nothing could: an undeclared `.rs` beside a target is not
a rustc error, it is simply not compiled, and `policy/test-targets.rego` refuses
only a new TOP-LEVEL target. A file that lands INSIDE the group and is never
declared is invisible to both.

So this declares it — and gates it, because the repair alone leaves the cause
live (non-negotiable rule 2). `test_targets.rs` gains a case over the LIVE tree,
for `mediated_verbs.rs`'s reason: the question is what THIS repository's group
declares, and a fixture would assert about a `main.rs` the case wrote itself.

Both directions, because either alone is satisfied by a degenerate tree — an
empty group declares nothing and is missing nothing — plus a floor on the
declared count so the pair cannot pass over the wrong directory. A directory is
a module too: `mod common;` resolves to `it/common/mod.rs`, and counting only
files would report it missing.

Shown able to fail (CLOUD-418): with the `mod` line removed the case fails
naming `["target_consolidation"]`. All three restored cases pass.

Refs: CLOUD-1210, CLOUD-418
…ger exists

The restructure left two `mise.toml` tasks addressing cargo targets it had just
removed. Autodiscovery now yields exactly `it` and `policy_modules`.

`test:hook-profile` filtered `binary(hook_profile)`, which is now the module
`it::hook_profile`. Its four sibling task filters were repointed and this one was
missed — it is also the only one of the five carrying neither a `description` nor
the `BATTEN_TEST_SCRATCH_LANE` its siblings set, which is the tell. Nothing
invokes it, so it was dead rather than breaking; its subject still runs under
`test:cargo`. Now selects 8 cases and passes.

`snapshots` was the other, and it landed on `main` from another branch while this
one was in the loop — so what survives the rebase is one line the two of us both
had to touch and only this side did: the task's opening comment still announced
the bound as `--test snapshots`, a target that no longer exists, while the `run`
line beneath it had moved to `--test it snapshots::`. A note naming the retired
spelling is how the next reader re-derives the wrong repair, which is the defect
that note exists to prevent.

Refs: CLOUD-1210
`mediated_verbs.rs` pins `python3 -c "open('p','w')"` as an asserted-allowed
known gap, so a reader can see the protected-path gate does not reach it. The
heredoc form had no case, which is the CLOUD-418 shape one layer out: a suite
that reads complete over a shape it never exercises.

The two lose for different reasons, and the second is structural rather than a
scanning limit. `-c` loses because the path sits inside a quoted word, so a wider
word scan could in principle reach it — and was tried, and was reverted for
refusing ordinary mentions. `python3 - <<'PY'` loses earlier: heredoc bodies are
dropped before any predicate exists, because a body is data and not shell
(CLOUD-723). The segment's words are `["python3", "-", "<<'PY'"]`, so the operand
list contains no path at all and no operand-based check can reach it however wide.

Measured 2026-09-01 on `5a9924b6`: a `python3` heredoc wrote a registered policy
module and was not refused.

This is a pin, not a fix. CLOUD-1141's own commit names this shape as
deliberately open, and its body says closing it "needs the prospective content as
a fact rather than a string to grep, which is its own row" — so the case flips
when that lands, and the flip is the signal, exactly as its sibling carries.

Refs: CLOUD-1141, CLOUD-723, CLOUD-418
Closes CLOUD-1147.

A host may refuse to hand over a large tool result, write the bytes to a file and
substitute a plain-text notice naming it. The notice is prose, so
`facts::payload_in` cannot parse it, `record_mints` returns early, and no
`issue-read` receipt is written — silently, because a mint's failure is silent by
design. `an-update-owes-a-recent-read` then refuses the write and names RECENCY,
so the agent re-reads, is intercepted identically, and is refused again. The
remedy the refusal states is the operation that fails, and it fails BECAUSE the
row is large. Three rows reached that state: CLOUD-1128, CLOUD-1151, and
CLOUD-1147 itself.

WHAT THE ROW BELIEVED, AND WHAT IS TRUE. CLOUD-1147 was written on the premise
that the payload is gone once intercepted — it says the adjacent capture spine
"has nothing to recover either" — and its §2 offers two shapes around that: a
receipt over a declared field subset, or a route that never reads the body.

Probed 2026-09-01, and the premise is false. The envelope's `result` is not
`null`, so the early return is not what stops the mint; it is a STRING whose text
names an absolute path, and that file holds the complete payload the server
returned. The bytes survive interception. Nothing looked.

So this recovers rather than compromises, and CLOUD-691's forgery objection does
not reach it: the receipt attests exactly the bytes the server sent, not a subset
standing in for them.

THE BOUNDS, each load-bearing. `spilled_path` is pure and does no IO — it keys on
the notice's SHAPE (`saved to <absolute path>`, terminated at end of line) rather
than one host's wording, so a host phrasing it differently recovers nothing and
falls back to the ordinary no-mint path, which is the direction a miss must fail
in. Only a path a HOST placed in a result it substituted is followed, never one a
caller supplied. It is read once, and only after the ordinary decode already
failed, so no clean result pays for it. Everything downstream is unchanged: the
recovered value goes through `payload_in` and a mint's `requires` like any other.

The negatives are the half that makes this a recovery rather than an amnesty, and
they earned their place during the work: the recovery was first wired into
`write_records` by mistake. It compiled, and all three negatives still passed —
only the positive case caught it. A suite of negatives alone would have shipped
that.

Shown able to fail (CLOUD-418): with the `or_else` removed, the positive case is
red.

`batten.toml`'s prose is corrected in the same change, under two declared
admissions. It stated twice that the receipt is minted by `mise run
issue-read-check`, a task retired out of the tree — so a reader hitting this
refusal was sent to a program that cannot be run. `record_mints` is the only
producer.

Refs: CLOUD-1147, CLOUD-691, CLOUD-418, CLOUD-1121

Admits: 2d32dcbde1763dc12d161f65e9d5523b8bf99b2d41964fa97273cdcfe780bc57
Admits-rule: protected-mutation
Admits-verdict: V-PROTECTED-MUTATION
Admits-subject: batten.toml
Admits-head: 54796b1
Admits-epoch: 71bf7f9f4f378e71c73ceeea5eb44c0217d75bdca1c242d0f9cdfb31f03c5117
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: The config keeps telling a reader that a hand-run fallback mints this receipt. That is the exact remedy CLOUD-1147 is open about being unsatisfiable, and it now names a program that cannot be run at all — so an agent hitting the refusal is sent to a task that does not exist, which is strictly worse than being sent nowhere. The fix would land with its own configuration contradicting it.
Admits-answer-precondition: `batten.toml` is the policy authority itself and has no owning surface that edits it — no generator emits it, and the class exists so an agent cannot quietly rewrite the rules it is judged by. The change is comment prose only, in the rows around `an-update-owes-a-recent-read`, and no rule, pattern, verb or verdict is touched. It states twice that the `issue-read` receipt is minted by `mise run issue-read-check` — a task that no longer exists anywhere in the tree, leaving `record_mints` as the only producer. CLOUD-1147's fix lands in that same producer, so correcting the prose beside it is the honest half of the change rather than a separate tidy-up.
Admits-answer-rejected-route: R-RESTORE-IT is the one rejected: restoring the committed bytes keeps prose that is false about the code beside it, and false in the direction that wastes a reader's time on a retired program. R-USE-THE-OWNING-SURFACE does not apply — `batten.toml` is the authority and nothing generates it.

Admits: 8eb18048e8a06943b7bf55e3384fdd96a8ee72fd7d5ea91abd03fd8d816083c3
Admits-rule: protected-mutation
Admits-verdict: V-PROTECTED-MUTATION
Admits-subject: batten.toml
Admits-head: 54796b1
Admits-epoch: ef2e608c2770140091f311f59fa609b80479417dcfa04281ae45a23aa59d0592
Admits-author: alec@wenzowski.com
Admits-prev: 2d32dcbde1763dc12d161f65e9d5523b8bf99b2d41964fa97273cdcfe780bc57
Admits-answer-lost: A second false claim about a retired program stays in the authority every gate reads, in the same rows CLOUD-1147's fix lands in. Leaving one of the two corrected and not the other is worse than leaving both: it reads as if the remaining one had been checked and kept.
Admits-answer-precondition: Second comment-only correction in the same file and the same change, needing its own admission because an admission is spent per write. `batten.toml` has no owning surface — nothing generates the policy authority. This clause says `mise run issue-read-check` "still writes five" fields; that task is retired and writes nothing, so the sentence is false about a program that no longer exists. What the clause is really about — a sixth field appended rather than inserted, leaving every positional reader working — stays exactly as it was. No rule, pattern, verb or verdict is touched.
Admits-answer-rejected-route: R-RESTORE-IT is the one rejected: the committed bytes assert that a deleted task still mints receipts. R-USE-THE-OWNING-SURFACE does not apply — `batten.toml` is the authority and nothing emits it.
…form's spelling

`spilled_path` gated recovery on `path.starts_with('/')`. That is the shape of
an absolute path on Unix, not the question — `D:\a\_temp\result.txt` fails it,
so on Windows the recovery never fired and the mint starvation CLOUD-1147
records stayed live there while the positive case passed green everywhere else.

Measured: the windows job on 6bec24f went red on
`an_intercepted_read_recovers_the_spilled_payload_and_mints` alone, 1 of 1634
run. `Path::is_absolute` answers on both platforms and keeps the bound the
comment already claimed.

The bound now has an arm of its own rather than resting on the platform that
happened to run it. `a_notice_naming_a_relative_path_recovers_nothing` puts a
real payload at `result.txt` and names it relatively: whatever is at the path,
a relative name resolves against whatever directory the hook is running in and
so cannot be what the notice meant.

Shown able to fail: with the absoluteness check removed entirely the suite is
1 failed of 1640 run, and it is that case.

Refs: CLOUD-1147
@wenzowski
wenzowski marked this pull request as ready for review September 1, 2026 22:04
@wenzowski
wenzowski force-pushed the claude/policy-module-mutations-rm4hz1 branch from 6bec24f to 9ece058 Compare September 1, 2026 22:04
@sonarqubecloud

sonarqubecloud Bot commented Sep 1, 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

@wenzowski
wenzowski merged commit 9ece058 into main Sep 1, 2026
10 of 11 checks passed
@wenzowski
wenzowski deleted the claude/policy-module-mutations-rm4hz1 branch September 1, 2026 22:24
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