fix(ci): stop writing cache no run can read, and land the loop family's spine - #833
Conversation
CLOUD-1342 CLOUD-1331's `perf-base` entry can never be read: the merge-base SHA in the cache KEY guarantees a miss, and a PR-scoped entry cannot cross a PR — 4 runs, 0 hits, ~190 MB written each
Why CLOUD-1331 landed (#825, The CI half cannot collect that, measured 2026-09-02. Every Two independent failures, either one sufficient:
Four The SHA is doing two jobs and only one of them is load-bearing. In the directory name it is what makes a stale arm unmeasurable and must stay. In the cache key it guarantees the miss and buys nothing: the dependency closure depends on the toolchain and Refinement — Ready Refinement gate: Definition of Ready & Done. This body carries only specializations. {
"source_of_truth": ".github/workflows/ci.yml",
"gate": { "task": "verify", "exits": [0, 1] },
"commit_type": "fix",
"blockers": [],
"tests": [{
"file": "crates/batten/tests/it/ci_parity.rs",
"mutation": "cache-path-may-carry-the-base"
}]
}
Acceptance
Refinement history — the cross-PR half was split out, and this row is what remains. As first filed, §2 asked for "an entry readable by an arbitrary PR", which requires an entry written from a That half is now **out of scope and lives on **CLOUD-840, whose whole subject is "no PR can read another's"; the candidates noted here (a scheduled What remains needs no new trigger: a stable cached path, the SHA in the key, a prefix restore, and a seed step that drops the stale binary. §2, §7 and Acceptance above are rewritten to that scope and no longer presuppose the forbidden trigger. Second correction, 2026-09-02. The narrowed row still specified the wrong mechanism — "drop the SHA from the key, keep it in the directory name" — which §2 now records as a no-op, because CLOUD-1331 `perf pair` builds the base arm's whole release closure from nothing on every CI run — the base binary is a pure function of the merge base, so the `perf` job pays 13.5 min for a build no run can reuse
Why The **Measured 2026-09-02 from two **
For comparison the same runs' What the step does, read from the engine rather than the job. Where the cache reaches and where it does not. The base arm is a pure function of the merge base. Its inputs are the merge-base SHA, the pinned toolchain and Why the skip does not save this. CLOUD-875 widened Refinement — Ready (reuse the base arm across runs) Decided 2026-09-02, so the implementer does not choose: the base arm keeps its OWN target directory, keyed on the merge-base SHA, and is restored WHOLE from a cache keyed on that SHA plus the toolchain hash. Closure sharing between the two arms is NOT taken — the Refinement gate: Definition of Ready & Done. This body carries only specializations.
Acceptance
Found while grooming the CI-cost dispatch: measured against the runs above, CLOUD-1343 The prescribed claim order mints the receipt on the wrong branch: claim before code, branch after, and every write is then refused — recovered only by `--takeover` against yourself
Why AGENTS.md prescribes the order plainly: "In Progress = pulled — claim by hand, before writing code ( Those two are incompatible whenever the feature branch does not exist yet, which is the ordinary case. Measured 2026-09-02 landing CLOUD-1331, in this exact sequence:
Why each existing row is adjacent and none is this. CLOUD-733 (Done) is a renamed branch stranding its receipt. CLOUD-516 / CLOUD-1091 are a restarted branch inheriting a stale one. CLOUD-1231 is one key per branch against a multi-row PR. This is none of those: nothing was renamed, restarted or multi-row. Following the documented order, first time, correctly, produces the refusal — and the recovery writes a takeover record that CLOUD-786 observes nothing reads and CLOUD-1139 observes never checks whether the holder is alive. Here the holder is the same session, which is the one case a liveness check would answer trivially. Why it matters beyond the friction. A self-takeover is indistinguishable in the record from taking a row out from under a working sibling — the exact event CLOUD-1139 measured twice in one hour. Making the ordinary path mint one teaches every session that a takeover is routine, which is precisely the signal that row needs to stay rare. Refinement — Ready Refinement gate: Definition of Ready & Done. This body carries only specializations. {
"source_of_truth": "AGENTS.md",
"gate": { "task": "verify", "exits": [0, 1] },
"commit_type": "fix",
"blockers": [],
"tests": [{
"file": "crates/batten/tests/it/claim_order.rs",
"mutation": "order-may-go-unstated"
}]
}This clause was first filed as feedforward with an empty
Acceptance
Found landing CLOUD-1331: step 4 cost a full stop mid-session, and step 6 put a takeover in the record for a row nobody else had touched. Refined 2026-09-02: the mechanism is chosen, and the row is SPLIT because the two halves have different owners. The previous revision named two mechanisms and chose neither. Working all three of its open questions through: Q3 first, because it is the cheapest and the row half-admitted it. The resolution is NOT in the engine. Working the sequence: create the branch, THEN What actually produced the measured failure is a re-run, and the refusal invites it. Q1 and Q2 — the engine half is CLOUD-1139's, on a reason rather than by deferral. Recognising a self-claim needs to distinguish this session from a sibling. It cannot be done from the row's assignee: every session in a fleet carries the same configured accountable identity ( So §1 and §2 are rewritten to the ordering half only, and
What is not in doubt: the measured sequence in the body is a real defect, it is reachable by following the documented order correctly on first use, and the recovery writes a takeover record for a row nobody else touched. The acceptance bullets stand as written, narrowed to the ordering half; the mechanism is no longer open. CLOUD-1344 Four of five `Extraction` members reach no consumer, and repetition — the one thing a doom loop is made of — has no member at all
WhyBatten has no doom-loop gate. It has nearly all the substrate for one, unused. Measured in the tree 2026-09-02:
The cost this leaves ungated is this repository's own most expensive failure mode. CLOUD-1337 measured eleven duplicate watchers running 9h 35m, found by a human reading What this row builds, and why it is the one that must land firstThree things, and none of them is a predicate — this is the spine every later predicate stands on.
3. A per-extraction capability declaration — and this is the load-bearing half. An extraction resolves three-valued, the way This is what lets the surface EXPAND rather than shrink. Batten is harness-agnostic, and the wrong reading of that is to implement only what every harness shares. The right one is: if any agent emits a signal, Batten should read it, and cross-compatibility is carried as declared capability rather than as refusal to implement. The declaration is the precondition for every later member.
The framing the module header must carry, because the honest claim is narrower than "loop detection"Prior art, for the predicate shapes: OpenHands' stuck detector (4+ identical action-observation cycles, 3+ action-error, 3+ monologue, 6+ ping-pong), Kilocode / opencode (identical Academic anchor: When Agents Do Not Stop (IAL-Scan) — the defect is a feedback path that repeatedly reaches a costly or state-growing action without an effective bound; 91.9% precision over 68 failures in 47 of 6,549 projects. And the honest limit, from termination analysis: a ranking function buys no better predicate here. It needs an observable decreasing measure, and every quantity Batten can see is a monotonically growing count; manufacturing a descent ( Refinement — ReadyRefinement gate: Definition of Ready & Done. This body carries only specializations. {
"source_of_truth": "crates/batten/src/transcript.rs",
"gate": { "task": "verify", "exits": [0, 1] },
"commit_type": "feat",
"blockers": [],
"tests": [{
"file": "crates/batten/tests/it/repetition.rs",
"mutation": "run-may-go-unbounded"
}]
}
Acceptance
Filed 2026-09-02 from a doom-loop grooming pass. The substrate was found live and unused; the gap is that nothing declares it. Claims object added 2026-09-03, and the row was un-pullable without it. Every field is taken from this body's own clauses rather than chosen: The one field the body did not already contain is the mutation name. It was first written as What shipped are two mutations that do discriminate, both over the module and both naming cases in the compiled tier: Two defects were caught on the way in, both by the tier rather than by reading. The declarations first named Refined by the session that then claimed it, so the claim carries Landed 2026-09-03 on
The bound is stated in the code rather than absorbed: this cannot separate a host that records no hook runs from a session that triggered none, and it resolves that toward could-not-look — the safe direction, since a missing answer is reported where a false zero is indistinguishable from a clean session. One adjacent cleanup rode along, named here rather than left to a reader of the diff: CLOUD-1341 An across-turn poll is invisible to every gate: `run-shape` decides over a command string, so 1079 no-op tool calls — 59% of one session — read as work
Why CLOUD-821 measured 490 backgrounded Measured 2026-09-02 (CLOUD-1331's landing session). With
AGENTS.md already states the invariant — "The exit notification IS the wake-up; waiting for it costs nothing… 'idle' means a turn with NOTHING backgrounded" — and for this family it is prose, which non-negotiable rule 2 calls half a change. The instrument exists and is one declaration short. CLOUD-1172 built Refinement — Ready Refinement gate: Definition of Ready & Done. This body carries only specializations. {
"source_of_truth": "crates/batten/src/transcript.rs",
"gate": { "task": "verify", "exits": [0, 1] },
"commit_type": "feat",
"blockers": [],
"tests": [{
"file": "crates/batten/tests/it/extracted_facts.rs",
"mutation": "repeats-may-go-unpriced"
}]
}
Acceptance
Refined 2026-09-02. All three open questions are closed — two by analysis, one by measurement over a real transcript. The row's own §2 predicate was refuted in the process. The measurement: the one session transcript this container holds (13 MB, 4298 turns), parsed to The defect arm is far worse than this row claimed. The body says "~60 consecutive turns". The truth is Q1 — "no intervening state change" is replaced, and the replacement is forced by the data. The polls arrive in 249 separate runs, median length 5, longest 8. So "longest consecutive run of one tool" tops out at 8, which any healthy session reaches — the row's original §2 cannot discriminate and would have shipped as coverage, which is exactly what CLOUD-418 exists to catch. The recurrence count does discriminate, because it ignores what sits between two identical calls. That is the whole reason for the reformulation. Q2 — the null, and the separation is ~60x rather than marginal:
Both discriminate comfortably. The shipped fact is tool+args, at threshold 100 — 2.6x above the healthy ceiling of 38 and 10.8x below the defect — because the result-bearing identity would require the parser to read result BODIES to digest them. Paying 28x instead of 60x to never touch that payload is the trade rule 4 asks for, and CLOUD-199's false-positive worry is answered either way. Its limit, stated rather than absorbed: n = 1 session, and a pathological one. That biases the healthy arm upward — a well-behaved session should recur less — so 38 is an upper bound on the healthy ceiling and the direction is conservative. A cross-session null is unavailable here: this container holds exactly one transcript. Widening it is worth doing when a corpus exists, and would move the constant within its decade at most. Q3 — the fact shape fits, and the question dissolves. "Longest consecutive run" would have been a max over RUNS. The recurrence count is a scalar reduction over the stream, so CORRECTED 2026-09-03, and this sentence shipped the defect once. It read "a plain total over the stream, the same shape as Measured consequence: shipped as a sum at One residual, decided rather than left open. A legitimate repeated command — What is not in doubt: the measured instance, and that no existing gate can reach it. Both Second null, measured 2026-09-03 — the silence check this row's own incident demands. The shipped predicate was run against a real, healthy session's transcript over the compiled binary: That is a second point on a null this row previously had at n = 1, and that one pathological. The healthy arm is 0 here against a measured ceiling of 38 and a threshold of 100, so the constant clears an ordinary working session by the whole of its range rather than by a margin. It ships at CLOUD-1347 The measured doom-loop shape — the same call, again — has no predicate, and the fingerprint that decides it must never leave `transcript.rs`
WhyThis is the shape every practitioner detector converges on, and the one Batten cannot express.
It is also the shape measured in this container: CLOUD-1337's eleven watchers were the same call written eleven times, and the session that wrote them had read and agreed with the prose forbidding it. A rule an agent agrees with and then violates eleven times is the definition of a gate-shaped problem. The rule-4 question, and why it dissolvesRepetition needs identity to compute, not to express. Every predicate above reduces to a threshold on an integer — "the last 3 hashes are equal", "one fingerprint 3+ times". None of them references an argv, and none of them needs to. So the fingerprint is computed inside The precedent is in the same file and should be cited in the implementation: A "last call fingerprint" token is explicitly refused. It would put a correlatable identity of session content on the policy input, and no predicate here wants one. If a future row believes it needs one, that is its own rule-4 argument to make. The two members and the predicate
One predicate in
Threshold in the module, never a Why trailing-run adjacency is the design, not an implementation detailIt is what does the false-positive work, and the trap is measured: OpenHands' detector kills agents legitimately waiting on long-running processes and leaves them unrecoverable once flagged. CLOUD-199 already set this repo's bar — a guard with false positives gets bypassed.
Second conjunct, from the same trap: Refinement — ReadyRefinement gate: Definition of Ready & Done. This body carries only specializations.
Acceptance
Filed 2026-09-02 from a doom-loop grooming pass. CLOUD-97 Flag sessions that signal done with work not landed
Why Rejected alternative Definition of done
Acceptance
Refinement — Ready (completion-signaled ∧ ¬landed as a structural predicate; advisory finding, self-clearing on land) Refinement gate: Definition of Ready & Done. This body carries only specializations.
|
|
Warning Review limit reachedNext included review available in 59 minutes. View limit detailsLimit 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. Review configuration: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Free Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (18)
ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Free Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe performance job now caches a merge-base-independent seed directory. It restores the seed, removes the measured binary, rebuilds the base arm, and saves the resulting closure. Batten now reports Merge Risk: ⚪ Minimal · up to The PR stabilizes the CI performance-base cache path while preserving correctness, and the supplied checks are green. No actionable merge-blocking risk remains beyond normal review. Note 🎁 Summarized by CodeRabbit FreeYour 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 |
de872d6 to
a907565
Compare
…not a total An across-turn poll was invisible to every gate. Measured over one real transcript: `ReadNotifications` called 1079 times, 59% of every tool call in the session, every one with identical arguments and the identical "No queued notifications" result. All 1079 carry `role: assistant`, so this judges agent conduct and cannot fire on harness behaviour. No landed arm could reach it. `run-shape` keys on a backgrounded `sleep` and CLOUD-489 on `until`/`while` inside one command string; repetition spread across turns carries neither token, and a harness verb with no argv gives a `shape` row nothing to match. THE FACT IS A MAXIMUM OVER IDENTITIES, NEVER A SUM. `repeated_calls` is how far the session's most-repeated (tool, arguments) identity ran. A running total across every identity is monotonic in the length of the session and never resets, so it fires on anything long enough to repeat anything: over the same transcript the sum was 1294 where the max was 1079 against a healthy ceiling of 38. A threshold derived from one identity's recurrence and applied to that total is not a threshold. The row's own §2 is refuted and needs rewriting on the tracker. It proposed "consecutive calls to one tool": the polling arrived in 249 bursts whose longest run was 8, a length any healthy session reaches, so a consecutive reading ships as coverage while deciding nothing. Identity is the tool name and a DIGEST of the arguments, so no argument text is retained. The RESULT is deliberately excluded — it separates better (60x against 28x) and would oblige the parser to read every result body, the one payload `transcript.rs` exists never to touch. A replayed `tool_use` id is deduped: counting replays measures what the host chose to re-emit rather than what the session did. IT SHIPS AT `warn`. A `mediated_call` row at `deny` refuses every later tool call once it fires, and no admission can clear it — `batten override request` and `spend` are themselves mediated calls, so requesting one requires making the call the deny refuses. This predicate was landed at `deny` once and locked its own authoring session out at ~1300, push included. Promotion needs it shown silent against a real transcript first. The compiled tier drives the engine rather than a fabricated input, which is what pins the off-by-one: N calls are N-1 recurrences, so clearing 100 takes 102 calls and 101 is clean. Refs: CLOUD-1341, CLOUD-1172, CLOUD-418, CLOUD-489 Admits: 3d6283f7ae44fe7b8349bbb4ef3aa0b22e03bc84ad4059098ff15576b3729337 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: batten.toml Admits-head: a907565 Admits-epoch: 93d8b1112718944856b6e81b9cfac7c903dbb2c28470be1fc6e59786fb42f404 Admits-author: alec@wenzowski.com Admits-prev: a387bad2ea98589d0f75455b02cba853248836b7c51d4707cf39585329262b06 Admits-answer-lost: The module cannot load, so CLOUD-1341 ships nothing. An unregistered `policy/*.rego` file is inert — the engine reads its rule set from the committed authority, and a module with no registering row is never evaluated. The extractor row is what makes the count reach the predicate at all: without it the fact is undefined, Rego reads undefined as does-not-hold, and the gate loads clean while deciding nothing, which is the dead-gate class `.claude/rules/policy-modules.md` exists to warn about. Admits-answer-precondition: Registering a policy module IS an edit to the committed authority: a `[[rule]]` row naming the module, a `[[rule.extract]]` row binding the new `repeated-calls` extractor to the key the module reads, and the `[[verdict]]` plus `[[verdict.route]]` rows declaring the `turn ask twice` token. A module raising an undeclared token fails to load, and a declared verdict row nothing raises fails the load too, so the rows and the module are one indivisible change. There is no other surface that expresses them, and the write lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` is what this IS — that route names reading configuration before writing it, and the rows being added do not exist to be read, because the module is new. `patch run first` does not apply: the file is hand-authored and has no generator behind it, so a patch route would write the same bytes to the same protected path; the generated `schema/*.json` are derived FROM the crate rather than producing it. Admits: 247eaff38bfbc59e1afae9cae684be633d05c8eca2d901051cad86757508560c Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: policy/a-repeated-call-is-not-progress.rego Admits-head: a907565 Admits-epoch: 93d8b1112718944856b6e81b9cfac7c903dbb2c28470be1fc6e59786fb42f404 Admits-author: alec@wenzowski.com Admits-prev: e4dad46c2f5408cdb8bdb3487a98b9e7035341caad7cb39e7e1d555605d0d1f9 Admits-answer-lost: CLOUD-1341 stays prose. The always-loaded instructions already state that a backgrounded task's exit notification is the wake-up and that re-asking is not waiting, and non-negotiable rule 2 calls a rule without a runnable gate half a change. The two landed arms cannot reach this family: one keys on a backgrounded `sleep`, the other on `until`/`while` inside a single command string, while the measured defect was 1079 identical harness-verb calls spread across turns with no argv to match. Without the module there is no gate at all. Admits-answer-precondition: CLOUD-1341's remedy is a policy predicate, and a predicate is a Rego module: the threshold constant, the null guard and the eight test rules are the module's own body, which no configuration surface can express. The committed authority carries only the rows that register it. Writing the protected path is the only route left, and the file lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` does not apply: the committed authority has no surface for a predicate body or a threshold constant, and `.claude/rules/policy-modules.md` refuses a threshold spelled as a pattern row outright, so the number has to live in the consumer module. `patch run first` does not apply either: the module is hand-authored with no generator behind it, so a patch route would write the same bytes to the same protected path.
…a pull request CLOUD-1331's keyed base arm collected nothing across 4 runs and 2 pull requests: 0 hits, ~190 MB written and discarded each time. The first diagnosis blamed the key; the second blamed the path. Both were reading the same design, which optimised for a hit and never asked what a miss costs. An entry written from a pull request is scoped to `refs/pull/N/merge` and no other pull request can read it. An entry written from the default branch is readable by every branch. So the PR side now RESTORES and never saves — `actions/cache/restore`, a key carrying no merge base because nothing saves and there is no miss to engineer — and the scheduled `perf` job on `main`, which already pays a full `--release` build, stages that closure and saves it under the same key. No new trigger, no second build. The seed is never measured: it is copied to `target/perf/base-$SHA` with its binary removed, so `base_arm_is_built()` stays false and cargo runs against the base tree actually materialised. CLOUD-840 measured this repository at 225.2 GB of Actions cache, 203.7 GB of it across 1,144 PR-ref entries nothing can restore. This job now writes zero of them, and the hit arrives on a pull request's FIRST run rather than its second. `cache-path-is-rebase-stable` gains the two sub-action spellings. `actions/cache/restore` and `actions/cache/save` derive an entry's version from `path` exactly as the composite does, so matching only `actions/cache@` would have left the predicate live and reaching nothing the moment a job split restore from save — which is what this commit does. Its own case is declared rather than assumed. Refs: CLOUD-1342, CLOUD-1331, CLOUD-840 Admits: b0862b76d140be6746e8cdd0fcaf3c28e5bc685ef40c4e3164a1079bf12d075b Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: .github/workflows/ci.yml Admits-head: fb2987d Admits-epoch: 93d8b1112718944856b6e81b9cfac7c903dbb2c28470be1fc6e59786fb42f404 Admits-author: alec@wenzowski.com Admits-prev: f0a932f52d256fb53185a39552efa1b6ac28551baa5b3c020412742000541260 Admits-answer-lost: The job keeps writing ~190 MB per run of cache scoped to the pull request's own merge ref, which no later run can ever restore — CLOUD-840 measured 203.7 GB of such entries across 1,144 of them in this repository — and every pull request keeps compiling the base arm's whole release closure cold, 239 crates in 4m42s of a 13.5 min step. The design being replaced optimised for a hit and never asked what a miss costs. Admits-answer-precondition: The change is a GitHub Actions step in the `perf` job — swapping the composite cache action for its restore-only sub-action, dropping the merge-base SHA from the key, and deleting the pull-request-side save step. No batten surface expresses a workflow step, so writing the protected path directly is the only route left, and the write lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` does not apply: the committed authority declares rules, verdicts and patterns and carries no representation of a workflow job or step, so there is nothing to read or set there. `patch run first` does not apply either: the workflow is hand-authored YAML with no generator behind it, so a patch route would write the same bytes to the same protected path. Admits: a76c9a87c95fa32cf60a4b398c75005fdba89cb88255963faf88c77be092502f Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: .github/workflows/perf.yml Admits-head: fb2987d Admits-epoch: 93d8b1112718944856b6e81b9cfac7c903dbb2c28470be1fc6e59786fb42f404 Admits-author: alec@wenzowski.com Admits-prev: 22dc584248885224d59327013514c1c438affb90392824a98c742447a18e3b7e Admits-answer-lost: The restore-only step this branch lands has nothing to restore. Only a run on the default branch can write a cache entry every branch can read, and this scheduled job is the one place that already pays a full release build. Absent the seed, every pull request keeps compiling the base arm's whole release closure cold, and writing unreadable per-pull-request entries stays the only alternative. Admits-answer-precondition: The change adds two GitHub Actions steps to the scheduled job that already runs on the default branch — staging the release closure it has just built, and saving it under the key the pull-request side restores. No batten surface expresses a workflow step, so writing the protected path directly is the only route left, and the write lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` does not apply: the committed authority declares rules, verdicts and patterns and carries no representation of a workflow job or step, so there is nothing to read or set there. `patch run first` does not apply either: the workflow is hand-authored YAML with no generator behind it, so a patch route would write the same bytes to the same protected path. Admits: 7d1ad5bf1d04296f330e798f42f2a74aba2e5d7f78ab4b9a8d2e2474c9dea5e3 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: policy/ci-parity.rego Admits-head: fb2987d Admits-epoch: 93d8b1112718944856b6e81b9cfac7c903dbb2c28470be1fc6e59786fb42f404 Admits-author: alec@wenzowski.com Admits-prev: d2e45c5830908bdc08f6f59141f3a9b031f68ad27ffdd7d133ebf108d0b30921 Admits-answer-lost: The gate goes dead against its own subject. The sub-actions derive an entry's version from the cached path exactly as the composite does, so an interpolated path is the identical silent total miss — the one measured at 4 runs, 0 hits, ~190 MB discarded each time — and after this branch the repository's only cache steps of that kind are the sub-action spellings the predicate cannot see. A gate that loads clean and matches nothing reads exactly like a clean tree. Admits-answer-precondition: The predicate matches only the composite spelling of the cache action, and this branch moves the job to the restore-only sub-action plus a save on the scheduled side — so the gate that exists to refuse an interpolated cached path would stop covering the very steps this branch lands. Widening it and adding the discriminating case is a change to the Rego module itself; no configuration surface expresses a predicate body, so writing the protected path directly is the only route, and it lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` does not apply: the committed authority carries the rows that register this module, not the predicate body, so nothing there can widen which action spellings the rule matches. `patch run first` does not apply either: the module is hand-authored and has no generator behind it, so a patch route would write the same bytes to the same protected path.
…viting the second claim The claim receipt is minted on `claim check`'s pullable path and keyed by the branch checked out at that moment. That one fact makes two orderings fail in opposite directions, and both are reachable by following the instructions correctly the first time. Claim, then branch: the receipt is minted against the branch you were standing on, and the first edit on the real branch is refused with `receipt read missing claim branch claim-needs-receipt`. Claim twice: having branched first, the pullable message read as go-do-this-and-come-back, so the next move was to move the row and run `claim-check` again. The second run arrives after the row has left Todo, reads it as held, and refuses `not-todo` — where the holder is the caller ninety seconds earlier. The only route past is `--takeover` against oneself, which writes a takeover record for a row nobody else touched, and CLOUD-1139 needs that signal to stay rare. Measured twice, in two sessions, both by an agent following the documented order. NO ENGINE CHANGE, and the first revision of this row assumed one was needed. Creating a branch writes only under `.git`, which `claim-needs-receipt` never judges, so there is no ordering deadlock — the deadlock was imagined. `claim.rs`'s `not-todo` decision is correct as it stands for a row genuinely held elsewhere and is untouched. Recognising a self-claim in the engine stays CLOUD-1139's: it needs to tell this session from a sibling, and the row's assignee cannot, since every fleet session carries the same configured accountable identity. An assignee-keyed re-mint would let a sibling silently re-mint over a working holder — that row's measured harm, reintroduced through this row's fix. The gate decides what a gate can: whether the always-loaded file still states the order and whether the triggered file still carries both failure directions. Whether a given session actually claimed before branching is not a property of the tree, and a rule resolving to it would be the model verdict non-negotiable rule 3 forbids. Two files because a budget forced it. `policy-budget` caps the index at 3500 tokens and 199 lines, and an earlier draft of this change blew both at 3516/202; the index carries only the ORDER and the reason lives in the rules file that loads at the trigger. Both arms are therefore required. The compiled tier is what proves the two markdown files reach the module at all: a dead gate and a tree that still states the order are byte-identical on the decision surface, so each drift case can only go red if the lines actually arrived. Refs: CLOUD-1343, CLOUD-1139, CLOUD-786, CLOUD-733 Admits: 3fe3ee98cc0ec6d5f1a9ea595f823c2daa76332568b0ca2332363539c5979d99 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: batten.toml Admits-head: f9f48e6 Admits-epoch: 84237be51e176ca21fe43fbe5f50492b50c188b8881b9745dfb2b4004a75a220 Admits-author: alec@wenzowski.com Admits-prev: 3d6283f7ae44fe7b8349bbb4ef3aa0b22e03bc84ad4059098ff15576b3729337 Admits-answer-lost: The gate is dead rather than absent, which is strictly worse. Without the `line_sources` list the engine builds no lines for those two paths, every clause reads undefined, Rego takes undefined as does-not-hold, and the module loads clean while deciding nothing — indistinguishable on the decision surface from a tree that still states the order. That is the exact failure class `.claude/rules/policy-modules.md` records, and without the registering row the module is never evaluated at all. Admits-answer-precondition: Registering the claim-order module IS an edit to the committed authority: a tree-scoped `[[rule]]` row naming it, the `line_sources` list that makes the two instruction files reach the predicate at all, and the `[[verdict]]` plus `[[verdict.route]]` rows declaring the `claim declare dropped` token. A module raising an undeclared token fails to load, and a declared verdict row nothing raises fails the load too, so the rows and the module are one indivisible change. There is no other surface that expresses them, and the write lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` is what this IS — that route names reading configuration before writing it, and the rows being added do not exist to be read, because the module is new. `patch run first` does not apply: the file is hand-authored and has no generator behind it, so a patch route would write the same bytes to the same protected path. Admits: f4a603586d8e7912a35d4358fe20ed1955c112b132ecbaf9ad46eebc2bfa1742 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: policy/claim-order-is-stated.rego Admits-head: f9f48e6 Admits-epoch: 84237be51e176ca21fe43fbe5f50492b50c188b8881b9745dfb2b4004a75a220 Admits-author: alec@wenzowski.com Admits-prev: - Admits-answer-lost: The order evaporates. The failure mode here is DRIFT — the remedy is prose, and prose is what a later edit silently rewords or drops, after which the next session walks into the same refusal that cost this one a full stop mid-work and a takeover record against its own claim. A prose assertion with no gate is exactly the half-change rule 2 refuses, and the Ready gate already refused an earlier revision of this row for carrying an empty tests array. Admits-answer-precondition: CLOUD-1343's remedy is words in two instruction files, and non-negotiable rule 2 calls a rule without a runnable gate half a change. The gate over words is a Rego module: which literal phrases each file must still carry, which file a finding points at, and the could-not-look arm are the module's own body, and no configuration surface expresses a predicate. Writing the protected path is the only route, and the file lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` does not apply: the committed authority carries the rows that register a module and the literal phrases the predicate matches are not expressible there. `patch run first` does not apply either: the module is hand-authored with no generator behind it, so a patch route would write the same bytes to the same protected path.
…iling-run adjacency The predicate this branch first landed was a second authority. CLOUD-1347 and CLOUD-1350 already design one module for the whole loop vocabulary, and `a-repeated-call-is-not-progress` was CLOUD-1350's window-recurrence detector under another name, computing another fact. Two predicates over one question can disagree, and the disagreement is discovered by a session being refused, so the module is renamed onto the declared vocabulary rather than left beside it. WHAT REPLACES IT IS NOT A RENAME. `repeated-calls` was the MAXIMUM recurrences of any identity over the whole stream. `repeat-depth` is the TRAILING run of identical calls, and `distinct-calls` is the progress term beside it. Adjacency is what does the false-positive work: any intervening distinct call clears the run, so the edit-then-retest loop is false by construction rather than by carve-out, and the threshold is the 3 that opencode, hermes-agent and OpenHands all converge on rather than a constant derived from one pathological session. AND ADJACENCY IS WHY THIS ONE CANNOT LOCK A SESSION OUT. A whole-stream maximum is monotonic: once it crossed its threshold it stayed crossed for the rest of the session, which is how the earlier predicate refused every subsequent tool call including its own author's push, with no route to an admission — `batten override request` and `spend` are themselves mediated calls. A trailing run resets on the next distinct call, so the escape is automatic and the override CLOUD-1352 names is actually reachable. Every member of this family is non-monotonic for that reason. The fingerprint stays inside `transcript.rs`, hashed and dropped in the same expression as `Event::HookOutput`'s digest, so only a run length is projected and `Extraction` stays integers-only. A replayed `tool_use` id is deduped: counting replays measures what the host chose to re-emit rather than what the session did. Severity stays `warn`. CLOUD-1352 owns the promotion and makes a measured firing rate over this repository's own history a hard precondition. Refs: CLOUD-1347, CLOUD-1341, CLOUD-1350, CLOUD-1352, CLOUD-1337 Admits: 04117a36b963f590a81de944785fade06bd0eb7b27cd2b35f33d59fa3bae4b07 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: batten.toml Admits-head: 6420fb9 Admits-epoch: 6f8c3ea4c54e6263a8ca91ed3f034ea58de55f4324b3d93f7737b467fcc2467d Admits-author: alec@wenzowski.com Admits-prev: 3fe3ee98cc0ec6d5f1a9ea595f823c2daa76332568b0ca2332363539c5979d99 Admits-answer-lost: The module cannot load, so CLOUD-1341 ships nothing. An unregistered `policy/*.rego` file is inert — the engine reads its rule set from the committed authority, and a module with no registering row is never evaluated. The extractor row is what makes the count reach the predicate at all: without it the fact is undefined, Rego reads undefined as does-not-hold, and the gate loads clean while deciding nothing, which is the dead-gate class `.claude/rules/policy-modules.md` exists to warn about. Admits-answer-precondition: Registering a policy module IS an edit to the committed authority: a `[[rule]]` row naming the module, a `[[rule.extract]]` row binding the new `repeated-calls` extractor to the key the module reads, and the `[[verdict]]` plus `[[verdict.route]]` rows declaring the `turn ask twice` token. A module raising an undeclared token fails to load, and a declared verdict row nothing raises fails the load too, so the rows and the module are one indivisible change. There is no other surface that expresses them, and the write lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` is what this IS — that route names reading configuration before writing it, and the rows being added do not exist to be read, because the module is new. `patch run first` does not apply: the file is hand-authored and has no generator behind it, so a patch route would write the same bytes to the same protected path; the generated `schema/*.json` are derived FROM the crate rather than producing it. Admits: 368813d8fbd4cfef084b59aaa4b102fdf06d6bdb3d21ceaf0df87cc61629d2a8 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: policy/repetition-without-progress.rego Admits-head: 6420fb9 Admits-epoch: 6f8c3ea4c54e6263a8ca91ed3f034ea58de55f4324b3d93f7737b467fcc2467d Admits-author: alec@wenzowski.com Admits-prev: - Admits-answer-lost: The branch ships a second authority over a question the tracker already owns. A family of rows designs one module holding the whole loop vocabulary; landing a differently-named module computing a differently-named fact means whoever builds that family writes the second one, and this repository refuses exactly that — two predicates over one question can disagree, and the disagreement is discovered by a session being refused. Without the restructure the duplicate is what lands. Admits-answer-precondition: The predicate is a Rego module: the threshold constant, the three-valued posture guard, the null guard and the ten test rules are the module's own body, and no configuration surface expresses a predicate. This replaces a module landed earlier on this same branch under a name that duplicated an already-designed family, so the write is a restructure onto the declared vocabulary rather than a new gate. It lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` does not apply: the committed authority has no surface for a predicate body or a threshold constant, and a threshold spelled as a pattern row is refused outright, so the number has to live in the consumer module. `patch run first` does not apply either: the module is hand-authored with no generator behind it, so a patch route would write the same bytes to the same protected path. Admits: 8a3ff5e4351e38ed26fdc4fd7eeaa30b80bd7d096ab7ebb2433024111f576156 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: policy/a-repeated-call-is-not-progress.rego Admits-head: 6420fb9 Admits-epoch: 6f8c3ea4c54e6263a8ca91ed3f034ea58de55f4324b3d93f7737b467fcc2467d Admits-author: alec@wenzowski.com Admits-prev: 247eaff38bfbc59e1afae9cae684be633d05c8eca2d901051cad86757508560c Admits-answer-lost: Two modules over one question ship together. The registering rows can be pointed at the replacement, but leaving the file behind leaves a second authority in the tree that a later reader may register again, and the two can disagree over exactly the cases neither author had in mind. Keeping it would also leave its rule id in the mutation census naming a gate nothing registers. Admits-answer-precondition: The module being removed was landed earlier on this same branch and duplicates an already-designed family's detector under a different name. Its replacement lands in the same change on the declared path, so the removal is half of one restructure rather than a deletion of coverage — every predicate it carried survives, renamed onto the vocabulary the family declares. There is no surface that retires a module except removing the file and its registering rows. Admits-answer-rejected-route: `config read first` does not apply: the committed authority registers modules and cannot delete one, and pointing the rows elsewhere is what leaves the orphan this removal exists to prevent. `patch run first` does not apply either: there is no generator behind the module, so no patch route reaches it.
…f answering zero Four of five `Extraction` members reached no consumer, and repetition — the one thing a doom loop is made of — had no member at all. This is the spine the predicates stand on, landed alone because the risk is the plumbing rather than the detection. `Extraction::of` widens from `&Counts` to `&Stream`. `Counts` is unchanged: it is the `-J` capability report's own shape and its comments refuse members no reader of that document needs, so runs live in `Stream::repeats()` beside `Stream::counts()` rather than as fields on it. THE LOAD-BEARING HALF IS THE PER-EXTRACTION CAPABILITY. Zero is a real answer and means the extractor ran. An extraction whose underlying event kind never appears is not a session that did none of it — it is a host that does not record it, and answering zero there is a false green over a session nobody measured. `of` returns `Option<usize>` and the projection omits the key, so a module reads undefined and Rego takes that as does-not-hold. Decided in the engine: a per-module conjunct asking whether this host records turns is a dead gate on every harness but the one its author tested. The bound is stated rather than absorbed: this cannot separate a host that records no hook runs from a session that triggered none. It resolves that toward could-not-look, which is the safe direction — a missing answer is reported, where a false zero is indistinguishable from a clean session. `agent-turn-run` is the first member and deliberately the simplest: a trailing run of assistant turns carrying no tool call, over the already-typed `Event::Turn`. No hashing, so no argument or result is read even internally. It maps to OpenHands' monologue detector at 3+. The honest claim is narrower than loop detection, and the module header says so. Termination is undecidable and every quantity here is a monotonically growing count, so there is no ranking function to be had. What the literature buys is the shape of the declaration: a SET of extractions with a set of thresholds, supplying an effective bound on a SUSPECTED feedback path. It does not detect non-termination. Adjacency also makes the member non-monotonic — one action clears the run — which is the property any later promotion depends on. CLOUD-894 owns the firing-rate ceiling and CLOUD-1352 the promotion; this ships at `warn`. The compiled tier is what proves the capability rather than the author's arithmetic. It caught a wrong fixture doing it: a `user` record parses as a turn, so the first cross-compatibility case recorded turns after all and answered a real zero. A host recording hook runs and no turn boundaries is the shape the claim is about. Also retires the two now-spent rows in `policy/harness-declared.json`. CLOUD-1079 has landed, so the user-level hooks those rows excused are no longer provisioned and the merged surfaces carry zero hook commands — `harness-wiring` reported both, which is the gate working rather than misfiring. Reproduced against this same tree with `--config-from a907565`, so the finding predates this diff and is not caused by it. Leaving them is not neutral once their owner has landed: they would silently excuse anything that later matched either pattern by name. Refs: CLOUD-1344, CLOUD-1079, CLOUD-1049, CLOUD-418, CLOUD-894 Admits: d0ac8a4640f2a747b01bedc8449254f45d80865a7de7f0d3f765c40975627f93 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: batten.toml Admits-head: aed8b44 Admits-epoch: 3ce215f766f4c55fa0be890ef01ffdb5bfc93d7e597090d5f2098d074c5f389f Admits-author: alec@wenzowski.com Admits-prev: 04117a36b963f590a81de944785fade06bd0eb7b27cd2b35f33d59fa3bae4b07 Admits-answer-lost: The module cannot load, so CLOUD-1341 ships nothing. An unregistered `policy/*.rego` file is inert — the engine reads its rule set from the committed authority, and a module with no registering row is never evaluated. The extractor row is what makes the count reach the predicate at all: without it the fact is undefined, Rego reads undefined as does-not-hold, and the gate loads clean while deciding nothing, which is the dead-gate class `.claude/rules/policy-modules.md` exists to warn about. Admits-answer-precondition: Registering a policy module IS an edit to the committed authority: a `[[rule]]` row naming the module, a `[[rule.extract]]` row binding the new `repeated-calls` extractor to the key the module reads, and the `[[verdict]]` plus `[[verdict.route]]` rows declaring the `turn ask twice` token. A module raising an undeclared token fails to load, and a declared verdict row nothing raises fails the load too, so the rows and the module are one indivisible change. There is no other surface that expresses them, and the write lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` is what this IS — that route names reading configuration before writing it, and the rows being added do not exist to be read, because the module is new. `patch run first` does not apply: the file is hand-authored and has no generator behind it, so a patch route would write the same bytes to the same protected path; the generated `schema/*.json` are derived FROM the crate rather than producing it. Admits: 67106f144c06ca5c3aeaae3482a922741375d1168c3b3a6016979aadbe61b4a4 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: policy/repetition-without-progress.rego Admits-head: aed8b44 Admits-epoch: 3ce215f766f4c55fa0be890ef01ffdb5bfc93d7e597090d5f2098d074c5f389f Admits-author: alec@wenzowski.com Admits-prev: 368813d8fbd4cfef084b59aaa4b102fdf06d6bdb3d21ceaf0df87cc61629d2a8 Admits-answer-lost: The spine row ships with no predicate reading it, which is the half-change non-negotiable rule 2 refuses: an extraction nothing consumes is exactly the defect CLOUD-1344 was filed about, since four of five existing members already reach no consumer. The branch would add a sixth unread member while claiming to fix that. Admits-answer-precondition: CLOUD-1344's predicate is a Rego module: the adopted threshold, the null guard and the seven test rules are the module's own body, and no configuration surface expresses a predicate. This replaces the predicate landed earlier on this same branch, which was sequenced ahead of the spine row it is blocked on, so the write is a re-sequencing onto the row that must land first. It lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` does not apply: the committed authority has no surface for a predicate body or a threshold, and a threshold spelled as a pattern row is refused outright. `patch run first` does not apply either: the module is hand-authored with no generator behind it, so a patch route would write the same bytes to the same protected path. Admits: 06de726f4ec787c03543081e96d8e7ae1d46932d5306fe7b68e4465d56f8db24 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: policy/harness-declared.json Admits-head: aed8b44 Admits-epoch: 6785dbe2aaf01f887e2ec5902922351fd259cb877a4e4d8c830aeefa1d3079ba Admits-author: alec@wenzowski.com Admits-prev: - Admits-answer-lost: The gate stays red on every commit for a reason nobody caused, which is how a correct refusal gets skipped per commit until it is skipped by habit. Worse, the rows keep excusing two commands by name: were anything to reintroduce a hook matching either pattern, the exemption would silently cover it, so leaving them is not neutral once their owner has landed. Admits-answer-precondition: The exemption table is a policy data file the engine reads, and the two rows in it are now spent: the issue that owns them has landed, the user-level hooks they excused are no longer provisioned, and the merged surfaces carry zero hook commands. `harness-wiring` reports both, which is the gate working rather than misfiring. Retiring a row means editing that file; there is no other surface for it, and the write lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` does not apply: the committed authority declares which external surfaces are read and holds no exemption rows, which is deliberate — an exemption table is data a gate reads rather than part of the gate. `patch run first` does not apply either: the file is hand-authored with no generator behind it, so a patch route would write the same bytes to the same protected path.
…not a total An across-turn poll was invisible to every gate. Measured over one real transcript: `ReadNotifications` called 1079 times, 59% of every tool call in the session, every one with identical arguments and the identical "No queued notifications" result. All 1079 carry `role: assistant`, so this judges agent conduct and cannot fire on harness behaviour. No landed arm could reach it. `run-shape` keys on a backgrounded `sleep` and CLOUD-489 on `until`/`while` inside one command string; repetition spread across turns carries neither token, and a harness verb with no argv gives a `shape` row nothing to match. THE FACT IS A MAXIMUM OVER IDENTITIES, NEVER A SUM. `repeated_calls` is how far the session's most-repeated (tool, arguments) identity ran. A running total across every identity is monotonic in the length of the session and never resets, so it fires on anything long enough to repeat anything: over the same transcript the sum was 1294 where the max was 1079 against a healthy ceiling of 38. A threshold derived from one identity's recurrence and applied to that total is not a threshold. The row's own §2 is refuted and needs rewriting on the tracker. It proposed "consecutive calls to one tool": the polling arrived in 249 bursts whose longest run was 8, a length any healthy session reaches, so a consecutive reading ships as coverage while deciding nothing. Identity is the tool name and a DIGEST of the arguments, so no argument text is retained. The RESULT is deliberately excluded — it separates better (60x against 28x) and would oblige the parser to read every result body, the one payload `transcript.rs` exists never to touch. A replayed `tool_use` id is deduped: counting replays measures what the host chose to re-emit rather than what the session did. IT SHIPS AT `warn`. A `mediated_call` row at `deny` refuses every later tool call once it fires, and no admission can clear it — `batten override request` and `spend` are themselves mediated calls, so requesting one requires making the call the deny refuses. This predicate was landed at `deny` once and locked its own authoring session out at ~1300, push included. Promotion needs it shown silent against a real transcript first. The compiled tier drives the engine rather than a fabricated input, which is what pins the off-by-one: N calls are N-1 recurrences, so clearing 100 takes 102 calls and 101 is clean. Refs: CLOUD-1341, CLOUD-1172, CLOUD-418, CLOUD-489 Admits: 3d6283f7ae44fe7b8349bbb4ef3aa0b22e03bc84ad4059098ff15576b3729337 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: batten.toml Admits-head: a907565 Admits-epoch: 93d8b1112718944856b6e81b9cfac7c903dbb2c28470be1fc6e59786fb42f404 Admits-author: alec@wenzowski.com Admits-prev: a387bad2ea98589d0f75455b02cba853248836b7c51d4707cf39585329262b06 Admits-answer-lost: The module cannot load, so CLOUD-1341 ships nothing. An unregistered `policy/*.rego` file is inert — the engine reads its rule set from the committed authority, and a module with no registering row is never evaluated. The extractor row is what makes the count reach the predicate at all: without it the fact is undefined, Rego reads undefined as does-not-hold, and the gate loads clean while deciding nothing, which is the dead-gate class `.claude/rules/policy-modules.md` exists to warn about. Admits-answer-precondition: Registering a policy module IS an edit to the committed authority: a `[[rule]]` row naming the module, a `[[rule.extract]]` row binding the new `repeated-calls` extractor to the key the module reads, and the `[[verdict]]` plus `[[verdict.route]]` rows declaring the `turn ask twice` token. A module raising an undeclared token fails to load, and a declared verdict row nothing raises fails the load too, so the rows and the module are one indivisible change. There is no other surface that expresses them, and the write lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` is what this IS — that route names reading configuration before writing it, and the rows being added do not exist to be read, because the module is new. `patch run first` does not apply: the file is hand-authored and has no generator behind it, so a patch route would write the same bytes to the same protected path; the generated `schema/*.json` are derived FROM the crate rather than producing it. Admits: 247eaff38bfbc59e1afae9cae684be633d05c8eca2d901051cad86757508560c Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: policy/a-repeated-call-is-not-progress.rego Admits-head: a907565 Admits-epoch: 93d8b1112718944856b6e81b9cfac7c903dbb2c28470be1fc6e59786fb42f404 Admits-author: alec@wenzowski.com Admits-prev: e4dad46c2f5408cdb8bdb3487a98b9e7035341caad7cb39e7e1d555605d0d1f9 Admits-answer-lost: CLOUD-1341 stays prose. The always-loaded instructions already state that a backgrounded task's exit notification is the wake-up and that re-asking is not waiting, and non-negotiable rule 2 calls a rule without a runnable gate half a change. The two landed arms cannot reach this family: one keys on a backgrounded `sleep`, the other on `until`/`while` inside a single command string, while the measured defect was 1079 identical harness-verb calls spread across turns with no argv to match. Without the module there is no gate at all. Admits-answer-precondition: CLOUD-1341's remedy is a policy predicate, and a predicate is a Rego module: the threshold constant, the null guard and the eight test rules are the module's own body, which no configuration surface can express. The committed authority carries only the rows that register it. Writing the protected path is the only route left, and the file lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` does not apply: the committed authority has no surface for a predicate body or a threshold constant, and `.claude/rules/policy-modules.md` refuses a threshold spelled as a pattern row outright, so the number has to live in the consumer module. `patch run first` does not apply either: the module is hand-authored with no generator behind it, so a patch route would write the same bytes to the same protected path.
7dfcc36 to
889f97b
Compare
…a pull request CLOUD-1331's keyed base arm collected nothing across 4 runs and 2 pull requests: 0 hits, ~190 MB written and discarded each time. The first diagnosis blamed the key; the second blamed the path. Both were reading the same design, which optimised for a hit and never asked what a miss costs. An entry written from a pull request is scoped to `refs/pull/N/merge` and no other pull request can read it. An entry written from the default branch is readable by every branch. So the PR side now RESTORES and never saves — `actions/cache/restore`, a key carrying no merge base because nothing saves and there is no miss to engineer — and the scheduled `perf` job on `main`, which already pays a full `--release` build, stages that closure and saves it under the same key. No new trigger, no second build. The seed is never measured: it is copied to `target/perf/base-$SHA` with its binary removed, so `base_arm_is_built()` stays false and cargo runs against the base tree actually materialised. CLOUD-840 measured this repository at 225.2 GB of Actions cache, 203.7 GB of it across 1,144 PR-ref entries nothing can restore. This job now writes zero of them, and the hit arrives on a pull request's FIRST run rather than its second. `cache-path-is-rebase-stable` gains the two sub-action spellings. `actions/cache/restore` and `actions/cache/save` derive an entry's version from `path` exactly as the composite does, so matching only `actions/cache@` would have left the predicate live and reaching nothing the moment a job split restore from save — which is what this commit does. Its own case is declared rather than assumed. Refs: CLOUD-1342, CLOUD-1331, CLOUD-840 Admits: b0862b76d140be6746e8cdd0fcaf3c28e5bc685ef40c4e3164a1079bf12d075b Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: .github/workflows/ci.yml Admits-head: fb2987d Admits-epoch: 93d8b1112718944856b6e81b9cfac7c903dbb2c28470be1fc6e59786fb42f404 Admits-author: alec@wenzowski.com Admits-prev: f0a932f52d256fb53185a39552efa1b6ac28551baa5b3c020412742000541260 Admits-answer-lost: The job keeps writing ~190 MB per run of cache scoped to the pull request's own merge ref, which no later run can ever restore — CLOUD-840 measured 203.7 GB of such entries across 1,144 of them in this repository — and every pull request keeps compiling the base arm's whole release closure cold, 239 crates in 4m42s of a 13.5 min step. The design being replaced optimised for a hit and never asked what a miss costs. Admits-answer-precondition: The change is a GitHub Actions step in the `perf` job — swapping the composite cache action for its restore-only sub-action, dropping the merge-base SHA from the key, and deleting the pull-request-side save step. No batten surface expresses a workflow step, so writing the protected path directly is the only route left, and the write lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` does not apply: the committed authority declares rules, verdicts and patterns and carries no representation of a workflow job or step, so there is nothing to read or set there. `patch run first` does not apply either: the workflow is hand-authored YAML with no generator behind it, so a patch route would write the same bytes to the same protected path. Admits: a76c9a87c95fa32cf60a4b398c75005fdba89cb88255963faf88c77be092502f Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: .github/workflows/perf.yml Admits-head: fb2987d Admits-epoch: 93d8b1112718944856b6e81b9cfac7c903dbb2c28470be1fc6e59786fb42f404 Admits-author: alec@wenzowski.com Admits-prev: 22dc584248885224d59327013514c1c438affb90392824a98c742447a18e3b7e Admits-answer-lost: The restore-only step this branch lands has nothing to restore. Only a run on the default branch can write a cache entry every branch can read, and this scheduled job is the one place that already pays a full release build. Absent the seed, every pull request keeps compiling the base arm's whole release closure cold, and writing unreadable per-pull-request entries stays the only alternative. Admits-answer-precondition: The change adds two GitHub Actions steps to the scheduled job that already runs on the default branch — staging the release closure it has just built, and saving it under the key the pull-request side restores. No batten surface expresses a workflow step, so writing the protected path directly is the only route left, and the write lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` does not apply: the committed authority declares rules, verdicts and patterns and carries no representation of a workflow job or step, so there is nothing to read or set there. `patch run first` does not apply either: the workflow is hand-authored YAML with no generator behind it, so a patch route would write the same bytes to the same protected path. Admits: 7d1ad5bf1d04296f330e798f42f2a74aba2e5d7f78ab4b9a8d2e2474c9dea5e3 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: policy/ci-parity.rego Admits-head: fb2987d Admits-epoch: 93d8b1112718944856b6e81b9cfac7c903dbb2c28470be1fc6e59786fb42f404 Admits-author: alec@wenzowski.com Admits-prev: d2e45c5830908bdc08f6f59141f3a9b031f68ad27ffdd7d133ebf108d0b30921 Admits-answer-lost: The gate goes dead against its own subject. The sub-actions derive an entry's version from the cached path exactly as the composite does, so an interpolated path is the identical silent total miss — the one measured at 4 runs, 0 hits, ~190 MB discarded each time — and after this branch the repository's only cache steps of that kind are the sub-action spellings the predicate cannot see. A gate that loads clean and matches nothing reads exactly like a clean tree. Admits-answer-precondition: The predicate matches only the composite spelling of the cache action, and this branch moves the job to the restore-only sub-action plus a save on the scheduled side — so the gate that exists to refuse an interpolated cached path would stop covering the very steps this branch lands. Widening it and adding the discriminating case is a change to the Rego module itself; no configuration surface expresses a predicate body, so writing the protected path directly is the only route, and it lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` does not apply: the committed authority carries the rows that register this module, not the predicate body, so nothing there can widen which action spellings the rule matches. `patch run first` does not apply either: the module is hand-authored and has no generator behind it, so a patch route would write the same bytes to the same protected path.
…viting the second claim The claim receipt is minted on `claim check`'s pullable path and keyed by the branch checked out at that moment. That one fact makes two orderings fail in opposite directions, and both are reachable by following the instructions correctly the first time. Claim, then branch: the receipt is minted against the branch you were standing on, and the first edit on the real branch is refused with `receipt read missing claim branch claim-needs-receipt`. Claim twice: having branched first, the pullable message read as go-do-this-and-come-back, so the next move was to move the row and run `claim-check` again. The second run arrives after the row has left Todo, reads it as held, and refuses `not-todo` — where the holder is the caller ninety seconds earlier. The only route past is `--takeover` against oneself, which writes a takeover record for a row nobody else touched, and CLOUD-1139 needs that signal to stay rare. Measured twice, in two sessions, both by an agent following the documented order. NO ENGINE CHANGE, and the first revision of this row assumed one was needed. Creating a branch writes only under `.git`, which `claim-needs-receipt` never judges, so there is no ordering deadlock — the deadlock was imagined. `claim.rs`'s `not-todo` decision is correct as it stands for a row genuinely held elsewhere and is untouched. Recognising a self-claim in the engine stays CLOUD-1139's: it needs to tell this session from a sibling, and the row's assignee cannot, since every fleet session carries the same configured accountable identity. An assignee-keyed re-mint would let a sibling silently re-mint over a working holder — that row's measured harm, reintroduced through this row's fix. The gate decides what a gate can: whether the always-loaded file still states the order and whether the triggered file still carries both failure directions. Whether a given session actually claimed before branching is not a property of the tree, and a rule resolving to it would be the model verdict non-negotiable rule 3 forbids. Two files because a budget forced it. `policy-budget` caps the index at 3500 tokens and 199 lines, and an earlier draft of this change blew both at 3516/202; the index carries only the ORDER and the reason lives in the rules file that loads at the trigger. Both arms are therefore required. The compiled tier is what proves the two markdown files reach the module at all: a dead gate and a tree that still states the order are byte-identical on the decision surface, so each drift case can only go red if the lines actually arrived. Refs: CLOUD-1343, CLOUD-1139, CLOUD-786, CLOUD-733 Admits: 3fe3ee98cc0ec6d5f1a9ea595f823c2daa76332568b0ca2332363539c5979d99 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: batten.toml Admits-head: f9f48e6 Admits-epoch: 84237be51e176ca21fe43fbe5f50492b50c188b8881b9745dfb2b4004a75a220 Admits-author: alec@wenzowski.com Admits-prev: 3d6283f7ae44fe7b8349bbb4ef3aa0b22e03bc84ad4059098ff15576b3729337 Admits-answer-lost: The gate is dead rather than absent, which is strictly worse. Without the `line_sources` list the engine builds no lines for those two paths, every clause reads undefined, Rego takes undefined as does-not-hold, and the module loads clean while deciding nothing — indistinguishable on the decision surface from a tree that still states the order. That is the exact failure class `.claude/rules/policy-modules.md` records, and without the registering row the module is never evaluated at all. Admits-answer-precondition: Registering the claim-order module IS an edit to the committed authority: a tree-scoped `[[rule]]` row naming it, the `line_sources` list that makes the two instruction files reach the predicate at all, and the `[[verdict]]` plus `[[verdict.route]]` rows declaring the `claim declare dropped` token. A module raising an undeclared token fails to load, and a declared verdict row nothing raises fails the load too, so the rows and the module are one indivisible change. There is no other surface that expresses them, and the write lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` is what this IS — that route names reading configuration before writing it, and the rows being added do not exist to be read, because the module is new. `patch run first` does not apply: the file is hand-authored and has no generator behind it, so a patch route would write the same bytes to the same protected path. Admits: f4a603586d8e7912a35d4358fe20ed1955c112b132ecbaf9ad46eebc2bfa1742 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: policy/claim-order-is-stated.rego Admits-head: f9f48e6 Admits-epoch: 84237be51e176ca21fe43fbe5f50492b50c188b8881b9745dfb2b4004a75a220 Admits-author: alec@wenzowski.com Admits-prev: - Admits-answer-lost: The order evaporates. The failure mode here is DRIFT — the remedy is prose, and prose is what a later edit silently rewords or drops, after which the next session walks into the same refusal that cost this one a full stop mid-work and a takeover record against its own claim. A prose assertion with no gate is exactly the half-change rule 2 refuses, and the Ready gate already refused an earlier revision of this row for carrying an empty tests array. Admits-answer-precondition: CLOUD-1343's remedy is words in two instruction files, and non-negotiable rule 2 calls a rule without a runnable gate half a change. The gate over words is a Rego module: which literal phrases each file must still carry, which file a finding points at, and the could-not-look arm are the module's own body, and no configuration surface expresses a predicate. Writing the protected path is the only route, and the file lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` does not apply: the committed authority carries the rows that register a module and the literal phrases the predicate matches are not expressible there. `patch run first` does not apply either: the module is hand-authored with no generator behind it, so a patch route would write the same bytes to the same protected path.
…iling-run adjacency The predicate this branch first landed was a second authority. CLOUD-1347 and CLOUD-1350 already design one module for the whole loop vocabulary, and `a-repeated-call-is-not-progress` was CLOUD-1350's window-recurrence detector under another name, computing another fact. Two predicates over one question can disagree, and the disagreement is discovered by a session being refused, so the module is renamed onto the declared vocabulary rather than left beside it. WHAT REPLACES IT IS NOT A RENAME. `repeated-calls` was the MAXIMUM recurrences of any identity over the whole stream. `repeat-depth` is the TRAILING run of identical calls, and `distinct-calls` is the progress term beside it. Adjacency is what does the false-positive work: any intervening distinct call clears the run, so the edit-then-retest loop is false by construction rather than by carve-out, and the threshold is the 3 that opencode, hermes-agent and OpenHands all converge on rather than a constant derived from one pathological session. AND ADJACENCY IS WHY THIS ONE CANNOT LOCK A SESSION OUT. A whole-stream maximum is monotonic: once it crossed its threshold it stayed crossed for the rest of the session, which is how the earlier predicate refused every subsequent tool call including its own author's push, with no route to an admission — `batten override request` and `spend` are themselves mediated calls. A trailing run resets on the next distinct call, so the escape is automatic and the override CLOUD-1352 names is actually reachable. Every member of this family is non-monotonic for that reason. The fingerprint stays inside `transcript.rs`, hashed and dropped in the same expression as `Event::HookOutput`'s digest, so only a run length is projected and `Extraction` stays integers-only. A replayed `tool_use` id is deduped: counting replays measures what the host chose to re-emit rather than what the session did. Severity stays `warn`. CLOUD-1352 owns the promotion and makes a measured firing rate over this repository's own history a hard precondition. Refs: CLOUD-1347, CLOUD-1341, CLOUD-1350, CLOUD-1352, CLOUD-1337 Admits: 04117a36b963f590a81de944785fade06bd0eb7b27cd2b35f33d59fa3bae4b07 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: batten.toml Admits-head: 6420fb9 Admits-epoch: 6f8c3ea4c54e6263a8ca91ed3f034ea58de55f4324b3d93f7737b467fcc2467d Admits-author: alec@wenzowski.com Admits-prev: 3fe3ee98cc0ec6d5f1a9ea595f823c2daa76332568b0ca2332363539c5979d99 Admits-answer-lost: The module cannot load, so CLOUD-1341 ships nothing. An unregistered `policy/*.rego` file is inert — the engine reads its rule set from the committed authority, and a module with no registering row is never evaluated. The extractor row is what makes the count reach the predicate at all: without it the fact is undefined, Rego reads undefined as does-not-hold, and the gate loads clean while deciding nothing, which is the dead-gate class `.claude/rules/policy-modules.md` exists to warn about. Admits-answer-precondition: Registering a policy module IS an edit to the committed authority: a `[[rule]]` row naming the module, a `[[rule.extract]]` row binding the new `repeated-calls` extractor to the key the module reads, and the `[[verdict]]` plus `[[verdict.route]]` rows declaring the `turn ask twice` token. A module raising an undeclared token fails to load, and a declared verdict row nothing raises fails the load too, so the rows and the module are one indivisible change. There is no other surface that expresses them, and the write lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` is what this IS — that route names reading configuration before writing it, and the rows being added do not exist to be read, because the module is new. `patch run first` does not apply: the file is hand-authored and has no generator behind it, so a patch route would write the same bytes to the same protected path; the generated `schema/*.json` are derived FROM the crate rather than producing it. Admits: 368813d8fbd4cfef084b59aaa4b102fdf06d6bdb3d21ceaf0df87cc61629d2a8 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: policy/repetition-without-progress.rego Admits-head: 6420fb9 Admits-epoch: 6f8c3ea4c54e6263a8ca91ed3f034ea58de55f4324b3d93f7737b467fcc2467d Admits-author: alec@wenzowski.com Admits-prev: - Admits-answer-lost: The branch ships a second authority over a question the tracker already owns. A family of rows designs one module holding the whole loop vocabulary; landing a differently-named module computing a differently-named fact means whoever builds that family writes the second one, and this repository refuses exactly that — two predicates over one question can disagree, and the disagreement is discovered by a session being refused. Without the restructure the duplicate is what lands. Admits-answer-precondition: The predicate is a Rego module: the threshold constant, the three-valued posture guard, the null guard and the ten test rules are the module's own body, and no configuration surface expresses a predicate. This replaces a module landed earlier on this same branch under a name that duplicated an already-designed family, so the write is a restructure onto the declared vocabulary rather than a new gate. It lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` does not apply: the committed authority has no surface for a predicate body or a threshold constant, and a threshold spelled as a pattern row is refused outright, so the number has to live in the consumer module. `patch run first` does not apply either: the module is hand-authored with no generator behind it, so a patch route would write the same bytes to the same protected path. Admits: 8a3ff5e4351e38ed26fdc4fd7eeaa30b80bd7d096ab7ebb2433024111f576156 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: policy/a-repeated-call-is-not-progress.rego Admits-head: 6420fb9 Admits-epoch: 6f8c3ea4c54e6263a8ca91ed3f034ea58de55f4324b3d93f7737b467fcc2467d Admits-author: alec@wenzowski.com Admits-prev: 247eaff38bfbc59e1afae9cae684be633d05c8eca2d901051cad86757508560c Admits-answer-lost: Two modules over one question ship together. The registering rows can be pointed at the replacement, but leaving the file behind leaves a second authority in the tree that a later reader may register again, and the two can disagree over exactly the cases neither author had in mind. Keeping it would also leave its rule id in the mutation census naming a gate nothing registers. Admits-answer-precondition: The module being removed was landed earlier on this same branch and duplicates an already-designed family's detector under a different name. Its replacement lands in the same change on the declared path, so the removal is half of one restructure rather than a deletion of coverage — every predicate it carried survives, renamed onto the vocabulary the family declares. There is no surface that retires a module except removing the file and its registering rows. Admits-answer-rejected-route: `config read first` does not apply: the committed authority registers modules and cannot delete one, and pointing the rows elsewhere is what leaves the orphan this removal exists to prevent. `patch run first` does not apply either: there is no generator behind the module, so no patch route reaches it.
…f answering zero Four of five `Extraction` members reached no consumer, and repetition — the one thing a doom loop is made of — had no member at all. This is the spine the predicates stand on, landed alone because the risk is the plumbing rather than the detection. `Extraction::of` widens from `&Counts` to `&Stream`. `Counts` is unchanged: it is the `-J` capability report's own shape and its comments refuse members no reader of that document needs, so runs live in `Stream::repeats()` beside `Stream::counts()` rather than as fields on it. THE LOAD-BEARING HALF IS THE PER-EXTRACTION CAPABILITY. Zero is a real answer and means the extractor ran. An extraction whose underlying event kind never appears is not a session that did none of it — it is a host that does not record it, and answering zero there is a false green over a session nobody measured. `of` returns `Option<usize>` and the projection omits the key, so a module reads undefined and Rego takes that as does-not-hold. Decided in the engine: a per-module conjunct asking whether this host records turns is a dead gate on every harness but the one its author tested. The bound is stated rather than absorbed: this cannot separate a host that records no hook runs from a session that triggered none. It resolves that toward could-not-look, which is the safe direction — a missing answer is reported, where a false zero is indistinguishable from a clean session. `agent-turn-run` is the first member and deliberately the simplest: a trailing run of assistant turns carrying no tool call, over the already-typed `Event::Turn`. No hashing, so no argument or result is read even internally. It maps to OpenHands' monologue detector at 3+. The honest claim is narrower than loop detection, and the module header says so. Termination is undecidable and every quantity here is a monotonically growing count, so there is no ranking function to be had. What the literature buys is the shape of the declaration: a SET of extractions with a set of thresholds, supplying an effective bound on a SUSPECTED feedback path. It does not detect non-termination. Adjacency also makes the member non-monotonic — one action clears the run — which is the property any later promotion depends on. CLOUD-894 owns the firing-rate ceiling and CLOUD-1352 the promotion; this ships at `warn`. The compiled tier is what proves the capability rather than the author's arithmetic. It caught a wrong fixture doing it: a `user` record parses as a turn, so the first cross-compatibility case recorded turns after all and answered a real zero. A host recording hook runs and no turn boundaries is the shape the claim is about. Also retires the two now-spent rows in `policy/harness-declared.json`. CLOUD-1079 has landed, so the user-level hooks those rows excused are no longer provisioned and the merged surfaces carry zero hook commands — `harness-wiring` reported both, which is the gate working rather than misfiring. Reproduced against this same tree with `--config-from a907565`, so the finding predates this diff and is not caused by it. Leaving them is not neutral once their owner has landed: they would silently excuse anything that later matched either pattern by name. Refs: CLOUD-1344, CLOUD-1079, CLOUD-1049, CLOUD-418, CLOUD-894 Admits: d0ac8a4640f2a747b01bedc8449254f45d80865a7de7f0d3f765c40975627f93 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: batten.toml Admits-head: aed8b44 Admits-epoch: 3ce215f766f4c55fa0be890ef01ffdb5bfc93d7e597090d5f2098d074c5f389f Admits-author: alec@wenzowski.com Admits-prev: 04117a36b963f590a81de944785fade06bd0eb7b27cd2b35f33d59fa3bae4b07 Admits-answer-lost: The module cannot load, so CLOUD-1341 ships nothing. An unregistered `policy/*.rego` file is inert — the engine reads its rule set from the committed authority, and a module with no registering row is never evaluated. The extractor row is what makes the count reach the predicate at all: without it the fact is undefined, Rego reads undefined as does-not-hold, and the gate loads clean while deciding nothing, which is the dead-gate class `.claude/rules/policy-modules.md` exists to warn about. Admits-answer-precondition: Registering a policy module IS an edit to the committed authority: a `[[rule]]` row naming the module, a `[[rule.extract]]` row binding the new `repeated-calls` extractor to the key the module reads, and the `[[verdict]]` plus `[[verdict.route]]` rows declaring the `turn ask twice` token. A module raising an undeclared token fails to load, and a declared verdict row nothing raises fails the load too, so the rows and the module are one indivisible change. There is no other surface that expresses them, and the write lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` is what this IS — that route names reading configuration before writing it, and the rows being added do not exist to be read, because the module is new. `patch run first` does not apply: the file is hand-authored and has no generator behind it, so a patch route would write the same bytes to the same protected path; the generated `schema/*.json` are derived FROM the crate rather than producing it. Admits: 67106f144c06ca5c3aeaae3482a922741375d1168c3b3a6016979aadbe61b4a4 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: policy/repetition-without-progress.rego Admits-head: aed8b44 Admits-epoch: 3ce215f766f4c55fa0be890ef01ffdb5bfc93d7e597090d5f2098d074c5f389f Admits-author: alec@wenzowski.com Admits-prev: 368813d8fbd4cfef084b59aaa4b102fdf06d6bdb3d21ceaf0df87cc61629d2a8 Admits-answer-lost: The spine row ships with no predicate reading it, which is the half-change non-negotiable rule 2 refuses: an extraction nothing consumes is exactly the defect CLOUD-1344 was filed about, since four of five existing members already reach no consumer. The branch would add a sixth unread member while claiming to fix that. Admits-answer-precondition: CLOUD-1344's predicate is a Rego module: the adopted threshold, the null guard and the seven test rules are the module's own body, and no configuration surface expresses a predicate. This replaces the predicate landed earlier on this same branch, which was sequenced ahead of the spine row it is blocked on, so the write is a re-sequencing onto the row that must land first. It lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` does not apply: the committed authority has no surface for a predicate body or a threshold, and a threshold spelled as a pattern row is refused outright. `patch run first` does not apply either: the module is hand-authored with no generator behind it, so a patch route would write the same bytes to the same protected path. Admits: 06de726f4ec787c03543081e96d8e7ae1d46932d5306fe7b68e4465d56f8db24 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: policy/harness-declared.json Admits-head: aed8b44 Admits-epoch: 6785dbe2aaf01f887e2ec5902922351fd259cb877a4e4d8c830aeefa1d3079ba Admits-author: alec@wenzowski.com Admits-prev: - Admits-answer-lost: The gate stays red on every commit for a reason nobody caused, which is how a correct refusal gets skipped per commit until it is skipped by habit. Worse, the rows keep excusing two commands by name: were anything to reintroduce a hook matching either pattern, the exemption would silently cover it, so leaving them is not neutral once their owner has landed. Admits-answer-precondition: The exemption table is a policy data file the engine reads, and the two rows in it are now spent: the issue that owns them has landed, the user-level hooks they excused are no longer provisioned, and the merged surfaces carry zero hook commands. `harness-wiring` reports both, which is the gate working rather than misfiring. Retiring a row means editing that file; there is no other surface for it, and the write lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` does not apply: the committed authority declares which external surfaces are read and holds no exemption rows, which is deliberate — an exemption table is data a gate reads rather than part of the gate. `patch run first` does not apply either: the file is hand-authored with no generator behind it, so a patch route would write the same bytes to the same protected path.
`claim-order-is-stated` fired on any repository carrying an instructions file, so it refused a fixture whose `AGENTS.md` is the single word `instructions` and which has no `.claude/rules/` surface at all. `cli::the_committed_repo_config_gates_a_repository` runs the whole committed ruleset over exactly that tree and asserts its entire output, so it went red — the foreign-tree failure every tree-scoped row here has to answer, caught by the one case that was watching for it. The guard is keyed on the TRIGGERED file rather than the index, and that is the whole of the reasoning. This row is about a SPLIT: the order in the always-loaded file, the reason in the file that loads at the trigger. A tree with no `.claude/rules/toolchain.md` has not made that split and is answering for nothing here. Guarding on the index instead would be circular, since an absent index is precisely what one arm exists to refuse. Same shape as `hk-fix-selection`'s `governed`, which asks whether the repository has the config it judges before judging it. Both tiers gain the case, so the fixture shape is pinned rather than merely un-refused: `test_an_index_without_the_rules_file_is_not_judged` and `an_index_without_the_rules_file_is_not_judged`. Refs: CLOUD-1343 Admits: 03d9f351561fe5a7cbd22d78ad942b3f541a3ac5a92b241d8c327d36becf58b9 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: policy/claim-order-is-stated.rego Admits-head: 889f97b Admits-epoch: fb78c0a9b65b102b88a160ecb71c69eb089570a736ea14b96c2ea053177664fd Admits-author: alec@wenzowski.com Admits-prev: f4a603586d8e7912a35d4358fe20ed1955c112b132ecbaf9ad46eebc2bfa1742 Admits-answer-lost: The compiled suite stays red on a case that asserts the committed configuration's whole output over a fixture, so the branch cannot land at all. Worse as a shipped property: a consumer adopting this row would have it fire on any repository with an instructions file and no triggered rules file, which is a refusal about a document split that repository never made — the foreign-tree failure every tree-scoped row here has to answer. Admits-answer-precondition: The module judges any tree carrying an index file, so it fires on a fixture repository whose stub instructions file has never stated the claim order — a foreign tree answering for a split this repository's own instruction surface has. The fix is a not-applicable guard in the predicate body, which no configuration surface expresses; the committed authority registers the module and cannot narrow what it decides. It lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` does not apply: the committed authority declares the module's sources and severity and has no surface for a not-applicable conjunct, which is part of the predicate. `patch run first` does not apply either: the module is hand-authored with no generator behind it, so a patch route would write the same bytes to the same protected path.
Handoff — everything is pushed, one gate remainsWritten down because the container is ephemeral and the two things below live out of tree, so they die with it. Where the branch isRebased onto current The one remaining gate
The table rows are committed. The rows alone do not clear it — that predicate reads The record lives out of tree and does not survive a container, so a fresh session must re-run both before Things that will bite whoever picks this up
Findings recorded, and findings not yet filedAlready on the rows that own them:
Not yet filed — no duplicate search has been run for these, and
Deliberately not filed: a Not in scope for this PRCLOUD-1347 ( Watch for conflict with #829, already Generated by Claude Code |
…rebase CLOUD-1331 landed a keyed base arm for `perf pair` and CI collected none of it: 4 runs across 2 pull requests, 0 hits, ~190 MB written and discarded each time, and the `perf` job still paid 13.5 min of its 13.7 to rebuild a binary it had already built. The cause is the cached PATH, not the key. `actions/cache` identifies an entry by key AND version, and defines version as "a hash generated for a combination of compression tool used ... and the `path` of directories being cached". The step cached `target/perf/base-<merge base>`, interpolated, and `land` rebases every lap — so the path moved on every lap and took the entry's identity with it. The remedy first written down was to drop the SHA from the key, which would have changed nothing whatever. So the path holds still (`target/perf/base-seed`) and the SHA stays in the key, which is what makes a moved base MISS primarily and therefore SAVE — a key that never moves never saves, since "if the provided `key` matches an existing cache, a new cache is not created". A `restore-keys` prefix supplies the previous base's closure. Correctness is preserved by deleting the seeded binary rather than by the directory name: `base_arm_is_built()` is left false, so cargo runs against the base tree actually materialised and no renamed old binary is ever measured. `crates/batten/src/perf.rs` is untouched. Measured locally with `Cargo.lock` byte-identical across the pair, so the residue is the cost of the copy and not lockfile churn: cold 239 `Compiling` in 4m42s, seeded 64 in 2m07s, same base 0. The win is ~2m35s of a 13.5 min step and is stated as that rather than as the whole problem — the 239->0 path needs a run whose merge base matches a cached one, which needs cross-PR scoping and is CLOUD-840's. Rule 2: it ships with a mechanism. `cache-path-is-rebase-stable` in `policy/ci-parity.rego` refuses a cached path carrying an expression, and deliberately does not judge the key, since an expression belongs there. The declared mutation reddens exactly its case and only it (27 passed, 1 failed). The predicate is the consumer's rather than the core's because it names this repository's workflow (non-negotiable rule 1). Refs: CLOUD-1342, CLOUD-1331, CLOUD-840, CLOUD-1225 Admits: 792a6d9ef6e998176b2123ff38eaa85cf8a24854d3694c554258f364e5e83b19 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: .github/workflows/ci.yml Admits-head: 116ebc1 Admits-epoch: 6c89a13a27eb648df22dd80a90d058b5dfbb32e883ced257202104a3f598b416 Admits-author: alec@wenzowski.com Admits-prev: d7c27bde12b7fe89dce932035c38e953b155f388b91bca1a449d43dee5739fe2 Admits-answer-lost: The `perf` job keeps rebuilding the merge base's release binary cold on every CI run: 4 runs across 2 pull requests, 0 cache hits, ~190 MB written and discarded each time, ~2m35s per run that a stable cached path recovers. Without the write the CLOUD-1331 mechanism stays uncollectable and the defect is only documented, not fixed. Admits-answer-precondition: The `perf` job's cache step lives only in this workflow file; there is no other surface that expresses which path a runner restores, so writing `.github/workflows/ci.yml` directly is the only route to the fix. The change is three steps and a cache `path`/`key`/`restore-keys` triple, all visible in the diff a reviewer reads. Admits-answer-rejected-route: `config read first` does not apply: no `batten.toml` key selects a workflow cache path — the value is GitHub Actions' own and has no projection in this repository's config. `patch run first` does not apply either: there is no generator or task that emits this workflow, so there is no upstream artifact to patch and re-emit; the file is hand-maintained and is its own source of truth. Admits: a9a9a523ecb285b98020fd8456634f1c2b427856132965d5588cce74bd1ad070 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: batten.toml Admits-head: 116ebc1 Admits-epoch: 6c89a13a27eb648df22dd80a90d058b5dfbb32e883ced257202104a3f598b416 Admits-author: alec@wenzowski.com Admits-prev: - Admits-answer-lost: Without it the predicate cannot load, so the `perf` cache fix ships as a workflow edit with no gate behind it — exactly the half-change non-negotiable rule 2 refuses, leaving the next author free to reintroduce a merge-base SHA in the cached path and silently lose every hit again. Admits-answer-precondition: `batten.toml` is this repository's one authority for `[[verdict]]` rows, and a policy module raising a token no row declares fails to LOAD. So the new `cache-path-is-rebase-stable` predicate cannot exist without writing this file; no other surface can declare its verdict class on its behalf. The addition is one `[[verdict]]` row and its route, read in the diff. Admits-answer-rejected-route: `config read first` is the route being taken rather than rejected in spirit — but as a substitute for the write it does not apply: reading `batten.toml` cannot add a row to it, and the verdict registry has no override surface that admits a token from elsewhere. `patch run first` does not apply: `batten.toml` is hand-maintained policy, emitted by no generator, so there is no upstream artifact to patch.
…not a total An across-turn poll was invisible to every gate. Measured over one real transcript: `ReadNotifications` called 1079 times, 59% of every tool call in the session, every one with identical arguments and the identical "No queued notifications" result. All 1079 carry `role: assistant`, so this judges agent conduct and cannot fire on harness behaviour. No landed arm could reach it. `run-shape` keys on a backgrounded `sleep` and CLOUD-489 on `until`/`while` inside one command string; repetition spread across turns carries neither token, and a harness verb with no argv gives a `shape` row nothing to match. THE FACT IS A MAXIMUM OVER IDENTITIES, NEVER A SUM. `repeated_calls` is how far the session's most-repeated (tool, arguments) identity ran. A running total across every identity is monotonic in the length of the session and never resets, so it fires on anything long enough to repeat anything: over the same transcript the sum was 1294 where the max was 1079 against a healthy ceiling of 38. A threshold derived from one identity's recurrence and applied to that total is not a threshold. The row's own §2 is refuted and needs rewriting on the tracker. It proposed "consecutive calls to one tool": the polling arrived in 249 bursts whose longest run was 8, a length any healthy session reaches, so a consecutive reading ships as coverage while deciding nothing. Identity is the tool name and a DIGEST of the arguments, so no argument text is retained. The RESULT is deliberately excluded — it separates better (60x against 28x) and would oblige the parser to read every result body, the one payload `transcript.rs` exists never to touch. A replayed `tool_use` id is deduped: counting replays measures what the host chose to re-emit rather than what the session did. IT SHIPS AT `warn`. A `mediated_call` row at `deny` refuses every later tool call once it fires, and no admission can clear it — `batten override request` and `spend` are themselves mediated calls, so requesting one requires making the call the deny refuses. This predicate was landed at `deny` once and locked its own authoring session out at ~1300, push included. Promotion needs it shown silent against a real transcript first. The compiled tier drives the engine rather than a fabricated input, which is what pins the off-by-one: N calls are N-1 recurrences, so clearing 100 takes 102 calls and 101 is clean. Refs: CLOUD-1341, CLOUD-1172, CLOUD-418, CLOUD-489 Admits: 3d6283f7ae44fe7b8349bbb4ef3aa0b22e03bc84ad4059098ff15576b3729337 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: batten.toml Admits-head: a907565 Admits-epoch: 93d8b1112718944856b6e81b9cfac7c903dbb2c28470be1fc6e59786fb42f404 Admits-author: alec@wenzowski.com Admits-prev: a387bad2ea98589d0f75455b02cba853248836b7c51d4707cf39585329262b06 Admits-answer-lost: The module cannot load, so CLOUD-1341 ships nothing. An unregistered `policy/*.rego` file is inert — the engine reads its rule set from the committed authority, and a module with no registering row is never evaluated. The extractor row is what makes the count reach the predicate at all: without it the fact is undefined, Rego reads undefined as does-not-hold, and the gate loads clean while deciding nothing, which is the dead-gate class `.claude/rules/policy-modules.md` exists to warn about. Admits-answer-precondition: Registering a policy module IS an edit to the committed authority: a `[[rule]]` row naming the module, a `[[rule.extract]]` row binding the new `repeated-calls` extractor to the key the module reads, and the `[[verdict]]` plus `[[verdict.route]]` rows declaring the `turn ask twice` token. A module raising an undeclared token fails to load, and a declared verdict row nothing raises fails the load too, so the rows and the module are one indivisible change. There is no other surface that expresses them, and the write lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` is what this IS — that route names reading configuration before writing it, and the rows being added do not exist to be read, because the module is new. `patch run first` does not apply: the file is hand-authored and has no generator behind it, so a patch route would write the same bytes to the same protected path; the generated `schema/*.json` are derived FROM the crate rather than producing it. Admits: 247eaff38bfbc59e1afae9cae684be633d05c8eca2d901051cad86757508560c Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: policy/a-repeated-call-is-not-progress.rego Admits-head: a907565 Admits-epoch: 93d8b1112718944856b6e81b9cfac7c903dbb2c28470be1fc6e59786fb42f404 Admits-author: alec@wenzowski.com Admits-prev: e4dad46c2f5408cdb8bdb3487a98b9e7035341caad7cb39e7e1d555605d0d1f9 Admits-answer-lost: CLOUD-1341 stays prose. The always-loaded instructions already state that a backgrounded task's exit notification is the wake-up and that re-asking is not waiting, and non-negotiable rule 2 calls a rule without a runnable gate half a change. The two landed arms cannot reach this family: one keys on a backgrounded `sleep`, the other on `until`/`while` inside a single command string, while the measured defect was 1079 identical harness-verb calls spread across turns with no argv to match. Without the module there is no gate at all. Admits-answer-precondition: CLOUD-1341's remedy is a policy predicate, and a predicate is a Rego module: the threshold constant, the null guard and the eight test rules are the module's own body, which no configuration surface can express. The committed authority carries only the rows that register it. Writing the protected path is the only route left, and the file lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` does not apply: the committed authority has no surface for a predicate body or a threshold constant, and `.claude/rules/policy-modules.md` refuses a threshold spelled as a pattern row outright, so the number has to live in the consumer module. `patch run first` does not apply either: the module is hand-authored with no generator behind it, so a patch route would write the same bytes to the same protected path.
…a pull request CLOUD-1331's keyed base arm collected nothing across 4 runs and 2 pull requests: 0 hits, ~190 MB written and discarded each time. The first diagnosis blamed the key; the second blamed the path. Both were reading the same design, which optimised for a hit and never asked what a miss costs. An entry written from a pull request is scoped to `refs/pull/N/merge` and no other pull request can read it. An entry written from the default branch is readable by every branch. So the PR side now RESTORES and never saves — `actions/cache/restore`, a key carrying no merge base because nothing saves and there is no miss to engineer — and the scheduled `perf` job on `main`, which already pays a full `--release` build, stages that closure and saves it under the same key. No new trigger, no second build. The seed is never measured: it is copied to `target/perf/base-$SHA` with its binary removed, so `base_arm_is_built()` stays false and cargo runs against the base tree actually materialised. CLOUD-840 measured this repository at 225.2 GB of Actions cache, 203.7 GB of it across 1,144 PR-ref entries nothing can restore. This job now writes zero of them, and the hit arrives on a pull request's FIRST run rather than its second. `cache-path-is-rebase-stable` gains the two sub-action spellings. `actions/cache/restore` and `actions/cache/save` derive an entry's version from `path` exactly as the composite does, so matching only `actions/cache@` would have left the predicate live and reaching nothing the moment a job split restore from save — which is what this commit does. Its own case is declared rather than assumed. Refs: CLOUD-1342, CLOUD-1331, CLOUD-840 Admits: b0862b76d140be6746e8cdd0fcaf3c28e5bc685ef40c4e3164a1079bf12d075b Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: .github/workflows/ci.yml Admits-head: fb2987d Admits-epoch: 93d8b1112718944856b6e81b9cfac7c903dbb2c28470be1fc6e59786fb42f404 Admits-author: alec@wenzowski.com Admits-prev: f0a932f52d256fb53185a39552efa1b6ac28551baa5b3c020412742000541260 Admits-answer-lost: The job keeps writing ~190 MB per run of cache scoped to the pull request's own merge ref, which no later run can ever restore — CLOUD-840 measured 203.7 GB of such entries across 1,144 of them in this repository — and every pull request keeps compiling the base arm's whole release closure cold, 239 crates in 4m42s of a 13.5 min step. The design being replaced optimised for a hit and never asked what a miss costs. Admits-answer-precondition: The change is a GitHub Actions step in the `perf` job — swapping the composite cache action for its restore-only sub-action, dropping the merge-base SHA from the key, and deleting the pull-request-side save step. No batten surface expresses a workflow step, so writing the protected path directly is the only route left, and the write lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` does not apply: the committed authority declares rules, verdicts and patterns and carries no representation of a workflow job or step, so there is nothing to read or set there. `patch run first` does not apply either: the workflow is hand-authored YAML with no generator behind it, so a patch route would write the same bytes to the same protected path. Admits: a76c9a87c95fa32cf60a4b398c75005fdba89cb88255963faf88c77be092502f Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: .github/workflows/perf.yml Admits-head: fb2987d Admits-epoch: 93d8b1112718944856b6e81b9cfac7c903dbb2c28470be1fc6e59786fb42f404 Admits-author: alec@wenzowski.com Admits-prev: 22dc584248885224d59327013514c1c438affb90392824a98c742447a18e3b7e Admits-answer-lost: The restore-only step this branch lands has nothing to restore. Only a run on the default branch can write a cache entry every branch can read, and this scheduled job is the one place that already pays a full release build. Absent the seed, every pull request keeps compiling the base arm's whole release closure cold, and writing unreadable per-pull-request entries stays the only alternative. Admits-answer-precondition: The change adds two GitHub Actions steps to the scheduled job that already runs on the default branch — staging the release closure it has just built, and saving it under the key the pull-request side restores. No batten surface expresses a workflow step, so writing the protected path directly is the only route left, and the write lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` does not apply: the committed authority declares rules, verdicts and patterns and carries no representation of a workflow job or step, so there is nothing to read or set there. `patch run first` does not apply either: the workflow is hand-authored YAML with no generator behind it, so a patch route would write the same bytes to the same protected path. Admits: 7d1ad5bf1d04296f330e798f42f2a74aba2e5d7f78ab4b9a8d2e2474c9dea5e3 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: policy/ci-parity.rego Admits-head: fb2987d Admits-epoch: 93d8b1112718944856b6e81b9cfac7c903dbb2c28470be1fc6e59786fb42f404 Admits-author: alec@wenzowski.com Admits-prev: d2e45c5830908bdc08f6f59141f3a9b031f68ad27ffdd7d133ebf108d0b30921 Admits-answer-lost: The gate goes dead against its own subject. The sub-actions derive an entry's version from the cached path exactly as the composite does, so an interpolated path is the identical silent total miss — the one measured at 4 runs, 0 hits, ~190 MB discarded each time — and after this branch the repository's only cache steps of that kind are the sub-action spellings the predicate cannot see. A gate that loads clean and matches nothing reads exactly like a clean tree. Admits-answer-precondition: The predicate matches only the composite spelling of the cache action, and this branch moves the job to the restore-only sub-action plus a save on the scheduled side — so the gate that exists to refuse an interpolated cached path would stop covering the very steps this branch lands. Widening it and adding the discriminating case is a change to the Rego module itself; no configuration surface expresses a predicate body, so writing the protected path directly is the only route, and it lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` does not apply: the committed authority carries the rows that register this module, not the predicate body, so nothing there can widen which action spellings the rule matches. `patch run first` does not apply either: the module is hand-authored and has no generator behind it, so a patch route would write the same bytes to the same protected path.
…viting the second claim The claim receipt is minted on `claim check`'s pullable path and keyed by the branch checked out at that moment. That one fact makes two orderings fail in opposite directions, and both are reachable by following the instructions correctly the first time. Claim, then branch: the receipt is minted against the branch you were standing on, and the first edit on the real branch is refused with `receipt read missing claim branch claim-needs-receipt`. Claim twice: having branched first, the pullable message read as go-do-this-and-come-back, so the next move was to move the row and run `claim-check` again. The second run arrives after the row has left Todo, reads it as held, and refuses `not-todo` — where the holder is the caller ninety seconds earlier. The only route past is `--takeover` against oneself, which writes a takeover record for a row nobody else touched, and CLOUD-1139 needs that signal to stay rare. Measured twice, in two sessions, both by an agent following the documented order. NO ENGINE CHANGE, and the first revision of this row assumed one was needed. Creating a branch writes only under `.git`, which `claim-needs-receipt` never judges, so there is no ordering deadlock — the deadlock was imagined. `claim.rs`'s `not-todo` decision is correct as it stands for a row genuinely held elsewhere and is untouched. Recognising a self-claim in the engine stays CLOUD-1139's: it needs to tell this session from a sibling, and the row's assignee cannot, since every fleet session carries the same configured accountable identity. An assignee-keyed re-mint would let a sibling silently re-mint over a working holder — that row's measured harm, reintroduced through this row's fix. The gate decides what a gate can: whether the always-loaded file still states the order and whether the triggered file still carries both failure directions. Whether a given session actually claimed before branching is not a property of the tree, and a rule resolving to it would be the model verdict non-negotiable rule 3 forbids. Two files because a budget forced it. `policy-budget` caps the index at 3500 tokens and 199 lines, and an earlier draft of this change blew both at 3516/202; the index carries only the ORDER and the reason lives in the rules file that loads at the trigger. Both arms are therefore required. The compiled tier is what proves the two markdown files reach the module at all: a dead gate and a tree that still states the order are byte-identical on the decision surface, so each drift case can only go red if the lines actually arrived. Refs: CLOUD-1343, CLOUD-1139, CLOUD-786, CLOUD-733 Admits: 3fe3ee98cc0ec6d5f1a9ea595f823c2daa76332568b0ca2332363539c5979d99 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: batten.toml Admits-head: f9f48e6 Admits-epoch: 84237be51e176ca21fe43fbe5f50492b50c188b8881b9745dfb2b4004a75a220 Admits-author: alec@wenzowski.com Admits-prev: 3d6283f7ae44fe7b8349bbb4ef3aa0b22e03bc84ad4059098ff15576b3729337 Admits-answer-lost: The gate is dead rather than absent, which is strictly worse. Without the `line_sources` list the engine builds no lines for those two paths, every clause reads undefined, Rego takes undefined as does-not-hold, and the module loads clean while deciding nothing — indistinguishable on the decision surface from a tree that still states the order. That is the exact failure class `.claude/rules/policy-modules.md` records, and without the registering row the module is never evaluated at all. Admits-answer-precondition: Registering the claim-order module IS an edit to the committed authority: a tree-scoped `[[rule]]` row naming it, the `line_sources` list that makes the two instruction files reach the predicate at all, and the `[[verdict]]` plus `[[verdict.route]]` rows declaring the `claim declare dropped` token. A module raising an undeclared token fails to load, and a declared verdict row nothing raises fails the load too, so the rows and the module are one indivisible change. There is no other surface that expresses them, and the write lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` is what this IS — that route names reading configuration before writing it, and the rows being added do not exist to be read, because the module is new. `patch run first` does not apply: the file is hand-authored and has no generator behind it, so a patch route would write the same bytes to the same protected path. Admits: f4a603586d8e7912a35d4358fe20ed1955c112b132ecbaf9ad46eebc2bfa1742 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: policy/claim-order-is-stated.rego Admits-head: f9f48e6 Admits-epoch: 84237be51e176ca21fe43fbe5f50492b50c188b8881b9745dfb2b4004a75a220 Admits-author: alec@wenzowski.com Admits-prev: - Admits-answer-lost: The order evaporates. The failure mode here is DRIFT — the remedy is prose, and prose is what a later edit silently rewords or drops, after which the next session walks into the same refusal that cost this one a full stop mid-work and a takeover record against its own claim. A prose assertion with no gate is exactly the half-change rule 2 refuses, and the Ready gate already refused an earlier revision of this row for carrying an empty tests array. Admits-answer-precondition: CLOUD-1343's remedy is words in two instruction files, and non-negotiable rule 2 calls a rule without a runnable gate half a change. The gate over words is a Rego module: which literal phrases each file must still carry, which file a finding points at, and the could-not-look arm are the module's own body, and no configuration surface expresses a predicate. Writing the protected path is the only route, and the file lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` does not apply: the committed authority carries the rows that register a module and the literal phrases the predicate matches are not expressible there. `patch run first` does not apply either: the module is hand-authored with no generator behind it, so a patch route would write the same bytes to the same protected path.
…iling-run adjacency The predicate this branch first landed was a second authority. CLOUD-1347 and CLOUD-1350 already design one module for the whole loop vocabulary, and `a-repeated-call-is-not-progress` was CLOUD-1350's window-recurrence detector under another name, computing another fact. Two predicates over one question can disagree, and the disagreement is discovered by a session being refused, so the module is renamed onto the declared vocabulary rather than left beside it. WHAT REPLACES IT IS NOT A RENAME. `repeated-calls` was the MAXIMUM recurrences of any identity over the whole stream. `repeat-depth` is the TRAILING run of identical calls, and `distinct-calls` is the progress term beside it. Adjacency is what does the false-positive work: any intervening distinct call clears the run, so the edit-then-retest loop is false by construction rather than by carve-out, and the threshold is the 3 that opencode, hermes-agent and OpenHands all converge on rather than a constant derived from one pathological session. AND ADJACENCY IS WHY THIS ONE CANNOT LOCK A SESSION OUT. A whole-stream maximum is monotonic: once it crossed its threshold it stayed crossed for the rest of the session, which is how the earlier predicate refused every subsequent tool call including its own author's push, with no route to an admission — `batten override request` and `spend` are themselves mediated calls. A trailing run resets on the next distinct call, so the escape is automatic and the override CLOUD-1352 names is actually reachable. Every member of this family is non-monotonic for that reason. The fingerprint stays inside `transcript.rs`, hashed and dropped in the same expression as `Event::HookOutput`'s digest, so only a run length is projected and `Extraction` stays integers-only. A replayed `tool_use` id is deduped: counting replays measures what the host chose to re-emit rather than what the session did. Severity stays `warn`. CLOUD-1352 owns the promotion and makes a measured firing rate over this repository's own history a hard precondition. Refs: CLOUD-1347, CLOUD-1341, CLOUD-1350, CLOUD-1352, CLOUD-1337 Admits: 04117a36b963f590a81de944785fade06bd0eb7b27cd2b35f33d59fa3bae4b07 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: batten.toml Admits-head: 6420fb9 Admits-epoch: 6f8c3ea4c54e6263a8ca91ed3f034ea58de55f4324b3d93f7737b467fcc2467d Admits-author: alec@wenzowski.com Admits-prev: 3fe3ee98cc0ec6d5f1a9ea595f823c2daa76332568b0ca2332363539c5979d99 Admits-answer-lost: The module cannot load, so CLOUD-1341 ships nothing. An unregistered `policy/*.rego` file is inert — the engine reads its rule set from the committed authority, and a module with no registering row is never evaluated. The extractor row is what makes the count reach the predicate at all: without it the fact is undefined, Rego reads undefined as does-not-hold, and the gate loads clean while deciding nothing, which is the dead-gate class `.claude/rules/policy-modules.md` exists to warn about. Admits-answer-precondition: Registering a policy module IS an edit to the committed authority: a `[[rule]]` row naming the module, a `[[rule.extract]]` row binding the new `repeated-calls` extractor to the key the module reads, and the `[[verdict]]` plus `[[verdict.route]]` rows declaring the `turn ask twice` token. A module raising an undeclared token fails to load, and a declared verdict row nothing raises fails the load too, so the rows and the module are one indivisible change. There is no other surface that expresses them, and the write lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` is what this IS — that route names reading configuration before writing it, and the rows being added do not exist to be read, because the module is new. `patch run first` does not apply: the file is hand-authored and has no generator behind it, so a patch route would write the same bytes to the same protected path; the generated `schema/*.json` are derived FROM the crate rather than producing it. Admits: 368813d8fbd4cfef084b59aaa4b102fdf06d6bdb3d21ceaf0df87cc61629d2a8 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: policy/repetition-without-progress.rego Admits-head: 6420fb9 Admits-epoch: 6f8c3ea4c54e6263a8ca91ed3f034ea58de55f4324b3d93f7737b467fcc2467d Admits-author: alec@wenzowski.com Admits-prev: - Admits-answer-lost: The branch ships a second authority over a question the tracker already owns. A family of rows designs one module holding the whole loop vocabulary; landing a differently-named module computing a differently-named fact means whoever builds that family writes the second one, and this repository refuses exactly that — two predicates over one question can disagree, and the disagreement is discovered by a session being refused. Without the restructure the duplicate is what lands. Admits-answer-precondition: The predicate is a Rego module: the threshold constant, the three-valued posture guard, the null guard and the ten test rules are the module's own body, and no configuration surface expresses a predicate. This replaces a module landed earlier on this same branch under a name that duplicated an already-designed family, so the write is a restructure onto the declared vocabulary rather than a new gate. It lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` does not apply: the committed authority has no surface for a predicate body or a threshold constant, and a threshold spelled as a pattern row is refused outright, so the number has to live in the consumer module. `patch run first` does not apply either: the module is hand-authored with no generator behind it, so a patch route would write the same bytes to the same protected path. Admits: 8a3ff5e4351e38ed26fdc4fd7eeaa30b80bd7d096ab7ebb2433024111f576156 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: policy/a-repeated-call-is-not-progress.rego Admits-head: 6420fb9 Admits-epoch: 6f8c3ea4c54e6263a8ca91ed3f034ea58de55f4324b3d93f7737b467fcc2467d Admits-author: alec@wenzowski.com Admits-prev: 247eaff38bfbc59e1afae9cae684be633d05c8eca2d901051cad86757508560c Admits-answer-lost: Two modules over one question ship together. The registering rows can be pointed at the replacement, but leaving the file behind leaves a second authority in the tree that a later reader may register again, and the two can disagree over exactly the cases neither author had in mind. Keeping it would also leave its rule id in the mutation census naming a gate nothing registers. Admits-answer-precondition: The module being removed was landed earlier on this same branch and duplicates an already-designed family's detector under a different name. Its replacement lands in the same change on the declared path, so the removal is half of one restructure rather than a deletion of coverage — every predicate it carried survives, renamed onto the vocabulary the family declares. There is no surface that retires a module except removing the file and its registering rows. Admits-answer-rejected-route: `config read first` does not apply: the committed authority registers modules and cannot delete one, and pointing the rows elsewhere is what leaves the orphan this removal exists to prevent. `patch run first` does not apply either: there is no generator behind the module, so no patch route reaches it.
…f answering zero Four of five `Extraction` members reached no consumer, and repetition — the one thing a doom loop is made of — had no member at all. This is the spine the predicates stand on, landed alone because the risk is the plumbing rather than the detection. `Extraction::of` widens from `&Counts` to `&Stream`. `Counts` is unchanged: it is the `-J` capability report's own shape and its comments refuse members no reader of that document needs, so runs live in `Stream::repeats()` beside `Stream::counts()` rather than as fields on it. THE LOAD-BEARING HALF IS THE PER-EXTRACTION CAPABILITY. Zero is a real answer and means the extractor ran. An extraction whose underlying event kind never appears is not a session that did none of it — it is a host that does not record it, and answering zero there is a false green over a session nobody measured. `of` returns `Option<usize>` and the projection omits the key, so a module reads undefined and Rego takes that as does-not-hold. Decided in the engine: a per-module conjunct asking whether this host records turns is a dead gate on every harness but the one its author tested. The bound is stated rather than absorbed: this cannot separate a host that records no hook runs from a session that triggered none. It resolves that toward could-not-look, which is the safe direction — a missing answer is reported, where a false zero is indistinguishable from a clean session. `agent-turn-run` is the first member and deliberately the simplest: a trailing run of assistant turns carrying no tool call, over the already-typed `Event::Turn`. No hashing, so no argument or result is read even internally. It maps to OpenHands' monologue detector at 3+. The honest claim is narrower than loop detection, and the module header says so. Termination is undecidable and every quantity here is a monotonically growing count, so there is no ranking function to be had. What the literature buys is the shape of the declaration: a SET of extractions with a set of thresholds, supplying an effective bound on a SUSPECTED feedback path. It does not detect non-termination. Adjacency also makes the member non-monotonic — one action clears the run — which is the property any later promotion depends on. CLOUD-894 owns the firing-rate ceiling and CLOUD-1352 the promotion; this ships at `warn`. The compiled tier is what proves the capability rather than the author's arithmetic. It caught a wrong fixture doing it: a `user` record parses as a turn, so the first cross-compatibility case recorded turns after all and answered a real zero. A host recording hook runs and no turn boundaries is the shape the claim is about. Also retires the two now-spent rows in `policy/harness-declared.json`. CLOUD-1079 has landed, so the user-level hooks those rows excused are no longer provisioned and the merged surfaces carry zero hook commands — `harness-wiring` reported both, which is the gate working rather than misfiring. Reproduced against this same tree with `--config-from a907565`, so the finding predates this diff and is not caused by it. Leaving them is not neutral once their owner has landed: they would silently excuse anything that later matched either pattern by name. Refs: CLOUD-1344, CLOUD-1079, CLOUD-1049, CLOUD-418, CLOUD-894 Admits: d0ac8a4640f2a747b01bedc8449254f45d80865a7de7f0d3f765c40975627f93 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: batten.toml Admits-head: aed8b44 Admits-epoch: 3ce215f766f4c55fa0be890ef01ffdb5bfc93d7e597090d5f2098d074c5f389f Admits-author: alec@wenzowski.com Admits-prev: 04117a36b963f590a81de944785fade06bd0eb7b27cd2b35f33d59fa3bae4b07 Admits-answer-lost: The module cannot load, so CLOUD-1341 ships nothing. An unregistered `policy/*.rego` file is inert — the engine reads its rule set from the committed authority, and a module with no registering row is never evaluated. The extractor row is what makes the count reach the predicate at all: without it the fact is undefined, Rego reads undefined as does-not-hold, and the gate loads clean while deciding nothing, which is the dead-gate class `.claude/rules/policy-modules.md` exists to warn about. Admits-answer-precondition: Registering a policy module IS an edit to the committed authority: a `[[rule]]` row naming the module, a `[[rule.extract]]` row binding the new `repeated-calls` extractor to the key the module reads, and the `[[verdict]]` plus `[[verdict.route]]` rows declaring the `turn ask twice` token. A module raising an undeclared token fails to load, and a declared verdict row nothing raises fails the load too, so the rows and the module are one indivisible change. There is no other surface that expresses them, and the write lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` is what this IS — that route names reading configuration before writing it, and the rows being added do not exist to be read, because the module is new. `patch run first` does not apply: the file is hand-authored and has no generator behind it, so a patch route would write the same bytes to the same protected path; the generated `schema/*.json` are derived FROM the crate rather than producing it. Admits: 67106f144c06ca5c3aeaae3482a922741375d1168c3b3a6016979aadbe61b4a4 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: policy/repetition-without-progress.rego Admits-head: aed8b44 Admits-epoch: 3ce215f766f4c55fa0be890ef01ffdb5bfc93d7e597090d5f2098d074c5f389f Admits-author: alec@wenzowski.com Admits-prev: 368813d8fbd4cfef084b59aaa4b102fdf06d6bdb3d21ceaf0df87cc61629d2a8 Admits-answer-lost: The spine row ships with no predicate reading it, which is the half-change non-negotiable rule 2 refuses: an extraction nothing consumes is exactly the defect CLOUD-1344 was filed about, since four of five existing members already reach no consumer. The branch would add a sixth unread member while claiming to fix that. Admits-answer-precondition: CLOUD-1344's predicate is a Rego module: the adopted threshold, the null guard and the seven test rules are the module's own body, and no configuration surface expresses a predicate. This replaces the predicate landed earlier on this same branch, which was sequenced ahead of the spine row it is blocked on, so the write is a re-sequencing onto the row that must land first. It lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` does not apply: the committed authority has no surface for a predicate body or a threshold, and a threshold spelled as a pattern row is refused outright. `patch run first` does not apply either: the module is hand-authored with no generator behind it, so a patch route would write the same bytes to the same protected path. Admits: 06de726f4ec787c03543081e96d8e7ae1d46932d5306fe7b68e4465d56f8db24 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: policy/harness-declared.json Admits-head: aed8b44 Admits-epoch: 6785dbe2aaf01f887e2ec5902922351fd259cb877a4e4d8c830aeefa1d3079ba Admits-author: alec@wenzowski.com Admits-prev: - Admits-answer-lost: The gate stays red on every commit for a reason nobody caused, which is how a correct refusal gets skipped per commit until it is skipped by habit. Worse, the rows keep excusing two commands by name: were anything to reintroduce a hook matching either pattern, the exemption would silently cover it, so leaving them is not neutral once their owner has landed. Admits-answer-precondition: The exemption table is a policy data file the engine reads, and the two rows in it are now spent: the issue that owns them has landed, the user-level hooks they excused are no longer provisioned, and the merged surfaces carry zero hook commands. `harness-wiring` reports both, which is the gate working rather than misfiring. Retiring a row means editing that file; there is no other surface for it, and the write lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` does not apply: the committed authority declares which external surfaces are read and holds no exemption rows, which is deliberate — an exemption table is data a gate reads rather than part of the gate. `patch run first` does not apply either: the file is hand-authored with no generator behind it, so a patch route would write the same bytes to the same protected path.
`claim-order-is-stated` fired on any repository carrying an instructions file, so it refused a fixture whose `AGENTS.md` is the single word `instructions` and which has no `.claude/rules/` surface at all. `cli::the_committed_repo_config_gates_a_repository` runs the whole committed ruleset over exactly that tree and asserts its entire output, so it went red — the foreign-tree failure every tree-scoped row here has to answer, caught by the one case that was watching for it. The guard is keyed on the TRIGGERED file rather than the index, and that is the whole of the reasoning. This row is about a SPLIT: the order in the always-loaded file, the reason in the file that loads at the trigger. A tree with no `.claude/rules/toolchain.md` has not made that split and is answering for nothing here. Guarding on the index instead would be circular, since an absent index is precisely what one arm exists to refuse. Same shape as `hk-fix-selection`'s `governed`, which asks whether the repository has the config it judges before judging it. Both tiers gain the case, so the fixture shape is pinned rather than merely un-refused: `test_an_index_without_the_rules_file_is_not_judged` and `an_index_without_the_rules_file_is_not_judged`. Refs: CLOUD-1343 Admits: 03d9f351561fe5a7cbd22d78ad942b3f541a3ac5a92b241d8c327d36becf58b9 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: policy/claim-order-is-stated.rego Admits-head: 889f97b Admits-epoch: fb78c0a9b65b102b88a160ecb71c69eb089570a736ea14b96c2ea053177664fd Admits-author: alec@wenzowski.com Admits-prev: f4a603586d8e7912a35d4358fe20ed1955c112b132ecbaf9ad46eebc2bfa1742 Admits-answer-lost: The compiled suite stays red on a case that asserts the committed configuration's whole output over a fixture, so the branch cannot land at all. Worse as a shipped property: a consumer adopting this row would have it fire on any repository with an instructions file and no triggered rules file, which is a refusal about a document split that repository never made — the foreign-tree failure every tree-scoped row here has to answer. Admits-answer-precondition: The module judges any tree carrying an index file, so it fires on a fixture repository whose stub instructions file has never stated the claim order — a foreign tree answering for a split this repository's own instruction surface has. The fix is a not-applicable guard in the predicate body, which no configuration surface expresses; the committed authority registers the module and cannot narrow what it decides. It lands in PR #833's diff where a reviewer sees it. Admits-answer-rejected-route: `config read first` does not apply: the committed authority declares the module's sources and severity and has no surface for a not-applicable conjunct, which is part of the predicate. `patch run first` does not apply either: the module is hand-authored with no generator behind it, so a patch route would write the same bytes to the same protected path.
CLOUD-1342 replaced the composite `actions/cache` with `actions/cache/restore` in `ci.yml` and added `actions/cache/save` to `perf.yml`. Both are SHA-pinned, so both are `pkg:github` components in the inventory, and neither was in the table `sbom-inventory` reads — `sbom-action-unenriched` reported 2. Same repository, same commit as the composite entry already in the table, so the licence and the copyright line are the same facts rather than a guess. The counts that rule decides over come from the RECORDED scan rather than from this file, so the table alone does not clear it: the record is regenerated by `mise run record-sbom`, and it lives out of tree, so a fresh container has to re-run that before `batten-check` can pass. Refs: CLOUD-1342, CLOUD-667
Two workspace lints, both against the spine landed two commits ago, and both fixed rather than suppressed. `struct_excessive_bools` on `Records`. A struct of four flags asks a reader to remember which one is which, and clippy's own remedy — a closed vocabulary — is the better shape here anyway: `Kind` is an enum and `records()` returns a `BTreeSet<Kind>`, so a caller asks `contains` and a kind added later is a variant every exhaustive match decides or fails to compile over. That is the same argument `Extraction`'s own closed set already makes one module across. `match_same_arms` in `Stream::repeats()`. Here the arms really are one decision: a tool call and a user turn both end the model's trailing monologue — it acted, or the operator spoke — so they merge under one comment rather than carrying an `expect` over an arm that decides the same thing twice. `Stream::counts()`'s `expect` stays, because its two silent arms are genuinely different reasons. No behaviour changes. The compiled tier is unchanged and still green, including the pair that carries CLOUD-1344: a host recording no turns answers could-not-look, and a recorded session with no run answers a real zero. Refs: CLOUD-1344
`a_clean_final_message_says_nothing` asserts silence and got `unlanded: 1 commit(s) not on the landing target` — a finding from the checkout the suite was running in, not from its fixture. The Stop tier reads the out-of-tree findings store, and `hook()` set no state home, so it inherited the ambient one. The consequence is worse than one flaky case: the test passes on a tree with nothing unlanded and fails on any branch carrying unpushed work, which is every branch this suite is ever run from mid-development. It went unnoticed because the green path is the one a clean checkout takes. The suite already knew. Its unlanded fixture's own runner contains the state home and says why in as many words — "an ambient one would let a real session's findings decide a fixture's verdict" — and the posture half simply never did it. This applies the same containment there, so both halves of the file now hold the same invariant. Derived per fixture from the directory name rather than shared, because nextest runs each case in its own process and a shared scratch name is a wipe under another process's read. All 16 cases pass, including `unlanded_work_at_a_declared_stopping_point_is_pointed_at` — the containment isolates the store rather than emptying it, so the cases that must READ a finding still do. Refs: CLOUD-97
0be1024 to
55ca205
Compare
|
❌ The last analysis has failed. |
|
/fast-forward |
Punt audit — session closeThis PR is merged ( Filed on the rows that own them
Measured, gate-visible, no note needed
Two bot signals on this PR, unactioned at merge
Corrections to my own conduct in this session, for the record
Dies with the container, deliberately not preserved
Generated by Claude Code |
|
Index correction — the audit table above was itself incomplete. Two more findings are now on CLOUD-1051, which is where the exit question lives:
Neither became a new row: Generated by Claude Code |
|
Second index correction — the audit above recorded the shell findings without citing the campaign that owns them. All bash here is scheduled for retirement under CLOUD-843, so
And the mutation findings have a campaign frame too: CLOUD-1355 (a declared mutation naming a case that does not exist is only reachable from the nightly sweep) is CLOUD-843's child and the precedent for both — linked from CLOUD-1341 and CLOUD-1344, with the distinction stated so they are not merged: 1355 is the module's declaration, 1341 is the issue's obligation, and nothing reports the second at all. Generated by Claude Code |
Closes CLOUD-1342.
Closes CLOUD-1343.
Closes CLOUD-1344.
DO-NOT-CLOSE CLOUD-1341
DO-NOT-CLOSE CLOUD-1347
DO-NOT-CLOSE CLOUD-97
Three rows, one branch. The cache row is independent; the other two came out of the same session's own failures.
CLOUD-1342 — the
perfcache writes nothing, and that is the fixThe previous design on this branch kept the merge-base SHA in the cache key so that a moved base would miss and therefore save. That is sound about
actions/cachein general and wrong here, because it still takes the arm the row was trying to escape: a key engineered to miss is a key engineered to write, and every byte written from a pull request is scoped torefs/pull/N/mergewhere no other pull request can read it. It optimised for a hit and never priced the miss.What lands instead:
actions/cache/restoreand writes nothing, so the key drops the merge base — with no save there is no miss to engineer, and a key that never moves is exactly what is wanted;perfjob onmain, which already pays a full--releasebuild. An entry written from the default branch is readable by every branch, which is the property no PR-scoped entry can have. No new trigger, no second build, and AGENTS.md's prohibition on a push-to-maintrigger is untouched.Correctness is unchanged and does not rest on the directory name: the seed is copied to
target/perf/base-$SHAwith its binary removed, sobase_arm_is_built()stays false and no stale arm is ever measured.The claim is different from the one this PR used to make. It is no longer ~18%: the hit arrives on a pull request's first run rather than its second, and this job now writes zero PR-scoped bytes. CLOUD-840 measured this repository at 225.2 GB of Actions cache, 203.7 GB of it across 1,144 entries nothing can restore.
cache-path-is-rebase-stablegains the two sub-action spellings.actions/cache/restoreand.../savederive an entry's version frompathexactly as the composite does, so matching onlyactions/cache@would have left the predicate live and reaching nothing the moment a job split restore from save — which is what this row does. Its own case is declared rather than assumed.CLOUD-1343 — the claim order, and the message that invited the second claim
The receipt is minted on
claim check's pullable path and keyed by the branch checked out at that moment. Two orderings fail in opposite directions and both are reachable by following the instructions correctly the first time: claim-then-branch strands the receipt on a branch nothing will land, and claiming a second time runs after the row has left Todo, reads it as held, and refusesnot-todo— where the holder is the caller, ninety seconds earlier. The only route past that is--takeoveragainst oneself, and CLOUD-1139 needs that signal to stay rare.No engine change: creating a branch writes only under
.git, whichclaim-needs-receiptnever judges, so the deadlock an earlier revision assumed does not exist.claim.rs'snot-tododecision is correct for a row genuinely held elsewhere and is untouched.AGENTS.md carries the order (a substitution — the file sits at 198 of 199 lines and 3404 of 3500 tokens),
.claude/rules/toolchain.mdcarries the reason with both failure directions, and the pullable message now says the receipt is already minted and not to re-run.policy/claim-order-is-stated.regokeeps the prose from evaporating; whether a given session actually claimed before branching is not a property of the tree, and a rule deciding that would be the model verdict rule 3 forbids.This fix was used to claim CLOUD-1344 later in the same session, and the rewritten message did its job.
CLOUD-1344 — the extraction spine
Four of five
Extractionmembers reached no consumer and repetition had no member at all. This is the plumbing every later predicate stands on, landed alone because the risk is the plumbing rather than the detection.Extraction::ofwidens from&Countsto&Stream, and answers with anOptionwhoseNoneis could-not-look.Countsis unchanged — it is the-Jcapability report's own shape, so runs live inStream::repeats()besideStream::counts().agent-turn-runis the first member and deliberately the simplest — a trailing run of assistant turns carrying no tool call, over the already-typedEvent::Turn. No hashing, so no argument or result is read even internally.The bound is stated rather than absorbed: this cannot separate a host that records no hook runs from a session that triggered none, and it resolves that toward could-not-look — the safe direction.
The header keeps the claim narrower than "loop detection". Termination is undecidable and every quantity here is a monotonically growing count, so there is no ranking function to be had; what the literature buys is the shape of the declaration.
Ships at
warn. CLOUD-894 owns the firing-rate ceiling and CLOUD-1352 the promotion.Also retires
policy/harness-declared.json's two now-spent exemption rows: CLOUD-1079 has landed, so the hooks they excused are no longer provisioned andharness-wiringreports both — the gate working. Reproduced with--config-from a907565, so the finding predates this diff.The three keys this PR serves and does not close
CLOUD-1341. Its measured defect — an across-turn poll, 1079 identical calls — needs
identical-call-runover call identity, which is CLOUD-1347.agent-turn-runcatches monologue, not repeated tool calls, so nothing here fixes it. Back in Backlog and unassigned.Its own §2 has been corrected on the tracker: it specified "a plain total over the stream", an implementation followed that literally, and a sum across identities is monotonic in session length — it never resets, so it fires on any session long enough to repeat anything. Shipped that way at
deny, it refused every subsequent tool call in its authoring session, the push included. Every member of this family is non-monotonic by design, which is the property that makes a later promotion survivable.CLOUD-1347. The spine this PR lands is exactly what unblocks it, and the commits say so — but it is
blockedByCLOUD-1344 and CLOUD-1345, and only the first is cleared here. CLOUD-1345's whole subject is that per-call fingerprinting turns a latent parse cost into a real one, which is precisely what CLOUD-1347 adds. Not this branch's to clear, and a comment on CLOUD-1345 records the walk-count regression this PR's own&Streamwidening introduces (1 walk to 2 × declared extractions).CLOUD-97. Cited by the stop-posture test fix because it owns
unlanded-check, whose finding is what the fixture was accidentally reading. The row itself is untouched.Verification
mise run verifygreen: HEAD carries verify + linear-check receipts.policy test50 bundles / 638 passed. The compiled tier over the shipped module is seven cases including the pair that carries the row — a host recording no turns answers could-not-look, and a recorded session with no run answers a real zero — which needs a probe module because both are silent under a>= 3predicate.Two defects were caught by that tier rather than by reading: the first cross-compatibility fixture was built from a
userrecord, which the parser reads as a turn, so it recorded turns after all; and both#MUTANTrows first namedtest_rules inside the.regowhile#MUTANT-SUITEresolves the compiled file — declared, never applied, counted by nobody, the shape PR #829 measured inpolicy/harness-wiring.rego.Two more were caught by
verifyitself and are fixed here rather than worked around: two workspace lints against the spine (struct_excessive_bools,match_same_arms), and a pre-existing isolation bug where the Stop posture cases read the live findings store rather than their fixture's — soa_clean_final_message_says_nothingpassed on a clean checkout and failed on any branch carrying unpushed work, which is every branch that suite is run from mid-development.Every protected-path write carries an issued
Admits:block in its commit message.🤖 Generated with Claude Code
https://claude.ai/code/session_017o7xUB8okt7TbcymPP667s