Plan: canopy-cli ADR-007 Parity Catchup (Issue #385)
On this page
Status
| Step | Description | Status |
|---|---|---|
1 |
|
Done (2026-05-10) |
2 |
|
Done (2026-05-10) |
3 |
|
Done (2026-05-10) |
4 |
|
Done (2026-05-10) |
5 |
|
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 |
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-83—Commandenum to extend. -
tools/canopy-cli/src/client.rs:1-60—ApiClientto 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.
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
securityor a newauditsubcommand 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; theinterviewsubcommand 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 |
|---|---|
|
Add 5 new variants to the |
|
New file |
|
New file |
|
New file |
|
New file |
|
New file |
|
Re-export the 5 new modules |
|
25 unit tests (5 per subcommand × 5 commands) |
|
Document the 5 new subcommands |
|
|
Verification
-
cargo nextest run -p canopy-cli— unit tests pass. -
cargo build --release -p canopy-cli && ./target/release/canopy --help— top-level help lists all new subcommands. -
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,medicaidEach prints the API’s pretty-printed JSON response.
-
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