Plan: Library-API Docs Burn-down (#463)

On this page
NOTE

B6 of the Backlog Cleanup Campaign. #463’s first two goals are already met (panic-class lints via ADR-030 / epic &62; unused_crate_dependencies via the cargo machete gate, #464); this plan executes the remaining library doc-lints. Decomposed per the split-large-issues rule into epic &68 library-api-docs-burndown with 14 child issues (#940–#953). Each batch table row is a living Status table (cargo xtask plan-lint scans the Status column; canonical tokens only).

At a glance

  • What: roll ![warn(missing_docs)] + ![warn(unreachable_pub)] across the library crates and document/fix every emission.

  • Scope: 3,240 missing_docs items across 24 crates + 128 unreachable_pub items (127 canopy-web, 1 canopy-api). Two service crates excluded from missing_docs by documented policy; tool libs included per the product owner.

  • Shape: 14–15 MRs (M0 pilot → M9 contracts → tool libs → service-crate cleanup; M12/seed may split a/b), one child issue per MR. Multi-session; each MR is its own ~10–13 min pre-push battery.

  • How: per-MR a parallel agent-draft + skeptic-verify workflow writes the docs; then cargo clippy -D warnings + the battery + ship.

Context

Measured 2026-06-28 via cargo rustc -p <crate> --lib — --force-warn <lint> (deps excluded), per crate: missing_docs = 3,980 items across 26 of the 39 library crates (the other 13 already carry the attr and are fully documented); unreachable_pub = 128.

Service-crate exclusion (documented policy → formalized here). canopy-eligibility/src/lib.rs:2-5 states a general policy: "Service-level crates do not enforce missing_docs — the HTTP API is the public contract; the library crate exists only so integration tests can reach into the orchestrator." canopy-web/src/lib.rs:3-6 says the same in effect ("this lib export is narrow") but does not name the policy. So: apply it to both and make it explicit — M13 adds the matching comment to canopy-web/src/lib.rs. Net: exclude canopy-web (645) + canopy-eligibility (95) from missing_docs (−740 → 3,240); canopy-web still gets its unreachable_pub demotion (which enforces the "narrow lib export" the policy describes).

Tool libs included (product-owner decision): canopy-seed (891) + canopy-cli (74) — no documented exclusion.

Already done (13 documented libs, unchanged): canopy-api, -auth, -common, -db, -mq, -policy, -reference, -rules-client, -secrets, -signing, -store, -test-lib, -typst.

Doc-authoring standard (match the 13 documented crates)

  • Placement: ![warn(missing_docs)] and ![warn(unreachable_pub)] go immediately after the crate //! doc block and before the first item — including crates that are just a list of pub mod (e.g. canopy-contracts-rules/src/lib.rs). Model: canopy-common/src/lib.rs:8. warn, never deny; no cfg(test) carve-out.

  • Granularity: a /// on every public item and every public field / enum variant (canopy-api/src/lib.rs:45-62, canopy-reference/src/types.rs:15-26).

  • Style: one-line summary, sentence fragment, no trailing period, no backticks-around-types on line one; optional multi-line elaboration with domain context + ADR/issue cites (canopy-contracts-persons/src/income.rs:17-33).

  • J8 (anti-tautology): the pre-commit J8 gate rejects docs that restate the name — write intent. For genuinely self-evident homogeneous clusters (e.g. geographic enums) use the existing escape #[allow(missing_docs)] // <reason> (precedent: canopy-reference/src/fips.rs:13), sparingly, at the type level — never a blanket dodge. A lone self-evident field gets a short real doc adding its unit/constraint/nullability.

  • OpenAPI coupling: types deriving utoipa::ToSchema embed their doc comment verbatim as the schema description. Confirmed OpenAPI-feeding crates in scope: all contracts-* + canopy-composition + canopy-overpayments. Any such batch MUST regenerate the snapshots. Per-batch self-check: grep -l <CrateType> docs/modules/ROOT/openapi/*.json; the pre-push api-docs drift gate is the backstop.

GitLab structure (epic &68, children #940–#953)

  • #463 stays open as the originating umbrella (rescoped); the final batch MR (M13) closes it — the only MR that names #463 in a close keyword.

  • Each MR Closes its own child issue on merge (campaign model — no withholding); each close gets the mandatory comment (impl SHA + bare merge SHA + changed crates + checked criteria).

MR batches

The n after each crate is its measured missing_docs count (these sum to 3,240); the unreachable_pub column is emissions to fix — both attrs are added to every in-scope crate regardless. Doc-only diffs are additive/low-risk, so the campaign "<500 LOC" guideline is relaxed for the larger single crates; batches may be re-grouped.

MR (issue) Crates (missing_docs n) unreachable_pub Wt OpenAPI Status

M0 (#940)

validators(11), rules(24), facts(0), crypto-shred(0), plugin-macros(0); + unreachable_pub attr on the 13 documented libs; + demote canopy-api otel(1)

api:1

3

rules

Done (2026-06-28) — !710

M1 (#941)

contracts eligibility(40), notices(55), enrollment(59)

5

yes

Done (2026-06-28) — !711

M2 (#942)

contracts wic(67), renewals(71), security(73)

5

yes

Done (2026-06-28) — !712

M3 (#943)

contracts caps(94), appeals(112)

5

yes

Done (2026-06-28) — !713

M4 (#944)

contracts verification(117), tanf(134)

5

yes

Done (2026-06-28) — !714

M5 (#945)

contracts reporting(171)

5

yes

Done (2026-06-28) — !715

M6 (#946)

contracts snap(204)

5

yes

Done (2026-06-28) — !716

M7 (#947)

contracts medicaid(206)

5

yes

Done (2026-06-29) — !717

M8 (#948)

contracts persons(230)

5

yes

Done (2026-06-29) — !718

M9 (#949)

contracts applications(328)

8

yes

Done (2026-06-29) — !719

M10 (#950)

composition(230), overpayments(49)

5

yes (both)

Done (2026-06-29) — !720

M11 (#951)

canopy-cli(74)

3

no

Done (2026-06-29) — !721

M12 (#952)

canopy-seed(891) — split a/b (model.rs=797 ≫450 LOC ⇒ M12a model.rs, M12b the remaining 10 files=94); shipped as 2 MRs against the single #952 (one shippable unit ⇒ no redundant child, per gitlab-issue-mr-standards: M12a Relates to, M12b Closes)

8

no

Done (2026-06-29) — !722 (model.rs) + !723 (rest + lints)

M13 (#953)

service crates: canopy-web demote 127 + attrs + exclusion comment; canopy-eligibility attr (no missing_docs); closes #463

web:127

3

no

Done (2026-06-29) — !724

Coverage: M0–M12 list each of the 24 in-scope crates exactly once and sum to 3,240; M13 is the service-crate unreachable_pub cleanup. Sequencing: pilot (de-risk) → contracts (highest ROI: OpenAPI + cross-crate consumers) → shared/tool libs → service cleanup last.

Per-MR execution loop

Branch chore/cleanup-docs-<slug> (campaign convention).

  1. Enumerate: per crate, cargo rustc -p <crate> --lib — --force-warn missing_docs 2>&1 | grep "missing documentation" (and --force-warn unreachable_pub for M0/M13) → the exact file:line set.

  2. Draft (Workflow pipeline over the batch’s .rs files — distinct files ⇒ conflict-free parallel edits): each Draft agent documents its file’s undocumented public items per the standard + J8; a Verify agent re-reads that file’s diff, kills tautologies, adds missed fields. For M13, agents demote the enumerated pubpub(crate)/pub(super).

  3. Enable the lints: add both attrs to each in-scope crate’s lib.rs (the 13 documented libs get only unreachable_pub, M0; service crates get only unreachable_pub, M13).

  4. Verify zero residual: cargo clippy -p <crate> --all-targets — -D warnings exits 0 with no output, and RUSTDOCFLAGS="-D rustdoc::broken_intra_doc_links" cargo doc --no-deps -p <crate> is clean (matches the pre-push gate, .githooks/pre-push).

  5. OpenAPI (batches whose crates feed snapshots — M0 rules, M1–M9, M10):

    cargo xtask dev refresh && cargo xtask api-docs --update && cargo fmt --all && cargo fmt --check --all

    && (fail-fast); never ; echo $?. Commit the snapshot diff; doc-text-only diffs are expected — flag any semantic schema change in the MR description.

  6. Battery + ship: full pre-push battery → fresh J1–J8 subagent on the staged diff (report to user; fix any J8 tautology in-place + re-stage) → commit (signed, human author, Co-Authored-By the session model) → push → MR (Closes #<child>; M13 also Closes #463).

  7. Post-merge: close the child with the mandatory comment; tick the epic task list; flip the batch StatusDone (YYYY-MM-DD) — !<MR>; git branch -d + git remote prune origin.

M13 demotion safety: a grep for external users is a hint; the authority is the compilercargo check --workspace --all-targets (in the battery) fails if any demoted item had an external user, so a green workspace check is the proof.

Verification

  • Per MR: step 4 (clippy -D warnings + intra-doc-link cargo doc both clean) + OpenAPI regen’d iff a feeding crate changed + full battery green + MR links its child.

  • Final: all 24 in-scope libs carry ![warn(missing_docs)]; all 39 libs carry ![warn(unreachable_pub)] (13 via M0 + 24 via M1–M12 + 2 service via M13); workspace cargo clippy --all-targets — -D warnings green; #463 closed; epic &68 fully checked; campaign B6 row → Done.

Out of scope

  • missing_docs on canopy-web + canopy-eligibility — documented service-crate policy (formalized on web in M13).

  • B3b (#466 mutants), B7 (#484) — separate, decision-blocked.

  • missing_docs_in_private_items (1,321, transition-allowed in Cargo.toml) — owned by epic &62 M4.

  • Any logic/refactor bug found while documenting → file a fix: issue + /relate; never fold into a doc MR.

Edit this page · default