"Metering api_calls emit at auth verify_jwt — WalQueue push on success"
TASK-TEN-205: api_calls metering emit at verify_jwt
Summary
Wire the second live metering emit path: after a successful JWT-gated request (verify_jwt → handler → 2xx/3xx), push one MeteringEvent with axis = api_calls, quantity = 1 onto a process-local WalQueue / InMemoryRecorder. Failed auth never bills.
Problem
TASK-TEN-004 §1 #6 requires auth-middleware api_calls emit. TEN-204 shipped ai_tokens at cost_reconcile only. Auth middleware has the tenant on the response path and is the single inheritance point for every JWT-gated route.
Proposed Solution
- Add
cyberos-meteringdep tocyberos-auth(alongside existingcyberos-ten). services/auth/src/metering_emit.rsmirroring ai-gateway: OnceLock WalQueue + InMemoryRecorder;emit_api_call(tenant_id, idempotency_key, method, path).- In
verify_jwt, afternext.run(request), ifresponse.status().is_success(), call emit withidempotency_key = format!("{jti}:{method}:{path}:{uuid}")(unique per request; jti alone is session-scoped). - Never emit on early 401 paths (missing/invalid/revoked JWT).
- WAL overflow / lock poison: log; never change the response.
- Unit + integration tests for emit helper; middleware success-path covered by helper contract + a unit test that documents the call site.
Alternatives Considered
- Overage 402 in the same slice. Rejected: needs period usage MV + plan caps admission; ledger Out of scope.
- Emit on request path before handler. Rejected: TEN-004 ≤200µs doctrine + metering must not block; response path after success matches spec.
- Shared crate for emit helpers. Deferred; copy the TEN-204 shape for speed.
Success Metrics
- Primary: one successful JWT-gated call → one ApiCalls event qty=1.
- Guardrail: 401 paths add zero events; existing auth middleware tests still pass.
Scope
In scope
- Dep + emit helper + verify_jwt success-path hook + tests.
Out of scope / Non-Goals
- Overage evaluate /
402 PAYMENT_REQUIRED - Pg drain / sqlx migrate CI for metering
- INV Wise host (next deferred batch)
- Seats/storage snapshot jobs
Dependencies
- TASK-TEN-004 done; TASK-TEN-204 done (emit pattern).
AI Authorship Disclosure
Generated then reviewed against as-built auth middleware + TEN-204 emit (2026-07-26).
Acceptance Criteria
- Success emit —
emit_api_callwith qty=1 axis api_calls records once. - Idempotency — duplicate same key → recorder length unchanged.
- Non-blocking — overflow does not panic / does not alter return contract.
- Auth fail — early unauthorized path does not call emit (documented + unit coverage of helper not invoked when status would be 401).
- source_service —
"auth". - extra — includes
methodandpath. - Dep —
Cargo.tomllistscyberos-metering. - verify_jwt hook — success response path calls emit (source citation).
Verification
cd services
cargo test -p cyberos-auth --lib metering_emit -- --test-threads=1
cargo test -p cyberos-auth --test metering_api_calls_emit_test -- --test-threads=1
bash .cyberos/cuo/gates/run-gates.sh
Failure Modes
| Failure | Detection | Outcome | Recovery |
|---|---|---|---|
| WAL overflow | WalError | log; request OK | Drain later |
| Lock poison | lock err | log; request OK | Restart |
| Bad quantity | validate | skip | N/A (qty=1) |
| Missing jti | UUID fallback | still emit | — |
| Double middleware | unique key | two events if two success | OK |
| Path PII | path only | no query string | Strip query |
| Test isolation | unique keys | — | — |
| Dep missing | cargo | build fail | Add dep |
| Emit before next.run | code review | fail AC | Move after |
| 5xx billed | is_success false | no emit | OK |
End of TASK-TEN-205.
Audit
§1 — Verdict summary
Residual api_calls emit at verify_jwt success path. 8 ACs, 10 failure modes, mirrors TEN-204 shape; overage explicitly Out of scope.
§2 — Findings (all resolved)
ISS-001 — Idempotency key weak if jti-only
Resolved: key includes jti+method+path+uuid; AC #2.
ISS-002 — Emit on 5xx would bill failures
Resolved: is_success() only; failure table.
ISS-003 — Hot-path latency
Resolved: post-response emit; non-blocking overflow AC #3.
ISS-004 — Overage creep
Resolved: Out of scope.
ISS-005 — Query string PII in path
Resolved: strip query; failure row.
ISS-006 — Pattern drift from TEN-204
Resolved: Proposed Solution mirrors ai-gateway metering_emit.
§3 — Resolution
All 6 mechanical concerns addressed. Score = 10/10.
End of TASK-TEN-205 audit.