"Wise webhook HTTP host — POST /v1/webhooks/wise/{profile_id} + 24h key cache + WAL processor"
TASK-INV-012: Wise webhook HTTP host
Summary
Wire the shipped cyberos-inv Wise library into an axum host: POST /v1/webhooks/wise/{profile_id} with 24h PEM cache + one rotation re-fetch on verify failure, in-memory WAL push for fast 200, and a background processor that accepts events into an in-memory append log (cash-app stubbed until TASK-INV-006).
Problem
TASK-INV-004 landed verify + parse + migrations as a library only. No HTTP route, no key cache, no WAL processor — Wise cannot deliver webhooks.
Proposed Solution
public_key.rs:PublicKeySourcetrait +CachedPublicKeys(24h TTL); on verify fail →force_refreshonce → retry verify (DEC-848).wal.rs: bounded queue ofWiseWalItem { profile_id, event_id, event_type, body }.handler.rs: extract raw body +X-Signature-SHA256→ verify (with rotation) → parse → reject unknown type / stale / profile mismatch (200 for stale per DEC-844) → WAL push → empty 200.processor.rs: pop WAL → idempotent in-memory receipt set → statereceived; cash-app call is a documented stub (CashAppStub).- Binary
cyberos-inv: axum router,WISE_PUBLIC_KEY_PEM/ fetch URL env for key source in dev; listenINV_LISTEN_ADDRdefault:7710. - Tests: cache TTL/refresh, rotation retry accepts new key, handler happy path with generated RSA keypair, stale → 200 without WAL growth.
Alternatives Considered
- Mount on auth binary. Rejected: webhook is signature-auth, not JWT; obs-router pattern is a dedicated host.
- Full sqlx persist + INV-006 match. Rejected for host-c size; in-memory append + stub ledgered; migration SQL already shipped in INV-004.
Success Metrics
- Primary: signed valid event → 200 + one WAL/processor receipt.
- Guardrail: bad signature → 401; no cash-app panic.
Scope
In scope
- Host route, cache+rotation, WAL, processor stub, binary, unit/integration tests.
Out of scope / Non-Goals
- Live Postgres INSERT / migrate CI for inv
- TASK-INV-006 cash-app cascade (stub only)
- Memory audit kinds / admin restore route
- Real Wise HTTP fetch in CI (trait + static PEM)
Dependencies
- TASK-INV-004 done (library).
AI Authorship Disclosure
Generated then reviewed against as-built inv crate + INV-004 deferred host list (2026-07-26).
Acceptance Criteria
- POST route exists at
/v1/webhooks/wise/{profile_id}. - Valid signature → 200 empty body; event recorded once.
- Invalid signature (no refresh help) → 401.
- Rotation retry — stale cache PEM fails, refreshed PEM succeeds → 200.
- Stale event → 200 without processor receipt.
- Unknown event_type → not processed as receipt (dead-letter or drop with test).
- Cash-app stub — processor does not call INV-006; documents stub.
- 24h cache — second fetch not called within TTL for same profile.
Verification
cd services
cargo test -p cyberos-inv -- --test-threads=1
bash .cyberos/cuo/gates/run-gates.sh
Failure Modes
| Failure | Detection | Outcome | Recovery |
|---|---|---|---|
| Bad sig | verify err | 401 | Operator check key |
| Key fetch fail | source err | 401 after retry | Retry later |
| WAL overflow | push err | 503 or 200+drop | Raise capacity |
| Stale | is_stale | 200 no process | — |
| Dup event | HashSet | 200 no reprocess | — |
| Unknown type | parse None | dead-letter | Ops |
| Profile mismatch | check | 401/200 policy | Spec: treat as bad |
| Cash-app missing | stub | received only | INV-006 |
| Body too large | limit 1MiB | 413 | Client |
| Panic in processor | catch/log | event retried | DLQ later |
End of TASK-INV-012.
Audit
§1 — Verdict summary
Wise HTTP host residual over INV-004 library. 8 ACs, 10 failure modes; cash-app and Postgres persist honestly Out of scope.
§2 — Findings (all resolved)
ISS-001 — Network in CI
Resolved: PublicKeySource trait + static PEM tests.
ISS-002 — Cash-app blocked on INV-006
Resolved: CashAppStub; AC #7.
ISS-003 — Stale must 200
Resolved: AC #5 + DEC-844.
ISS-004 — Rotation once only
Resolved: AC #4 force_refresh once.
ISS-005 — JWT on webhook
Resolved: no JWT; signature auth only.
ISS-006 — Persist vs in-memory
Resolved: Out of scope Postgres; in-memory receipt set for host-c.
§3 — Resolution
All 6 mechanical concerns addressed. Score = 10/10.
End of TASK-INV-012 audit.