fix(financial): make payment issue and clear transitions reachable with truthful events

Summary

The payment state machine defines pending -> approved -> issued -> cleared, but the live API exposes only pending -> approved. No production writer moves a payment to issued or cleared, while claim generation aggregates only rows whose status is issued.

The approval handler also emits financial.payment_created a second time instead of an approval event. Runtime-created payments therefore cannot enter a real claim without direct database manipulation, and event consumers receive a false duplicate-create signal.

Evidence

  • services/craig-financial/src/api/mod.rs:220-263 mounts calculate/list/get/approve routes but no issue or clear route.
  • services/craig-financial/src/transitions.rs:17-30 defines approved-to-issued and issued-to-cleared as valid transitions.
  • services/craig-financial/src/store/claims.rs:157-180 aggregates only status = issued.
  • The only production call to update_payment_status is approval in services/craig-financial/src/api/payments.rs:305-335.
  • That approval transaction calls publish_payment_created at lines 327-334.
  • financial.payment_issued is documented and parsed by craig-security, but no financial publisher emits it.

Why it matters

This blocks the normal Title IV-E payment-to-claim workflow and makes the audit/event stream misrepresent approval as resource creation.

Add explicit, authorization-gated issue and clear transitions (and a deliberate manual-void path if required), each using an atomic expected-state update plus transactional outbox event. Emit distinct payment_approved, payment_issued, and payment_cleared events. Carry the endpoints through typed clients, CLI, and web so the documented API/CLI/Web parity remains true.

Acceptance criteria

  • pending -> approved -> issued -> cleared is reachable through supported service APIs without direct SQL.
  • Every transition uses WHERE status = <expected> or a row lock so concurrent callers cannot double-transition or double-publish.
  • Approval no longer emits financial.payment_created.
  • Distinct lifecycle events are staged in the same transaction as each state change.
  • Claim generation includes payments issued through the supported workflow.
  • REST, typed-client, CLI, and web coverage exercise the full lifecycle and invalid/concurrent transitions.
  • Event documentation and craig-security event parsing remain synchronized.

References

  • Source: 2026-07-09 repository review.
  • Backlog reconciliation: not tracked. #773 (closed)-#775 (closed) cover rounding, decimal wire representation, and input validation; none covers lifecycle reachability or approval event semantics.