epic: Enforced code-quality gating — port + extend craig's lint regime & debt ratchet
## Why
The canopy-vs-craig code-quality review (10 independent agents, 5 per repo, 2026-06-09) found both codebases at **A−** with the same engineering DNA, but craig consistently ahead on the *enforced-gating* dimensions:
- **KISS / complexity** (craig A− vs canopy B+) — a 40-line function ceiling (`clippy.toml too-many-lines-threshold = 40`) + `cognitive_complexity = deny`. Canopy's single most-repeated review complaint was oversized files (`services/canopy-web/src/api/case_detail.rs` **3,792 lines**, `canopy-medicaid/src/main.rs` 935).
- **Panic-safety / idiomatic** — craig denies the whole panic class workspace-wide (`unwrap_used`/`expect_used`/`panic`/`indexing_slicing`/`arithmetic_side_effects`), with `allow_attributes_without_reason = deny` (every suppression carries a `reason`).
- **Error handling** (craig A vs canopy A−) — `unused_must_use = deny`, structured field-level errors.
- All backstopped by an **`xtask quality-budgets` debt ratchet** that caps residual debt and **fails the build on regression**.
Canopy's quality today is **conventional** (discipline + review + `clippy -D warnings` + per-crate `forbid(unsafe_code)`); craig's is **enforced** (mechanically regression-proof). This epic ports craig's model so canopy's quality cannot silently regress — a real asset for ATO-bound eligibility software — and **improves on it**: centralize via `[workspace.lints]` (Rust 1.74+) to avoid craig's own flagged smell (an ~80-line lint header copy-pasted at 60+ crate roots; canopy currently has **no** `[workspace.lints]` table, itself a review finding).
**Precedent:** &46 (Crate Quality Parity, closed) already backported docs / test-lib / newtype-IDs *from craig*. This continues that lineage with the lint-gating regime + ratchet specifically.
Honest framing: canopy is **already near-craig** (A−, ~6 production `unwrap`s). The value here is **lock-in + closing the last gap + regression-proofing**, not rescuing bad code. The ratchet makes it tractable on 234k LOC without a big-bang freeze.
## Workstreams (each gets an ADR-013 plan before issues are cut)
1. **Centralized lint regime** — `[workspace.lints]` in root `Cargo.toml`: panic-class denies, `pedantic`+`nursery` as deny-with-documented-`#[expect(reason=…)]`-escapes, `allow_attributes_without_reason = deny`, `unused_must_use = deny`; `clippy.toml` with `too-many-lines-threshold` + `cognitive_complexity = deny`.
2. **Quality-budgets debt ratchet** — port craig's `xtask quality-budgets` (budgets.toml + .lock), seed at current canopy debt, wire into pre-push + CI (fail-on-regression). Extends the existing `.coverage-baseline.json` pattern.
3. **Ratchet-driven burndown** (per lint family) — production `.expect()` → `Result`/`?` where not startup-fatal; function-size + complexity splits.
4. **High-value specific fixes the review surfaced (free, evidence-based seed backlog):**
- Split `case_detail.rs` (3,792) + `canopy-medicaid/src/main.rs` (935, inline ELE handler → `events.rs`).
- Hoist the inlined program-scope authz gate (`worker.in_program_scope(...)`, ~39× across 9 files) into one extractor/middleware (**DRY + security** — a new handler can silently forget the copy today).
- Retire the fail-open empty-program-claim default (`canopy-web/src/session.rs`, see-all) → deny-by-default before ATO.
- Fix the blanket `From<sqlx::Error> → Internal/500` that masks 409/422 constraint violations.
- Dedup per-service signer (`EcdsaSigner`/`NoopSigner`) + event-publisher boilerplate into shared helpers (`canopy-signing`).
- Doc fix: `CLAUDE.md` "compile-time verified queries (sqlx)" overstates — code uses runtime `query_as` + `.bind()` (injection-safe, not `query!`-checked). Correct the claim, or adopt `query!` where a build-time DB is acceptable.
5. **Property-based testing** (a shared craig+canopy weakness — both ~7 `proptest` blocks) — expand on calculation/validation paths. (Stretch.)
## Next
Author a per-area ADR-013 plan — start with workstreams 1+2 (the tooling), since they enable the ratchet-driven rest — **before cutting issues**. Sequencing vs epic &56 (fact-authoring) + Sept-2026 SNAP UAT: the ratchet means this runs **incrementally in parallel**, no freeze.
epic