feat(api): SP-8 · payment rails — DORMANT, re-cut payments-only (founder-gated merge)#79
feat(api): SP-8 · payment rails — DORMANT, re-cut payments-only (founder-gated merge)#79hizrianraz wants to merge 1 commit into
Conversation
| tenant_id=tenant.id, | ||
| flattened_msgs=flattened_msgs, | ||
| idempotency_key=idempotency_key, | ||
| ) |
There was a problem hiding this comment.
Streaming path ignores vendor passthrough model selection
High Severity
When stream=true, the handler unconditionally delegates to _serve_messages_stream which always calls dispatch_with_brain. Unlike the non-streaming path (which delegates to post_inference with its _is_routed(body.model) check), the streaming path never handles vendor passthrough models (e.g. claude-opus-4-7). A user requesting a specific pinned backend with streaming enabled will have their model choice ignored and get brain-routed to a potentially different model.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit c4026f9. Configure here.
| max_tokens=body.max_tokens, | ||
| temperature=body.temperature, | ||
| stream=body.stream, | ||
| stream=False, |
There was a problem hiding this comment.
Tools silently dropped in non-streaming messages path
High Severity
InferenceRequest has no tools or tool_choice field, so body.tools from the Anthropic request is never passed to post_inference. Tools are silently dropped in the non-streaming /v1/messages path. The except ToolsNotSupportedError handler (line 327) is dead code since adapters never receive tools through this pipeline.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit c4026f9. Configure here.
| "rail": rail, | ||
| "message": f"webhook to {rail} arrived without its rail-specific signature header", | ||
| }, | ||
| ) |
There was a problem hiding this comment.
Webhook reports wrong error for unknown rail
Low Severity
The signature-header lookup dictionary only contains the three known rails. If an unknown rail value is provided, .get(rail) returns None, and the handler raises a misleading 400 missing_signature_header error instead of reaching select_adapter(rail) which would give the correct unknown_rail error.
Reviewed by Cursor Bugbot for commit c4026f9. Configure here.
…itch Last build sprint. Three rails (CDP/x402/USDC, Stripe Customer Balance, Xendit Customer Balance) integrated as code behind a SINGLE master flag AINFERA_PAYMENTS_LIVE. Default OFF. Every payment path is inert until the founder flips post-SG-incorporation per docs/payment-activation-runbook.md. See PR description for full details: master flag + 3 rail adapters + metering→charge orchestrator (read-only on §16) + webhook router + reconciliation dry-run + 7-step activation runbook + comprehensive inertness/margin-math/routing-outcomes-readonly/router tests + OpenAPI contract updated. After SP-8, everything Aulë can build is built. Remaining distance to launch is exclusively founder/legal. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
c4026f9 to
12b5ea2
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 8 potential issues.
There are 11 total unresolved issues (including 3 from previous reviews).
Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issues.
Reviewed by Cursor Bugbot for commit 12b5ea2. Configure here.
| cost_actual_usd=cost_actual_usd, | ||
| margin_rate=rate, | ||
| customer_charged_usd=customer_charged, | ||
| debit_record=debit, |
There was a problem hiding this comment.
ChargeResult margin rate mismatch
Medium Severity
charge_for_inference stores the caller’s raw margin_rate on ChargeResult, but compute_customer_charge clamps out-of-range rates before computing customer_charged_usd. Audit output can show a margin that does not match the amount actually debited.
Reviewed by Cursor Bugbot for commit 12b5ea2. Configure here.
| inference_id=inference_id, | ||
| amount_usd=customer_charged, | ||
| idempotency_key=str(inference_id), | ||
| ) |
There was a problem hiding this comment.
Zero cost still debits
Medium Severity
_read_outcome_cost skips only missing cost_actual_usd, not zero. charge_for_inference then computes a zero customer charge and calls adapter.debit with amount_usd=0, which conflicts with ledger_entries requiring amount_usd > 0.
Reviewed by Cursor Bugbot for commit 12b5ea2. Configure here.
| row = result.first() | ||
| if row is None or row.cost_actual_usd is None: | ||
| return None | ||
| return row.agent_id, row.cost_actual_usd |
There was a problem hiding this comment.
Outcome lookup ignores duplicates
Medium Severity
_read_outcome_cost uses result.first() on routing_outcomes rows for an inference_id, but that column is not unique. Duplicate rows yield an arbitrary agent and cost instead of failing or picking a defined row.
Reviewed by Cursor Bugbot for commit 12b5ea2. Configure here.
| inference_id=inference_id, | ||
| amount_usd=customer_charged, | ||
| idempotency_key=str(inference_id), | ||
| ) |
There was a problem hiding this comment.
Charge path uncaught rail errors
Medium Severity
charge_for_inference calls select_adapter(rail) and adapter.debit(...) with no handling for ValueError (unknown rail) or NotImplementedError (live stub). Callers get an unhandled exception instead of a skip or structured failure after the DB read and margin math.
Reviewed by Cursor Bugbot for commit 12b5ea2. Configure here.
| "message": "live topup wiring lands in the post-SG activation PR", | ||
| "rail": adapter.rail, | ||
| }, | ||
| ) |
There was a problem hiding this comment.
Live topup wrong error code
Low Severity
When AINFERA_PAYMENTS_LIVE is on, POST /v1/payments/topup/{rail} still returns detail.code of payments_not_live even though /v1/payments/status reports live: true. Clients and alerts keyed on that code will misread activation state.
Reviewed by Cursor Bugbot for commit 12b5ea2. Configure here.
| raise HTTPException( | ||
| status_code=status.HTTP_401_UNAUTHORIZED, | ||
| detail={"code": "webhook_signature_invalid", "rail": exc.rail}, | ||
| ) from exc |
There was a problem hiding this comment.
Live webhook stub returns 401
Medium Severity
With AINFERA_PAYMENTS_LIVE on, webhook handlers still raise WebhookSignatureError from unwired adapters, and the router maps every such error to 401 webhook_signature_invalid. Processors treat that as a bad signature, not “verification not implemented yet.”
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 12b5ea2. Configure here.
| "rail": rail, | ||
| "message": f"webhook to {rail} arrived without its rail-specific signature header", | ||
| }, | ||
| ) |
There was a problem hiding this comment.
Unknown webhook rail misreported
Low Severity
For POST /v1/payments/webhook/{rail}, an unsupported {rail} path value yields missing_signature_header because header lookup uses .get(rail) before select_adapter. Topup correctly returns unknown_rail for the same mistake.
Reviewed by Cursor Bugbot for commit 12b5ea2. Configure here.
| "live reconciliation per-rail lookup lands in the post-activation " | ||
| "PR per docs/payment-activation-runbook.md step 7 (post-activation " | ||
| "verification — first reconciliation run + 1-cent canary per rail)." | ||
| ) |
There was a problem hiding this comment.
Live reconcile raises uncaught
Medium Severity
When is_payments_live() is true, reconcile_rail always raises NotImplementedError after summing ledger debits. There is no ReconcileResult return path, so any caller (cron or runbook step) crashes instead of completing reconciliation.
Reviewed by Cursor Bugbot for commit 12b5ea2. Configure here.


Re-cut: payments-only, DORMANT, conflict-free on current
mainThis PR was a stale 5-commit sprint omnibus. Re-cut on 2026-05-31 to isolate the one feature that needs deliberate review:
Dropped during re-cut (all already in
main):aa_index_sourcemigration (AIN-271b) — merged via the audit/v1/messages— already upstream/metrics+ structured logging — split off and merged separately as feat(api): SP-5 · /metrics surface + structured JSON logging (AIN-238 + AIN-249) #101What remains (this PR): SP-8 payment rails only — built DORMANT behind
AINFERA_PAYMENTS_LIVE=0. +1950 / -0 across 15 files (Stripe/CDP/Xendit adapters, charge math, reconciliation, flag gate, payments router, activation runbook).Verified locally (this exact head):
test_payments_flag_off_inertness(proves the rails are inert with the flag off)Held for founder merge (not auto-merged): per the settlement/payments founder-gate (Disc #12; MAS PSA legal-review-pre-Seed). The code is dormant and safe to land, but landing payment rails into
mainis your call. One-click merge when you're ready.