Repository navigation
ci: check in the main-branch protection ruleset - #1
Merged
Merged
Conversation
Documents the trunk-based protection applied to main: PR required, CI must be green and up to date, linear history, no force-push or deletion.
wenzowski
enabled auto-merge (squash)
August 5, 2026 22:57
This was referenced Aug 7, 2026
wenzowski
pushed a commit
that referenced
this pull request
Aug 7, 2026
Every release so far shipped zero assets. `mise run release` is release-plz only — tag plus a release entry, never a build — and cross-check runs `cargo check` (codegen to metadata, no linking), so a green `cross` proved compilation without ever producing a runnable binary. There was nothing to install, which blocks dogfooding Batten as consumer #1 and left house-style §12 prose with no mechanism. mise-tasks/dist builds --profile dist (the profile Cargo.toml already declared and nothing used) for one target and stages a named archive. The release workflow calls that same task per matrix leg, so the archive CI uploads is the one a maintainer reproduces locally. The archive name is a contract, not a convenience: CLOUD-65's binstall path resolves assets by name, so naming and per-platform format are pinned in tests/dist.bats rather than left to whatever the last release emitted. Provenance binds to the binary, not the archive, so repackaging cannot launder it. mise-action runs with cache: false — a poisoned cache entry would land inside a signed artifact and be faithfully attested. Refs CLOUD-173.
wenzowski
pushed a commit
that referenced
this pull request
Aug 7, 2026
Onboarding never ran: it is skipped when memories exist, and the curated memory set pre-dated Serena's first successful connection, so the existence check has always answered "already performed" for content that was authored by hand rather than by onboarding. Run now in a fresh session under an additive-only contract: the curated memories are authoritative and untouched; only genuinely missing onboarding content is written. The audit found one real gap. Workflow, GitHub etiquette, toolchain, and Rust style are all owned by AGENTS.md, the rule files, or an existing memory — onboarding's tech-stack/commands/conventions templates were skipped as drift-prone restatement. Nothing, however, mapped the crate's internals: rust.md's layout tree is the scaffold-era skeleton naming 5 of 12 source files. `core` is that map — one entry per src module stating what it owns and pointing at the file's own rationale doc comment rather than restating it (verified against the sources: the CLOUD refs match each file's own doc comment), plus where behavioral tests live and the consumer-#1 setup. `core` is also the discovery root of Serena's memory graph, per its memory-maintenance model. rust.md's skeleton now points at it instead of growing a second, drifting copy. memory_maintenance is Serena's own operating contract for how its tools write and reference memories (the mem: graph model); it is committed so that behavior is pinned and reviewable like everything else in .serena/memories rather than regenerating unversioned per machine. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ga5K9K38p6qt8shSTPKY9t
wenzowski
pushed a commit
that referenced
this pull request
Aug 7, 2026
…sitory
Refs: CLOUD-71
Three committed files asserted that the checked-in batten.toml is what
`batten check` runs against this repository. Nothing did: no hk step, no
mise task, no workflow invoked the binary, so the engine's one configured
rule had never executed anywhere. The assertion becomes a mechanism:
mise.toml batten-check — `cargo run --quiet -p batten -- check`, so
the gate judges the working tree's engine and config as the
pair that ships
hk.pkl a batten-check step in the shared gate, chained after `test`
so one cargo build holds the target-dir lock at a time; no
glob, because any file can carry a violation
tests/cli.rs the committed batten.toml pinned behaviourally: it loads,
and its rule fires on the shape it names, pointer-only and
byte-stable
The bash gates this repo runs are not migrated here: none of them is a
content predicate the current rule table can express (path existence,
reference resolution, exemption lists) — each such gap is capability-issue
material on the board, not a reason to widen this change.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VS5eTnMTruCWytpAk2gW7k
This was referenced Aug 7, 2026
Merged
wenzowski
pushed a commit
that referenced
this pull request
Aug 8, 2026
…CLOUD-32) Policy changes need to be attributable after the fact. The epoch is a deterministic hash of the files that GOVERN a run, so two records carrying the same epoch were produced under provably the same rules, and one carrying a different epoch was not. The tracked set is config, never code. Which files govern a repository is that repository's business — an agent settings file, a contributor guide, a hook config, each meaningful in one repository and meaningless in the next. So the set is `[epoch] tracked` in batten.toml, and the core carries only the default: batten.toml itself, the one file that governs every consumer by definition. Batten's own list lives in Batten's own config, as consumer #1, which is where a worked example belongs. That keeps non-negotiable rule 1 true as a GREP, not merely in spirit. The first draft named this repo's files in doc comments and in a test fixture — prose and fixtures, not behaviour, but the rule states its predicate as "a grep returns zero hits", and a rule stated that way is one a gate can be written on. Both are now generic; `git grep` over crates/batten for any of those names returns nothing. An unreadable tracked path is exit 1, NAMING THE PATH, never a skip. The tracked set IS config — it defaults to batten.toml itself — so an unreadable tracked path is unreadable config, which §7 routes to 1; 3 stays for an I/O failure not attributable to the config. Same shape as config_trust's unreadable-ref case, which also names what it could not read: a refusal the reader cannot act on is barely better than a silent skip, and a skip would compute a STABLE epoch over a changed surface, which looks exactly like a valid answer. The epoch follows --config-from. Under a base ref both the tracked list and the bytes come from the ref, never the working tree: an epoch attributing a run to a config that did not govern it would be worse than none. The construction is identity::surface_fingerprint, not a second hash of the same bytes. tagged_fingerprint and write_field are lifted out of fingerprint_of, so findings and the epoch share ONE length-prefixed SHA-256 framing rather than two that can drift. Without the length prefix ("ab","c") and ("a","bc") hash identically, so a rename could be hidden by choosing names — asserted directly. Text is NFC/LF-canonicalized so a CRLF checkout attributes identically; non-UTF-8 content is hashed verbatim, since there is nothing to normalize and refusing it would make the tracked set silently text-only. Paths are canonicalized, sorted and deduplicated, and the path is hashed as well as the bytes, so adding an empty file to the tracked set still moves the epoch — the SET is part of what governs. batten.toml raises min_batten_version off 0.0.0 now that it declares `[epoch]`: a new key must be REFUSED by an older binary, never ignored. deny_unknown_fields is what forces that; the floor states the intent so the refusal names the version rather than the key. House style §2 gains `epoch` on its `config` row in the same move — §11 makes the binary the emitter of the spec, not a warrant to outrun the doc, and CLOUD-19 settled §2 as authoritative for the surface. Surfaced two ways that must agree: `batten config epoch` and doctor --json's config_epoch, with a test asserting the join CLOUD-133 will key guard records on. doctor OMITS the field when the surface cannot be read rather than emitting a placeholder — a placeholder would be a stable value over an unknown surface — and still exits 1 rather than 3, so it stays the config-or-usage-only verb §7 needs it to be. Scope: the value only. Stamping onto guard records is CLOUD-133's, which defines the record; §8's cache and etag-style revalidation are CLOUD-232's. What lands here is recompute-per-invocation, the reference implementation that cannot go stale. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0119C7XbeQLWR5TTaW2ca6Gp
wenzowski
pushed a commit
that referenced
this pull request
Aug 8, 2026
…CLOUD-32) Policy changes need to be attributable after the fact. The epoch is a deterministic hash of the files that GOVERN a run, so two records carrying the same epoch were produced under provably the same rules, and one carrying a different epoch was not. The tracked set is config, never code. Which files govern a repository is that repository's business — an agent settings file, a contributor guide, a hook config, each meaningful in one repository and meaningless in the next. So the set is `[epoch] tracked` in batten.toml, and the core carries only the default: batten.toml itself, the one file that governs every consumer by definition. Batten's own list lives in Batten's own config, as consumer #1, which is where a worked example belongs. That keeps non-negotiable rule 1 true as a GREP, not merely in spirit. The first draft named this repo's files in doc comments and in a test fixture — prose and fixtures, not behaviour, but the rule states its predicate as "a grep returns zero hits", and a rule stated that way is one a gate can be written on. Both are now generic; `git grep` over crates/batten for any of those names returns nothing. An unreadable tracked path is exit 1, NAMING THE PATH, never a skip. The tracked set IS config — it defaults to batten.toml itself — so an unreadable tracked path is unreadable config, which §7 routes to 1; 3 stays for an I/O failure not attributable to the config. Same shape as config_trust's unreadable-ref case, which also names what it could not read: a refusal the reader cannot act on is barely better than a silent skip, and a skip would compute a STABLE epoch over a changed surface, which looks exactly like a valid answer. The epoch follows --config-from. Under a base ref both the tracked list and the bytes come from the ref, never the working tree: an epoch attributing a run to a config that did not govern it would be worse than none. The construction is identity::surface_fingerprint, not a second hash of the same bytes. tagged_fingerprint and write_field are lifted out of fingerprint_of, so findings and the epoch share ONE length-prefixed SHA-256 framing rather than two that can drift. Without the length prefix ("ab","c") and ("a","bc") hash identically, so a rename could be hidden by choosing names — asserted directly. Text is NFC/LF-canonicalized so a CRLF checkout attributes identically; non-UTF-8 content is hashed verbatim, since there is nothing to normalize and refusing it would make the tracked set silently text-only. Paths are canonicalized, sorted and deduplicated, and the path is hashed as well as the bytes, so adding an empty file to the tracked set still moves the epoch — the SET is part of what governs. batten.toml raises min_batten_version off 0.0.0 now that it declares `[epoch]`: a new key must be REFUSED by an older binary, never ignored. deny_unknown_fields is what forces that; the floor states the intent so the refusal names the version rather than the key. House style §2 gains `epoch` on its `config` row in the same move — §11 makes the binary the emitter of the spec, not a warrant to outrun the doc, and CLOUD-19 settled §2 as authoritative for the surface. Surfaced two ways that must agree: `batten config epoch` and doctor --json's config_epoch, with a test asserting the join CLOUD-133 will key guard records on. doctor OMITS the field when the surface cannot be read rather than emitting a placeholder — a placeholder would be a stable value over an unknown surface — and still exits 1 rather than 3, so it stays the config-or-usage-only verb §7 needs it to be. Scope: the value only. Stamping onto guard records is CLOUD-133's, which defines the record; §8's cache and etag-style revalidation are CLOUD-232's. What lands here is recompute-per-invocation, the reference implementation that cannot go stale. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0119C7XbeQLWR5TTaW2ca6Gp
This was referenced Aug 8, 2026
Merged
wenzowski
pushed a commit
that referenced
this pull request
Aug 10, 2026
CLOUD-86. Two `[[rule]]` rows in this repository's own batten.toml, as consumer #1 — no engine change. A tool built here is a tool nobody attested: the build runs on whatever toolchain the runner had and produces a binary no checksum covers, while every tool this repo uses is pinned in mise.toml as a prebuilt artifact with a locked url and a per-platform checksum. CLOUD-281 is the instance where the one tool not pinned that way was the one installed from an unverified download. Two rows because a `forbid` pattern is a literal substring and the two files spell the same mistake differently: `"cargo:` in mise.toml (the opening quote is load-bearing — TOML bare keys cannot contain `:`, so a backend key is always quoted, which keeps the row off prose that merely says "cargo"), and `cargo install` in a workflow. The exemption half is what this waited on CLOUD-208 for: an exemption is a `[[waiver]]` carrying a reason and an expiry, never `severity = "allow"`, which records no reason and lapses never. The suite covers the whole shape — a waived entry passes, a lapsed one is red again with nobody having acted, an exemption with no reason is exit 1 rather than applied, and a second violation still blocks. release-artifacts.yml's CLOUD-259 comment narrated install-action's fallback chain ending in the literal command, so it was the one hit on a clean tree. Reworded, because a gate that fires on its own explanation is a gate people delete. Narrowing the pattern to `run: cargo install` was the alternative and would have missed the block form — coverage that looks total and isn't. Not gated here: whether a PREBUILT artifact is attested. That is CLOUD-90's manifest and CLOUD-281's `github:`-vs-`ubi:` distinction; a row firing on `ubi:` would be answering a different question.
wenzowski
pushed a commit
that referenced
this pull request
Aug 11, 2026
Caught by running the gate against its own PR body, which it failed. The match was a markdown table cell — `| judgement call | keep |` — documenting the shape, not deferring anything. Being consumer #1 is what surfaced it, and a gate that fires on its own documentation would teach exactly the wrong lesson. The fix is the house pattern rather than a new idea: `gh-guard` and `issue-guard` neutralise QUOTED spans before judging a command, for the same reason a commit message mentioning `gh pr merge` is not that command. Backticked spans get the same treatment here. Discrimination intact after the change, re-measured: the corpus still fires on exactly [243], the one genuinely untracked deferral. A regression test pins the table row. Two linter traps recorded in the source, both hit while writing this: * a literal backtick inside single quotes reads as a missed expansion (SC2016), so it is a named constant rather than a suppression * the comment explaining that then began with the linter's own name, which parses as a DIRECTIVE (SC1073) The bats header also described `.claude/rules/toolchain.md` as it was FOUND rather than as this branch corrects it — two artifacts disagreeing in miniature, which is the defect this whole change is about. Refs: CLOUD-323
wenzowski
pushed a commit
that referenced
this pull request
Aug 11, 2026
Caught by running the gate against its own PR body, which it failed. The match was a markdown table cell — `| judgement call | keep |` — documenting the shape, not deferring anything. Being consumer #1 is what surfaced it, and a gate that fires on its own documentation would teach exactly the wrong lesson. The fix is the house pattern rather than a new idea: `gh-guard` and `issue-guard` neutralise QUOTED spans before judging a command, for the same reason a commit message mentioning `gh pr merge` is not that command. Backticked spans get the same treatment here. Discrimination intact after the change, re-measured: the corpus still fires on exactly [243], the one genuinely untracked deferral. A regression test pins the table row. Two linter traps recorded in the source, both hit while writing this: * a literal backtick inside single quotes reads as a missed expansion (SC2016), so it is a named constant rather than a suppression * the comment explaining that then began with the linter's own name, which parses as a DIRECTIVE (SC1073) The bats header also described `.claude/rules/toolchain.md` as it was FOUND rather than as this branch corrects it — two artifacts disagreeing in miniature, which is the defect this whole change is about. Refs: CLOUD-323
This was referenced Aug 11, 2026
wenzowski
added a commit
that referenced
this pull request
Aug 21, 2026
…y name Batten shipped no default policy at all — the tree carried one `.rego` file and it was a test fixture. That is the anomaly rather than the discipline: Conftest ships OCI bundles, Semgrep `p/default`, ESLint `eslint:recommended`, Clippy its lint groups. And the non-negotiable that looks like it forbids this argues for it — a preset is prior art shipped as DATA, which is the opposite of expanding the core. This is not the OCI distribution CLOUD-129 rejected, and that verdict is intact. Its ground was that remote policy fetch is a supply-chain surface: there is no network here, no registry and no trust-on-first-use. `include_str!` at build time, so the bytes ship inside the binary the operator already trusts, under the same checksum as everything else in it. Its other ground — one committed authority per repo — does not reach a preset either, because a preset is not an authority; it is content the authority enables. `preset` is the third of three mutually-exclusive sources on a policy row, and the valid names are derived from the embedded set rather than hand-maintained. `surface::SURFACE`'s discipline: the loader, the published schema's enum and the tests all read one list, so a name that parses in an editor cannot be one the loader then refuses. An unknown name is a config error naming what IS available, because a consumer who typed `trunk-basd` should be told rather than quietly gated by nothing. Two presets, both true of a PRACTICE and naming no path, task, tracker key or entity. `trunk-based/no-force-push` deliberately does not match `--force-with-lease`: that is the sanctioned form — it refuses when the remote moved — and a preset banning both would push its consumers toward the bypass rather than toward the safer flag. `commit-hygiene/no-empty-commit` refuses the commit that records only that somebody wanted a new SHA. The id-collision refusal reaches across the vendored/in-repo boundary for free, because a preset is just another bundle with a declared id set. Bundles are isolated as ENGINES and still visible to each other for id claims, and that difference is the design: a preset cannot supply a helper to a consumer's module and cannot silently shadow one of its predicate ids either. ## Rule 1 over presets: the mechanism already existed §7(c) asked for a gate refusing a preset source that names a consumer-specific identifier. This ships the assertion rather than a second scanner, and the reasoning is the finding. `batten.toml`'s rule-1 `forbid` rows glob `crates/**`, and the embedded preset sources are under it — they are already scanned on every gate invocation. A second scanner over the same paths with the same patterns is the two-authorities-that-drift shape `mise-tasks/rules-drift` exists to warn about, and the patterns cannot be restated in this crate anyway, because naming them here is the violation. So `presets_are_inside_the_rule_one_glob` asserts the COVERAGE — the sources are inside `crates/`, and the authority still carries a `crates/**` row — which turns "rule 1 reaches presets" from an assumption into a fact and fails if either half moves. ## Consumer #1 eats it, and declines the other one on the merits This repo enables `trunk-based`: AGENTS.md's landing contract IS trunk-based development, `main` is the one long-lived branch, and a force push there is exactly what the practice refuses. `commit-hygiene` is deliberately not enabled, which is a decision rather than an oversight. Its predicate overlaps AGENTS.md's own ban on empty commits to kick CI, which the landing loop already enforces where it can act on it; a second gate over one practice doubles a refusal without doubling coverage, and which row fired would become a question of which one a reader hit first. Refs: CLOUD-836
wenzowski
added a commit
that referenced
this pull request
Aug 21, 2026
…y name Batten shipped no default policy at all — the tree carried one `.rego` file and it was a test fixture. That is the anomaly rather than the discipline: Conftest ships OCI bundles, Semgrep `p/default`, ESLint `eslint:recommended`, Clippy its lint groups. And the non-negotiable that looks like it forbids this argues for it — a preset is prior art shipped as DATA, which is the opposite of expanding the core. This is not the OCI distribution CLOUD-129 rejected, and that verdict is intact. Its ground was that remote policy fetch is a supply-chain surface: there is no network here, no registry and no trust-on-first-use. `include_str!` at build time, so the bytes ship inside the binary the operator already trusts, under the same checksum as everything else in it. Its other ground — one committed authority per repo — does not reach a preset either, because a preset is not an authority; it is content the authority enables. `preset` is the third of three mutually-exclusive sources on a policy row, and the valid names are derived from the embedded set rather than hand-maintained. `surface::SURFACE`'s discipline: the loader, the published schema's enum and the tests all read one list, so a name that parses in an editor cannot be one the loader then refuses. An unknown name is a config error naming what IS available, because a consumer who typed `trunk-basd` should be told rather than quietly gated by nothing. Two presets, both true of a PRACTICE and naming no path, task, tracker key or entity. `trunk-based/no-force-push` deliberately does not match `--force-with-lease`: that is the sanctioned form — it refuses when the remote moved — and a preset banning both would push its consumers toward the bypass rather than toward the safer flag. `commit-hygiene/no-empty-commit` refuses the commit that records only that somebody wanted a new SHA. The id-collision refusal reaches across the vendored/in-repo boundary for free, because a preset is just another bundle with a declared id set. Bundles are isolated as ENGINES and still visible to each other for id claims, and that difference is the design: a preset cannot supply a helper to a consumer's module and cannot silently shadow one of its predicate ids either. ## Rule 1 over presets: the mechanism already existed §7(c) asked for a gate refusing a preset source that names a consumer-specific identifier. This ships the assertion rather than a second scanner, and the reasoning is the finding. `batten.toml`'s rule-1 `forbid` rows glob `crates/**`, and the embedded preset sources are under it — they are already scanned on every gate invocation. A second scanner over the same paths with the same patterns is the two-authorities-that-drift shape `mise-tasks/rules-drift` exists to warn about, and the patterns cannot be restated in this crate anyway, because naming them here is the violation. So `presets_are_inside_the_rule_one_glob` asserts the COVERAGE — the sources are inside `crates/`, and the authority still carries a `crates/**` row — which turns "rule 1 reaches presets" from an assumption into a fact and fails if either half moves. ## Consumer #1 eats it, and declines the other one on the merits This repo enables `trunk-based`: AGENTS.md's landing contract IS trunk-based development, `main` is the one long-lived branch, and a force push there is exactly what the practice refuses. `commit-hygiene` is deliberately not enabled, which is a decision rather than an oversight. Its predicate overlaps AGENTS.md's own ban on empty commits to kick CI, which the landing loop already enforces where it can act on it; a second gate over one practice doubles a refusal without doubling coverage, and which row fired would become a question of which one a reader hit first. Refs: CLOUD-836
wenzowski
added a commit
that referenced
this pull request
Aug 21, 2026
…y name Batten shipped no default policy at all — the tree carried one `.rego` file and it was a test fixture. That is the anomaly rather than the discipline: Conftest ships OCI bundles, Semgrep `p/default`, ESLint `eslint:recommended`, Clippy its lint groups. And the non-negotiable that looks like it forbids this argues for it — a preset is prior art shipped as DATA, which is the opposite of expanding the core. This is not the OCI distribution CLOUD-129 rejected, and that verdict is intact. Its ground was that remote policy fetch is a supply-chain surface: there is no network here, no registry and no trust-on-first-use. `include_str!` at build time, so the bytes ship inside the binary the operator already trusts, under the same checksum as everything else in it. Its other ground — one committed authority per repo — does not reach a preset either, because a preset is not an authority; it is content the authority enables. `preset` is the third of three mutually-exclusive sources on a policy row, and the valid names are derived from the embedded set rather than hand-maintained. `surface::SURFACE`'s discipline: the loader, the published schema's enum and the tests all read one list, so a name that parses in an editor cannot be one the loader then refuses. An unknown name is a config error naming what IS available, because a consumer who typed `trunk-basd` should be told rather than quietly gated by nothing. Two presets, both true of a PRACTICE and naming no path, task, tracker key or entity. `trunk-based/no-force-push` deliberately does not match `--force-with-lease`: that is the sanctioned form — it refuses when the remote moved — and a preset banning both would push its consumers toward the bypass rather than toward the safer flag. `commit-hygiene/no-empty-commit` refuses the commit that records only that somebody wanted a new SHA. The id-collision refusal reaches across the vendored/in-repo boundary for free, because a preset is just another bundle with a declared id set. Bundles are isolated as ENGINES and still visible to each other for id claims, and that difference is the design: a preset cannot supply a helper to a consumer's module and cannot silently shadow one of its predicate ids either. ## Rule 1 over presets: the mechanism already existed §7(c) asked for a gate refusing a preset source that names a consumer-specific identifier. This ships the assertion rather than a second scanner, and the reasoning is the finding. `batten.toml`'s rule-1 `forbid` rows glob `crates/**`, and the embedded preset sources are under it — they are already scanned on every gate invocation. A second scanner over the same paths with the same patterns is the two-authorities-that-drift shape `mise-tasks/rules-drift` exists to warn about, and the patterns cannot be restated in this crate anyway, because naming them here is the violation. So `presets_are_inside_the_rule_one_glob` asserts the COVERAGE — the sources are inside `crates/`, and the authority still carries a `crates/**` row — which turns "rule 1 reaches presets" from an assumption into a fact and fails if either half moves. ## Consumer #1 eats it, and declines the other one on the merits This repo enables `trunk-based`: AGENTS.md's landing contract IS trunk-based development, `main` is the one long-lived branch, and a force push there is exactly what the practice refuses. `commit-hygiene` is deliberately not enabled, which is a decision rather than an oversight. Its predicate overlaps AGENTS.md's own ban on empty commits to kick CI, which the landing loop already enforces where it can act on it; a second gate over one practice doubles a refusal without doubling coverage, and which row fired would become a question of which one a reader hit first. Refs: CLOUD-836
wenzowski
added a commit
that referenced
this pull request
Aug 22, 2026
…orrect one
THE LEVER IS COST, NOT PROHIBITION, and the earlier attempt on this branch got
that wrong twice. `NOT_REGULAR` was a denylist of subjects; it covered one
program, said nothing about the failure that actually recurs, and blessed
everything it did not name. Reverted already. This is the mechanism that
replaces it.
A `regex.*` builtin may not reach a string literal. Expressions are `[[pattern]]`
rows in the one committed authority, projected into the evaluator's `data`
document, referenced by id:
regex.match(`CLOUD-[0-9]+`, w) refused at load
regex.match(data.batten.patterns["tracker-key"], w) loads and decides
The gradient is the point. A one-off pattern costs a config row and an id; a
shared one is free after the first; and asking the same question of an
already-parsed document costs a field access with no config edit at all. Effort
now orders the same way correctness does, which is the only version of this that
survives a translator that does not reason carefully — and 86 bash gates written
in `grep -E` are queued to be translated.
WHY THIS IS NOT FRICTION BOLTED ON. `Config::verbs` already carries the argument
verbatim for the mutating-verb table: "consumer-specific by nature, so it lives
here and never in the crate (non-negotiable rule 1)". A tracker-key expression is
a consumer identifier by exactly that test. Two more things fall out rather than
being designed in: duplication becomes unwritable instead of detectable — one
declaration, one home — and the pattern inventory becomes reviewable data (§11),
readable out of one file.
Measured on the tree this engine gates, comments stripped: 82 of 140 shell
programs use `grep -E`/`sed -E`/`awk`/`=~` over 338 sites, and ONE concept — a
tracker key — carries 19 distinct spellings across 17 programs. `CLOUD-[0-9]+`
alone sits at 15 sites in 9 of them, with a `CLOUD-*` glob spelling beside the
regexes and one program holding two variants of its own pattern 52 lines apart.
Correct regexes over a regular thing, duplicated until they drifted.
CLOSED AT BOTH ENDS, and the second half is not optional. Refusing the inline
form alone leaves the identical hole one step later: `data.batten.patterns["typo"]`
resolves to UNDEFINED, Rego reads undefined as "this rule body does not hold", so
the module loads clean, evaluates clean and gates nothing. So a reference no row
declares is refused too — the same shape `check_tree_paths_are_emittable` uses
against a `tree` key the engine never emits.
The same failure arriving by deletion is `WeakeningKind::PatternRemoved`: a local
override dropping a row silences every module referencing it, and one row can
silence several predicates at once, because a declared pattern is shared by
design. Reported with the id, never the expression.
TWO BUGS ONLY RUNNING IT COULD HAVE FOUND, both from an AST probe rather than a
reading:
- a Rego backtick literal serialises as `RawString`, not `String`. Reading only
`String` let every realistic inline pattern through, since backticks are how
a regex carrying backslashes is written.
- a REFERENCE contains a literal: `data.batten.patterns["x"]` is a `RefBrack`
whose index is the string `"x"`, so a naive sweep read the sanctioned form as
the refused one and no module could load at all.
CONSUMER #1 PROVES IT. `policy/run-shape.rego`'s flag-cluster shape is declared
in `batten.toml` and referenced by name; its `#MUTANT` directive now corrupts the
REFERENCE, so the mutation exercises the silent-disarm path directly — the
allow cases go red when the id stops resolving.
Four completeness gates in this repo refused the new table until it was
classified, and the third found a defect this change would otherwise have
shipped: validation call site (`config.rs`), resolve provenance, trust verdict —
which is where the silent-disarm consequence surfaced — and every-kind-exercised.
Refs: CLOUD-885
wenzowski
added a commit
that referenced
this pull request
Aug 22, 2026
…correct one
THE LEVER IS COST, NOT PROHIBITION, and the earlier attempt on this branch got
that wrong twice. `NOT_REGULAR` was a denylist of subjects; it covered one
program, said nothing about the failure that actually recurs, and blessed
everything it did not name. Reverted already. This is the mechanism that
replaces it.
A `regex.*` builtin may not reach a string literal. Expressions are `[[pattern]]`
rows in the one committed authority, projected into the evaluator's `data`
document, referenced by id:
regex.match(`CLOUD-[0-9]+`, w) refused at load
regex.match(data.batten.patterns["tracker-key"], w) loads and decides
The gradient is the point. A one-off pattern costs a config row and an id; a
shared one is free after the first; and asking the same question of an
already-parsed document costs a field access with no config edit at all. Effort
now orders the same way correctness does, which is the only version of this that
survives a translator that does not reason carefully — and 86 bash gates written
in `grep -E` are queued to be translated.
WHY THIS IS NOT FRICTION BOLTED ON. `Config::verbs` already carries the argument
verbatim for the mutating-verb table: "consumer-specific by nature, so it lives
here and never in the crate (non-negotiable rule 1)". A tracker-key expression is
a consumer identifier by exactly that test. Two more things fall out rather than
being designed in: duplication becomes unwritable instead of detectable — one
declaration, one home — and the pattern inventory becomes reviewable data (§11),
readable out of one file.
Measured on the tree this engine gates, comments stripped: 82 of 140 shell
programs use `grep -E`/`sed -E`/`awk`/`=~` over 338 sites, and ONE concept — a
tracker key — carries 19 distinct spellings across 17 programs. `CLOUD-[0-9]+`
alone sits at 15 sites in 9 of them, with a `CLOUD-*` glob spelling beside the
regexes and one program holding two variants of its own pattern 52 lines apart.
Correct regexes over a regular thing, duplicated until they drifted.
CLOSED AT BOTH ENDS, and the second half is not optional. Refusing the inline
form alone leaves the identical hole one step later: `data.batten.patterns["typo"]`
resolves to UNDEFINED, Rego reads undefined as "this rule body does not hold", so
the module loads clean, evaluates clean and gates nothing. So a reference no row
declares is refused too — the same shape `check_tree_paths_are_emittable` uses
against a `tree` key the engine never emits.
The same failure arriving by deletion is `WeakeningKind::PatternRemoved`: a local
override dropping a row silences every module referencing it, and one row can
silence several predicates at once, because a declared pattern is shared by
design. Reported with the id, never the expression.
TWO BUGS ONLY RUNNING IT COULD HAVE FOUND, both from an AST probe rather than a
reading:
- a Rego backtick literal serialises as `RawString`, not `String`. Reading only
`String` let every realistic inline pattern through, since backticks are how
a regex carrying backslashes is written.
- a REFERENCE contains a literal: `data.batten.patterns["x"]` is a `RefBrack`
whose index is the string `"x"`, so a naive sweep read the sanctioned form as
the refused one and no module could load at all.
CONSUMER #1 PROVES IT. `policy/run-shape.rego`'s flag-cluster shape is declared
in `batten.toml` and referenced by name; its `#MUTANT` directive now corrupts the
REFERENCE, so the mutation exercises the silent-disarm path directly — the
allow cases go red when the id stops resolving.
Four completeness gates in this repo refused the new table until it was
classified, and the third found a defect this change would otherwise have
shipped: validation call site (`config.rs`), resolve provenance, trust verdict —
which is where the silent-disarm consequence surfaced — and every-kind-exercised.
Refs: CLOUD-885
BREAKING CHANGE: the pattern table has to reach the evaluator, so it is threaded
beside `provisions` the way that table already is. `Config` gains `patterns`,
`trust::WeakeningKind` gains `PatternRemoved`, and `rules::run_static`,
`rules::run_recorded`, `rules::run_all`, `policy::load` and `policy::compile`
each take one more argument. Caught by `mise run verify`'s semver gate, which
named all three lint classes rather than letting a patch-compatible claim ship
over an API that moved.
wenzowski
added a commit
that referenced
this pull request
Aug 22, 2026
…correct one
THE LEVER IS COST, NOT PROHIBITION, and the earlier attempt on this branch got
that wrong twice. `NOT_REGULAR` was a denylist of subjects; it covered one
program, said nothing about the failure that actually recurs, and blessed
everything it did not name. Reverted already. This is the mechanism that
replaces it.
A `regex.*` builtin may not reach a string literal. Expressions are `[[pattern]]`
rows in the one committed authority, projected into the evaluator's `data`
document, referenced by id:
regex.match(`CLOUD-[0-9]+`, w) refused at load
regex.match(data.batten.patterns["tracker-key"], w) loads and decides
The gradient is the point. A one-off pattern costs a config row and an id; a
shared one is free after the first; and asking the same question of an
already-parsed document costs a field access with no config edit at all. Effort
now orders the same way correctness does, which is the only version of this that
survives a translator that does not reason carefully — and 86 bash gates written
in `grep -E` are queued to be translated.
WHY THIS IS NOT FRICTION BOLTED ON. `Config::verbs` already carries the argument
verbatim for the mutating-verb table: "consumer-specific by nature, so it lives
here and never in the crate (non-negotiable rule 1)". A tracker-key expression is
a consumer identifier by exactly that test. Two more things fall out rather than
being designed in: duplication becomes unwritable instead of detectable — one
declaration, one home — and the pattern inventory becomes reviewable data (§11),
readable out of one file.
Measured on the tree this engine gates, comments stripped: 82 of 140 shell
programs use `grep -E`/`sed -E`/`awk`/`=~` over 338 sites, and ONE concept — a
tracker key — carries 19 distinct spellings across 17 programs. `CLOUD-[0-9]+`
alone sits at 15 sites in 9 of them, with a `CLOUD-*` glob spelling beside the
regexes and one program holding two variants of its own pattern 52 lines apart.
Correct regexes over a regular thing, duplicated until they drifted.
CLOSED AT BOTH ENDS, and the second half is not optional. Refusing the inline
form alone leaves the identical hole one step later: `data.batten.patterns["typo"]`
resolves to UNDEFINED, Rego reads undefined as "this rule body does not hold", so
the module loads clean, evaluates clean and gates nothing. So a reference no row
declares is refused too — the same shape `check_tree_paths_are_emittable` uses
against a `tree` key the engine never emits.
The same failure arriving by deletion is `WeakeningKind::PatternRemoved`: a local
override dropping a row silences every module referencing it, and one row can
silence several predicates at once, because a declared pattern is shared by
design. Reported with the id, never the expression.
TWO BUGS ONLY RUNNING IT COULD HAVE FOUND, both from an AST probe rather than a
reading:
- a Rego backtick literal serialises as `RawString`, not `String`. Reading only
`String` let every realistic inline pattern through, since backticks are how
a regex carrying backslashes is written.
- a REFERENCE contains a literal: `data.batten.patterns["x"]` is a `RefBrack`
whose index is the string `"x"`, so a naive sweep read the sanctioned form as
the refused one and no module could load at all.
CONSUMER #1 PROVES IT. `policy/run-shape.rego`'s flag-cluster shape is declared
in `batten.toml` and referenced by name; its `#MUTANT` directive now corrupts the
REFERENCE, so the mutation exercises the silent-disarm path directly — the
allow cases go red when the id stops resolving.
Four completeness gates in this repo refused the new table until it was
classified, and the third found a defect this change would otherwise have
shipped: validation call site (`config.rs`), resolve provenance, trust verdict —
which is where the silent-disarm consequence surfaced — and every-kind-exercised.
Refs: CLOUD-885
BREAKING CHANGE: the pattern table has to reach the evaluator, so it is threaded
beside `provisions` the way that table already is. `Config` gains `patterns`,
`trust::WeakeningKind` gains `PatternRemoved`, and `rules::run_static`,
`rules::run_recorded`, `rules::run_all`, `policy::load` and `policy::compile`
each take one more argument. Caught by `mise run verify`'s semver gate, which
named all three lint classes rather than letting a patch-compatible claim ship
over an API that moved.
wenzowski
added a commit
that referenced
this pull request
Aug 22, 2026
…correct one
THE LEVER IS COST, NOT PROHIBITION, and the earlier attempt on this branch got
that wrong twice. `NOT_REGULAR` was a denylist of subjects; it covered one
program, said nothing about the failure that actually recurs, and blessed
everything it did not name. Reverted already. This is the mechanism that
replaces it.
A `regex.*` builtin may not reach a string literal. Expressions are `[[pattern]]`
rows in the one committed authority, projected into the evaluator's `data`
document, referenced by id:
regex.match(`CLOUD-[0-9]+`, w) refused at load
regex.match(data.batten.patterns["tracker-key"], w) loads and decides
The gradient is the point. A one-off pattern costs a config row and an id; a
shared one is free after the first; and asking the same question of an
already-parsed document costs a field access with no config edit at all. Effort
now orders the same way correctness does, which is the only version of this that
survives a translator that does not reason carefully — and 86 bash gates written
in `grep -E` are queued to be translated.
WHY THIS IS NOT FRICTION BOLTED ON. `Config::verbs` already carries the argument
verbatim for the mutating-verb table: "consumer-specific by nature, so it lives
here and never in the crate (non-negotiable rule 1)". A tracker-key expression is
a consumer identifier by exactly that test. Two more things fall out rather than
being designed in: duplication becomes unwritable instead of detectable — one
declaration, one home — and the pattern inventory becomes reviewable data (§11),
readable out of one file.
Measured on the tree this engine gates, comments stripped: 82 of 140 shell
programs use `grep -E`/`sed -E`/`awk`/`=~` over 338 sites, and ONE concept — a
tracker key — carries 19 distinct spellings across 17 programs. `CLOUD-[0-9]+`
alone sits at 15 sites in 9 of them, with a `CLOUD-*` glob spelling beside the
regexes and one program holding two variants of its own pattern 52 lines apart.
Correct regexes over a regular thing, duplicated until they drifted.
CLOSED AT BOTH ENDS, and the second half is not optional. Refusing the inline
form alone leaves the identical hole one step later: `data.batten.patterns["typo"]`
resolves to UNDEFINED, Rego reads undefined as "this rule body does not hold", so
the module loads clean, evaluates clean and gates nothing. So a reference no row
declares is refused too — the same shape `check_tree_paths_are_emittable` uses
against a `tree` key the engine never emits.
The same failure arriving by deletion is `WeakeningKind::PatternRemoved`: a local
override dropping a row silences every module referencing it, and one row can
silence several predicates at once, because a declared pattern is shared by
design. Reported with the id, never the expression.
TWO BUGS ONLY RUNNING IT COULD HAVE FOUND, both from an AST probe rather than a
reading:
- a Rego backtick literal serialises as `RawString`, not `String`. Reading only
`String` let every realistic inline pattern through, since backticks are how
a regex carrying backslashes is written.
- a REFERENCE contains a literal: `data.batten.patterns["x"]` is a `RefBrack`
whose index is the string `"x"`, so a naive sweep read the sanctioned form as
the refused one and no module could load at all.
CONSUMER #1 PROVES IT. `policy/run-shape.rego`'s flag-cluster shape is declared
in `batten.toml` and referenced by name; its `#MUTANT` directive now corrupts the
REFERENCE, so the mutation exercises the silent-disarm path directly — the
allow cases go red when the id stops resolving.
Four completeness gates in this repo refused the new table until it was
classified, and the third found a defect this change would otherwise have
shipped: validation call site (`config.rs`), resolve provenance, trust verdict —
which is where the silent-disarm consequence surfaced — and every-kind-exercised.
Refs: CLOUD-885
BREAKING CHANGE: the pattern table has to reach the evaluator, so it is threaded
beside `provisions` the way that table already is. `Config` gains `patterns`,
`trust::WeakeningKind` gains `PatternRemoved`, and `rules::run_static`,
`rules::run_recorded`, `rules::run_all`, `policy::load` and `policy::compile`
each take one more argument. Caught by `mise run verify`'s semver gate, which
named all three lint classes rather than letting a patch-compatible claim ship
over an API that moved.
wenzowski
added a commit
that referenced
this pull request
Aug 22, 2026
…correct one
THE LEVER IS COST, NOT PROHIBITION, and the earlier attempt on this branch got
that wrong twice. `NOT_REGULAR` was a denylist of subjects; it covered one
program, said nothing about the failure that actually recurs, and blessed
everything it did not name. Reverted already. This is the mechanism that
replaces it.
A `regex.*` builtin may not reach a string literal. Expressions are `[[pattern]]`
rows in the one committed authority, projected into the evaluator's `data`
document, referenced by id:
regex.match(`CLOUD-[0-9]+`, w) refused at load
regex.match(data.batten.patterns["tracker-key"], w) loads and decides
The gradient is the point. A one-off pattern costs a config row and an id; a
shared one is free after the first; and asking the same question of an
already-parsed document costs a field access with no config edit at all. Effort
now orders the same way correctness does, which is the only version of this that
survives a translator that does not reason carefully — and 86 bash gates written
in `grep -E` are queued to be translated.
WHY THIS IS NOT FRICTION BOLTED ON. `Config::verbs` already carries the argument
verbatim for the mutating-verb table: "consumer-specific by nature, so it lives
here and never in the crate (non-negotiable rule 1)". A tracker-key expression is
a consumer identifier by exactly that test. Two more things fall out rather than
being designed in: duplication becomes unwritable instead of detectable — one
declaration, one home — and the pattern inventory becomes reviewable data (§11),
readable out of one file.
Measured on the tree this engine gates, comments stripped: 82 of 140 shell
programs use `grep -E`/`sed -E`/`awk`/`=~` over 338 sites, and ONE concept — a
tracker key — carries 19 distinct spellings across 17 programs. `CLOUD-[0-9]+`
alone sits at 15 sites in 9 of them, with a `CLOUD-*` glob spelling beside the
regexes and one program holding two variants of its own pattern 52 lines apart.
Correct regexes over a regular thing, duplicated until they drifted.
CLOSED AT BOTH ENDS, and the second half is not optional. Refusing the inline
form alone leaves the identical hole one step later: `data.batten.patterns["typo"]`
resolves to UNDEFINED, Rego reads undefined as "this rule body does not hold", so
the module loads clean, evaluates clean and gates nothing. So a reference no row
declares is refused too — the same shape `check_tree_paths_are_emittable` uses
against a `tree` key the engine never emits.
The same failure arriving by deletion is `WeakeningKind::PatternRemoved`: a local
override dropping a row silences every module referencing it, and one row can
silence several predicates at once, because a declared pattern is shared by
design. Reported with the id, never the expression.
TWO BUGS ONLY RUNNING IT COULD HAVE FOUND, both from an AST probe rather than a
reading:
- a Rego backtick literal serialises as `RawString`, not `String`. Reading only
`String` let every realistic inline pattern through, since backticks are how
a regex carrying backslashes is written.
- a REFERENCE contains a literal: `data.batten.patterns["x"]` is a `RefBrack`
whose index is the string `"x"`, so a naive sweep read the sanctioned form as
the refused one and no module could load at all.
CONSUMER #1 PROVES IT. `policy/run-shape.rego`'s flag-cluster shape is declared
in `batten.toml` and referenced by name; its `#MUTANT` directive now corrupts the
REFERENCE, so the mutation exercises the silent-disarm path directly — the
allow cases go red when the id stops resolving.
Four completeness gates in this repo refused the new table until it was
classified, and the third found a defect this change would otherwise have
shipped: validation call site (`config.rs`), resolve provenance, trust verdict —
which is where the silent-disarm consequence surfaced — and every-kind-exercised.
Refs: CLOUD-885
BREAKING CHANGE: the pattern table has to reach the evaluator, so it is threaded
beside `provisions` the way that table already is. `Config` gains `patterns`,
`trust::WeakeningKind` gains `PatternRemoved`, and `rules::run_static`,
`rules::run_recorded`, `rules::run_all`, `policy::load` and `policy::compile`
each take one more argument. Caught by `mise run verify`'s semver gate, which
named all three lint classes rather than letting a patch-compatible claim ship
over an API that moved.
wenzowski
added a commit
that referenced
this pull request
Aug 22, 2026
…correct one
THE LEVER IS COST, NOT PROHIBITION, and the earlier attempt on this branch got
that wrong twice. `NOT_REGULAR` was a denylist of subjects; it covered one
program, said nothing about the failure that actually recurs, and blessed
everything it did not name. Reverted already. This is the mechanism that
replaces it.
A `regex.*` builtin may not reach a string literal. Expressions are `[[pattern]]`
rows in the one committed authority, projected into the evaluator's `data`
document, referenced by id:
regex.match(`CLOUD-[0-9]+`, w) refused at load
regex.match(data.batten.patterns["tracker-key"], w) loads and decides
The gradient is the point. A one-off pattern costs a config row and an id; a
shared one is free after the first; and asking the same question of an
already-parsed document costs a field access with no config edit at all. Effort
now orders the same way correctness does, which is the only version of this that
survives a translator that does not reason carefully — and 86 bash gates written
in `grep -E` are queued to be translated.
WHY THIS IS NOT FRICTION BOLTED ON. `Config::verbs` already carries the argument
verbatim for the mutating-verb table: "consumer-specific by nature, so it lives
here and never in the crate (non-negotiable rule 1)". A tracker-key expression is
a consumer identifier by exactly that test. Two more things fall out rather than
being designed in: duplication becomes unwritable instead of detectable — one
declaration, one home — and the pattern inventory becomes reviewable data (§11),
readable out of one file.
Measured on the tree this engine gates, comments stripped: 82 of 140 shell
programs use `grep -E`/`sed -E`/`awk`/`=~` over 338 sites, and ONE concept — a
tracker key — carries 19 distinct spellings across 17 programs. `CLOUD-[0-9]+`
alone sits at 15 sites in 9 of them, with a `CLOUD-*` glob spelling beside the
regexes and one program holding two variants of its own pattern 52 lines apart.
Correct regexes over a regular thing, duplicated until they drifted.
CLOSED AT BOTH ENDS, and the second half is not optional. Refusing the inline
form alone leaves the identical hole one step later: `data.batten.patterns["typo"]`
resolves to UNDEFINED, Rego reads undefined as "this rule body does not hold", so
the module loads clean, evaluates clean and gates nothing. So a reference no row
declares is refused too — the same shape `check_tree_paths_are_emittable` uses
against a `tree` key the engine never emits.
The same failure arriving by deletion is `WeakeningKind::PatternRemoved`: a local
override dropping a row silences every module referencing it, and one row can
silence several predicates at once, because a declared pattern is shared by
design. Reported with the id, never the expression.
TWO BUGS ONLY RUNNING IT COULD HAVE FOUND, both from an AST probe rather than a
reading:
- a Rego backtick literal serialises as `RawString`, not `String`. Reading only
`String` let every realistic inline pattern through, since backticks are how
a regex carrying backslashes is written.
- a REFERENCE contains a literal: `data.batten.patterns["x"]` is a `RefBrack`
whose index is the string `"x"`, so a naive sweep read the sanctioned form as
the refused one and no module could load at all.
CONSUMER #1 PROVES IT. `policy/run-shape.rego`'s flag-cluster shape is declared
in `batten.toml` and referenced by name; its `#MUTANT` directive now corrupts the
REFERENCE, so the mutation exercises the silent-disarm path directly — the
allow cases go red when the id stops resolving.
Four completeness gates in this repo refused the new table until it was
classified, and the third found a defect this change would otherwise have
shipped: validation call site (`config.rs`), resolve provenance, trust verdict —
which is where the silent-disarm consequence surfaced — and every-kind-exercised.
Refs: CLOUD-885
BREAKING CHANGE: the pattern table has to reach the evaluator, so it is threaded
beside `provisions` the way that table already is. `Config` gains `patterns`,
`trust::WeakeningKind` gains `PatternRemoved`, and `rules::run_static`,
`rules::run_recorded`, `rules::run_all`, `policy::load` and `policy::compile`
each take one more argument. Caught by `mise run verify`'s semver gate, which
named all three lint classes rather than letting a patch-compatible claim ship
over an API that moved.
This was referenced Aug 28, 2026
This was referenced Aug 31, 2026
feat(facts): build the verdict-store producer, and retire three wrappers into the rows it wakes
#797
Merged
This was referenced Sep 1, 2026
Merged
This was referenced Sep 1, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Documents and version-controls the trunk-based protection now active on
main.Protection applied (GitHub ruleset
protect-main, id 20481001):maintest (ubuntu-latest),test (macos-latest),cargo-denyMerge settings: auto-merge + delete-branch-on-merge enabled to keep branches short-lived, per trunk-based development.
This PR also exercises the flow: it is the first change that had to go through a PR because a direct push to
mainis now rejected.