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 DeterminationStatus variants (Terminated, Sanctioned, TimeLimitExceeded, AbawdExceeded, Disqualified)

  • Missing IncomeType variants (Tanf, Veterans, Rental, WorkersCompensation, StrikeBenefits, Irregular, SelfEmploymentNet, ChildSupportPaid)

  • Missing AssetType variants (IdaAccount, AbleAccount, Plan529, IndianTrustLand, BusinessEquity)

  • Missing NoticeType variants (ExpeditedNotice, AbawdNotice, SanctionNotice, TimeLimitNotice, ExpungementNotice, IvdReferralNotice, ContinuedBenefitsNotice, ChangeInCircumstancesNotice, OverpaymentNotice)

  • Missing FederalProgram variants (WicPc, CcdfAcf801)

  • New VerificationSource enum (Attestation, DocumentaryEvidence, Ievs, SaveMatch, CollateralContact, CrossProgramQuery, Decal)

  • New crates/canopy-reference/src/types.rs with VerificationRequirement enum and VerificationItem struct

  • New fields on services/canopy-eligibility/src/determination.rs: denial_reason_codes, verification_items_required, categorical_eligibility_basis, abawd_month_count

  • FormerFosterCare added to MedicaidCategory enum (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>,
NOTE
Do NOT add #[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).

Step 5: Verify no downstream breakage

Run cargo build --workspace and cargo test --workspace. All existing tests should pass — additions are backward compatible. Fix any exhaustive match patterns in tests that enumerate all enum variants (update counts).

Files Touched

File Change

crates/canopy-reference/src/enums.rs

Add variants to DeterminationStatus, IncomeType, AssetType, NoticeType, FederalProgram; add doc comments with regulatory cites; update tests

crates/canopy-reference/src/types.rs

New file: VerificationItem struct, VerificationRequirement enum

crates/canopy-reference/src/lib.rs

Add pub mod types; pub use types::*;

services/canopy-eligibility/src/determination.rs

Add denial_reason_codes, verification_items_required, categorical_eligibility_basis, abawd_month_count fields; update existing test

Verification

  1. cargo build --workspace — zero errors

  2. cargo nextest run --workspace --lib — all tests pass

  3. Verify DeterminationStatus::AbawdExceeded serializes to "abawd_exceeded" (serde snake_case)

  4. Verify VerificationItem serializes/deserializes correctly

  5. Verify Determination struct still passes determination_serializes test with new fields initialized to defaults

Documentation Updates

  • .claude/CLAUDE.md — canopy-reference Status section (mark variants as added)

  • CHANGELOG.adoc — entry under == Unreleased

Edit this page · default