Skip to content

policy(no-panic): burn down 150 new debt sites toward zero #575

Description

@EffortlessSteven

Summary

cargo xtask check-no-panic-family runs in no-new-debt mode against policy/no-panic-baseline.toml and currently reports 150 new debt sites, 0 stale, 0 expired (the original brief said 149; the live count from a fresh run on main is 150). The new debt clusters heavily in test code: 46 in crates/uselesskey-cli/tests/cli.rs alone, and the majority of the remaining 104 are in #[cfg(test)] mod tests { ... } sections inside production source files (e.g. seed.rs, sink.rs, cache.rs, xtask/src/main.rs). A small minority (~15) appears to be genuine production-path expect/panic! (Serialize::to_value, downcast_or_panic) that should either propagate Result or move into the receipted allowlist as infallible_invariant. The lane is open — no other open issue tracks this — and per docs/NO_PANIC_POLICY.md the baseline is expected to burn down toward zero, not be refreshed to absorb new debt.

Snapshot

no-panic: 4307 finding(s); 0 allowlisted; 4157 baselined; 150 new-debt; 54 stale-baseline; 0 expired (mode=no-new-debt; baseline=2485/2539)
Error: no-panic policy is no-new-debt and there are 150 new debt site(s), 0 stale allowlist entries, and 0 expired allowlist entries

(Reports: target/no-panic.md, target/no-panic.json.)

Top files by site count

Derived by cross-referencing target/policy-proposed/no-panic-proposed-allowlist.toml (cargo xtask no-panic propose) against policy/no-panic-baseline.toml.

File Count Dominant family Notes
crates/uselesskey-cli/tests/cli.rs 46 expect (46) pure integration-test expect-ladder
xtask/src/main.rs 31 expect (22), unwrap (9) all inside #[cfg(test)] mod tests
crates/uselesskey-jwk/src/srp/shape.rs 15 expect (15) ~8 production to_value/Display::fmt + 7 in #[test]
crates/uselesskey-token/src/srp/shape.rs 15 expect (15) mix of production JWT split/decode + tests
crates/uselesskey-core/src/srp/seed.rs 10 unwrap (10) all in #[cfg(test)] mod tests
crates/uselesskey-core/src/srp/sink.rs 8 unwrap (8) all in #[cfg(test)] mod tests
crates/uselesskey-core/src/srp/cache.rs 6 unwrap (4), panic_macro (1), expect (1) one production downcast_or_panic (L174) + 5 test
crates/uselesskey-jwk/src/srp/kid.rs 4 expect (4) test code
crates/uselesskey-core/src/srp/factory.rs 3 panic_macro (2), expect (1) review for infallible_invariant
crates/uselesskey-x509/src/srp/derive.rs 2 expect (1), unwrap (1) review
crates/uselesskey-jwk/src/srp/builder.rs 2 expect (2) review
crates/uselesskey-core/src/srp/negative/pem.rs 2 expect (1), unreachable (1) review
crates/uselesskey-token/tests/negative_fixtures.rs 2 unwrap (2) test code
crates/uselesskey-core/src/srp/negative/der.rs 1 unreachable (1) review
crates/uselesskey-jwk/tests/negative_fixtures.rs 1 expect (1) test code
crates/uselesskey-x509/src/srp/spec/chain_spec.rs 1 unreachable (1) review
xtask/src/public_surface.rs 1 expect (1) review

Cluster breakdown

By family:

  • expect: 110
  • unwrap: 34
  • panic_macro: 3
  • unreachable: 3

By crate (entries / dominant cluster):

  • crates/uselesskey-cli: 46 — 100% integration-test expect in tests/cli.rs
  • xtask/src: 32 — 31 in #[cfg(test)] mod tests, 1 production in public_surface.rs
  • crates/uselesskey-core: 30 — all but cache.rs:174 (downcast_or_panic) and small bits in factory.rs/negative/* are unit tests
  • crates/uselesskey-jwk: 22 — ~8 production Serialize::to_value/Display::fmt expect, rest are unit tests
  • crates/uselesskey-token: 17 — mix of production JWT split/decode expect and unit tests
  • crates/uselesskey-x509: 3 — review individually (derive.rs, spec/chain_spec.rs)

By suspected root cause:

  • Test code that should use uselesskey-test-support helpers (ensure!, ensure_eq!, require_some, require_ok) — ~135 of 150 sites. This is the dominant cluster across cli.rs, xtask/src/main.rs#[cfg(test)], and the #[cfg(test)] mod tests blocks in seed.rs, sink.rs, cache.rs, jwk/src/srp/shape.rs, token/src/srp/shape.rs, kid.rs, etc.
  • Production code that needs case-by-case review — ~15 of 150 sites. Examples:
    • crates/uselesskey-core/src/srp/cache.rs:174 — documented panic! in downcast_or_panic (infallible_invariant candidate)
    • crates/uselesskey-jwk/src/srp/shape.rs:53,90,328,339,371,382,419,430serde_json::to_value(self).expect(...) / to_string(self).expect(...) on a struct whose Serialize impl is total (either drop the expect and document, or move to allowlist as infallible_invariant)
    • crates/uselesskey-token/src/srp/shape.rs:191,294-296,315,316,319,328,353,361,373,374,604,609,610 — JWT segment split/base64-decode helpers in production; some should propagate Result, others may be invariants on caller-controlled input
    • crates/uselesskey-core/src/srp/factory.rs:* — 2 panic_macro + 1 expect (review)
    • crates/uselesskey-core/src/srp/negative/{pem,der}.rs, crates/uselesskey-x509/src/srp/{derive,spec/chain_spec}.rs — handful of unreachable!/expect in negative-fixture builders (likely fixture classification)
  • Genuinely pre-existing debt that should have stayed in the baseline — 0 expected; if any surface during phase 1, allowlist them with owner+reason+expiry rather than refreshing the baseline.

Burndown plan

  1. Phase 1 — Migrate test-code expect/unwrap to uselesskey-test-support fallible helpers. Target: 0 sites in the test-code cluster (~135 sites). Convert affected tests to -> TestResult<()> and use require_ok, require_some, ensure!, ensure_eq!. One PR per crate to keep diffs reviewable. After each crate PR, run cargo xtask no-panic baseline to drop the now-resolved entries from policy/no-panic-baseline.toml (the refresh refuses to absorb new debt — it only removes vanished ones).
  2. Phase 2 — Review remaining production-code sites case-by-case (~15 sites). For each: prefer converting to ?-propagated errors with thiserror-shaped types; for genuinely total operations (e.g. Serialize impls on known structs, downcast_or_panic documented as an API contract), add a receipted entry to policy/no-panic-allowlist.toml with classification (infallible_invariant / production / fixture), owner, explanation, and expires.
  3. Phase 3 — Flip policy/clippy-lints.toml panic_family_level from warn to deny. This is Stage C in the [stage] block (currently current = "A", description references warn-stage panic-family lints burning down to deny). Gate: mode = "no-new-debt" checker exits 0, baseline has burned down to entries that are exclusively infallible_invariant/production and have been promoted into the allowlist with receipts. At that point the baseline can be retired and the checker can advance to mode = "blocking".
  4. Optional intermediatecargo xtask no-panic baseline to refresh the snapshot only after a deliberate burndown PR (it auto-drops vanished entries). Do NOT use --reset for this lane; that resets the snapshot and absorbs all new debt, which is what we are explicitly trying to avoid.

Crates / PR slicing

Each crate with more than 5 sites is a candidate for its own PR:

  • crates/uselesskey-cli — 46 sites, 1 file (tests/cli.rs). Largest single-PR win.
  • xtask/src — 32 sites (31 in main.rs#[cfg(test)], 1 production in public_surface.rs). Single PR.
  • crates/uselesskey-core — 30 sites across seed.rs, sink.rs, cache.rs, factory.rs, negative/{pem,der}.rs. Test-cluster (test modules) is a clean PR; the small production-path subset (cache.rs:174, factory.rs panics) can be a follow-up PR that adds receipted allowlist entries.
  • crates/uselesskey-jwk — 22 sites in shape.rs, kid.rs, builder.rs, tests/negative_fixtures.rs. Mix of test + production; split into test-migration PR and production-receipts PR.
  • crates/uselesskey-token — 17 sites in shape.rs and tests/negative_fixtures.rs. Same split as uselesskey-jwk.

Crates with 5 sites or fewer (crates/uselesskey-x509, crates/uselesskey-core/src/srp/negative/*) can be cleaned up in a single mop-up PR after the big ones land.

Acceptance criteria

  • cargo xtask check-no-panic-family exits 0 with mode = "no-new-debt", i.e. 0 new-debt, 0 stale, 0 expired.
  • All production-path findings either no longer panic or have a receipted allowlist entry (owner is not FIXME, explanation is concrete, expires is set).
  • After phase 3, policy/clippy-lints.toml panic_family_level = "deny" and [stage].current = "C".

References

  • docs/NO_PANIC_POLICY.md — definition, stages, identity model, classifications, modes.
  • policy/no-panic-allowlist.toml — receipted authoritative ledger.
  • policy/no-panic-baseline.toml — snapshot, should burn down to zero.
  • policy/clippy-lints.tomlpanic_family_level = "warn", [stage] block (current = "A", description = "Warn-stage panic-family lints; semantic no-panic checker advisory; bare-allow blocking.").
  • crates/uselesskey-test-supportensure!, ensure_eq!, require_ok, require_some, TestResult.
  • Generated artifacts (re-run cargo xtask check-no-panic-family + cargo xtask no-panic propose to refresh): target/no-panic.md, target/no-panic.json, target/policy-proposed/no-panic-proposed-allowlist.toml.

Not in scope

This issue is the burndown plan and tracking ticket, not a code change. No files in this repository should be edited as part of resolving this issue — the PRs that execute the plan are the deliverables. In particular: do not run cargo xtask no-panic baseline --reset to paper over the symptom; the entire point of the no-new-debt mode is to force the burndown.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions