Migration Readiness
Status
Active
Date
2026-04-28
Owners
- Platform Backend
Last Verified Commit
Use git log -- <this file> for current last-touch history; this field is intentionally not pinned to a static hash so it does not become stale after unrelated commits.
Goal
Track whether servers_v2 can replace the legacy servers/ runtime without
user-visible or external-integration-visible regressions.
Current Decision
servers_v2 is ready for slice-based cutover in several areas, but it is
not yet ready for a full one-shot replacement of the entire servers/ tree.
Readiness Matrix
| Legacy Scope | servers_v2 Replacement | Current State | Evidence |
|---|---|---|---|
web_server player routes | gateway + domain services | ready for controlled cutover | gateway route parity review and contract tests |
agent_server | agent_service | ready for controlled cutover | route parity review and test_agent_integration.py |
game_server | game_service | ready for controlled cutover | route parity review and test_legacy_contracts.py |
middle_server recon slice | recon_service + admin_service top-info aggregation | ready for controlled cutover | recon service routes, admin top-info aggregation tests, local Docker validation |
middle_server web CMS slice | removed by ADR-009; callers must migrate or retire | not ready | legacy web routes were deleted from admin_service |
wallet_server split domain behavior | wallet_service + rolling_service + promotion_service | operationally strong, but depends on caller cutover | worker and contract tests |
| whole legacy back-office sidecar scope | partially admin_service, partially unmapped | not ready | docs/runbooks/legacy-middle-server-retirement.md (per-prefix migration plan); 410-Gone + Sunset stubs in servers_v2/admin_service/app/api/routes/legacy_sunset.py fail loud for any forgotten caller |
What Is Already Strong
- player-facing gateway compatibility is documented and covered by route contract tests
- agent-facing compatibility exists with zero old-only route gaps in the current comparison
- provider callback surface exists with zero old-only route gaps in the current comparison
- recon extraction is now documented, implemented, workerized, and locally Docker-tested
- admin top-info can aggregate recon counts without reintroducing the deleted legacy recon route surface
- worker health and settlement semantics now align with production expectations better than the legacy stack
What Still Blocks Full Legacy Retirement
The main blocker is not the player, agent, or provider path.
It is the remaining middle_server scope that still has active or potentially
active frontend callers:
/api/admin/role/*/api/admin/menu/*/api/admin/config/*/api/admin/i18n/*/api/admin/bi/*/coin/*- generic catch-all forwarding behavior from legacy
POST /{path:path}
/api/admin/web/* was previously not a blocker as of the 2026-04-22
admin compatibility migration; under ADR-009 (2026-04-27) the legacy
web content compatibility surface (legacy_web_content.py) is deleted
together with the rest of the staff-coupled legacy files. Any
bo/admin consumer that still depends on /api/admin/web/rules/*,
/api/admin/web/faq/*, or /api/admin/web/config/* must be migrated
or retired before that deletion lands.
Compatibility evidence for this slice:
servers_v2/admin_service/tests/test_admin_integration.pydocs/runbooks/legacy-middle-server-retirement.md— per-prefix migration plan, owners, effort estimates, cut-over sequenceservers_v2/admin_service/app/api/routes/legacy_sunset.py— 410-Gone +Sunset+Deprecationstubs so forgotten callers fail loud instead of silent-404servers_v2/admin_service/tests/test_legacy_sunset.py— guard test locking the 410 + header + auth contract so a regression cannot re-add a 200 OK to a retired prefix
Posture Summary (2026-05-06)
servers_v2 is production-ready for slice-based cutover of the
following surfaces. Each item below is shipping-grade as of this
posture entry — they have route parity, contract tests, and
operational runbooks.
- player gateway traffic
- agent service traffic
- provider callbacks
- recon worker
- multi-brand isolation (Phase 16 hard-flip closed)
servers_v2 does NOT cover the following surfaces and they remain on the
legacy servers/ runtime until a named migration or retirement project is
chartered, implemented, and validated:
/api/admin/role/*/api/admin/menu/*/api/admin/config/*/api/admin/i18n/*/api/admin/bi/*/coin/*- legacy back-office catch-all
POST /{path:path}
There is no in-flight project to migrate the surfaces above. Any
document or release announcement that claims "full replacement of the
legacy servers/ tree" is incorrect and must be corrected. The honest
posture is: servers_v2 plus residual servers/ middle_server slice,
co-deployed.
If a future project is chartered to migrate any of these surfaces, the corresponding bullet above should be moved into the readiness matrix and this section updated to reflect the new shipping-grade scope.
Production Claim Guardrails
Use these statements when preparing release notes, migration plans, or operator handoff docs:
| Claim | Current Status | Required Wording |
|---|---|---|
servers_v2 can receive selected production traffic | true for listed slices | "production-ready for controlled slice-based cutover of player gateway, agent, provider callbacks, recon worker, and Phase-16 multi-brand isolation" |
servers_v2 fully replaces legacy servers/ | false | do not claim; residual middle_server sidecar must co-exist |
legacy middle_server can be retired | false | do not claim; the seven-prefix retirement runbook is planning-only |
| service data ownership is independently deployable | false | do not claim; services share PostgreSQL and one Alembic chain, and wallet still writes legacy player-balance compatibility fields |
| all emitted events are consumed | false | do not claim; several wallet, rolling, player, and agent events are producer-only |
A full-platform production closure announcement requires all four gates to be green at the same time: legacy route retirement, frontend caller migration, data-ownership exit gates, and event-consumer closure gates. Slice readiness does not imply any of the other three.
Non-Closure Gate Matrix
This matrix is the release-note and operator-handoff source of truth for what
is still not closed. A row can move to true only in the same change that
updates the governing architecture doc, service doc, implementation evidence,
and guard tests.
| Gate | Current Status | Blocking Evidence | Exit Condition |
|---|---|---|---|
| Full legacy replacement | false | middle_server web CMS is removed, whole back-office sidecar scope is still partially unmapped, and the seven residual prefixes remain planning-only. | Every remaining legacy route family is migrated or explicitly retired, frontend callers are proven migrated by replay/access logs, and the legacy sidecar is no longer required. |
| Data ownership closure | false | Services still share PostgreSQL and one Alembic chain; wallet_service still writes legacy player-balance compatibility fields; agent-domain money writes still bypass the final owner boundary. | Per-service migration ownership/deploy order is tested, legacy player-balance compatibility writes are removed or projection-only, and agent-domain money writes move behind the replacement owner command path. |
| Event graph closure | false | DEPOSIT_APPROVED, WITHDRAW_APPROVED, and ROLLING_COMPLETED_CONFIRMED are emitted but producer-only; rolling/player/agent stream events are future projection surfaces unless named in event-catalog.md. | Every production-significant emitted event has an active consumer group, deployed worker, idempotency/inbox handling, health signals, and dispatch-path tests, or is intentionally documented as producer-only. |
| Agent-money Single Money Writer closure | false | admin_service and agent_service still directly mutate agent.balance / agent.coupon while emitting agent_balance_legacy_write_total; ADR-010 is a proposed multi-phase migration, not an implemented closure. | agent_balance_legacy_write_total == 0 for the required observation window, legacy direct write call sites are removed, and agent money commands terminate in the wallet-owned command path. |
Multi-Brand Migration Readiness
Per ADR-009, multi-brand isolation is implemented as a forward
migration, not a separate cutover:
- a
defaultbrand is seeded at migration time and every existing row is backfilled into it via column-levelDEFAULT(see plan Phase 2); - the existing single-brand environment continues to operate against
the
defaultbrand throughout every implementation phase; - gateway JWT/domain enforcement was staged through
observemode during the rollout; the repository compose default is nowenforceafter the Phase 16 in-repo closure.
Multi-brand readiness is tracked separately from the legacy-cutover
matrix above. A second brand is enabled only after the multi-brand
runbook's release gate
(docs/runbooks/multi-brand/multi-brand-isolation-rollout.md) passes.
Cutover Guidance
Safe current cutover candidates:
- player web traffic to
gateway - agent frontend traffic to
agent_service - provider callbacks to
game_service - recon shooter/pushbullet workflows to
admin_service+recon_service
Do not declare full legacy replacement complete until:
- every remaining
middle_serverroute family is either migrated or explicitly retired bo/adminno longer depends on unmapped legacy route families- the team chooses whether any generic forwarding behavior is intentionally removed or must be replaced explicitly
- migration runbooks are updated to reflect the final target state
- data-ownership docs confirm no blocking legacy write boundary remains for the claim being made
- event-catalog docs confirm every event needed by the claim has an active consumer or is intentionally producer-only