CRUD Completion — All Operationally Relevant Resources

On this page

Context

CRAIG has several operationally relevant resources that lack full CRUD API coverage. Some (NIST controls, rate tables) are seed-only with no create endpoint. Others are missing get-by-ID, update, or delete operations. Agencies need complete API control over all configurable data — seed scripts should only be used for test/demo data, not operational configuration.

This plan adds 31 new endpoints across 5 services, organized as 5 independent MRs (one per service). Each MR includes store functions, API handlers, route registration, OpenAPI annotations, test-lib client methods, integration tests, and CLI commands.

Design Decisions

  • Hard delete for tables without an active column; soft-delete (active=false) for exchange_partners which has one

  • FK constraint violations on delete → 409 Conflict (let PostgreSQL enforce, catch in handler)

  • Delete of stateful records: only allowed in safe states (e.g., only pending adjustments, only draft agreements)

  • RBAC: matches sibling endpoints on the same resource (admin for config data, caseworker_or_above for case/placement data)

  • POST returns 200, DELETE returns 204, per project conventions

MR 1: craig-security (5 endpoints + UUID v7 migration)

craig-security’s 5 original tables (audit_log, security_reviews, archive_records, nist_controls, review_evidence) still use gen_random_uuid() (v4). All other services were migrated via 20260305100000_uuid_v7_defaults.sql. This MR includes a migration (20260310100000_uuid_v7_defaults.sql) to fix this.

Branch: feature/crud-gaps-security

Endpoint Method RBAC

/v1/security/nist

POST

admin

/v1/security/nist/{control_id}

GET

admin

/v1/security/nist/{control_id}

DELETE

admin

/v1/security/reviews/{id}

GET

admin

/v1/security/reviews/{id}

DELETE

admin

Files

File Changes

services/craig-security/src/store/nist.rs

Add create_control(), delete_control()

services/craig-security/src/store/reviews.rs

Add get_review() (if missing), delete_review()

services/craig-security/src/api.rs

Add CreateNistControlRequest struct; 5 handlers; update routes + OpenAPI

crates/craig-test-lib/src/clients/security.rs

Add 5 client methods

services/craig-security/tests/api/nist.rs

Add: create, create-duplicate-409, get, get-404, delete, delete-404

services/craig-security/tests/api/reviews.rs

Add: get-review, get-404, delete-review, delete-with-evidence-409

services/craig-cli/src/cmd/nist.rs

Add Create, Get, Delete subcommands

services/craig-cli/src/cmd/security.rs

Add GetReview, DeleteReview subcommands

services/craig-cli/tests/cli/nist.rs

Add create/get/delete tests; rewrite update to use CLI-created control

services/craig-cli/tests/cli/security.rs

Add get/delete review tests

Notes

  • NIST control_id is TEXT with UNIQUE constraint → duplicate → 409 Conflict

  • review_evidence FK to security_reviews → delete review with evidence → 409

  • delete_control uses DELETE …​ WHERE control_id = $1 RETURNING *

MR 2: craig-exchange (4 endpoints)

Branch: feature/crud-gaps-exchange

Endpoint Method RBAC

/v1/exchange/partners/{id}

GET

admin

/v1/exchange/partners/{id}

DELETE

admin (soft-delete)

/v1/exchange/agreements/{id}

GET

admin

/v1/exchange/agreements/{id}

DELETE

admin (draft only)

Files

File Changes

services/craig-exchange/src/store/agreements.rs

Add delete_agreement() (WHERE status = 'draft')

services/craig-exchange/src/api/partners.rs

Add get_partner, delete_partner handlers

services/craig-exchange/src/api/agreements.rs

Add get_agreement, delete_agreement handlers

services/craig-exchange/src/api/mod.rs

Update route registration

crates/craig-test-lib/src/clients/exchange.rs

Add 4 client methods

services/craig-exchange/tests/api/

Add get/delete tests for partners + agreements

services/craig-cli/src/cmd/exchange.rs

Add GetPartner, DeletePartner subcommands

services/craig-cli/src/cmd/agreement.rs

Add Get, Delete subcommands

Notes

  • Partner delete = soft-delete via existing update_partner(active=Some(false)); get_partner store fn already exists

  • Agreement delete only works on draft status; get_agreement store fn already exists

  • Agreement FK (partner_id → exchange_partners) uses RESTRICT, so soft-delete on partners is the right approach

MR 3: craig-financial (6 endpoints)

Branch: feature/crud-gaps-financial

Endpoint Method RBAC

/v1/financial/rates/{id}

GET

eligibility_worker_or_above

/v1/financial/rates/{id}

PUT

admin

/v1/financial/rates/{id}

DELETE

admin

/v1/financial/adjustments

GET

eligibility_worker_or_above

/v1/financial/adjustments/{id}

GET

eligibility_worker_or_above

/v1/financial/adjustments/{id}

DELETE

supervisor_or_above (pending only)

Files

File Changes

services/craig-financial/src/store/rates.rs

Add get_rate(), update_rate(), delete_rate()

services/craig-financial/src/store/adjustments.rs

Add list_adjustments_paged(), count_adjustments(), delete_adjustment() (WHERE status = 'pending')

services/craig-financial/src/api/rates.rs

Add UpdateRateRequest; 3 handlers

services/craig-financial/src/api/adjustments.rs

Add ListAdjustmentsQuery; 3 handlers

services/craig-financial/src/api/mod.rs

Add routes + OpenAPI paths/schemas

crates/craig-test-lib/src/clients/financial.rs

Add 6 client methods

services/craig-financial/tests/api/

Add rate get/update/delete tests, adjustment list/get/delete tests

services/craig-cli/src/cmd/financial.rs

Add GetRate, UpdateRate, DeleteRate subcommands

services/craig-cli/src/cmd/adjustment.rs

Add List, Get, Delete subcommands

Notes

  • Rate delete_rate is hard delete; no FK children reference rate_tables

  • Adjustment delete restricted to pending status at SQL level (WHERE status = 'pending')

  • get_adjustment store fn already exists

MR 4: craig-placement (7 endpoints)

Branch: feature/crud-gaps-placement

Endpoint Method RBAC

/v1/placement/homes/{id}/training

GET

caseworker_or_above

/v1/placement/training/{id}

GET

caseworker_or_above

/v1/placement/training/{id}

PUT

caseworker_or_above

/v1/placement/training/{id}

DELETE

caseworker_or_above

/v1/placement/kinship/{id}

GET

caseworker_or_above

/v1/placement/kinship/{id}

PUT

caseworker_or_above

/v1/placement/kinship/{id}

DELETE

caseworker_or_above

Files

File Changes

services/craig-placement/src/store/foster_homes.rs

Add get_training(), update_training(), delete_training() (list_training already exists)

services/craig-placement/src/store/kinship.rs

Add get_kinship_option(), update_kinship_option(), delete_kinship_option()

services/craig-placement/src/api/foster_homes.rs

Add UpdateTrainingRequest; 4 handlers (list, get, update, delete)

services/craig-placement/src/api/kinship.rs

Add UpdateKinshipOptionRequest; 3 handlers (get, update, delete)

services/craig-placement/src/api/mod.rs

Add routes + OpenAPI

crates/craig-test-lib/src/clients/placement.rs

Add 7 client methods

services/craig-placement/tests/api/

Add training + kinship CRUD tests

services/craig-cli/src/cmd/home.rs

Add ListTraining, GetTraining, UpdateTraining, DeleteTraining

services/craig-cli/src/cmd/kinship.rs

Add Get, Update, Delete

Notes

  • Training list (list_training) store function exists but has no API handler — expose it

  • Training routes: collection under /homes/{id}/training, individual under /training/{id}

  • Hard delete for both tables (no active column, no FK children)

MR 5: craig-cases (9 endpoints)

Branch: feature/crud-gaps-cases

Endpoint Method RBAC

/v1/cases/contacts/{id}

GET

caseworker_or_above

/v1/cases/contacts/{id}

PUT

caseworker_or_above

/v1/cases/contacts/{id}

DELETE

caseworker_or_above

/v1/cases/court-orders/{id}

GET

caseworker_or_above

/v1/cases/court-orders/{id}

PUT

caseworker_or_above

/v1/cases/court-orders/{id}

DELETE

caseworker_or_above

/v1/cases/plans/{id}/tasks

GET

caseworker_or_above

/v1/cases/tasks/{id}

GET

caseworker_or_above

/v1/cases/tasks/{id}

DELETE

caseworker_or_above

Files

File Changes

services/craig-cases/src/store/contacts.rs

Add update_contact(), delete_contact() (get_contact exists)

services/craig-cases/src/store/court_orders.rs

Add update_court_order(), delete_court_order() (get_court_order exists)

services/craig-cases/src/store/case_plans.rs

Add list_tasks(), delete_task() (get_task exists)

services/craig-cases/src/api/contacts.rs

Add UpdateContactRequest; 3 handlers

services/craig-cases/src/api/court_orders.rs

Add UpdateCourtOrderRequest; 3 handlers

services/craig-cases/src/api/case_plans.rs

3 handlers (list_tasks, get_task, delete_task)

services/craig-cases/src/api/mod.rs

Add routes + OpenAPI

crates/craig-test-lib/src/clients/cases.rs

Add 9 client methods

services/craig-cases/tests/api/

Add contact/court_order/task CRUD tests

services/craig-cli/src/cmd/case.rs

Add contact/court-order get/update/delete subcommands

services/craig-cli/src/cmd/plan.rs

Add task list/get/delete subcommands

Notes

  • Delete contact must also clean up contact_attachments from object store (follow pattern in contact_attachments::delete_attachment)

  • Delete court order must clean up document from object store if object_key is set

  • Individual resource routes (GET/PUT/DELETE) use flat paths (/contacts/{id}) not nested (/cases/{case_id}/contacts/{id})

  • get_contact, get_court_order, get_task store functions already exist

Status

All 5 MRs completed and merged:

  1. MR 1: craig-security — MR !4 merged. 5 endpoints + UUID v7 migration.

  2. MR 2: craig-exchange — MR !6 merged. 4 endpoints (partner/agreement get/delete).

  3. MR 3: craig-financial — MR !7 merged. 6 endpoints (rate CRUD + adjustment list/get/delete).

  4. MR 4: craig-placement — MR !8 merged. 7 endpoints (training + kinship CRUD).

  5. MR 5: craig-cases — MR !9 merged. 9 endpoints (contact/court-order/task CRUD).

Total: 31 new endpoints, 790 tests passing.

Implementation Order

  1. Save this plan as docs/modules/ROOT/pages/plans/crud-completion.adoc and add nav entry. No code changes until committed.

  2. MR 1: craig-security — smallest, admin-only, no object store. Good warmup.

  3. MR 2: craig-exchange — small, store functions mostly exist already.

  4. MR 3: craig-financial — medium, new paginated list + query types.

  5. MR 4: craig-placement — medium, two sub-resources.

  6. MR 5: craig-cases — largest, most complex (object store cleanup on delete).

Documentation

After each MR merges, update:

  • .claude/docs/services.md — Add new endpoints to service endpoint tables

  • docs/modules/ROOT/pages/implementation-guide.adoc — Update endpoint and test counts

  • .claude/CLAUDE.md — Update phase status endpoint counts

  • This plan file — Mark completed MRs

GitLab

  • Epic: "CRUD Completion — All Operationally Relevant Resources" (labels: feat, P2-medium)

  • Issue 1: feat: Add NIST + review CRUD endpoints to craig-security (weight: 3)

  • Issue 2: feat: Add partner + agreement get/delete endpoints to craig-exchange (weight: 2)

  • Issue 3: feat: Add rate + adjustment CRUD endpoints to craig-financial (weight: 3)

  • Issue 4: feat: Add training + kinship CRUD endpoints to craig-placement (weight: 3)

  • Issue 5: feat: Add contact, court-order, task CRUD endpoints to craig-cases (weight: 5)

Verification (per MR)

  1. cargo fmt --check --all && cargo clippy --workspace --locked — -D warnings

  2. cargo nextest run --workspace --lib (unit tests)

  3. cargo xtask dev restart (MR 1 has schema change) or cargo xtask dev reload (MRs 2-5)

  4. cargo nextest run --workspace (all integration tests)

  5. Swagger UI — verify new endpoints appear with correct schemas

  6. CLI smoke test — exercise new create/get/delete commands

Edit this page · latest