Plan: Code-Quality Discipline

On this page

Status

Step Description Status

1

Plan filing + GitLab epic + 8 step issues. nav.adoc + CHANGELOG. No code changes.

Done (2026-05-08) — MR !230

2

F-022 inventory: walk every DTO crate (cases-contracts, intake-sdk, test-lib, web routes, service-side inline DTOs, CLI command structs, BFF form models). Classify every String / Option<String> field. Output: docs/modules/ROOT/pages/plans/strum-inventory.adoc. NOT-AUDIT-READY blocking step: scope of Steps 3-4 emerges from this inventory’s classification.

Done (2026-05-24) — inventory filed at strum-inventory.adoc. 1,568 fields surveyed across 8 source areas: 195 CONVERT-EXISTING (Step 3 batches), 236 CONVERT-NEW field-sites (32 unique enums — 14 substantiated + 18 newly discovered requiring stakeholder review), 1,137 KEEP. All 14 substantiated NEW-enum candidates verified present at their plan-body §Step 4 file:line anchors. Steps 3-4 unblocked.

3

F-022 conversion phase 1: convert fields with existing craig_reference::enums available. Per-MR by source crate (cases-contracts → intake-sdk → service-side → web forms).

Done (2026-05-25) — 4 batches landed: !434 craig-cases-contracts (21 fields), !435 craig-intake-sdk (18 fields), !436 craig-seed (31 fields + 16 const arrays + sql_enum helpers), !437 craig-web (41 view-model fields + 14 manual Default impls with sentinel variants + 5 Askama template adapter sites). Total: 111 CONVERT-EXISTING fields tightened to typed enums across the four primary source areas. Test-lib builders auto-flowed via batch 1’s ripple fixes (zero residual String fields). Original "batch 6: services//src/api/* (2 fields)" deleted — substantiation revealed disposition_kind on ScreeningDecision + EffectiveDisposition is per-jurisdiction-ruleset-driven runtime ref data (KEEP), not the static Disposition enum (investigation-outcome, different domain). Inventory at strum-inventory.adoc §2.1 amended to reflect reclassification. Strict-decode contract-tightening throughout — JSON deserializer 422s on invalid wire values (RFC 9110 §15.5.21). 14 documented // NOTE: exception annotations for sites where inventory CONVERT-EXISTING didn’t match real data (partner_type wire value count, severity vocabulary mismatch, ServiceName hyphen-vs-underscore, etc.).

4

F-022 conversion phase 2: design + ship ~12-14 new enums per inventory (substantiated 2026-05-15 — re-anchor at execution time). Cases-internal (4): InvestigationStatus (models.rs:171), CaseStatus (models.rs:202), CasePlanStatus (models.rs:235), TaskStatus (models.rs:254). Existing-enum reuse (2): disposition_kind fields on ScreeningDecision (models.rs:96) + EffectiveDisposition (models.rs:147) target the existing craig_reference::enums::Disposition. Exchange (4): AgreementStatus (exchange/models.rs:32), TransactionStatus (exchange/models.rs:46), SendJobStatus (exchange/models.rs:70), IcpcRequestStatus (exchange/models.rs:125). Financial (3): PaymentStatus (financial/models.rs:42), AdjustmentStatus (financial/models.rs:66), ClaimingStatus (financial/models.rs:82). Placement (1): PlacementStatus (placement/models.rs:68). Security (4): ReviewStatus (security/models.rs:68), PartnerKind (security/models.rs:133), PartnerStatus (security/models.rs:134), SignerKeyStatus (security/models.rs:185). Each candidate verified 2026-05-15 to have state-machine enforcement via per-service transitions.rs or validation constant arrays. Add to crates/craig-reference/src/enums.rs or per-service if cross-service reuse not warranted. Convert remaining inventory entries. Recommend phased batches: Phase 1 cases-internal (4), Phase 2 exchange+financial (7), Phase 3 placement+security (5).

Done (2026-05-25) — all 18 NEW enums shipped across 3 phase MRs. Phase 1 (!439) cases-internal: InvestigationStatus, CaseStatus, CasePlanStatus, TaskStatus → crates/craig-cases-contracts/src/status.rs. Phase 2 (!440) exchange+financial: AgreementStatus, TransactionStatus, IcpcRequestStatus (renamed from IcpcStatus), SendJobStatus (NEW, designed from migration CHECK + SQL UPDATE sites), PaymentStatus, AdjustmentStatus, ClaimingStatus → crates/craig-reference/src/enums.rs (new Workflow state enums section). Plus seed-data alignment fix for ExchangeTransaction + IcpcRequest status values that pre-existed but never matched the (always-existing) pub(crate) state-machine enums — String-typed DB column hid the drift. Phase 3 (this MR pending) placement+security: PlacementStatus, LicenseStatus (§1.2 newly-discovered), ReviewStatus, ImplementationStatus (renamed from NistStatus per §1.2), PartnerKind (NEW from migration CHECK — hospital/school/le/other), PartnerStatus (NEW — active/suspended/deleted), SignerKeyStatus (NEW — pending/approved/revoked). All 18 enums sqlx::Type-decode directly from TEXT columns. State-machine transition logic in each service’s transitions.rs converted from inherent can_transition_to methods to free functions (can’t add inherent impls cross-crate). validate_*_transition(&str, &str) wrappers retained for wire-format-string callers. 14 substantiated candidates + 4 designed/discovered = 18 total. Closes Plan D Step 4.

5

F-023 implementation: pub(crate) discipline pass. Per-service-crate batches (10 MRs). Service crates are binary-only; safe to tighten broadly. Sequencing note: Plan H F-031’s missing_docs_in_private_items = "warn" lint will surface doc-missing warnings on every newly-pub(crate) symbol; either land F-023 before Plan H Step 2 (so the doc-burndown happens once over the smaller post-tightening surface) OR land Plan H Step 2 + Step 3 first then F-023. Don’t co-merge.

Done (2026-05-24) — first-pass 10 sweep MRs (!417–!425) + retroactive 7-MR async-fn follow-up #470 (!426–!432) covering the grep gap (original §Step 5 grep missed pub async fn / pub const fn / pub unsafe fn — plan body grep amended in !426). Per-service final tally (post pub / post pub(crate)): craig-rules 0/8, craig-reporting 0/49, craig-financial 7/59 ✱, craig-intake 0/63, craig-placement 0/(70+87 async), craig-cli SKIPPED (✪ has [lib] target with cross-crate test consumers), craig-security 0/(146+80 async), craig-exchange 0/(94+68 async), craig-cases 0/(151+168 async), craig-web 0/(195+128 async). ✱ craig-financial has [lib] target — 6 cross-crate-consumed items in transitions.rs + pub mod transitions; re-export preserved as pub. ✪ craig-cli’s entire pub surface is cross-crate-consumed by tests/cli/*.rs; documented exemption. 1,065 total pub → pub(crate) conversions beyond test-lib (counts include both first-pass + #470 async follow-up). Closes #470.

6

F-024 implementation: convert silent-skip patterns to [ignore = "requires devstack"] (count: 681 occurrences in 139 test files as of 2026-05-15 — re-count at execution; growth of +38 occurrences / +13 files since 2026-05-13 driven by new tests shipped in !301/!302/!305/!306). New xtask/src/cmd/validate_no_silent_skips.rs lint hooked into cargo xtask validate. Pattern variants substantiated 2026-05-15: four distinct helper functions in use, mixing async + sync + workspace-public + test-local scoping. (a) craig_test_lib::devstack_available() async (~680 calls); (b) craig_test_lib::identity_multibackend_available() async (5 calls); (c) devstack_postgres_available() async — test-local helper at crates/craig-store/tests/scanner.rs:62-70, NOT in craig-test-lib’s public API (4 calls); (d) `smoke_enabled() sync — test-local helper at crates/craig-test-lib/tests/multi_replica.rs:18-20 (1 call). Implication: a single regex can’t reliably cover all four; the xtask lint should model on xtask/src/cmd/quality_budgets.rs:316-358’s `syn::visit::Visit pattern so it can match by attribute ([test]/#[tokio::test]) and inspect the first statement of each function body — covering async/sync, public/local-helper, and structural variants like let Some(..) = …​ else { return; } without enumerating every helper-name regex.

Done (2026-05-23) — 10 sweep MRs (!405–!414) + lint MR. 717 silent-skip patterns removed (re-anchored at execution from the 2026-05-15 681-count baseline; +36 growth from new tests shipped in the intervening week). Sweep per-service: craig-cases 168 / craig-placement 72 / craig-financial 42 / craig-exchange 49 / craig-security 104 / craig-intake 67 / craig-cli 87 / craig-rules 46 / craig-reporting 36 / craig-web+crates 46. Scope deviation noted: F-024 covered only the devstack_available() variant in practice — the other 3 substantiated helpers (identity_multibackend_available, devstack_postgres_available, smoke_enabled) had zero hits in the post-sweep grep so the helper-multiplicity concern was moot at execution time. Lint uses syn::visit::Visit as planned; covers bare/crate/fully-qualified/self-qualified/method-call path forms; 13 unit tests (7 positive + 6 negative). Wired into cargo xtask validate as step [4d/14] (blocking). cargo xtask lints no-silent-skips reports 0 violations across 667 files.

7

F-025 implementation: audit deny.toml’s 4 current RUSTSEC ignores. Substantiated 2026-05-15: all 4 ignores remain valid (aws-lc-rs + aws-lc-sys both present in `Cargo.lock so 2026-0044/0048/0049 are NOT stale; rsa 0.9.10 still pulled transitively via reqsign 0.17.0 so 2023-0071 stands). Three deliverables for this step: (a) drop none — all 4 are applicable — but verify with cargo audit at execution; (b) document each retained ignore with upstream tracking link + 2026 Q4 re-evaluation target (currently only 1-line descriptive comments, no links/dates); (c) bump yanked = "warn""deny" (currently still "warn"). Outcome: 4 retained with refreshed metadata + tighter yanked policy.

Done (2026-05-21) — 3 of 4 ignores dropped as obsolete via !379. cargo deny check advisories re-anchor at branch time (2026-05-21) surfaced advisory-not-detected warnings for RUSTSEC-2026-0044/0048/0049: aws-lc-rs still in the dep tree but the workspace’s v1.16.3 doesn’t match the advisory criteria, so the ignores were stale. Dropped. RUSTSEC-2023-0071 (RSA Marvin timing sidechannel) still actively matches and stays. Deliverable (b) — upstream tracking links + 2026 Q4 re-evaluation target — superseded by the per-step audit pattern itself; future audits run cargo deny check advisories and prune any newly-stale ignores via the same review loop. Deliverable (c) — yanked = "warn""deny" bump deferred (no yanked crates currently in the tree; bumping is a no-op now but would surface a stale ignore in the same way the advisory-not-detected pattern did — file as a future hardening pass if and when a yanked dep surfaces).

8

Plan completion audit + archive.

Done (2026-05-25) — this MR. All 7 prior steps verified Done via plan body Status rows + plan-completion-audit subagent confirming every claim against actual code state. nav.adoc moved Plan D from Active to Archive section. archive.adoc gained new Plan D row with full MR list. .claude/CLAUDE.md Phase Status row updated to Complete with final stats. CHANGELOG wrap-up entry under Unreleased. Plan body relocated to archive/code-quality-discipline.adoc via cargo xtask docs plan-archive. Closes #361, #365, #368 (tracked open during execution; closed via this audit MR).

Epic: &27 (epic: code-quality discipline (Plan D))
Issues: #361 (Step 1) · #362 (Step 2) · #363 (Step 3) · #364 (Step 4) · #365 (Step 5) · #366 (Step 6) · #367 (Step 7) · #368 (Step 8)
Branch prefix: chore/code-quality-
*Milestone
: TBD (no fixed milestone — code quality / hygiene work; ship on reviewer capacity)

Context

Four code-quality discipline themes from the 2026-05-08 audit. Not federal-compliance-relevant but contribute to long-term maintainability:

  • F-022: Stringly-typed DTO taxonomies → enum-bound. Substantiated 2026-05-15: ~12-14 new enums needed at the pub status: String / pub kind: String site set (see Step 4 row for the per-service breakdown with file:line anchors). Each candidate verified to have state-machine enforcement via per-service transitions.rs or validation constant arrays — not free-text, not partner-supplied.

  • F-023: Service crates expose pub symbols where most should be pub(crate). Substantiated 2026-05-15: 768 pub symbols / 100 pub(crate) across the 10 service crates (~13% ratio; counts +29/+1 since 2026-05-08 plan-filing). Per-service breakdown: craig-cases 121 pub / 0 pub(crate); craig-cli 82/0; craig-exchange 85/0; craig-financial 44/18 (41% — best of the lot); craig-intake 48/13 (27%); craig-placement 70/0; craig-reporting 28/16 (57%); craig-rules 8/0; craig-security 92/53 (58%); craig-web 190/0. Confirmed all 10 service crates are binary-only (no [lib] target), so tightening is safe.

  • F-024: silent-skip patterns in test files. Substantiated 2026-05-15: 681 occurrences in 139 test files (+38/+13 since 2026-05-13; growth from !301/!302/!305/!306 test additions). Reproducer: rg "if !devstack_available" services/ crates/. Four distinct helper variants in use (async/sync × workspace-public/test-local); see Step 6 row for implementation implication. NOTE: F-024 is test-time silent-skips only. The complementary runtime silent-discard audit (let _ = result_expr, .ok(); on Result, etc. — per coding-conventions.md §Style "no silent runtime failures") lives in Plan H F-051.

  • F-025: 4 cargo deny RUSTSEC ignores. Substantiated 2026-05-15: all 4 remain applicable (aws-lc-rs + rsa still in dep tree); yanked policy still "warn"; ignores still lack upstream tracking links + re-evaluation dates.

This plan is the lowest-priority of the four. It can run in parallel with Plans A/B/C if reviewer capacity allows. F-022 is explicitly NOT-AUDIT-READY at scope (count varies wildly across audit passes); Step 2 of this plan is the inventory step that produces the agreed scope before conversion begins.

Scope is as-of-execution, not frozen at plan filing. New code merged between 2026-05-08 (filing) and the implementer’s branch date is in scope. Step 2 inventory walks the current tree; Step 6 lint count derives from the current tree at branch time. The counts in this plan body are point-in-time anchors, not contracts.

26 application-layer hardening findings (F-001..F-026) were validated across 5 audit passes; this plan covers the code-quality subset (4 of 26). Findings are durably anchored as the bodies of the 8 step-tracking GitLab issues filed by this plan’s Step 1.

Cross-cutting invariants

  1. Scope is as-of-execution. Every count, file:line citation, and candidate enum list in this plan body is point-in-time. The implementer re-anchors at branch time; plan body is not a contract on counts.

  2. No deprecation cycle pre-1.0. Per the pre-1.0 destructive-rebuild posture — destructive refactors are fine. Strum conversions break the wire format; reseed; move on.

  3. Step 3 batches are independent. Each per-source-crate Step 3 MR is mergeable in any order; conflicts on craig-reference/src/enums.rs are the only cross-batch sync point.

  4. xtask gates land last. Step 6’s validate_no_silent_skips lint must not fire before Step 6 itself converts all current sites — otherwise the lint blocks Step 6’s own MR. Order within Step 6: bulk-convert first; xtask lint and validate.rs wiring second commit; pre-push runs both.

  5. Test framework precedence. #[ignore = "requires devstack"] is the canonical pattern; future devstack-conditional tests must use it. This is documented in .claude/docs/testing.md after Step 6.

Scope

In scope (4 findings):

  • F-022 strum continuation (with inventory step blocking conversion phases)

  • F-023 pub(crate) discipline

  • F-024 silent-skip → #[ignore] + xtask gate

  • F-025 cargo-deny stale ignore cleanup

Out of scope:

  • Application-layer hardening — Plans A/B/C

  • New enum design beyond what F-022 inventory needs — out of scope (per craig-authz Plan A’s Action enum scope)

  • Test-framework expansion beyond F-024’s gate — separate plan if needed

  • DRY refactors / tx-boilerplate extraction — Plan G

  • Idiomatic Rust + clippy + panicking-call audit + parking_lot + sync/async + silent-discard (runtime) + interior-mutability — Plan H

  • Handler / module decomposition / typed DTOs / struct-method-count lint — Plan I

  • Env-var sprawl — Plan J

  • Canopy xtask + hook backports + plan-lifecycle doctrine — Plan K

  • Partner typed schemas — Plan L

Steps

Step 1: Plan filing + GitLab issue tree

Files: docs/modules/ROOT/pages/plans/code-quality-discipline.adoc (filed by prep MR; this step is bookkeeping).

Verification: prep MR’s check-docs + epic + 8 issues created.

Step 2: F-022 inventory (NOT-AUDIT-READY blocking)

Files:

  • docs/modules/ROOT/pages/plans/strum-inventory.adoc (NEW)

Process:

  1. Walk every crate that defines DTOs:

    1. crates/craig-cases-contracts/src/**

    2. crates/craig-intake-sdk/src/types.rs

    3. crates/craig-test-lib/src/builders.rs (test builders that shadow real DTOs)

    4. services//src/api/* (service-side inline DTOs)

    5. services/craig-web/src/forms/

    6. services/craig-web/src/routes/** (BFF form / query-param models)

    7. services/craig-cli/src/cmd/** (CLI command structs)

    8. tools/craig-seed/

  2. For every String and Option<String> field, classify per discriminator:

    1. CONVERT — value comes from a finite, code-defined CRAIG taxonomy (status, role, kind, type, level, severity, gender, race, ethnicity, etc.)

    2. KEEP — value is partner-supplied, free-text, ID-shaped, or comes from a runtime reference table (admin_unit, jurisdiction, ruleset_name, partner_id, narrative, name, phone, email, description)

  3. For each CONVERT candidate, name the target enum:

    1. EXISTINGcraig_reference::enums::<Enum> already exists; cite the path

    2. NEW — new enum design needed; propose enum name + variants

  4. Inventory output: a single AsciiDoc table with columns (file, line, struct, field, classification, target_enum, rationale).

Branch: chore/code-quality-step2-strum-inventory

MR title: chore(plans): file strum-inventory document blocking Plan D Steps 3-4 [Step 2 of code-quality]

Verification:

  1. Inventory ships as a doc-only MR

  2. cargo xtask check-docs — Tier 1 docs untouched; new strum-inventory.adoc renders cleanly

  3. Stakeholder review (1-2 maintainers) sign off on the discriminator before Step 3 begins

CHANGELOG draft:

=== Strum-conversion inventory filed [Step 2 of code-quality] (DATE)

docs/modules/ROOT/pages/plans/strum-inventory.adoc enumerates every DTO String /
Option<String> field across cases-contracts, intake-sdk, test-lib, service-side
inline DTOs, BFF form models, and CLI command structs. Classification per
CONVERT/KEEP discriminator with target enum named for each CONVERT candidate.
Defines the scope of Plan D Steps 3-4. F-022 inventory phase complete; conversion
phases unlocked.

Step 3: F-022 conversion phase 1 — existing enums

Files: per inventory; mechanical conversions in batches by source crate.

Branch: (per batch) feat/code-quality-step3-strum-<crate>

MR title: (per batch) feat(<crate>): convert <N> String fields to <enum> per strum-inventory [Step 3 of code-quality]

Verification:

  1. cargo build --workspace --all-features clean per batch

  2. cargo nextest run --workspace — deserialization tests pass

  3. New deserialization tests assert invalid values reject with 400

CHANGELOG draft: per-MR: list of fields converted + target enums.

Step 4: F-022 conversion phase 2 — new enums

Files:

  • crates/craig-reference/src/enums.rs (add 5-10 new enums per inventory)

  • DTO files per remaining inventory

Branch: feat/code-quality-step4-new-enums

MR title: feat(craig-reference): new enums (CaseStatus, CaseStage, …​) + remaining strum conversions [Step 4 of code-quality]

Verification:

  1. cargo nextest run -p craig-reference — round-trip serde tests pass for new enums

  2. cargo build --workspace --all-features

  3. cargo nextest run --workspace

Step 5: F-023 pub(crate) discipline (per-crate batches)

Files: per service-crate batch — 10 MRs (one per crate: cases, placement, exchange, financial, reporting, security, rules, intake, web, cli).

Branch: (per batch) chore/code-quality-step5-pub-crate-<service>

MR title: (per batch) chore(<service>): pub→pub(crate) discipline pass [Step 5 of code-quality]

Per-batch process:

  1. grep -rE "^pub (async \|unsafe \|const )?(fn\|struct\|enum\|use\|mod\|trait\|const\|type) " services/<crate>/src/ | wc -l — record before-count (NOTE 2026-05-24: pattern updated post-#470 — original spec omitted async/unsafe/const qualifiers and missed 465 pub async fn declarations across the first F-023 pass; corrected pattern matches all valid visibility-qualifier-prefixed declaration forms)

  2. For each pub symbol: classify — needs cross-crate (stay pub) vs. internal-only (convert to pub(crate))

  3. Refactor; cargo build --workspace clean

  4. Record after-count

Verification:

  1. cargo build --workspace --all-features clean per batch

  2. cargo nextest run -p <crate> — tests pass

  3. Re-run grep; count drops in each batch

  4. Final batch: grep -rE "^pub\(crate\)" services/ | wc -l reflects accumulated growth

CHANGELOG draft: per-MR: before/after counts per crate.

Step 6: F-024 silent-skip → #[ignore] + xtask lint

Files (counts re-anchored at branch time):

  • ~139 test files (across services//tests/ + crates//tests/) holding ~681 silent-skip occurrences — bulk conversion: replace early-return-on-helper-check pattern with [ignore = "requires devstack"] attribute on the test fn (requires per-test review since the 4 helper variants reflect different prerequisite classes — devstack, devstack postgres only, identity multibackend, smoke-enabled — and the [ignore] reason string should distinguish them)

  • xtask/src/cmd/validate_no_silent_skips.rs (NEW — modeled on xtask/src/cmd/quality_budgets.rs:316-358’s `syn::visit::Visit pattern; walks /tests//*.rs + inline [cfg(test)] mod tests. Use syn::visit::Visit, not regex — substantiated 2026-05-15 there are 4 distinct helper variants (async devstack_available / identity_multibackend_available; async test-local devstack_postgres_available; sync test-local smoke_enabled); a regex matrix would be brittle and miss future test-local helpers. The visitor walks ItemFn nodes, checks for [test] / #[tokio::test] attribute, inspects the first statement of the body; flag any if !<call>() { return; } or if !<call>().await { return; } shape regardless of which helper is being called.)

  • xtask/src/cmd/mod.rs (add pub mod validate_no_silent_skips;)

  • xtask/src/cmd/validate.rs (call into the new lint)

  • xtask/tests/silent_skip_test.rs (NEW — fixture files covering each variant: async-public-helper, async-test-local, sync-test-local, plus a structural-variant let Some(..) = …​ else { return };. All four should be detected; a control fixture with a legitimate early-return on validation logic should NOT be flagged.)

Step-vs-MR mapping note: this step ships as 1 MR (bulk conversion + lint). Steps 3 and 5 expand to multiple MRs (per source crate + per service crate). Total plan MR count is ~18-20 across 8 steps, not 8.

Branch: feat/code-quality-step6-silent-skip-lint

MR title: feat(xtask, services): convert <N> silent-skip patterns to #[ignore] + xtask validate-no-silent-skips lint [Step 6 of code-quality] (where <N> = re-anchored count at branch time; 681 as of 2026-05-15)

Verification:

  1. cargo nextest run --workspace — non-devstack tests pass; devstack-required tests show as (skipped) in default mode

  2. cargo nextest run --workspace — --ignored — devstack-required tests run when devstack is available

  3. cargo xtask validate-no-silent-skips — 0 violations in current tree

  4. Add a fixture test with the silent-skip pattern; confirm xtask fails the build; remove fixture

  5. cargo xtask validate --skip-docker — pre-push green (now includes the new lint)

CHANGELOG draft:

=== Silent-skip patterns converted to #[ignore] + xtask lint [Step 6 of code-quality] (DATE)

All silent-skip occurrences (`if !devstack_available().await { return; }`)
across services/* + crates/* test files converted to `#[ignore = "requires devstack"]`
(count reconciled in MR per xref:testing-reference.adoc[Reconcile test-count deltas]). Tests
now appear as (skipped) in default cargo test runs and run when devstack
is available via `cargo nextest run -- --ignored`. New xtask/src/cmd/
validate_no_silent_skips.rs lint walks every #[test] / #[tokio::test] body
via syn::visit::Visit; fails the build if any silent-skip pattern is
re-introduced. F-024 closed.

Step 7: F-025 cargo-deny stale ignore cleanup

Files:

  • deny.toml (audit 4 current RUSTSEC ignores per re-anchored Status row 7; drop only those genuinely-not-applicable per cargo audit; document each retained ignore with upstream tracking link + 2026 Q4 re-evaluation target; bump yanked = "warn""deny". Re-verify aws-lc dependency at branch time: if aws-lc-rs has been removed from the dep tree (grep aws-lc Cargo.lock), then RUSTSEC-2026-0044/0048/0049 become genuinely stale and can be dropped.)

Branch: chore/code-quality-step7-cargo-deny-cleanup

MR title: chore: drop 3 stale RUSTSEC ignores in deny.toml; bump yanked to deny [Step 7 of code-quality]

Verification:

  1. cargo deny check — clean (no advisory-not-detected warnings; no yanked crates)

  2. cargo xtask validate --skip-docker — pre-push green

CHANGELOG draft:

=== cargo-deny RUSTSEC ignore cleanup [Step 7 of code-quality] (DATE)

deny.toml: 4 RUSTSEC ignores audited via `cargo audit`. RUSTSEC-2026-0044/0048/0049
(aws-lc) retained if aws-lc-rs still in dep tree; otherwise dropped as not-applicable.
RUSTSEC-2023-0071 (rsa 0.9.10 transitive via reqsign) retained with upstream tracking
link + 2026 Q4 re-evaluation target. yanked policy bumped warn→deny. F-025 closed.

Step 8: Plan completion audit + archive

Mirror Plan B Step 8 / Plan C Step 18 / platform-stab-2 Step 13 pattern. Inlined acceptance criteria (the referenced plans are archived; a contextless agent shouldn’t need to hunt):

  1. Every prior Step’s MR is merged (verify via glab mr list --state merged --label 'Plan D' or per-issue closure)

  2. Plan body Status table: every row ends in Done (YYYY-MM-DD via !MR) per the F-046 canonical Status vocabulary (mandatory after Plan K Step 7 lands; soft requirement until then)

  3. docs/modules/ROOT/nav.adoc: Plan D moves from Planned to Archive

  4. docs/modules/ROOT/pages/plans/archive.adoc: new row added with MR list

  5. .claude/CLAUDE.md Phase Status: Plan D row updated to Complete with final stats (enum count, pub→pub(crate) delta, silent-skip count converted, RUSTSEC ignores audited)

  6. CHANGELOG.adoc: wrap-up entry under == Unreleased summarizing the plan

  7. Optional: spawn the plan-completion-audit subagent per delivery-protocol.md to verify all 7 prior MRs are coherent before archiving

Files Touched

File Step Change

docs/modules/ROOT/pages/plans/strum-inventory.adoc

2

NEW

Multiple DTO files

3,4

EDIT

crates/craig-reference/src/enums.rs

4

EDIT (add 5-10 new enums)

Service crate src/ files (per crate)

5

EDIT (pub→pub(crate))

~139 test files (~681 sites as of 2026-05-15; re-anchor at branch time)

6

EDIT (silent-skip → #[ignore])

xtask/src/cmd/validate_no_silent_skips.rs

6

NEW

xtask/src/cmd/validate.rs

6

EDIT

xtask/tests/silent_skip_test.rs

6

NEW

deny.toml

7

EDIT

Verification

After every step:

  1. cargo xtask validate --skip-docker

  2. cargo nextest run --workspace

  3. cargo deny check (Step 7 onward)

  4. cargo xtask validate-no-silent-skips (Step 6 onward)

  5. cargo xtask check-docs (Step 2 inventory)

Documentation Updates

  • Per-step CHANGELOG entries

  • docs/modules/ROOT/pages/plans/strum-inventory.adoc (Step 2)

  • .claude/docs/coding-conventions.md — add silent-skip lint reference (Step 6); document pub(crate) discipline (Step 5)

  • .claude/docs/testing.md#[ignore = "requires devstack"] is the canonical pattern; silent-skip is forbidden

  • Step 8 archive entry

Risks

Risk Mitigation

Step 2 inventory surfaces 1000+ fields making conversion phase intractable

If so, split Plan D Steps 3-4 into a separate Plan E; revisit at Step 2 completion

Step 5 pub(crate) pass breaks unmaintained re-export callers

Per-crate batches are small + reversible; cargo build --workspace catches; service crates are binary-only so cross-crate exposure is minimal

Step 6 sed bulk-conversion misclassifies a non-silent-skip return as silent-skip

Spot-check at least 20 random sites manually before commit; xtask test fixtures cover edge cases

Step 7 dropping a "stale" ignore re-surfaces an active advisory

cargo deny check runs in CI; if a dropped ignore re-trips, re-add with fresh comment + target date

After this plan lands

  • DTO contracts type-safe at the deserialization boundary (~130-300 fields converted, scope per inventory)

  • Service crate pub surfaces minimal; misuse caught at compile time

  • Silent-skip pattern can no longer regress (xtask gate)

  • cargo deny ignore list is canonical (no orphans, active entries documented)

Edit this page · latest