ADR-003: Ruleset-as-Data

On this page

Status

Accepted

Context

Each benefit program administered by Canopy has its own eligibility logic: income limits expressed as percentages of the Federal Poverty Level, asset tests, categorical eligibility pathways, income disregards, household composition rules, and benefit calculation formulas. This logic changes frequently — annual FPL updates, state plan amendments, federal regulatory changes, legislative action.

The question is where this logic lives and how it is managed.

Two broad approaches exist:

  1. Logic encoded in application code — Rust match statements, if-else trees, hardcoded thresholds.

  2. Logic expressed in declarative rule files evaluated by a runtime engine.

CRAIG chose zen-engine with JDM (JSON Decision Model) rule files for child welfare case routing and safety assessment. The question for Canopy is whether the same engine is appropriate for the substantially more complex domain of multi-program eligibility determination, and whether program-specific logic should be split across program service codebases or centralized.

Decision

All program eligibility logic is expressed as versioned JDM ruleset files evaluated by a single shared canopy-rules service. No program service implements its own rules engine. Program services call canopy-rules with a program-keyed ruleset name and an input context, receive an output, and use that output to produce a determination.

Ruleset files live in the repository under rulesets/georgia/ organized by program:

rulesets/
└── georgia/
    ├── snap-eligibility.json
    ├── snap-benefit-calculation.json
    ├── tanf-eligibility.json
    ├── tanf-benefit-calculation.json
    ├── tanf-work-requirements.json
    ├── medicaid-magi.json
    ├── medicaid-non-magi.json
    ├── medicaid-eligibility-hierarchy.json
    ├── chip-eligibility.json
    ├── caps-eligibility.json
    └── wic-eligibility.json

The jurisdiction prefix (georgia-) is prepended at evaluation time, following the CRAIG pattern. A future state adopting Canopy contributes its own rulesets under its jurisdiction directory without modifying shared code.

Rationale

Policy change velocity

Eligibility rules change constantly and must change quickly. FPL tables update annually. State plan amendments may take effect within 30 days. Emergency federal waivers during disasters can require same-week implementation.

If eligibility thresholds are encoded in Rust, every policy change requires a code change, code review, CI pipeline run, and deployment. If they are in ruleset files, a policy analyst can review the change directly, the file is committed, and the rules engine picks it up on next import — no deployment required for threshold changes.

Separation of policy from code

Eligibility policy is the domain of program staff, federal regulations, and state plan provisions. It is not inherently a software engineering concern. Expressing policy as data rather than code means that policy analysts and eligibility supervisors can participate meaningfully in reviewing rule changes without needing to read Rust.

No duplication

The alternative — each program service implements its own rules evaluation — produces five copies of the same evaluation infrastructure with no shared testing, no shared audit trail, and no shared import/export tooling. canopy-rules provides a single evaluation audit trail across all programs, which is valuable for PERM (Payment Error Rate Measurement) defense and federal reporting.

zen-engine validation

Before committing to zen-engine for Canopy’s eligibility domain, a proof-of-concept must validate that JDM can express:

  • MAGI household gross income calculation with income disregards

  • FPL percentage lookup for household size

  • Categorical eligibility pathways (TANF categorical, SSI categorical)

  • Most advantageous group assignment across Medicaid eligibility categories

  • SNAP net income test with standard deduction, earned income deduction, excess shelter deduction

This POC is a prerequisite to Phase 4 implementation (see implementation plan). If JDM proves insufficient for the complexity of Medicaid non-MAGI logic, an alternative rules engine will be evaluated and this ADR superseded.

Alternatives considered

Alternative 1: Hardcoded eligibility logic in program service code Rejected. Policy change velocity requires non-developer participation in rule changes. Hardcoded thresholds require a deployment for every FPL update. Logic is not auditable by policy staff.

Alternative 2: Per-program rules engines Each program service runs its own instance of zen-engine with its own ruleset management. Rejected. Duplicates infrastructure, splits the evaluation audit trail, and makes cross-program rule consistency impossible to verify.

Alternative 3: External rules engine (Drools, Corticon, Oracle OPA) Commercial rules engines with richer tooling and established eligibility track records. Corticon is explicitly named in DCH’s IAPD as the selected rules engine for Georgia Gateway’s replacement. Rejected for Canopy because: proprietary licensing is incompatible with AGPLv3; vendor dependency is exactly what Canopy exists to eliminate; zen-engine is sufficient for the domain pending POC validation. If the POC fails, this alternative is revisited.

Consequences

  • canopy-rules is a shared dependency for all program services. Its availability is on the critical path for eligibility determination. Circuit breakers and graceful degradation must be implemented in each program service.

  • Ruleset files are version-controlled and reviewed as code. Policy analysts who modify rulesets commit to the repository and go through the standard MR process.

  • The evaluation audit trail in canopy-rules records every eligibility evaluation across all programs, providing a unified PERM defense record.

  • A jurisdiction adopting Canopy contributes jurisdiction-specific rulesets. It does not fork the engine. The AGPLv3 license applies to the engine code; ruleset files are data and may be kept confidential by the jurisdiction if desired, though open publication is encouraged.

  • Annual FPL updates require a ruleset file commit and import — no code change, no deployment.

Edit this page · default