fix(lint): burn down the kanon-lint baseline — 104 entries down to 25 - #358
Merged
Conversation
added 16 commits
August 3, 2026 21:31
…order RUST/non-exhaustive-enum: add #[non_exhaustive] to FileFormat, RadioCommand, ExportFormat, RadioVariant, VaultCommand, VaultCliError — none of these are matched exhaustively cross-crate, so this is additive. RUST/unreachable-in-match: resolve_target's single-radio arm replaces an unreachable!() panic with a direct ok_or() — the len()==1 invariant is now expressed without a panic path at all. ARCHITECTURE/trait-impl-colocation: mirror the established #[rustfmt::skip] + trailing kanon:ignore pattern (already used for SerialPort/AlertSink/ Collector) on Hardware's stub impl — the real impl (SerialHardware) lives in serial_hardware.rs. RUST/import-order: reorder mesh/mod.rs test imports (external before crate-local). TOPOLOGY/shallow-struct: mark DetectedRadio as pure data (a detection result snapshot with no derived invariant). Refs #261
CONTEXT/preamble-required: add the scope/defers_to/tightens preamble to AGENTS.md's hand-authored section (the generated kanon:auto block already carried its own). DOCS/stale-local-link: docs/lexicon.md pointed at a GNOMON.md that has never existed in this repo (canonical copy lives in kanon); reword as a plain-text pointer instead of a promised-but-broken link. AGENTS.md's generated block linked workflow/AGENTS-mcp-tools.md the same broken way — CLAUDE.md already states the identical fact as a backtick path, not a link; match that. WRITING/weasel-word: drop "mostly" from a claim that holds without qualification (fjall-column-encryption.md). WRITING/elegant-variation, WRITING/temporal-staleness: reference-store.md cycled data/payload/content for the same concept within one section — settled on "content"; dropped "currently" from a fact that doesn't need temporal qualification. Refs #261
…ends RUST/primitive-for-domain-id: introduce NodeIdStr and MeshChannelId newtypes (types.rs) for the raw hex node-id and channel-name strings that mqtt.rs's GatewayInfo and node_db.rs's UserInfo.id carried as bare String. #[serde(transparent)] keeps the wire shape unchanged. TOPOLOGY/shallow-struct: MeshNode gains elapsed_since_heard(now), moving the last_heard-vs-now computation duplicated at its one call site (discovery.rs) onto the type that owns the field. RUST/test-missing-use-super: lib.rs's proto import now goes through super:: (functionally identical to crate::, but makes the `use super::` line real instead of adding a redundant unused glob). RUST/no-silent-result-swallow: discovery.rs's two `let _ = tx.send(..)` broadcast sends (no-receivers is a legitimate, non-fatal condition) now trace the miss instead of silently dropping it. TESTING/tautological-test: mqtt.rs's decode_invalid_bytes_returns_error asserted nothing; every byte in the fixture has its varint continuation bit set, so the decode deterministically errors — assert that. Refs #261
RUST/doc-promised-observability: ensure_active's doc says it "emits a failover event," but the cooldown-skip branch was previously silent — an operator watching logs would see nothing when a needed reselection was suppressed. Trace it. TOPOLOGY/shallow-struct: mark GatewayState as pure data (a tracked snapshot; nothing currently queries staleness on it). Refs #261
…havior TOPOLOGY/shallow-struct: PendingMessage and InflightMessage gain is_expired(now) (and InflightMessage a has_timed_out(now) for the ACK timeout), deduplicating the `now.duration_since(x.created) >= x.ttl` check that was repeated at four call sites in OutboundQueue. LinkQuality gains is_stale(cutoff), matching the fresh/stale cutoff comparison already duplicated in remove_stale_nodes/remove_stale_links. RUST/file-too-long: topology.rs's #[cfg(test)] block (338 lines) moves to topology_tests.rs via the #[path = "..."] sibling-file convention already used by collector_tests.rs/processor_tests.rs — 834 lines down to 496. Refs #261
…orts RUST/indexing-slicing (error severity): build_nonce wrote into a fixed [u8; 16] via bracket-range indexing; rewritten with split_at_mut so the 8/4-byte layout has no panic-shaped `nonce[a..b]` access at all. RUST/no-silent-result-swallow: clearing DTR/RTS on connect is best-effort (some backends don't support the control lines); both calls now trace a failure instead of silently discarding it. RUST/no-result-unwrap-or-default: store_forward.rs's drain_for used `.unwrap_or_default()` after HashMap::remove — that's an Option, not a Result (the rule's static heuristic doesn't special-case `remove`); added the WHY the rule's own carve-out asks for rather than leaving it flagged. RUST/import-order: transport/mod.rs's `use tracing::instrument` (external) sorted after the crate-local block; moved it into the external group. Refs #261
… split entry types RUST/no-debug-derive-on-public-types: LogEntryKind derived Debug while carrying a credential name (VaultMutation.credential_name) — a label, not the secret itself, but Debug output lands in logs. Manual Debug impl redacts just that field; every other variant mirrors the derived output exactly. RUST/no-result-unwrap-or-default: decode_entry and verify_chain both silently defaulted a failed u64->usize payload_len conversion to a zero-length buffer (only reachable on 32-bit-usize targets, since payload_len is already bounded by MAX_ENTRY_BYTES) — now report it as the same corruption/oversized-payload failure the surrounding code already uses instead of reading a truncated buffer. RUST/file-too-long: LogEntryKind (enum + its manual Debug impl) moves to tamper_log_entry.rs via the established #[path = "..."] sibling-file convention — tamper_log.rs was pushed to 851 lines by the Debug impl above; now 739. TOPOLOGY/shallow-struct: mark KnownUsbDevice (koinon/hardware.rs) and VerificationResult (tamper_log.rs) as pure data — a static lookup-table row and a verification result bag, neither with a derived invariant. Refs #261
…rite RUST/no-debug-derive-on-public-types: DecryptedEntry.secret is the actual decrypted plaintext credential — redact it in a manual Debug impl instead of deriving. EntryInfo and VaultEntry only touch `credential_type` (an enum tag) and `encrypted_data` (ChaCha20-Poly1305 ciphertext), neither of which is secret material; their manual impls mirror the derived output exactly. RUST/no-silent-result-swallow: hex()'s `write!` into a String is infallible (fmt::Write's Result exists only for the trait's generality over fallible writers) — documented with the WHY the rule's own carve-out asks for. TOPOLOGY/shallow-struct: mark EntryHistory as pure data (a lifecycle query result bag). Refs #261
…tor channel TESTING/tautological-test: tracing_sink_emit_does_not_panic only checked that emit() didn't panic; it never verified the sink emitted anything. Replaced with a minimal tracing::Subscriber that counts events, asserting TracingSink::emit records exactly one. smoke.rs's two constructor-only tests gained real assertions: SignalAggregator::extract_feature returns the expected dBm for an RF jamming signal; a fresh ConvergenceGrid with no ingested signals detects nothing. RUST/no-silent-result-swallow: pipeline.rs's fan-to-aggregator send (channel closed = aggregator exited, not fatal) now traces the miss instead of silently discarding it, matching the sibling grid-channel send right below it. TOPOLOGY/shallow-struct: mark AggregatedSignal, Alert, DomainHit, and Convergence as pure data — pipeline carriers and result records with no derived invariant. Refs #261
…-range saturation RUST/indexing-slicing (error severity): encode_channel wrote the rx/tx frequency and tone fields into a fixed [u8; 16] via bracket-range indexing; rewritten with split_at_mut, matching the codebase's own existing SAFETY-commented precedent for the two already-suppressed single-index writes in the same function. RUST/no-result-unwrap-or-default: BLOCK_SIZE was declared `usize` and converted at every one of its 10 use sites via `u16::try_from(BLOCK_SIZE).unwrap_or_default()`, even though every use site sends it as u16 — retyped the constant to u16 (matching the sibling u8 READ_BLOCK_SIZE/WRITE_BLOCK_SIZE constants' infallible-From pattern), which drops the conversion entirely. is_forbidden's `len` conversion (a real runtime value, unlike the constant) now saturates to u16::MAX instead of defaulting to 0 on overflow — this is a calibration-write guard, so a conversion failure must widen the checked range, not silently collapse it to a zero-length check that could let a forbidden-address write through. tone_codec.rs's DCS-index conversion (bounded 1..=104, matching the neighboring already-safe cast) switched from try_from+unwrap_or_default to a plain `as u16` with the same SAFETY comment its sibling arm already carries. TOPOLOGY/shallow-struct: mark BlockOp and PowerMapping as pure data (a protocol operation descriptor and a static lookup-table row). Refs #261
…colocation
RUST/prefer-expect-over-allow: cables.rs/detect.rs/usb.rs/warnings.rs test
modules used #[allow(...)] — switched to #[expect(...)], which fails
loudly once a listed lint stops firing instead of silently going stale.
RUST/no-silent-result-swallow: try_magic_sequence's closing ACK write (the
ident bytes are already read at that point, so a write failure doesn't
invalidate the identification) now traces the failure instead of
discarding it.
ARCHITECTURE/trait-impl-colocation: RadioProber's kanon:ignore marker sat
on the line inside the impl block, one line below the `impl X for Y {`
the violation actually anchors on — moved it onto that line, matching the
established #[rustfmt::skip] + trailing-comment pattern used elsewhere in
this fleet.
TOPOLOGY/shallow-struct: mark KnownCable, RadioIdent, DetectedRadio, and
UsbCable as pure data — static lookup-table rows and detection results
with no derived invariant.
Refs #261
TOPOLOGY/shallow-struct: RadioConstraints gained allows_frequency() and allows_power_level() methods, replacing the free freq_in_bands() function and the direct .power_levels.contains() call in validate_channel — the "does this constraint permit X" logic now lives on the constraint type itself instead of being scattered across the validating function. Refs #261
RUST/todo-marker-needs-quadrant-and-artifact: the four TODO(#80) markers in yaesu/{codec,protocol,variant}.rs carried a tracking issue but no debt-quadrant tag. All four are a deliberate, scoped-out stub awaiting real hardware capture (ADMS-14) — [deliberate-prudent]. Format is TODO(#NNN)[quadrant]: text rather than TODO[quadrant] #NNN: text, because the older RUST/todo-no-issue and META/rule-todo-without-issue rules specifically require the literal TODO(#NNN) substring. Refs #261
…b crates TESTING/no-tests: this rule only inspects lib.rs and the crate-root tests/ directory — module-local #[cfg(test)] blocks in mesh/radio/vault don't satisfy it, and neither crate had a tests/ directory. Added one exercising each crate's public boundary rather than a placeholder: akroasis's resolve_target()/StubHardware wiring and RadioVariant display names; akroasis-server's router actually serving a request (a malformed route table panics axum at build time, so a real response is a genuine assertion) plus ApiError's status-code mapping. Refs #261
… SHA YAML/missing-concurrency: gate-attestation.yml had no concurrency group; added one keyed on PR number (falling back to ref for the push trigger), matching the pattern already used by release-please/release/dependabot- auto-merge/security. SHELL/unpinned-action: actions/stale@v11 was a floating tag; pinned to its resolved commit SHA, matching every other action in this repo. Refs #261
Entry count: 104 -> 25 (23 further entries were already dead-anchor drift from PRs merged since the last refresh; --write-baseline recomputed every surviving entry's hash/line against current main rather than hand-editing line numbers, per the anchor-drift trap this campaign was warned about). Rule-classes cleared entirely (fixed, not suppressed): RUST/non-exhaustive-enum, RUST/import-order, CONTEXT/preamble-required, DOCS/stale-local-link, RUST/unreachable-in-match, RUST/test-missing-use-super, WRITING/weasel-word, WRITING/temporal-staleness, WRITING/elegant-variation, RUST/todo-marker-needs-quadrant-and-artifact, RUST/prefer-expect-over-allow, RUST/no-debug-derive-on-public-types, RUST/primitive-for-domain-id, TESTING/tautological-test, TESTING/no-tests, RUST/indexing-slicing, RUST/no-silent-result-swallow, RUST/no-result-unwrap-or-default, ARCHITECTURE/trait-impl-colocation, TOPOLOGY/shallow-struct. Also fixed, never baselined: RUST/file-too-long (topology.rs, tamper_log.rs split), YAML/missing-concurrency, SHELL/unpinned-action. Remaining 25 entries (17 RUST/no-arc-mutex-anti-pattern + 8 singles) are deliberate exceptions, not deferred mechanical work — see the baseline `reason` field and the PR description for the rationale on each. Refs #261
forkwright
pushed a commit
that referenced
this pull request
Aug 4, 2026
🤖 I have created a release *beep* *boop* --- ## [0.1.21](v0.1.20...v0.1.21) (2026-08-04) ### Bug Fixes * **lint:** burn down the kanon-lint baseline — 104 entries down to 25 ([#358](#358)) ([7004d73](7004d73)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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.
Summary
Burns down the
.kanon-lint-baseline.tomldebt from akroasis#261: 104 baseline entries → 25. Every entry deleted here was fixed, not re-suppressed — the underlying finding was addressed in the code, and the baseline was regenerated (kanon lint --write-baseline) rather than hand-edited, so no anchor drift is hiding in this diff.Rule-classes fixed (baseline entries deleted)
RUST/non-exhaustive-enum—#[non_exhaustive]on 6 CLI/error enumsRUST/import-order— reorder external-before-crate-local imports (2 files)CONTEXT/preamble-required— AGENTS.md scope/defers_to/tightens preambleDOCS/stale-local-link— 2 dead links reworded to plain-text pointersRUST/unreachable-in-match— removed a realunreachable!()panic pathRUST/test-missing-use-super— realuse super::instead of an unused globWRITING/weasel-word,WRITING/temporal-staleness,WRITING/elegant-variation— doc proseRUST/todo-marker-needs-quadrant-and-artifact— Fowler quadrant tags on 4 yaesu TODOsRUST/prefer-expect-over-allow—#[allow]→#[expect]on 4 test modulesRUST/no-debug-derive-on-public-types— manualDebugimpls redacting the one field that's actually secret material (a decrypted plaintext, a credential name) on 4 types; the rest mirror the derived output exactly since nothing else is secretRUST/primitive-for-domain-id—NodeIdStr/MeshChannelIdnewtypes replacing bareStringmesh-id fieldsTESTING/tautological-test— 4 tests that asserted nothing now assert real behavior (one via a minimaltracing::Subscriberevent counter)TESTING/no-tests— integrationtests/added forakroasisandakroasis-server(this rule only inspectslib.rs/tests/, not module-local#[cfg(test)])RUST/indexing-slicing(error severity) — 2 fixed-array bracket writes rewritten withsplit_at_mutRUST/no-silent-result-swallow— 8 previously-discardedResults now traced or documented as genuinely infallibleRUST/no-result-unwrap-or-default— 15 silent-default fallbacks replaced with explicit error handling, a retyped constant, or a saturating (not zero-defaulting) conversion in a calibration-write guardARCHITECTURE/trait-impl-colocation— 2 misplaced/missing inline suppressions fixed (one anchor was one line off; rustfmt needed#[rustfmt::skip]to keep the trailing comment on the anchor line)TOPOLOGY/shallow-struct— 23 structs: 4 gained real behavior extracted from duplicated call-site logic (is_expired,is_stale,elapsed_since_heard,allows_frequency/allows_power_level), 19 are genuinely pure-data DTOs marked with the rule's own// WHY: pure datacarve-outAlso fixed (pre-existing debt that was never baselined — surfaced by unrelated PRs after the original #261 baseline was written, but blocking a clean
kanon lint .):RUST/file-too-long—topology.rs(834→496 lines) andtamper_log.rs(851→739 lines) split via the sibling-file convention already used elsewhere in this codebaseYAML/missing-concurrency— concurrency group ongate-attestation.ymlSHELL/unpinned-action—actions/stale@v11pinned to its SHALeft deliberately (25 remaining baseline entries — not deferred, evaluated and kept)
RUST/no-arc-mutex-anti-pattern(17, allcollector.rs) — alreadytokio::sync::Mutex, the rule's own recommended async-safe primitive. The rule is a pure text match onArc<Mutex<and can't see whichMutexis imported. Converting further toRwLockneeds a per-callsite read/write classification across 43+ call sites in 5 files — an architecture change, not a lint fix.VOCAB/crate-name-collision+NAMING/no-fleet-collision(koinon) — a cross-repo naming call the team already explicitly deferred (akroasis#264: "left for an explicit naming decision rather than resolved unilaterally").NAMING/no-owner-prefix(akroasis-server) — needs a GNOMON-reviewed rename; an identity decision, not a mechanical fix.ARCH/substrate-dead-dep(sphragis) — deliberately staged git dependency; tracked follow-up (akroasis#172) is "wire sphragis into the pinax reference-store workflow" + a cryptographic review, neither of which has happened yet becausepinaxdoesn't exist as code yet.RUST/plain-string-secret(ListEntryReport.credential_type) — confirmed rule false positive: the field is a JSON-serialized category label ("ApiKey", "Password"), not secret material. The rule's allowlist covers_hash/_digest/_fingerprint/public_keysuffixes but not_type.RUST/doc-promised-observability(delivery.rs::prune_completed) — confirmed rule false positive: the doc contains "records" (the data-record noun) + a structured "WARNING:" comment tag, which collide with the rule's verb/object keyword heuristic. Nothing here claims tracing.CI/release-yml-missing-attestation— confirmed rule false positive:release-please.ymlonly creates a version-bump PR/tag, it builds no artifacts to attest.release.yml(the actual artifact publisher) already has SLSA/SBOM attestation.TOML/missing-trailing-comma(.gitleaks.toml) — confirmed rule false positive via minimal repro: a trailing# commentafter a valid comma on the last multi-line array element defeats the checker regardless of quote style.Verification
cargo fmt --all -- --check— cleancargo check --workspace --all-targets --features syntonia/hardware-serial— cleancargo clippy --workspace --all-targets -- -D warnings— clean (matches CI's clippy stage, which deliberately omits the feature per Lint marker debt: 7 unwired-dead-code-untracked sites need real tracking refs + 1 crate-name-collision (koinon) #264's NOTE)cargo nextest run --workspace --features syntonia/hardware-serial— 1056/1056 passedkanon lint .— 0 open violations, baselinedrifted: 0Refs #261