Architecture Decision Records
What an ADR is here
An Architecture Decision Record captures one significant, hard-to-reverse choice: the context that forced the decision, the decision itself, the consequences we accepted, and the alternatives we rejected. They are the “why” behind the platform. If you are taking over MVS Pay, read these before you change anything load-bearing — most of the surprising shapes in the codebase trace back to one of these eleven records.
The source of truth for each ADR is the markdown under docs/architecture/adrs/ in the repo. The pages linked from this index (./adrs/adr-XXX) are the published copies of those same files. This page is the index and the cross-reference map; the linked pages are verbatim.
Every ADR below was Accepted on 2026-05-28 except where the table says otherwise. Several carry an explicit “revisit when…” trigger — those are not closed decisions, they are decisions with a known expiry condition. The triggers are listed in the Revisit triggers section.
The index
If you only read three, read ADR-007 (the bias that explains why so little is rebuilt MVS-side), ADR-010 (the auth gap you will hit immediately), and ADR-002 (the MoR posture that shapes onboarding and compliance).
How the decisions hang together
ADR-001 is the root: choosing to self-host the open-source Hyperswitch is what makes every downstream decision possible (and necessary). The native-bias principle in ADR-007 is the lens through which most feature work is decided; ADR-011 is the first concrete application of that lens to a feature MVS partly owns.
The grouping by concern:
- Why Hyperswitch at all / how much of it we use: ADR-001, ADR-007, ADR-011. See The Hyperswitch Pivot for the narrative.
- Shape of the extensions service: ADR-006 (separate app), ADR-003 (Hono), ADR-008 (sidecar schema). See Extensions Service.
- Data + infra: ADR-004 (Neon), ADR-005 (Argo CD). See GitOps & Kubernetes and the Data Model.
- Trust + compliance posture: ADR-002 (MoR), ADR-010 (auth). See Authentication & Tenancy.
- Client surface scope: ADR-009 (web-only). See The SDK and Checkout UI Components.
Per-ADR summary
Self-host the open-source router on EKS. We own routing config, GSM rules, the decision-engine deployment, upgrade cadence, DR, and the PCI AOC for card-vault. Juspay’s managed tier was rejected to keep the trust boundary on our infrastructure and to make Smart Retries self-serve.
Per-tenant MoR by default, opt-in Platform Organization tier. Phase 1 ships only the schema toggle (MerchantExtension.isPlatformOrgMember). Full Platform Org KYC API integration is deferred until upstream stabilizes it and product confirms the tier ships. Two compliance trees, two onboarding flows.
Hono 4.12.23 for the extensions orchestrator: fast cold start, zod-validator for request schemas, ContentfulStatusCode typing for c.json. tRPC rejected because consumers span repos over HTTP; the typed SDK gives equivalent safety on a stable contract.
Neon Postgres, two projects (hyperswitch-prod, mvs-pay-extensions-prod). Branchable for staging/preview, PITR built in, PgBouncer pooling required for Vercel cold-start connection storms. Cross-region replica deferred (single-region today). RDS and Aurora Serverless v2 rejected.
Argo CD v3.4.2, multi-source Application (upstream chart + this repo’s values), API version argoproj.io/v1alpha1. Chosen over Flux for UI + multi-source ergonomics; raw Helm-via-CI rejected for lacking drift detection and self-heal.
apps/mvs-pay-extensions runs standalone in the payments K8s namespace, not inside the Next.js app. Keeps a clean PCI / non-PCI boundary (only Hyperswitch + card-vault sit inside the PCI ring), serves cross-repo consumers (healthos, mvs-c2), and gets EKS-only features like mTLS and network policies.
Default rule: surface every Hyperswitch feature through SDK/UI rather than rebuilding it MVS-side. Any rebuild (custom IIAS substantiation, HSA receipts, Plaid Link UX) needs an explicit counter-ADR. Keeps the MVS surface small; makes Smart Retries, decision-engine, Cost Observability, Revenue Recovery first-class.
Per-MCA metadata lives in a sidecar table MerchantConnectorExtension keyed by hyperswitch_merchant_connector_id, not a JSON column on MerchantExtension. Trade-off: slightly more schema surface, but indexable fields (e.g. filter by connectorName) and clean cascading delete.
@mvs/mvs-pay-ui ships web only. Hyperswitch’s native iOS/Android/RN/Flutter SDKs are not wrapped. Mobile wrappers are a separate future plan, gated on a real consumer (healthos mobile, an MVS native app, or a partner). Marked “revisit when first mobile consumer requests.”
Service-to-service trust uses one shared MVS_PAY_EXTENSIONS_INTERNAL_SECRET. Per-user identity is not propagated; phiAudit.logAccess() records userId: null until mvs-auth lands. Mitigations: rate-limit middleware (Phase 3), external WAF, quarterly rotation. JWT validation is added per-route when mvs-auth ships, with a 90-day overlap before decommissioning the secret.
Hyperswitch Revenue Recovery owns retry timing, BIN-aware curves, and connector selection. MVS keeps the BillingSubscription state machine, line items, invoicing, and the /revenue-recovery UI. New columns recoveryStatus/lastRecoveryAttempt/recoveryAttemptCount (migration 20261001000000_phase4_revenue_recovery) written only by the /webhooks/revenue-recovery handler.
Things these ADRs leave open
This is internal documentation, so the honest version: several ADRs are decisions to defer, not decisions that are done. Treat the items below as known gaps, not finished features.
The following are explicitly deferred or stubbed by the ADRs above. Do not assume they are implemented just because a toggle, column, or webhook exists.
- Per-user auth is not implemented (ADR-010). Everything authenticates with a single shared secret. PHI audit rows are written with
userId: null. The full integration is blocked on the separate mvs-auth plan. See Authentication & Tenancy. - Platform Organization tier is a toggle only (ADR-002).
MerchantExtension.isPlatformOrgMemberexists from Phase 1, but the Platform Organization KYC / sub-merchant onboarding API is deferred until upstream surfaces it stably and product confirms the tier ships. The simple per-tenant connector flow is the only onboarding path live today. - No cross-region database replica (ADR-004). Neon is single-region today; cross-region replication is deferred. DR for the extensions DB rests on PITR, not a hot replica.
- No mobile SDK (ADR-009). Web only. If you are integrating a mobile consumer, that is net-new work governed by a future plan, not an existing wrapper.
- Revenue Recovery is native and flag-gated (ADR-011). The retry engine is Hyperswitch’s, live behind a feature flag. If it under-performs an MVS baseline (the documented revisit threshold is “recovery rate < industry baseline by 5+ points sustained for 30 days”), the implementation can be swapped under the stable SDK contract. The
BillingSubscriptionrecovery columns are written by exactly one writer — the webhook handler atapps/mvs-pay-extensions/src/routes/webhooks-revenue-recovery.ts.
ADR-007’s corollary is procedural: if you find yourself rebuilding a Hyperswitch capability MVS-side, the expectation is that you write a counter-ADR in docs/architecture/adrs/ explaining why upstream can’t carry it, and add a published copy under ./adrs/. The bias is the default, not a hard prohibition.
Revisit triggers
Some ADRs carry an explicit condition under which they should be reopened. Track these — they are the most likely sources of future architecture churn.
Authoring a new ADR
Take the next number
ADRs are sequential. The next record is ADR-012. Numbers are never reused, even if an ADR is superseded.
Write the markdown in the repo
Create docs/architecture/adrs/adr-0NN-short-slug.md. Keep the canonical sections: Status, Context, Decision, Consequences, Alternatives considered. ADR-011 also uses a Rationale section where the decision needs more justification — that is fine for substantial decisions.
Where the ADRs live in the repo
Canonical source: docs/architecture/adrs/adr-001-*.md … adr-011-revenue-recovery.md.
Published copies (rendered in these docs): fern/pages/concepts/adrs/adr-001-*.mdx … adr-011-revenue-recovery.mdx.
Navigation registration: fern/docs.yml, under Core Concepts → Architecture Decision Records.
The published .mdx files are verbatim copies of the source markdown with a Fern frontmatter title added. Keep them in sync — if you edit the source ADR, update the published copy.
Related reading
- Architecture — the system overview these decisions implement.
- The Hyperswitch Pivot — narrative context for ADR-001, ADR-007, ADR-011.
- Extensions Service — the service shaped by ADR-003, ADR-006, ADR-008.
- Authentication & Tenancy — the current state of the gap in ADR-010.
- Revenue Recovery — the feature governed by ADR-011.
- API Reference — the HTTP contract that ADR-003 chose over tRPC.