Skip to content

ci: gate the steel-detailer-lookup crate, and fix what it had drifted into - #408

Merged
pawellisowski merged 5 commits into
mainfrom
routine/guardrails-2026-08-13
Aug 13, 2026
Merged

ci: gate the steel-detailer-lookup crate, and fix what it had drifted into#408
pawellisowski merged 5 commits into
mainfrom
routine/guardrails-2026-08-13

Conversation

@pawellisowski

Copy link
Copy Markdown
Contributor

Summary

  • 20-agents/aeco/engineering/steel-detailer-lookup is the repo's second Rust crate — it ships in every install archive next to aware — and no CI gate has ever run on it. It declares its own [workspace], so cd cli && cargo … (the only Rust CI runs) never reaches it. The only thing that ever compiled it was cargo build --release at release time, which runs neither clippy nor its five unit tests.
  • With nothing watching, it drifted from CLAUDE.md while CI stayed green: six .unwrap() calls in non-test code (§Code style: "No unwrap() outside of tests + main entry") and edition 2021 (§Tech stack: "Rust (edition 2024)").
  • This fixes both, gives the crate the same gates cli/ already carries, wires it into ci.yml, and adds a negative control so the gate can't be quietly removed.

Type of change

  • Other (specify): CI guardrail + the rule violations it was not catching

Decalog check

  • This change respects all five decalog truths (app=text, AI=runtime, OSS=inherent, no vendor in the loop, AECO=wedge-not-limit).

The gap

release.yml builds this crate on all three runners and stages it into the archive; scripts/install.sh, scripts/install.ps1 and packaging/wix/aware.wxs all place the three binaries. It is shipped code. But ci.yml's gates job is entirely working-directory: cli, and this crate's [workspace] isolates it, so:

gate cli/ steel-detailer-lookup (before)
cargo fmt --check ❌ never ran
cargo clippy -D warnings ❌ never ran
cargo test ❌ 5 unit tests never ran
unwrap_used / expect_used deny cli/src/main.rs ❌ absent
undocumented_unsafe_blocks deny cli/Cargo.toml ❌ absent

What changed

Violations fixed at root cause (no lint weakened, no #[allow], no test deleted):

  • Every result the binary emits now goes through print_json, which reports a serialization failure and exit(2) — the code this binary already uses for every other hard error — instead of unwrap()-panicking with exit 101. 101 is a code the cli transport reads as a failed invocation with no parseable reason (cli/src/runtime/invoker.rs).
  • edition = "2024". rustfmt's 2024 style edition reflows three long statements; no behaviour change.

Gates added, mirroring cli/ rather than inventing anything:

  • #[cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used))] on the crate root — byte-identical to the attribute in cli/src/main.rs, and for the reason stated there: [lints.clippy] would also fire on the unit tests, where CLAUDE.md permits unwrap().
  • [lints.clippy] undocumented_unsafe_blocks = "deny" in its Cargo.toml, matching cli/Cargo.toml. The crate has no unsafe today; the gate is what keeps it that way.
  • ci.yml: fmt + clippy + test on the crate with the pinned toolchain, and the rust-cache extended to both workspaces.

Negative control

tests/lint_gates.rs (new), mirroring cli/tests/lint_gates.rs. It compiles scratch crates carrying the gate and asserts clippy rejects unwrap()/expect() in non-test code and rejects for that lint specifically (so a probe that merely fails to build can't look like an enforcing gate), accepts clean code, still permits unwrap() under #[cfg(test)] — with a type-error witness so that assertion can't pass by never compiling a test target — and that src/main.rs and Cargo.toml still carry the gates unrelaxed. gate_reopener_classifier_matches_its_contract drives both scanners over synthetic input with known answers, since they otherwise report clean whether they work or have stopped matching anything.

Also verified by hand. Restoring one of the original calls:

-    print_json(&info);
+    println!("{}", serde_json::to_string_pretty(&info).unwrap());
error: used `unwrap()` on a `Result` value
  20 | #[cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used))]
  = help: … #unwrap_used

Verification

Run locally on Linux with the pinned toolchain (1.95.0) and CI's apt deps:

  • cli/: cargo fmt --all -- --check, cargo clippy --all-targets -- -D warnings, cargo test — all green on the base commit and unchanged by this PR (only ci.yml is touched there).
  • steel-detailer-lookup: fmt clean, clippy clean, CI=1 cargo test → 5 + 5 + 5 unit tests and 6 lint-gate tests pass.
  • Behaviour unchanged end-to-end against a fixture rule set: describe, lookup --list, lookup --rule <hit>, lookup --rule <miss>, lookup --category, and the --json-stdin transport all emit identical JSON and identical exit codes (0 found / 1 not-found standalone / 0 not-found under --json-stdin / 2 hard error).

Notes for reviewers

  • No rust-toolchain.toml for this crate, deliberately. cli/rust-toolchain.toml says it is the single source of truth for the pin, and ci.yml reads it rather than restating it. A second pin file would be a second thing to drift. In CI this is exact — dtolnay/rust-toolchain sets the pinned channel as the rustup default, so the new step uses it. Locally, a developer in this directory gets their own default toolchain; that residual gap is stated rather than papered over.
  • tests/lint_gates.rs is a copy of cli/'s, not a shared helper. The two live in separate cargo workspaces, so there is no crate they could both depend on without inventing one — and a gate's negative control that can be broken from another workspace is not much of a control. The duplication is the point.
  • tempfile added as a dev-dependency for the scratch crates. Dev-only, so cargo build --release — what release.yml runs — never builds it.
  • Serializing a serde_json::Value is not realistically fallible, so the six unwrap()s were unlikely to fire in practice. They were still the rule violation, and "not expected to fail" is precisely the claim the gate no longer takes on trust.

Generated by Claude Code

… into

`20-agents/aeco/engineering/steel-detailer-lookup` is the repo's second Rust
crate. It ships in every install archive next to `aware` (release.yml builds
it; install.sh, install.ps1 and packaging/wix/aware.wxs all place it), and no
CI gate has ever touched it: it declares its own `[workspace]`, so
`cd cli && cargo …` — the only Rust CI runs — never reaches it. The only thing
that ever compiled it was `cargo build --release` at release time, which runs
neither clippy nor its five unit tests.

With nothing watching, it drifted from CLAUDE.md while CI stayed green:

  * six `.unwrap()` calls in non-test code (`run_lookup`, `run_describe`),
    against §Code style "No `unwrap()` outside of tests + main entry";
  * edition 2021, against §Tech stack's "Rust (edition 2024)".

Fixes, then the gate that keeps them fixed:

  * every result now goes through `print_json`, which reports a serialization
    failure and exits 2 like the binary's other hard errors instead of
    panicking with exit 101 — a code the cli transport reads as a failed
    invocation with no parseable reason;
  * edition bumped to 2024 (rustfmt's 2024 style edition reflows three
    statements; no behaviour change);
  * the crate root carries the same
    `#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used))]`
    gate as `cli/src/main.rs`, and `Cargo.toml` the same
    `undocumented_unsafe_blocks = "deny"` as `cli/Cargo.toml`;
  * ci.yml runs fmt + clippy + test on it with the pinned toolchain.

`tests/lint_gates.rs` is the negative control, mirroring `cli/tests/lint_gates.rs`:
it compiles scratch crates carrying the gate and asserts clippy rejects
`unwrap()`/`expect()` in non-test code *for that lint*, accepts clean code,
still permits `unwrap()` under `cfg(test)`, and that `src/main.rs` and
`Cargo.toml` still carry the gates unrelaxed. Verified by hand as well:
restoring the original `to_string_pretty(&info).unwrap()` fails clippy with
`error: used unwrap() on a Result value`, citing the gate line.

Behaviour is unchanged — `describe`, `lookup --list/--rule/--category` and the
`--json-stdin` transport all emit identical JSON and identical exit codes
(0 found / 1 not-found standalone / 0 not-found under `--json-stdin` / 2 hard
error).

Copy link
Copy Markdown
Contributor Author

@codex review


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d6cffec20b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

"undocumented_unsafe_blocks",
"restriction",
];
gated.contains(&key) && matches!(value, "allow" | "warn")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Parse structured Cargo lint entries

When Cargo.toml contains Cargo's valid inline-table form, such as restriction = { level = "allow", priority = 1 }, value remains the whole table and never matches "allow" or "warn". With the pinned Cargo/Clippy 1.95, that group entry outranks the existing undocumented_unsafe_blocks = "deny", allowing undocumented unsafe code while nobody_reopened_the_gate_from_the_manifest remains green. Parse both the level and priority, as the existing reader in cli/tests/lint_gates.rs does.

Useful? React with 👍 / 👎.

Comment on lines +297 to +298
relaxes.iter().any(|level| trimmed.contains(level))
&& targets.iter().any(|target| trimmed.contains(target))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scan complete attributes for lint relaxations

When an override is formatted across lines—for example an #[allow(...)] carrying a long reason—rustfmt 1.95 can leave allow( on one line and clippy::unwrap_used on another. Because the caller checks each line separately and this predicate requires both tokens on the same line, nobody_reopened_the_gate_from_source reports no reopening even though Clippy honors the attribute and accepts the production unwrap(). Parse complete attributes rather than individual lines, as gate_reopeners in cli/tests/lint_gates.rs already does.

Useful? React with 👍 / 👎.

Both findings from Codex's review of d6cffec, both real, both in the
negative control rather than the gate it guards — a control that misses the
form it is supposed to catch is the worst kind of green.

P2, `reopens_gate` scanned line by line. rustfmt wraps an `#[allow(…)]` that
carries a long `reason`, so `allow(` and `clippy::unwrap_used` land on
different lines; a predicate requiring both on one line reported clean while
clippy honoured the attribute in full and accepted a production `unwrap()`.
Replaced with `gate_reopeners`, ported from `cli/tests/lint_gates.rs` as the
review suggested: it matches complete bracketed spans over a copy of the source
with comment and string *contents* blanked, so wrapped attributes, attributes
that are not line-initial, and levels nested in a `cfg_attr` predicate all
count, while a lint merely named in prose or in a `reason` string does not.

P2, `manifest_reopens_gate` compared the raw value against "allow"/"warn", so
cargo's inline-table form — `restriction = { level = "allow", priority = 1 }` —
left the whole table in `value`, matched nothing, and switched the unsafe gate
off with the test still green. Replaced with `manifest_clippy_entry`, also
ported: it reads both value forms, tracks the table header, skips commented-out
entries, accepts the dotted `[lints] clippy.<lint>` key, and returns priority so
a group entry is compared against the specific lint's priority rather than
against zero.

Verified end-to-end, not just by the classifier's own fixtures: injecting a
rustfmt-wrapped `#[allow(clippy::unwrap_used, reason = "…")]` into the real
`src/main.rs` and `restriction = { level = "allow", priority = 1 }` into the
real `Cargo.toml` fails `nobody_reopened_the_gate_from_source` and
`nobody_reopened_the_gate_from_the_manifest` respectively; both pass again once
restored. `manifest_lint_reader_matches_its_contract` is new and covers the
inline-table, single-quote, dotted-key and negative-priority cases.

Copy link
Copy Markdown
Contributor Author

Both P2 findings were real, and both were in the negative control rather than in the gate it guards — a control that misses the form it exists to catch is the worst kind of green. Fixed in 91fa0826 by porting the two scanners from cli/tests/lint_gates.rs, as both comments suggested.

Wrapped attributesgate_reopeners now matches complete bracketed spans over a copy of the source with comment and string contents blanked, instead of scanning line by line. So a rustfmt-wrapped #[allow(…, reason = "…")], an attribute that isn't line-initial (let w = { #[allow(clippy::unwrap_used)] v.unwrap() };), and a level nested in a cfg_attr predicate all count — while a lint merely named in prose or inside a reason string does not.

Inline-table lint entriesmanifest_clippy_entry now reads both value forms cargo accepts, tracks the table header, skips commented-out entries, accepts the dotted [lints] clippy.<lint> key, and returns priority. The group comparison is against the specific lint's priority rather than against zero, so restriction = { level = "allow", priority = -1 } (which loses to deny @ 0) isn't flagged while priority = 1 is.

Verified end-to-end, not only against the classifiers' own fixtures — injecting each finding's exact shape into the real files:

injected into test result
src/main.rs: #[allow(\n clippy::unwrap_used,\n reason = "…"\n)] nobody_reopened_the_gate_from_source FAILED
Cargo.toml: restriction = { level = "allow", priority = 1 } nobody_reopened_the_gate_from_the_manifest FAILED, reporting restriction = { level = "allow", priority = 1 } outranks undocumented_unsafe_blocks = { level = "deny", priority = 0 }

Both pass again once restored. manifest_lint_reader_matches_its_contract is new and covers the inline-table, single-quote, dotted-key and negative-priority cases; gate_reopener_classifier_matches_its_contract gained the wrapped-attribute and cfg_attr-wrapped forms plus the false-positive cases. Full suite on the pinned 1.95.0: fmt clean, clippy clean, 5+5+5 unit tests and 7 lint-gate tests green.

@codex review


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 91fa082652

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

fn manifest_clippy_entry(manifest: &str, name: &str) -> Option<(String, i64)> {
let dotted = format!("clippy.{name}");
let mut table = "";
for line in manifest.lines().map(str::trim) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Parse multiline lint-table values

With the pinned Cargo 1.95.0, [lints.clippy] restriction = {\n level = "allow",\n priority = 1,\n} is accepted and the group outranks undocumented_unsafe_blocks = "deny" at priority 0, but this line-by-line reader evaluates only restriction = {, records an empty level at priority 0, and ignores the continuation lines, leaving nobody_reopened_the_gate_from_the_manifest green. Fresh evidence beyond the previously fixed single-line inline-table case is Cargo's accepted multiline form; accumulate the complete value or parse the manifest as TOML.

Useful? React with 👍 / 👎.

.trim_start_matches('=')
.trim()
.chars()
.take_while(|c| c.is_ascii_digit() || *c == '-')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Accept leading plus signs in lint priorities

With the pinned Cargo 1.95.0, restriction = { level = "allow", priority = +1 } is valid and disables the priority-0 specific deny, but this filter rejects +, causing the priority parse to default to 0 and the strict outranking comparison to miss the reopening. Fresh evidence beyond the previously fixed unsigned-priority case is Cargo's accepted explicitly positive integer syntax; include + when parsing the signed priority.

Useful? React with 👍 / 👎.

Both findings from Codex's review of 91fa082, both real. `priority = +1`
is valid TOML and a digits-and-`-` filter rejected the `+`, so the parse
failed and defaulted to 0; and a multiline inline table left the reader
looking at `restriction = {` alone, recording an empty level at priority 0.
Each was measured to switch the unsafe gate off with the test still green.

Checking those two turned up a third the review had not named:
`[lints.clippy.restriction]` as its own sub-table does the same thing. Four
forms in two rounds is the answer to whether a hand-rolled reader can cover
TOML — so `manifest_clippy_entry` now parses with the `toml` crate (a
dev-dependency; `cargo build --release`, what release.yml runs, never builds
it) and navigates `lints.clippy.<name>`. That path resolves `[lints.clippy]`,
the dotted `[lints] clippy.<lint>` key and the `[lints.clippy.<lint>]`
sub-table alike, because the parser rather than this function decides what
each spelling means. Commented-out entries stop needing a special case too:
a comment is not data.

The real fix, though, is that the reader no longer has the last word.
`the_manifest_reader_agrees_with_clippy` runs clippy over a scratch crate
with an undocumented `unsafe` block for each form and fails if the reader's
verdict differs from what clippy actually did. Hand-written fixtures encode
what their author believes cargo accepts, and that belief was wrong four
times here; this measures instead. It earned its keep immediately by
rejecting one of its own fixtures — `[lints.clippy]` followed by a second
`[lints]` is a duplicate key — so `unsafe_gate_holds` now also asserts the
probe failed *for the lint* rather than for a manifest cargo could not read,
which would otherwise have looked like an open gate.

Verified against the real Cargo.toml: `priority = +1`, the multiline table
and the sub-table each fail `nobody_reopened_the_gate_from_the_manifest`,
naming the outranking, and all 8 tests pass once restored.

Copy link
Copy Markdown
Contributor Author

Both round-2 findings were real, and checking them turned up a third the review hadn't named. Fixed in 1b61bdd0.

I verified each against the pinned cargo/clippy 1.95.0 before changing anything — building a scratch crate with an undocumented unsafe block and the deny in place:

[lints.clippy] form cargo accepts unsafe gate old reader said
undocumented_unsafe_blocks = "deny" (baseline) HOLDS clean ✅
restriction = { level = "allow", priority = +1 } OFF clean ❌
restriction = { level = "allow", priority = 1,} OFF clean ❌
[lints.clippy.restriction] sub-table OFF clean ❌ (not flagged in review)

Four missed forms across two rounds is a sufficient answer to whether a hand-rolled reader can cover TOML, so manifest_clippy_entry now parses with the toml crate and navigates lints.clippy.<name>. That one path resolves [lints.clippy], the dotted [lints] clippy.<lint> key and the [lints.clippy.<lint>] sub-table alike, and +1 parses as an integer because the parser decides, not me. Commented-out entries stop needing a special case: a comment isn't data. toml is a dev-dependency — cargo build --release, what release.yml runs, never builds it.

The more important change is that the reader no longer has the last word. the_manifest_reader_agrees_with_clippy runs clippy over each form and fails if the reader's verdict differs from what clippy actually did. Hand-written fixtures encode what their author believes cargo accepts, and that belief was wrong four times here — this measures instead of believing, so a fifth form I haven't thought of fails the suite rather than sailing through it.

It earned its keep immediately by rejecting one of its own fixtures: [lints.clippy] followed by a second [lints] is a duplicate key, not valid TOML. unsafe_gate_holds now also asserts the probe failed for the lint rather than for a manifest cargo couldn't read — which would otherwise have looked exactly like an open gate.

Verified end-to-end against the real Cargo.toml: all three forms above fail nobody_reopened_the_gate_from_the_manifest, each reporting restriction = { level = "allow", priority = 1 } outranks undocumented_unsafe_blocks = { level = "deny", priority = 0 }. All 8 lint-gate tests plus 5+5+5 unit tests pass once restored; fmt and clippy clean on 1.95.0.

One thing I deliberately did not do: cli/tests/lint_gates.rs — which this file was ported from — has the same four holes in its own manifest_clippy_entry. That's a real gap and worth a follow-up, but it's outside this PR's diff and this is already round two of review here, so I've left it rather than widen the change unasked.

@codex review


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1b61bdd019

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


#[test]
fn nobody_reopened_the_gate_from_source() {
let source = std::fs::read_to_string(manifest_dir().join("src/main.rs")).expect("read main.rs");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scan every production Rust source

When implementation is moved into a new module such as src/helper.rs, that module can use #![allow(clippy::unwrap_used)] to override the crate-root deny; with the pinned Clippy 1.95.0 this permits an unwrap() while this test remains green because it reads only src/main.rs. Recursively scan all production .rs files under src so the guard continues to cover the crate after it is split into modules.

Useful? React with 👍 / 👎.

"the probe crate failed for a reason other than the gate — the manifest \
under test may not be valid TOML at all:\n{lints}\n{diagnostics}"
);
named

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require an error before declaring the unsafe gate held

When the lint is configured as warn or force-warn, Clippy exits successfully but still emits a diagnostic containing undocumented_unsafe_blocks, so this function returns true and reports that the gate “HOLDS” even though the probe was not rejected. The pinned cargo clippy --help describes -W / --warn [LINT] as “Set lint warnings”; require both a failing status and the named diagnostic before treating the gate as enforcing.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Stopping here — both round-3 findings are real, and I'm at my round cap

I reproduced both against the pinned 1.95.0 rather than take them on faith. Both are genuine, and I am not rejecting either.

1. The source scan reads only src/main.rs (tests/lint_gates.rs:194). Confirmed by adding src/helper.rs:

#[allow(clippy::unwrap_used)]
pub fn parse(s: &str) -> i32 { s.parse::<i32>().unwrap() }

…called from run_describe. Result: cargo clippy --all-targets -- -D warnings passes — an unwrap() in non-test production code — and all 8 lint-gate tests stay green. The crate is one file today, so this is latent rather than live, but it's exactly the shape the guard exists to stop. Fix: walk src/**/*.rs instead of the single root, as gated_source_files in cli/tests/lint_gates.rs does.

2. unsafe_gate_holds accepts a warning as enforcement (tests/lint_gates.rs:330). Confirmed with undocumented_unsafe_blocks = "warn" and an undocumented unsafe block: clippy exits 0 while its diagnostics still name the lint, so the function returns true — "GATE HOLDS" — for a configuration that enforces nothing. Fix: require a failing status and the named diagnostic. Note this weakens the oracle rather than the shipped gate (Cargo.toml is at deny), but an oracle that calls warn enforcing is precisely the thing that would let a future downgrade through.

Why I'm not pushing a third fix. The routine that opened this PR caps me at two fix-and-re-review rounds, then requires me to stop and leave the PR for a human rather than keep cycling unattended. I've used both rounds (d6cffec291fa08261b61bdd0), so this is where I hand over. I am not merging, and per CLAUDE.md §"PR review" I wouldn't merge on anything but a clean cross-model review anyway.

State as it stands on 1b61bdd0: all four CI checks green (fmt + clippy + test, stats, no Claude co-author trailers, connection-reader bridge tests). Locally on the pinned toolchain: fmt clean, clippy clean, 5+5+5 unit tests and 8 lint-gate tests pass. The shipped fix — the crate is gated in CI at all, and its six non-test unwrap()s are gone — is sound and independently verified; both open findings are in the negative control's reach, not in the gate it guards.

Carried over from the last round, still open: cli/tests/lint_gates.rs has the same four manifest-parsing holes this PR fixed here, and finding 1 above applies to it in mirror image. Worth a follow-up PR.


Generated by Claude Code

…nostic

Both findings from Codex's review of 1b61bdd, both real.

`nobody_reopened_the_gate_from_source` read `src/main.rs` and nothing
else. The crate-root `deny` is overridden by an `#![allow(...)]` at the
top of any other module, so the first `src/helper.rs` this crate grew
could re-open the gate with the guard still green. It now walks every
`.rs` file under `src/`, and asserts the walk reached `src/main.rs` so a
collapsed walk cannot read as a clean scan. Measured: a scratch
`src/helper.rs` carrying `#![allow(clippy::unwrap_used)]` is reported
before this change and flagged after it.

`unsafe_gate_holds` returned "the gate holds" whenever clippy named the
lint. At `warn` the lint fires and prints its name while clippy exits 0
and the undocumented `unsafe` ships, so a warning read as enforcement.
It now requires a failing status as well, matching `level_enforces`,
which has always counted only `deny`/`forbid`. `warn` joins the measured
cases in `the_manifest_reader_agrees_with_clippy` — that case fails
against the old oracle, which is what makes it worth having.

Copy link
Copy Markdown
Contributor Author

@codex review

Both P2 findings on 1b61bdd0 are fixed at root cause in a8a49a98; nothing else was touched.

  • Scan every production Rust sourcenobody_reopened_the_gate_from_source now walks every .rs file under src/ (recursive collect_rs_files, mirroring cli/tests/lint_gates.rs) instead of reading src/main.rs alone, and asserts the walk reached src/main.rs so a collapsed walk cannot pass as a clean scan. Measured: a scratch src/helper.rs carrying #[allow(clippy::unwrap_used)] is invisible to the old test and flagged by the new one (src/helper.rs:1: #[allow(clippy::unwrap_used)]).
  • Require an error before declaring the unsafe gate heldunsafe_gate_holds now requires a failing exit status as well as the named diagnostic. At warn the lint fires and prints its own name while clippy exits 0 and the undocumented unsafe compiles, which the old oracle read as enforcement. This now matches level_enforces, which has only ever counted deny/forbid. "the lint downgraded to a warning" joins the measured cases in the_manifest_reader_agrees_with_clippy; that case fails against the old oracle, which is what makes it load-bearing rather than decorative.

Verified on the pinned toolchain (1.95.0) with CI's apt deps: cargo fmt --all -- --check clean, cargo clippy --all-targets -- -D warnings clean, CI=1 cargo test → 5 + 5 unit tests and 8 lint-gate tests pass. cli/ is untouched by this commit.


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a8a49a982c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

/// `#[allow (clippy::unwrap_used)]` — which clippy honours — still counts.
fn opens_a_lint(collapsed: &str) -> bool {
let dense: String = collapsed.chars().filter(|c| !c.is_whitespace()).collect();
["allow(", "expect("].iter().any(|level| {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat warn attributes as gate reopeners

When a production module uses #![warn(clippy::unwrap_used)] (or the corresponding expect_used, undocumented_unsafe_blocks, or restriction form), it overrides the outer deny with a warning, so Clippy exits successfully and the prohibited code compiles. This classifier only recognizes allow and expect, leaving nobody_reopened_the_gate_from_source green in that scenario; include warning-level overrides in the reopener detection.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed on the real crate at a8a49a98, with one correction to the stated impact that changes the severity.

The classifier gap is real. opens_a_lint (line 770) matches only allow( and expect(. I added src/helper.rs with #[warn(clippy::unwrap_used)] and an unwrap(), called it from run_describe, and nobody_reopened_the_gate_from_source passed — so a warn-level override does slip past the guard. Worth fixing.

But "Clippy exits successfully and the prohibited code compiles" only holds without -D warnings, and that is not how this gate is invoked. Measured on the same crate, same module:

clippy invocation result
cargo clippy (bare) exit 0unwrap() accepted, as the comment says
cargo clippy --all-targets -- -D warnings (what ci.yml runs) exit 101, error: used unwrap()on aResult value

-D warnings promotes the downgraded lint straight back to an error, which is exactly what it cannot do for allow/expect — and that asymmetry is why those two are the reopeners that actually bite. So this is defense-in-depth (it would matter to someone running bare cargo clippy locally, or if -D warnings were ever dropped from CI), not a live hole. CI catches it today.

The fix is a one-liner — add "warn(" to the list at line 770, plus a fixture in gate_reopener_classifier_matches_its_contract. Note force-warn as mentioned isn't an attribute form, only a --force-warn flag / [lints] level, so the attribute classifier doesn't need it; the manifest side already handles it, since level_enforces counts only deny/forbid.

I'm not pushing it. This routine caps me at two fix-and-re-review rounds and I hit that at 1b61bdd0; a later session did round three in a8a49a98, and two agents pushing to one branch is a worse failure mode than one clear note. Flagging for whoever takes the next round.

Same hole in cli/tests/lint_gates.rs:301 — identical ["allow(", "expect("], since this file was ported from it. That's now the second defect this PR has found in cli/'s copy (the four manifest-parsing holes being the first), which is a decent argument that the follow-up PR should port the fixed version back rather than patch it twice.


Generated by Claude Code

`gate_reopeners` recognised `allow` and `expect` but not `warn`, so a
module-level `#![warn(clippy::unwrap_used)]` — which overrides the
crate-root `deny` — read as clean.

Measured against the pinned 1.95.0 rather than assumed, and the
mechanism is not quite the one the review described: with that attribute
in a `src/helper.rs`, clippy demotes `unwrap_used` to a warning, but
CI's `cargo clippy --all-targets -- -D warnings` promotes it straight
back to an error, so the build still fails today. The downgrade is worth
flagging anyway. A `deny` in the source is enforced by the source; a
`warn` is enforced by a flag in a workflow file, so the downgrade moves
the gate out of the file this test can see and makes the `deny` it
guards decorative. `level_enforces` already draws that line in the same
place for the manifest, counting `deny`/`forbid` and nothing else — this
keeps the two readers saying the same thing about the same word.

Flagged and not-flagged fixtures both extended: `#![warn(clippy::…)]`,
a `cfg_attr`-nested warn and a warn of the whole `restriction` group
count; `#[warn(dead_code)]` and a *tightening* `#[deny(clippy::…)]` do
not. The flagged set fails against the old classifier.

Copy link
Copy Markdown
Contributor Author

@codex review

Fixed in 776ebb71. Accepted, not argued down — but the mechanism is not the one described, and the difference is worth stating.

Treat warn attributes as gate reopenersgate_reopeners now counts warn alongside allow and expect.

The claim was that a module-level #[warn(clippy::unwrap_used)] leaves clippy exiting successfully with the prohibited code compiling. I measured it on the pinned 1.95.0 before changing anything, with that attribute and an unwrap() in a real src/helper.rs wired into the crate:

invocation result
cargo clippy --all-targets exits 0unwrap_used demoted to a warning, as described
cargo clippy --all-targets -- -D warnings (what ci.yml runs) fails — the warning is promoted back to an error

So the gate does still reject this today, and the PR was not shipping a live hole.

I made the change regardless, because the finding is right about the shape even where it is wrong about the blast radius: a deny in the source is enforced by the source, while a warn is enforced by a flag in a workflow file. Downgrading one to the other moves enforcement somewhere this test cannot see and makes the deny it guards decorative — and level_enforces already draws that exact line for the manifest, counting deny/forbid and nothing else. The two readers now say the same thing about the same word.

Both fixture sets grew: #[warn(clippy::unwrap_used)], a cfg_attr-nested warn and a warn of the whole restriction group are flagged; #[warn(dead_code)] and a tightening #[deny(clippy::unwrap_used)] are not. The flagged set fails against the old classifier, which is what makes it evidence.

Gates on the pinned toolchain with CI's apt deps: cargo fmt --all -- --check clean, cargo clippy --all-targets -- -D warnings clean, CI=1 cargo test → 5 + 5 unit tests and 8 lint-gate tests pass. CI was green on a8a49a98 on all four checks.

Round 2 of 2 for this pass. If a further finding comes back, I stop and leave this for a human rather than keep cycling unattended.


Generated by Claude Code

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Bravo.

Reviewed commit: 776ebb71f1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@pawellisowski
pawellisowski merged commit 0789633 into main Aug 13, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants