Created: 2026-06-12 (per V1_V7_PLAN_SET_AUDIT_2026-06-12.md §6.1 items 2–3)
Status: Proposed Companion: V_SERIES.md (portfolio map)
Problem 1 — Five parallel online-services stacks#
Every game version specs its own auth/matchmaking/leaderboards/anti-cheat/ telemetry layer, in a different language and framework:
| Ver | Stack | Count | Where |
|---|---|---|---|
| V2 | TypeScript Nx adapter libs | 50 | V2/services/ |
| V4 | Rust + Axum microservices | 25 specced (13 scaffolded) | V4/services/ |
| V5 | NestJS services over one shared runtime | 16 | V5/services/ |
| V6 | Rust services (world/gateway/Ori) | 9 | apps/v6/ |
| V7 | Rust/TS services | 8 | apps/v7/ |
All five doc sets simultaneously preach "layer on V1, don't fork." The duplicated surface — session auth, parties/friends, matchmaking, leaderboards, telemetry ingest, anti-cheat signal collection, replay storage — is the single most repeated engineering plan in the portfolio, while the genuinely game-specific parts (V2 rollback transport, V4 lockstep, V6 agent cognition, V7 realm meshing) are not shareable and rightly live per-version.
Decision#
Extract a shared game-platform service tier ("GPS") with V7's hybrid trust model as the design anchor (platform-inviolable identity/economy/safety; game-specific logic stays per-title):
- GPS-owned (one implementation, multi-tenant by
gameId): player session auth (on V1 identity), friends/parties/presence, matchmaking orchestration (per-game rule plugins), leaderboards/seasons, telemetry ingest + schema registry, anti-cheat signal bus + case management (per-game detectors plug in), replay blob storage + metadata, entitlements (see Problem 2), and creator-payout rails (Abundantia generalized). - Per-game (never GPS): netcode/simulation, game servers, world/realm state (Nephthys stays V7's), gameplay services (V6 Ori cognition tiers), and any latency-critical in-match path.
- Language ruling: GPS services follow the repo's stack policy — Rust for the hot paths (telemetry ingest, presence fan-out), TypeScript/Nx for control-plane CRUD. V5's NestJS tier and V2's adapter libs migrate by consumption, not rewrite: new GPS endpoints are stood up first, then each game's existing service spec is re-pointed and its local implementation retired section by section.
Sequencing (relative milestones, no calendar commitment)#
- M1 — contracts: define
libs/contracts/gps/(OpenAPI + event schemas) for the nine GPS domains above; each game version maps its existing spec to the contract and records deltas as ADRs in its owndocs/decisions/. - M2 — first consumer: V7 (earliest-stage implementation, strongest trust-model fit) builds against GPS natively instead of its own §-tier service stubs; V6's gateway adopts GPS presence/telemetry.
- M3 — brownfield adoption: V5 re-points its 14 wrapper services (only
anti-cheat + compliance-dsar hold real logic today — those migrate as GPS
plugins); V4's 12
/health-only routers are implemented once, as GPS, not re-scaffolded per game; V2's adapter libs consume GPS. - Exit criterion: no new per-game implementation of any GPS-owned domain is accepted; per-game TODOS sections for those domains point at GPS contract versions.
Risks#
- Multi-tenant blast radius (one GPS outage hits every title) → per-game cell isolation + per-tenant rate isolation are M1 contract requirements.
- Premature abstraction (only V3 is near-operational today) → GPS extracts only domains already specced ≥3 times (the table above), nothing speculative.
Problem 2 — No cross-game account/entitlement story#
V4 claims Iris-based identity reuse; V6's Ori carries agents across realms; V7 extends the Ori to carry player characters; V2 specs cross-product entitlements ("Lilith meditation-streak unlocks a V2 card border"). No document defines the player's single account, wallet, and entitlement graph across V2–V7.
Decision#
One Oshun player account (V1 identity, already the auth root everywhere) with three attached graphs:
- Profile graph — display identity, age/region attributes (single source for every game's age gate), platform links (PSN/Xbox/Steam/Apple/Google), block/mute lists shared portfolio-wide (a block in one title blocks in all — safety floor, per V7's platform-inviolable rule).
- Entitlement graph — purchases, unlocks, cross-product grants. Schema:
entitlement(id, ownerAccount, grantingProduct, consumingProduct, kind, sourceRef, platformConstraints, revocation). Platform-store constraints (an iOS-purchased item's visibility on PSN, cert-ban interactions perV2/legal/platform-cert-bans.json) are first-class fields, not afterthoughts. Wallet: Aje rails for crypto where certified; platform stores elsewhere; one ledger regardless of rail. - Character/being graph — the Ori event store, with V6 agents and V7 player
characters as distinct subject types (schema-evolution rules in
V6/docs/ori-schema-evolution.md). Game saves stay per-title; only portable identity state crosses titles.
Rules#
- A game may read cross-game attributes only through scoped, consented queries (V1 Iris consent model governs); no bulk profile export to game services.
- Cross-product grants (the V2↔Lilith example) are declared in the granting product's catalog and validated against the consuming product's cert-ban registry at publish time, not at runtime.
- Deletion: account erasure fans out to every attached graph with per-product
attestation (extends V1's DSAR pipeline; V3 already routes through it — the
same cascade covers entitlements and Ori subjects, with V7 realm-side deletion
handled per
V7/docs/operator-data-protection.md).
Open items (tracked, not silently deferred)#
Rewritten on 2026-09-18: the first and third lines were statements, not tasks.
The contracts library keeps each domain under libs/contracts/src/<domain>/, so
the GPS contracts go to libs/contracts/src/gps/, not the libs/contracts/gps/
path M1 names.
- GPS.M1 Start milestone M1: create
libs/contracts/src/gps/with zod schemas and the OpenAPI document for the nine GPS-owned domains listed under "Decision", exported as@oshun/contracts/gps, each schema carryinggameIdas the tenant key and the per-game cell and rate-isolation fields the "Risks" section requires. One child per domain so each can land alone.- GPS.M1.a Player session auth on V1 identity (session, platform
links, portfolio-wide block and mute lists). Verify:
gps/session.spec.tsround-trips valid and invalid fixtures; a block recorded for onegameIdis visible to a query for another. - GPS.M1.b Friends, parties and presence. Verify:
gps/social.spec.ts: a friend edge is symmetric and scoped to the account, not thegameId; a party carries onegameIdand refuses a member blocked by any other member; a presence record without an expiry is refused. - GPS.M1.c Matchmaking orchestration with a per-game rule-plugin contract. Verify: spec with two plugins that disagree.
- GPS.M1.d Leaderboards and seasons. Verify:
gps/leaderboards.spec.ts: a score is keyed bygameId, board, season and account; a submission to a closed season is refused; two games' boards with the same name do not collide. - GPS.M1.e Telemetry ingest and its schema registry. Verify: spec that an unregistered event type is refused.
- GPS.M1.f Anti-cheat signal bus and case management, with the
per-game detector plug-in contract. Verify:
gps/anticheat.spec.ts: a signal names its detector, its version and itsgameId; a case cannot close without an actor and a reason; a detector plug-in that declares no signal types is refused. - GPS.M1.g Replay blob metadata. Verify:
gps/replays.spec.ts: a record carries the content hash, the byte length, the game build it was recorded on and its retention class; a record with no hash is refused. - GPS.M1.h Entitlements:
entitlement(id, ownerAccount, grantingProduct, consumingProduct, kind, sourceRef, platformConstraints, revocation)with platform-store constraints as first-class fields, validated againstV2/legal/platform-cert-bans.jsonat publish time. Verify: spec that a grant violating a cert ban is refused when it is declared, not at runtime. - GPS.M1.i Creator-payout rails generalised from Abundantia.
Verify:
gps/payouts.spec.ts: amounts are integer minor units with a currency; a payout names its rail, its run and the ledger entries it settles; the creator floor Abundantia enforces (70%) is a field the schema requires, not a default. - GPS.M1.j Each game version maps its existing service spec to these
contracts and records the deltas as ADRs in its own
docs/decisions/(V2, V4, V5, V6, V7). Verify: one ADR per version; no domain left unmapped.
- GPS.M1.a Player session auth on V1 identity (session, platform
links, portfolio-wide block and mute lists). Verify:
- GPS.CERT Review the entitlement schema against each platform's
cross-buy policy before any cross-product grant ships. Board tag
2026-09-18: a review of store policies by a person with access to the
partner agreements; an agent prepares the policy-by-field table from the
public documents.
blocked:governance - GPS.ORI Add the Ori subject-type discriminator (V6 agent versus V7
player character) to V6's event schema under the evolution rules of
V6/docs/ori-schema-evolution.md, before V7 writes player characters into the store. Verify: a schema-evolution spec reads events written before the change, and an event without a subject type is refused after it.