Plan: Code-Quality Discipline
On this page
- Status
- Context
- Cross-cutting invariants
- Scope
- Steps
- Step 1: Plan filing + GitLab issue tree
- Step 2: F-022 inventory (NOT-AUDIT-READY blocking)
- Step 3: F-022 conversion phase 1 — existing enums
- Step 4: F-022 conversion phase 2 — new enums
- Step 5: F-023 pub(crate) discipline (per-crate batches)
- Step 6: F-024 silent-skip → #[ignore] + xtask lint
- Step 7: F-025 cargo-deny stale ignore cleanup
- Step 8: Plan completion audit + archive
- Files Touched
- Verification
- Documentation Updates
- Risks
- After this plan lands
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 |
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 |
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 |
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): |
Done (2026-05-25) — all 18 NEW enums shipped across 3 phase MRs. Phase 1 (!439) cases-internal: InvestigationStatus, CaseStatus, CasePlanStatus, TaskStatus → |
5 |
F-023 implementation: |
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 |
6 |
F-024 implementation: convert silent-skip patterns to |
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 |
7 |
F-025 implementation: audit |
Done (2026-05-21) — 3 of 4 ignores dropped as obsolete via !379. |
8 |
Plan completion audit + archive. |
Done (2026-05-25) — this MR. All 7 prior steps verified |
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: Stringsite set (see Step 4 row for the per-service breakdown with file:line anchors). Each candidate verified to have state-machine enforcement via per-servicetransitions.rsorvalidationconstant arrays — not free-text, not partner-supplied. -
F-023: Service crates expose
pubsymbols where most should bepub(crate). Substantiated 2026-05-15: 768pubsymbols / 100pub(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 denyRUSTSEC ignores. Substantiated 2026-05-15: all 4 remain applicable (aws-lc-rs + rsa still in dep tree);yankedpolicy 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
-
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.
-
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.
-
Step 3 batches are independent. Each per-source-crate Step 3 MR is mergeable in any order; conflicts on
craig-reference/src/enums.rsare the only cross-batch sync point. -
xtask gates land last. Step 6’s
validate_no_silent_skipslint 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 andvalidate.rswiring second commit; pre-push runs both. -
Test framework precedence.
#[ignore = "requires devstack"]is the canonical pattern; future devstack-conditional tests must use it. This is documented in.claude/docs/testing.mdafter 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
Actionenum 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:
-
Walk every crate that defines DTOs:
-
crates/craig-cases-contracts/src/** -
crates/craig-intake-sdk/src/types.rs -
crates/craig-test-lib/src/builders.rs(test builders that shadow real DTOs) -
services//src/api/*(service-side inline DTOs) -
services/craig-web/src/forms/ -
services/craig-web/src/routes/**(BFF form / query-param models) -
services/craig-cli/src/cmd/**(CLI command structs) -
tools/craig-seed/
-
-
For every
StringandOption<String>field, classify per discriminator:-
CONVERT — value comes from a finite, code-defined CRAIG taxonomy (status, role, kind, type, level, severity, gender, race, ethnicity, etc.)
-
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)
-
-
For each CONVERT candidate, name the target enum:
-
EXISTING —
craig_reference::enums::<Enum>already exists; cite the path -
NEW — new enum design needed; propose enum name + variants
-
-
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:
-
Inventory ships as a doc-only MR
-
cargo xtask check-docs— Tier 1 docs untouched; new strum-inventory.adoc renders cleanly -
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:
-
cargo build --workspace --all-featuresclean per batch -
cargo nextest run --workspace— deserialization tests pass -
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:
-
cargo nextest run -p craig-reference— round-trip serde tests pass for new enums -
cargo build --workspace --all-features -
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:
-
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 omittedasync/unsafe/constqualifiers and missed 465pub async fndeclarations across the first F-023 pass; corrected pattern matches all valid visibility-qualifier-prefixed declaration forms) -
For each
pubsymbol: classify — needs cross-crate (staypub) vs. internal-only (convert topub(crate)) -
Refactor;
cargo build --workspaceclean -
Record after-count
Verification:
-
cargo build --workspace --all-featuresclean per batch -
cargo nextest run -p <crate>— tests pass -
Re-run grep; count drops in each batch
-
Final batch:
grep -rE "^pub\(crate\)" services/ | wc -lreflects 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 onxtask/src/cmd/quality_budgets.rs:316-358’s `syn::visit::Visitpattern; walks/tests//*.rs+ inline[cfg(test)] mod tests. Usesyn::visit::Visit, not regex — substantiated 2026-05-15 there are 4 distinct helper variants (asyncdevstack_available/identity_multibackend_available; async test-localdevstack_postgres_available; sync test-localsmoke_enabled); a regex matrix would be brittle and miss future test-local helpers. The visitor walksItemFnnodes, checks for[test]/#[tokio::test]attribute, inspects the first statement of the body; flag anyif !<call>() { return; }orif !<call>().await { return; }shape regardless of which helper is being called.) -
xtask/src/cmd/mod.rs(addpub 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-variantlet 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:
-
cargo nextest run --workspace— non-devstack tests pass; devstack-required tests show as(skipped)in default mode -
cargo nextest run --workspace — --ignored— devstack-required tests run when devstack is available -
cargo xtask validate-no-silent-skips— 0 violations in current tree -
Add a fixture test with the silent-skip pattern; confirm xtask fails the build; remove fixture
-
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 percargo audit; document each retained ignore with upstream tracking link + 2026 Q4 re-evaluation target; bumpyanked = "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:
-
cargo deny check— clean (noadvisory-not-detectedwarnings; no yanked crates) -
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):
-
Every prior Step’s MR is merged (verify via
glab mr list --state merged --label 'Plan D'or per-issue closure) -
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) -
docs/modules/ROOT/nav.adoc: Plan D moves fromPlannedtoArchive -
docs/modules/ROOT/pages/plans/archive.adoc: new row added with MR list -
.claude/CLAUDE.mdPhase Status: Plan D row updated toCompletewith final stats (enum count, pub→pub(crate) delta, silent-skip count converted, RUSTSEC ignores audited) -
CHANGELOG.adoc: wrap-up entry under
== Unreleasedsummarizing the plan -
Optional: spawn the plan-completion-audit subagent per
delivery-protocol.mdto verify all 7 prior MRs are coherent before archiving
Files Touched
| File | Step | Change |
|---|---|---|
|
2 |
NEW |
Multiple DTO files |
3,4 |
EDIT |
|
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]) |
|
6 |
NEW |
|
6 |
EDIT |
|
6 |
NEW |
|
7 |
EDIT |
Verification
After every step:
-
cargo xtask validate --skip-docker -
cargo nextest run --workspace -
cargo deny check(Step 7 onward) -
cargo xtask validate-no-silent-skips(Step 6 onward) -
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); documentpub(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 |
Per-crate batches are small + reversible; |
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 |
|
After this plan lands
-
DTO contracts type-safe at the deserialization boundary (~130-300 fields converted, scope per inventory)
-
Service crate
pubsurfaces minimal; misuse caught at compile time -
Silent-skip pattern can no longer regress (xtask gate)
-
cargo denyignore list is canonical (no orphans, active entries documented)