API versioning + strangler-fig migration¶
Two of the three HTTP backends are mid-migration. Each runs the legacy Flask + Cerberus surface and the FastAPI + Pydantic rewrite side-by-side in the cluster against the same MySQL schema. The shorthand for this pattern is strangler-fig — the new app grows around the old one, route-by-route, until nothing of the legacy app is reachable.
The API reference covers the final-state v2 / v4 surfaces. This page covers everything else: what's still on legacy, what's already moved, and how we plan to retire the residue.
Service version map¶
| Service | Legacy module | Modern module | Modern mount | Status |
|---|---|---|---|---|
cashless-backend |
apiserver/cm/ — Flask 2.0 |
apiserver/cm_v2/ — FastAPI |
/v2 |
Mid strangler-fig |
personalaccount-api |
pa/api/endpoints/{v1,v2,v3}/ — Flask 2.3 |
pa_v4/ — FastAPI |
/v4 |
Mid strangler-fig |
cashless-auth |
— | cashless-auth — FastAPI |
/ |
Single version (v1) |
Both legacy and modern apps deploy as separate Helm releases. ArgoCD
syncs four backend Applications today:
| ArgoCD app | Values file | Routes |
|---|---|---|
cashless-backend |
deploy/argocd/values/cashless-backend.yaml |
Legacy Flask cm/ — internal only |
cashless-backend-v2 |
deploy/argocd/values/cashless-backend-v2.yaml |
api.dev2.mycashless.com/v2 |
personalaccount-api |
deploy/argocd/values/personalaccount-api.yaml |
Legacy Flask pa/ — internal only |
personalaccount-api-v4 |
deploy/argocd/values/personalaccount-api-v4.yaml |
pa-api.dev2.mycashless.com/v4 |
Strangler-fig shape¶
flowchart LR
C([Client]) --> ING[GCE Ingress<br/>dev2.mycashless.com]
ING -->|/v2/*| V2[cashless-backend-v2<br/>FastAPI · cm_v2]
ING -->|/v4/*| V4[personalaccount-api-v4<br/>FastAPI · pa_v4]
V2 -. internal HTTP .-> LEG1[cashless-backend<br/>Flask · cm/]
V4 -. internal HTTP .-> LEG2[personalaccount-api<br/>Flask · pa/]
V2 --> DB[(MySQL 8.0<br/>shared schema)]
V4 --> DB
LEG1 --> DB
LEG2 --> DB
The ingress only routes external traffic to the FastAPI rewrites. The legacy Flask apps remain in the cluster so that not-yet-ported background jobs, internal admin tools, and the residual operator surfaces keep working. Both halves point at the same MySQL instance; migrations 020–032 made the schema a strict superset of what either app needs.
cashless-backend — cm/ → cm_v2/¶
cm/ is Flask 2.0 on gunicorn + eventlet. It registers routes via a
custom @wrappers.api_resource(...) decorator and validates payloads
through cm/api/schemas.py — a single 4,267-LOC Cerberus blob
(source).
cm_v2/ is FastAPI on uvicorn. Schemas live next to the routes in
cm_v2/schemas/ as Pydantic models. The OpenAPI snapshot regression
test pins 434 paths / 435 schemas as of Wave 9.
| Side | Module count | Coverage |
|---|---|---|
cm_v2/routers/ |
76 routers (/v2) |
The full modern-app launch path: catalog, identity, devices, tickets, money movement, reports, events, payments. |
cm/api/endpoints/ |
97 files; 94 still on Flask | Long-tail reference data, legacy admin surfaces not yet flipped in the admin UI, the legacy sqlreports runner. |
Outstanding legacy categories (feature-level — full per-route list in
.planning/HANDOFF-not-ported.md §1):
| Area | Examples (cm/api/endpoints/*.py) |
|---|---|
| Reference data + tooling | androidapps, apiendpoints, banks, colors, countries, currencies, unit, unit_conversion, zipcodes |
| Legacy admin surfaces | eventclients, eventconfig, marketplaceconfig, brand, areas, locations, bundle, categories, ticketcategories, ticketnumbers, producttype, product_cost, userproducts, modifiers, modifieritems, recipedetail |
| Reporting (legacy runner) | sqlreports, reports, reportcustomdates, reportdevices |
| Internal / dev | tests, version, auth (Flask shim) |
Rough port budget: ~30–45 endpoint files of meaningful admin-facing
routes plus the long tail of reference-data CRUDs. Pre-launch the rule
is "ported when the admin UI feature is ported" — cm/ is what the
admin UI talks to for anything not yet flipped.
personalaccount-api — pa/ → pa_v4/¶
pa/ is Flask 2.3 on gunicorn. Endpoints are namespaced into three
historical versions under pa/api/endpoints/:
| Subpackage | Mount in legacy Flask | Files |
|---|---|---|
pa.api.endpoints.v1 |
/v1 (auth, hosts, tickets, online transactions) |
5 |
pa.api.endpoints.v2 |
/v2 (the bulk of cardholder + operator surface — 36 modules) |
36 |
pa.api.endpoints.v3 |
/v3 (new payment SDKs — Adyen, Mercado Pago, PayPal, Stripe; devices; externaltransaction) |
11 |
pa_v4/ is FastAPI on uvicorn — 48 routers under pa_v4/routers/
consolidate v1 + v2 + v3 onto a single surface mounted at /v4. Schemas
live in pa_v4/schemas/ as Pydantic models. The OpenAPI snapshot
regression test pins the full pa_v4 surface as of Wave 8.
| Side | Coverage |
|---|---|
pa_v4/routers/ |
Cardholder core (me, users, userchips, accesses, connections), commerce + cart + reload, tickets, devices + chips, money movement, all four payment SDKs (Stripe / Adyen / MP / PayPal) + webhooks, mPOS-flavored operator surface. |
pa/api/endpoints/ |
Legacy hosts + onlinetransactions (v1); long-tail v2 modules (androidapps, banks, client, company, cron, emails, fanki, openvoucher, orders, tablestages, verify, legacy refunds / transfers variants); v3 company, externaltransaction, plus legacy Stripe/PayPal/Adyen/MP entry-points still hit by older mobile builds. |
Per-route status lives in
.planning/HANDOFF-not-ported.md.
cashless-auth — single version¶
cashless-auth was greenfield FastAPI from day one. There is no
strangler-fig active here — the service ships a single v1 surface
covering password, phone OTP, Firebase token exchange, device,
API key, and MFA flows. Source lives directly in the parent
monorepo at
services/cashless-auth.
Deprecation policy¶
Post-launch deprecation policy is TBD
Until launch, every backend lives on cashless-v2 and we
forward-fix: when a legacy route is ported, the Flask
implementation gets deleted in the same commit. No Deprecation
headers, no Sunset dates, no parallel-deploy grace period.
Once cashless-v2 ships, this page will publish a written
policy covering: minimum deprecation window per version, the
Deprecation + Sunset response headers we emit, the changelog
surface, and the cadence at which legacy mount paths get
removed from the ingress.
No PRs pre-launch — even for strangler-fig moves
The parent monorepo and every submodule are on cashless-v2
until launch. Port a route by committing the new FastAPI
router and the deletion of the corresponding cm/api/...
or pa/api/... file in the same push — directly to
cashless-v2. Don't open a PR, don't split the work across
review rounds. See getting-started.md
for the full branching policy.
Migration playbook + handoff docs¶
The three documents below carry the day-to-day truth about what has and hasn't moved:
.planning/v2-migration-playbook.md— cutover sequence, gates, rollback plan. Updated 2026-05-11 for the k8s-MySQL data-move (Phases 0–3 prefix the original §2.4 traffic flip)..planning/migration-handoff.md— the live session-state snapshot. §1 is the source of truth for current submodule HEADs + test counts; §4 contains the agent task prompts..planning/HANDOFF-not-ported.md— exhaustive per-feature list of admin / cardholder surfaces still on Flask, with port estimates.
Wave history¶
Backend strangler-fig work is tracked alongside other modernization under a Wave numbering scheme. Recent waves:
| Wave | Closed | Backend / migration outcome |
|---|---|---|
| Wave 4 | 2026-04-29 | Stripe account.updated webhook lands in pa_v4; cm_v2 cross-tenant tests. |
| Wave 5 | 2026-04-30 | Cross-customer + cross-tenant pytest suites; models alembic round-trip suite. |
| Wave 6 | 2026-05-08 | KMP → Rust mobile SDK cutover (SDK, not backend). Backend side: modifiers leak fix. |
| Wave 7 | 2026-05-02 | Shared ConsentService + cookie banner across all four frontends. |
| Wave 8 | 2026-05-05 | ARS Stripe + per-country fee table; pa_v4 OpenAPI snapshot regression test. |
| Wave 9 | 2026-05-06 | cm_v2 OpenAPI snapshot (1.96 MB / 434 paths / 435 schemas); iOS SDK swap continued. |
Wave 6 is the SDK cutover, not a backend strangler-fig wave — listed
for context because every wave-close commit bumps backend submodule
pointers in the parent repo at the same time. See
.planning/wave6-kmp-to-rust-migration.md
for the SDK details.
Where to look next¶
- API reference — Swagger / OpenAPI for the modern-state v2 / v4 / auth surfaces.
- Architecture — full repo + service boundary map.
- Services — every deployed host and the workload behind it.