Plan: canopy-cli ADR-007 Parity Catchup (Issue #385)

On this page

Status

Step Description Status

1

household subcommand. New tools/canopy-cli/src/commands/household.rs with create, get, list, update, delete, add-member, remove-member actions. Wraps /v1/households/* endpoints in canopy-persons. Mirror the existing person subcommand layout in tools/canopy-cli/src/commands/person.rs. Use shared ApiClient (tools/canopy-cli/src/client.rs:1-60).

Done (2026-05-10)

2

income subcommand. New tools/canopy-cli/src/commands/income.rs with add, get, list-by-person, update, remove actions. Wraps /v1/persons/{id}/income and /v1/income/{id} endpoints in canopy-persons.

Done (2026-05-10)

3

asset subcommand. New tools/canopy-cli/src/commands/asset.rs with add, get, list-by-person, update, remove actions. Wraps the corresponding canopy-persons asset endpoints.

Done (2026-05-10)

4

interview subcommand. New tools/canopy-cli/src/commands/interview.rs with schedule, complete, list-by-application, cancel actions. Wraps the canopy-applications interview endpoints.

Done (2026-05-10)

5

determine subcommand (root-level). New tools/canopy-cli/src/commands/determine.rs with run (synonymous with the existing eligibility evaluate) accepting --application-id and optional --programs snap,tanf,medicaid,caps,wic. Output deserialises SignableDetermination from each program and pretty-prints program_extension fields per program (e.g., medicaid: block surfaces assigned_coa; tanf: surfaces denial_reason_code).

Done (2026-05-10)

6

Tests + docs. 5 unit tests per command (~25 total) covering arg parsing + correct HTTP path construction. 1 manual smoke per command exercised against devstack. Update docs/modules/ROOT/pages/cli-reference.adoc with the 5 new subcommands. Plan moves to plans/archive/canopy-cli-adr-007-catchup.adoc post-merge. Predecessor plan plans/archive/canopy-cli.adoc stays archived (referenced, not reopened).

Done (2026-05-10)

Issue: #385
Branch: feat/canopy-cli-adr-007-catchup
Labels: type::feature, priority::medium, service::xtask, program::cross-program, workflow::ready

Context

ADR-007 mandates that every API operation must be available as a canopy CLI subcommand; the CLI is a thin reqwest client and never reaches into databases directly. Today tools/canopy-cli/src/main.rs:30-83 defines a Command enum with login, token, completion, person, rules, application, eligibility, and security. The API has full household, income, asset, interview, and determine (parallel-program) coverage; the CLI does not. The archived canopy-cli plan established the framework but stopped at the subcommands listed above.

Adding the missing five subcommands brings the CLI back in lockstep with the API, satisfying ADR-007 and unblocking workflows that need scriptable household / income / asset / interview / determine operations (e.g., test-data seeding, ATO evidence collection, SDK conformance checks).

Code references

  • tools/canopy-cli/src/main.rs:30-83Command enum to extend.

  • tools/canopy-cli/src/client.rs:1-60ApiClient to reuse.

  • tools/canopy-cli/src/auth.rs:61-80 — Keycloak ROPC auth path; reused by all new subcommands.

  • tools/canopy-cli/src/commands/person.rs — closest precedent for the new subcommand layout.

  • Archived: canopy-cli.adoc — predecessor plan, referenced.

  • ADR-007 — CLI / API / UI parity

Scope

In scope:

  • 5 new top-level subcommands.

  • Argument parsing + HTTP-path construction tests.

  • CLI reference docs update.

Out of scope:

  • New API endpoints — the CLI surfaces existing API surface only.

  • Cross-program reporting / audit subcommands — those would extend security or a new audit subcommand if needed; separate plan.

  • Interactive / TUI flows — clap-style flags only.

  • Bulk-import wrappers — the CLI does one operation per invocation; bulk loaders live elsewhere.

Dependencies

  • applications-authorized-representatives.adoc (#401) does not block this plan; the interview subcommand surfaces canopy-applications interview endpoints, which exist independently of authorized-rep CRUD.

  • caps-provider-registry.adoc (#396) does not block this plan; CAPS provider CRUD would be its own subcommand if/when added (out of scope here).

Design

Each new subcommand follows the existing person subcommand’s shape:

#[derive(clap::Subcommand)]
pub enum HouseholdCommand {
    Create(CreateArgs),
    Get(GetArgs),
    List(ListArgs),
    Update(UpdateArgs),
    Delete(DeleteArgs),
    AddMember(AddMemberArgs),
    RemoveMember(RemoveMemberArgs),
}

pub async fn run(cmd: HouseholdCommand, client: &ApiClient) -> Result<()> {
    match cmd {
        HouseholdCommand::Create(args) => {
            let req: CreateHouseholdRequest = serde_json::from_str(&args.payload)?;
            let resp: Household = client.post_json("/v1/households", &req).await?;
            println!("{}", serde_json::to_string_pretty(&resp)?);
            Ok(())
        }
        // …
    }
}

The determine subcommand uses the orchestrator’s parallel-program endpoint:

pub async fn run(args: DetermineRunArgs, client: &ApiClient) -> Result<()> {
    let body = json!({
        "application_id": args.application_id,
        "programs": args.programs,
    });
    let resp: CombinedResult = client.post_json("/v1/eligibility/determine", &body).await?;
    pretty_print_combined(&resp);
    Ok(())
}

pretty_print_combined walks each per-program SignableDetermination, pulls fields out of program_extension, and prints program-specific blocks (medicaid surfaces assigned_coa; tanf surfaces denial_reason_code; etc.).

Files Touched

File Change

tools/canopy-cli/src/main.rs

Add 5 new variants to the Command enum + dispatch arms

tools/canopy-cli/src/commands/household.rs

New file

tools/canopy-cli/src/commands/income.rs

New file

tools/canopy-cli/src/commands/asset.rs

New file

tools/canopy-cli/src/commands/interview.rs

New file

tools/canopy-cli/src/commands/determine.rs

New file

tools/canopy-cli/src/commands/mod.rs

Re-export the 5 new modules

tools/canopy-cli/tests/cli_subcommands_test.rs

25 unit tests (5 per subcommand × 5 commands)

docs/modules/ROOT/pages/cli-reference.adoc

Document the 5 new subcommands

CHANGELOG.adoc

=== Added entry

Verification

  1. cargo nextest run -p canopy-cli — unit tests pass.

  2. cargo build --release -p canopy-cli && ./target/release/canopy --help — top-level help lists all new subcommands.

  3. Manual smoke against devstack:

    canopy household create --payload '{"head_person_id":"…"}'
    canopy income add --person-id "…" --payload '{"amount":1500,"frequency":"monthly"}'
    canopy determine run --application-id "…" --programs snap,medicaid

    Each prints the API’s pretty-printed JSON response.

  4. cargo xtask validate — full battery green; OpenAPI snapshots unchanged.

Documentation Updates

  • docs/modules/ROOT/pages/cli-reference.adoc — 5 new subcommand sections

  • CHANGELOG.adoc — entry under == Unreleased / === Added

  • .claude/docs/services.md — note ADR-007 parity restored

  • Plan archive: move to plans/archive/ post-merge

Edit this page · default