Plan: Reference Type Extensions
On this page
Status
| Step | Description | Status |
|---|---|---|
1 |
Add missing variants to DeterminationStatus, IncomeType, AssetType, NoticeType, FederalProgram |
Done (2026-03-28) |
2 |
Add VerificationRequirement enum and VerificationItem struct to new types.rs |
Done (2026-03-28) |
3 |
Update Determination struct with denial_reason_codes, verification_items_required, categorical_eligibility_basis, abawd_month_count |
Done (2026-03-28) |
4 |
Export new types from lib.rs; update all tests |
Done (2026-03-28) |
Epic: &33, &38
Branch: feature/reference-extensions
Labels: type::feature, priority::critical, program::cross-program, service::shared-crates, service::eligibility, workflow::ready
MR: !1
Context
The enums in crates/canopy-reference/src/enums.rs were written as minimal scaffolds.
Every program service implementation plan depends on accurate enum representations of eligibility states — a determination that exhausts SNAP ABAWD limits cannot be represented with the existing DeterminationStatus variants, and a denial notice cannot carry a regulatory basis code without a denial_reason_codes field on Determination.
This plan must complete in week 1 of Month 1. It has no dependencies and blocks every plan that follows. The changes are additive — no existing variants are removed or renamed, so no downstream breakage.
The regulatory basis for each addition is documented inline in the design section.
Scope
In scope:
-
Missing
DeterminationStatusvariants (Terminated, Sanctioned, TimeLimitExceeded, AbawdExceeded, Disqualified) -
Missing
IncomeTypevariants (Tanf, Veterans, Rental, WorkersCompensation, StrikeBenefits, Irregular, SelfEmploymentNet, ChildSupportPaid) -
Missing
AssetTypevariants (IdaAccount, AbleAccount, Plan529, IndianTrustLand, BusinessEquity) -
Missing
NoticeTypevariants (ExpeditedNotice, AbawdNotice, SanctionNotice, TimeLimitNotice, ExpungementNotice, IvdReferralNotice, ContinuedBenefitsNotice, ChangeInCircumstancesNotice, OverpaymentNotice) -
Missing
FederalProgramvariants (WicPc, CcdfAcf801) -
New
VerificationSourceenum (Attestation, DocumentaryEvidence, Ievs, SaveMatch, CollateralContact, CrossProgramQuery, Decal) -
New
crates/canopy-reference/src/types.rswithVerificationRequirementenum andVerificationItemstruct -
New fields on
services/canopy-eligibility/src/determination.rs:denial_reason_codes,verification_items_required,categorical_eligibility_basis,abawd_month_count -
FormerFosterCareadded toMedicaidCategoryenum (if it exists) or documented as a follow-on for the medicaid-eligibility plan — this is a mandatory coverage group (ACA §2004, 42 CFR 435.119) with no income test
Out of scope:
-
Changes to existing variants (backward compatible additions only)
-
New enums not identified in this plan (add via follow-on plan)
-
Any program service implementation — this plan is purely canopy-reference and canopy-eligibility determination struct
Design
DeterminationStatus additions
// SPDX-License-Identifier: AGPL-3.0-or-later
// Add these variants to the existing DeterminationStatus enum in enums.rs:
/// Benefits ended at the natural conclusion of the certification period.
Terminated,
/// Benefits reduced or eliminated due to work/cooperation requirement violation.
/// Used for TANF work non-compliance and SNAP IPV-adjacent sanctions.
Sanctioned,
/// Recipient has exhausted the federal 60-month TANF lifetime limit.
/// 42 USC §608(a)(7).
TimeLimitExceeded,
/// SNAP ABAWD 3-month time limit within the 36-month tracking window exhausted.
/// 7 USC §2015(o); 7 CFR 273.24.
AbawdExceeded,
/// Disqualified due to Intentional Program Violation (IPV) finding.
/// Separate from denial — IPV carries a disqualification period (1 year, 2 years, permanent).
Disqualified,
IncomeType additions
/// TANF cash assistance. Countable for SNAP; also required to identify categorical eligibility triggers.
Tanf,
/// VA benefits (disability compensation, pension, dependency and indemnity). Countable for SNAP.
Veterans,
/// Rental income from property. Countable; net of allowable expenses for self-employment method.
Rental,
/// Workers' compensation payments. Countable as unearned income.
WorkersCompensation,
/// Strike benefits from a union. Countable per 7 CFR 273.9(b). Striker households subject to special rules.
StrikeBenefits,
/// Irregular or infrequent income. May be excludable if under threshold (7 CFR 273.9(b)(2)).
Irregular,
/// Net self-employment income after allowable business expenses.
/// Separate from SelfEmployment (gross) to support both gross and net reporting.
SelfEmploymentNet,
/// Child support paid OUT to non-household members. Deduction, not income — tracked here for deduction calculation.
/// 7 CFR 273.9(d)(7).
ChildSupportPaid,
AssetType additions
/// Individual Development Account. Excluded for SNAP per 7 CFR 273.8(e)(19).
IdaAccount,
/// ABLE Act account. Excluded from all federal means-tested programs.
AbleAccount,
/// 529 education savings account. Excluded for SNAP and Medicaid.
Plan529,
/// Tribal land held in trust by federal government. Excluded for SNAP per 7 CFR 273.8(e).
IndianTrustLand,
/// Equity in business property essential to self-employment.
/// Excludable for SNAP per 7 CFR 273.8(e)(5) if essential to earning self-employment income.
BusinessEquity,
NoticeType additions
/// Expedited service identification notice. Informs household of 7-day processing timeline.
/// Required by 7 CFR 273.2(i)(3).
ExpeditedNotice,
/// ABAWD time limit warning notice. Sent in months 1 and 2 of the 3-month window.
/// Good practice; technically required by adequate notice principles.
AbawdNotice,
/// Sanction notice. TANF work non-compliance or SNAP IPV sanction.
SanctionNotice,
/// TANF 60-month time limit approaching notice. Sent at months 54, 57, 59.
TimeLimitNotice,
/// EBT stale benefit pre-expungement notice. Required 30 days before expungement.
/// 7 USC §2016(h)(9).
ExpungementNotice,
/// TANF IV-D child support referral notice. Notifies household of referral to child support agency.
IvdReferralNotice,
/// Confirmation that benefits continue pending fair hearing decision.
/// Issued when continued benefits are granted on appeal. 7 CFR 273.15(g).
ContinuedBenefitsNotice,
/// Notice of change in benefit amount or household circumstances.
ChangeInCircumstancesNotice,
/// Overpayment claim notice. Issued after hearing decided in agency favor when continued benefits were paid.
OverpaymentNotice,
FederalProgram additions
/// WIC Participant Characteristics biennial report. 7 CFR 246.25(b).
WicPc,
/// CCDF ACF-801 annual case-level data report. 45 CFR 98.70.
CcdfAcf801,
VerificationSource enum
Add to crates/canopy-reference/src/enums.rs — currently referenced by VerificationItem but not defined:
/// Source that provided verification evidence.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum VerificationSource {
/// Self-reported by the applicant on the application form.
Attestation,
/// Documentary evidence provided by the applicant (pay stubs, lease, etc.).
DocumentaryEvidence,
/// Income and Eligibility Verification System (7 USC §2025(e); 42 USC §1320b-7).
Ievs,
/// DHS SAVE system for immigration status verification (8 USC §1642).
SaveMatch,
/// Collateral contact when documents are unavailable (7 CFR 273.2(f)(5)).
CollateralContact,
/// Cross-program enrollment verification (adjunctive eligibility, categorical eligibility).
CrossProgramQuery,
/// State childcare provider registry (CAPS provider validation).
ProviderRegistry,
}
New types.rs
New file crates/canopy-reference/src/types.rs:
// SPDX-License-Identifier: AGPL-3.0-or-later
use chrono::{DateTime, NaiveDate, Utc};
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use crate::VerificationSource;
/// A specific item that must be verified before a determination can be finalized.
/// Used when DeterminationStatus is PendingVerification.
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct VerificationItem {
pub requirement: VerificationRequirement,
pub source: Option<VerificationSource>,
pub due_date: Option<NaiveDate>,
pub resolved_at: Option<DateTime<Utc>>,
pub notes: Option<String>,
}
/// What must be verified.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum VerificationRequirement {
Identity,
Residency,
SocialSecurityNumber,
GrossIncome,
SelfEmploymentIncome,
Assets,
ChildSupport,
CitizenshipAlienStatus,
StudentStatus,
DisabilityStatus,
PregnancyStatus,
HouseholdComposition,
ShelterExpenses,
DependentCareExpenses,
MedicalExpenses,
TaxFilingStatus,
AbawdWorkActivity,
ImmigrationEntryDate,
}
Determination struct additions
In services/canopy-eligibility/src/determination.rs, add four fields after basis:
/// Regulatory citation codes for denial reasons. Required for NOA content (7 CFR 273.13(a)).
/// Example: ["7CFR273.9.a.gross_income_exceeded", "7CFR273.9.c.net_income_exceeded"]
pub denial_reason_codes: Vec<String>,
/// Verification items that must be resolved before this determination is finalized.
/// Non-empty when status is PendingVerification.
pub verification_items_required: Vec<canopy_reference::VerificationItem>,
/// Basis of categorical eligibility, if applicable.
/// Example: "tanf_cash_receipt", "ssi_recipient", "bbce"
pub categorical_eligibility_basis: Option<String>,
/// For SNAP: months of ABAWD time limit used in the current 36-month tracking window.
/// None if household has no ABAWD members or is in a waiver area.
pub abawd_month_count: Option<u8>,
#[derive(Default)] to Determination — the struct contains non-defaultable required fields (program: Program, household_id: Uuid, status: DeterminationStatus, signature: String).
Instead, initialize the new fields explicitly in all test constructors: denial_reason_codes: vec![], verification_items_required: vec![], categorical_eligibility_basis: None, abawd_month_count: None.
Consider adding a DeterminationBuilder in the test module if construction becomes unwieldy.
Steps
Step 1: Enum additions in enums.rs
Files: crates/canopy-reference/src/enums.rs
Add the variants listed above to each enum. Keep all existing variants in place — additions only. Add doc comments with regulatory citations to each new variant.
Update the count assertion in all_programs_exist test — this checks Program::iter().len() == 6 which is unchanged.
Add a new test all_determination_statuses_roundtrip that iterates all variants and verifies serde roundtrip.
Step 2: New types.rs
Files: crates/canopy-reference/src/types.rs (new)
Create the file with the VerificationItem struct and VerificationRequirement enum as specified above.
Add unit tests for serde roundtrip of both types.
Step 3: Update lib.rs
Files: crates/canopy-reference/src/lib.rs
Add pub mod types; and pub use types::*;.
Step 4: Update Determination struct
Files: services/canopy-eligibility/src/determination.rs
Add the four new fields.
Update the existing determination_serializes test to set the new fields:
denial_reason_codes: vec![],
verification_items_required: vec![],
categorical_eligibility_basis: None,
abawd_month_count: None,
Add canopy_reference to services/canopy-eligibility/Cargo.toml if not already present (it should be via workspace).
Files Touched
| File | Change |
|---|---|
|
Add variants to DeterminationStatus, IncomeType, AssetType, NoticeType, FederalProgram; add doc comments with regulatory cites; update tests |
|
New file: VerificationItem struct, VerificationRequirement enum |
|
Add |
|
Add denial_reason_codes, verification_items_required, categorical_eligibility_basis, abawd_month_count fields; update existing test |
Verification
-
cargo build --workspace— zero errors -
cargo nextest run --workspace --lib— all tests pass -
Verify
DeterminationStatus::AbawdExceededserializes to"abawd_exceeded"(serde snake_case) -
Verify
VerificationItemserializes/deserializes correctly -
Verify
Determinationstruct still passesdetermination_serializestest with new fields initialized to defaults
Documentation Updates
-
.claude/CLAUDE.md— canopy-reference Status section (mark variants as added) -
CHANGELOG.adoc— entry under== Unreleased