V2 is a globally-shipped, age-rated, real-money-transacting competitive game, so
its security and compliance surface is cert-blocking — a gap here is not a
backlog item, it is a reason a platform holder refuses to ship. The defining
architectural choice is that V2 does not reinvent any of it: identity, PII
handling, data residency, consent, age-gating, audit, and data-subject-rights
are all delegated to existing Oshun, Aphrodite, Themis, and Nous packages,
reached through thin V2 service surfaces under apps/v2/. That composition — V2
binding to its sister monorepos rather than forking their logic — is the
"sister-monorepo integration" in the title, and it is precisely what lets a
deletion request, a ban, or a likeness revocation resolve consistently across
every Oshun product instead of just this one. The same instinct runs through
the engine: the deterministic, rollback-synchronized combat loop stays
sovereign-local, while everything non-deterministic (the "warm path") leans on
shared infrastructure. This page covers the security/privacy posture, the
data-subject-rights architecture, and the full integration surface that wires V2
to the monolith. The section hub is
../V2_ARCHITECTURE.md.
What ships, honestly#
The integration packages are real, and several are substantively
implemented. apps/v2/ holds 91 packages; every @v2/* compliance surface
this page cites is one of them and composes the corresponding shared contract.
@v2/oshun-identity-binding
(apps/v2/oshun-identity-binding/src/oshun-identity-binding.ts:49) wraps
@oshun/identity; @v2/aphrodite-age-gate and @v2/aphrodite-consent-surfaces
wrap @aphrodite/age-verification and @aphrodite/consent-engine;
@v2/themis-privacy-dsr-routing joins @themis/privacy with
@oshun/data-residency and @oshun/audit-platform; and
@v2/nous-anti-cheat-classifiers composes @nous/training + @nous/safety.
The strongest anchor is @v2/telemetry-privacy-compliance
(telemetry-privacy-compliance.ts), which is not a thin wrapper at all — it
carries a real 33-entry GDPR region-code set (:177), a CCPA US-CA branch
(:355), a per-data-class retention policy (operational 7d / optional 30d /
behavioral 14d, :250), and a PII-field redactor that drops forbidden fields at
ingest (:315).
The save-header identity fields are real engine state. FV2SaveHeader
(V2/ue/Source/V2Persistence/Public/V2PersistenceTypes.h:242) stores
OshunAccountId (:253), PlatformUserId (:256), and HomeRegionId
(:259) as three separate fields, and the save validator actively refuses to
load a slot whose account does not match the header
(V2/ue/Source/V2Persistence/Private/V2SaveGame.cpp:39,51,65). The legal data
is checked-in, not just prose: V2/legal/rating-boards.json,
V2/legal/sub-processors.md, and V2/legal/platform-cert-bans.json are real
files, and the cert-ban truth table is enforced by a real CI gate at
V2/ue/Tools/check-platform-cert-bans.py (region rules by
check-v2-region-rules.py).
What is policy rather than code, labelled as such throughout: the per-region
datacenter routing (Frankfurt/Stockholm, Tokyo, Seoul, São Paulo, CN
publisher cloud) is residency infrastructure these packages assume, not
infrastructure they provision; the runtime gRPC clients the integration
table promises (libs/proto/v2/online/) are a designed wire contract whose
proto package is not in the tree (see
Online Backbone & Competitive Integrity);
and some named cert artifacts — cook-region.py, check-cn-content-rules.py,
V2/legal/cert-windows.md — are not yet present and are honest "planned"
items. As ever: the enforcement seams are coded, the obligations are documented
and audited.
Security: identity, sessions & the save-header invariant#
Identity roots in the platform, not in V2. @oshun/identity is the single
account root; per-platform shadow accounts (PSN, Xbox Live, Nintendo Account,
Steam) bind to it through the V2 @v2/oshun-identity-binding surface, which
validates that every platform account binds to exactly one Oshun account before
it returns (oshun-identity-binding.ts:54). The engine keeps these separable on
purpose. Because FV2SaveHeader carries OshunAccountId and HomeRegionId
distinctly from PlatformUserId, and the header even stamps its provenance
(IdentitySourcePackageName = "@oshun/identity", V2PersistenceTypes.h:262), a
cloud save can never silently merge two identities — the cross-field equality
checks in V2SaveGame.cpp are the mechanism, not a comment. This is the
load-bearing security invariant of cross-progression: shared profile, Crowns
ledger, cosmetics, and ranked rank all hang off one root, so revoking that root
revokes everywhere at once.
Sensitive actions are gated, not assumed. The binding surface exposes an
accountMergeGate and a platformRebindGate
(oshun-identity-binding.ts:71-72) and a regionChangeDecision (:73) — the
TOTP-stepped, cool-off-bounded checks the monolith requires for account merge,
payment-detail change, and ranked-pro qualification. Session security itself is
conventional and strict: short-lived JWTs (15 min) with rotating 30-day refresh
tokens and cross-region session sync, the same foundation documented on the
platform Auth & Identity page. Critically,
the binding surface is marked offRollback: true /
mayInfluenceRollback: false (:78-79) — identity authority is in the warm
path and can never perturb a deterministic frame.
Privacy: PII stripping, consent & content gating#
V2 handles PII by stripping at ingest rather than trusting downstream
deletion, and @v2/telemetry-privacy-compliance is where that actually happens.
applyTelemetryPrivacyComplianceToBatch (telemetry-privacy-compliance.ts:262)
walks each telemetry event, redacts any forbidden field (device_ad_id,
full_name, phone_number, plus the inherited
V2_TELEMETRY_FORBIDDEN_PAYLOAD_FIELDS) before the event is accepted (:315),
classifies the event's data class, and then runs a genuine legal-basis decision
(evaluateTelemetryEventAllowance, :364): required operational telemetry
rides "contract-or-legal-obligation" with 7-day retention; optional and
behavioral telemetry require a consent basis; a GDPR-region player with no
explicit opt-in has optional telemetry rejected (:400); and a CCPA
Do-Not-Sell/Share opt-out suppresses behavioral telemetry outright (:387). Raw
IP, device fingerprint, and real-name fields therefore never reach the analytics
warehouse — by code, per-event, with an audit topic emitted when a redaction
fires (:470).
Per-feature consent is likewise not hand-rolled.
@v2/aphrodite-consent-surfaces (aphrodite-consent-surfaces.ts:45) composes
@aphrodite/consent-engine to govern telemetry, voice-processing, and
behavioural-profiling consent; it requires consent receipts when a feature is
enabled, defaults missing opt-ins to privacy-preserving fallbacks, and stays off
rollback. Age-gating runs through @v2/aphrodite-age-gate
(aphrodite-age-gate.ts:52) over @aphrodite/age-verification as a
backend-only driver for gore tier, adult fatalities, and region-conditional
cinematics. Two honesty details are encoded directly in the surface:
aphroditeBrandHiddenFromPlayers: true (the adult-platform brand never shows)
and record2257Required: false / antiCsamPerformerWorkflowRequired: false
(:42-44) — the audit explicitly applies the mature-rated-fighting-game
profile and rejects Aphrodite's adult-content default, so performer-protection
workflows meant for adult content are excluded from game-content gating.
Region content rules are real shared data, not prose: @oshun/region-rules
(libs/shared/region-rules/src/v2-regional-content-rules.ts:5) enumerates the
CN/DE/AU/NZ/KR/GLOBAL country axis, the gore-tier ladder
(none/mild/standard/extreme), finisher policies, and the per-region content
cuts (remove-blood, recolor-red-blood, disable-adult-fatalities, …) that the
checked-in V2/legal/rating-boards.json matrix (ESRB M, PEGI 18, CERO Z, USK
18, ACB R18+, GRAC, ClassInd 18, OFLC R18, with SKU tags and streaming-patch
downgrades) maps onto. Missing region data defaults to the China
most-restrictive profile. The per-region cook itself (cook-region.py) and
the CN content gate (check-cn-content-rules.py) are the planned-but-absent
half of this story.
Compliance & the data-subject-rights architecture#
Data-subject rights compose three packages with a clean separation of duties.
@v2/themis-privacy-dsr-routing (themis-privacy-dsr-routing.ts:91) joins
@themis/privacy — which owns the access / erasure / portability /
rectification workflow and the regulatory SLA — with @oshun/data-residency,
which owns the subject-home route, and @oshun/audit-platform, which owns the
retained evidence. The SLAs are real, frozen constants, not aspirations:
SLA_DAYS_BY_FRAMEWORK
(libs/themis/privacy/src/v2-data-subject-rights.ts:155) pins gdpr: 30,
uk_gdpr: 30, ccpa_cpra: 45, lgpd: 15, pipeda: 30, and a
global_privacy_baseline: 30. The workflow builder also fails loud on a
missing 2FA assertion (:172) and on a rectification request with no fields
(:179) — a DSR cannot be routed without authenticating the subject first.
The residency half is equally concrete. createDsrResidencyRoutingDecision
(libs/shared/data-residency/src/dsr-routing.ts:48) resolves the subject's home
zone from the JWT claim, produces a zone-scoped queue name under the
themis.privacy.dsr prefix (:13), attaches the cross-region-safeguard flag,
and hard-codes standaloneDsrOpsConsoleAllowed: false (:46). The V2 surface
re-asserts that as standaloneDsrOpsConsoleReplaced: true
(themis-privacy-dsr-routing.ts:165): V2 ships no bespoke deletion console
— a V2 player's request flows into V1's existing, home-zone-routed pipeline.
Erasure is explicitly scoped (account, profile, replay_anonymization,
telemetry_purge, cosmetic_ledger, :22) and produces a documented
three-step audit trail (request → action → confirmation) with anonymized
retention (:171), preserving anonymized aggregates per GDPR Recital 26.
Because identity binds through one root, that erasure cascades to every bound
shadow account — the cross-progression invariant paying off again.
Every moderation, anti-cheat, and DSR action publishes a canonical event through
@oshun/audit-platform. The shared module
(libs/shared/audit-platform/src/v2-audit-publication.ts) defines exactly three
publication kinds — moderation, anti_cheat, dsr (:12) — each with a
retention tag, and buildV2AuditPublicationRequest (:89) is invoked literally
by the DSR surface (themis-privacy-dsr-routing.ts:137) and the anti-cheat
surface (nous-anti-cheat-classifiers.ts:112). V2 produces the evidence; Oshun
retains and exports the investigation-ready trail. The anti-cheat ML suite
belongs to this compliance posture too: @v2/nous-anti-cheat-classifiers
(nous-anti-cheat-classifiers.ts:78) is governed by a Model Card and pins
automatedDisciplineAllowed: false, humanReviewRequired: true,
appealsRequired: true (:59-61), with a validateSuite guard (:143) that
refuses any configuration able to influence rollback. AI-system transparency is
its own surface: @v2/eu-ai-act-surface (eu-ai-act-surface.ts:30) registers
the four shipping AI services (adaptive-AI-director, AI-commentary,
anti-cheat-classifier-suite, generation-pipelines) against @nous/safety Model
Cards and @themis/accountability AI-system-of-record exports — the EU AI Act
conformity seam. DSA Statement-of-Reasons publication and appeals route out to
Themis (@v2/themis-dispute-resolution), keeping discipline auditable and
reversible by the same machinery that governs every Oshun product.
Sub-processor governance#
Compliance is not only about player-facing flows; it is also about who else
touches the data. V2/legal/sub-processors.md is a real, dated registry naming
the twelve production sub-processors V2 routes data to — the four platform
holders (Sony, Microsoft, Nintendo, Valve) plus Epic, the cloud tier (AWS for
regional hosting/telemetry/backups, Azure for failover/security analytics,
Google Cloud for aggregate analytics isolation), Cloudflare (CDN/DDoS/WAF),
Stripe (PC/web payments, no raw card storage by V2), Zendesk (support + DSR
intake), and Sentry (crash triage with scrubbed identifiers). Each row carries a
contract reference (DPA-CLOUD-AWS-V2-2026, …), a jurisdiction, and the exact
data categories accessed, and the document encodes the governing rules: a new
production sub-processor needs DPO/legal approval and 30 days' player
notice, per-platform DPAs are required before launch, and privacy legal
reviews the registry quarterly and before each launch region opens. The AWS row
makes the residency story concrete — "account, gameplay, telemetry, replay,
moderation, audit, backup data by residency zone" — which is exactly the
zone partitioning the @oshun/data-residency decisions above assume but do not
themselves provision.
The sister-monorepo integration surface#
The integration is governed by one rule: the deterministic inner loop is sovereign, everything else is delegated. The sovereign-local set is non-negotiable because it sits inside the frame-deterministic, rollback- synchronized, or cert-driven envelope — the hitbox/hurtbox engine, frame-data runtime, motion-input parser, rollback engine, save-format binary + replay container, Sequencer timelines, per-platform IMC files, and the anti-cheat client driver. Everything outside that envelope is in scope for delegation, and V2 consumes the monolith across three channels:
Channel A — build-time content pipeline (offline). Bellona orchestrates
Isis-generated assets, Aja-cleaned mocap, Hathor-authored narrative,
Euterpe-generated music, and Themis-cleared rights manifests into engine-native
.uasset / .umap artifacts through the existing @bellona/unreal bridge;
V2's cook consumes the output. The pattern for bulk generated content is
uniform: Isis generates → Themis verifies originality → Kuanyin moderates →
Bellona cooks → V2 consumes.
Channel B — runtime services bus (warm path, off-rollback). apps/v2/ is a
thin adapter layer over the Oshun @oshun/event-bus (Redis Streams) and — as
designed — gRPC clients generated from libs/proto/. V2 publishes lifecycle
events (v2.match.ended, v2.cosmetic.purchased, v2.player.reported) and
subscribes to ecosystem events (maat.balance.recommendation,
hathor.dialogue.ready, isis.asset.cooked). This is the channel the
per-surface mapping table fills in: ML balance to Maat
(@v2/maat-balance-dashboard), commentary to Iris/Calliope/Psyche, moderation
to Kuanyin/Themis, NPC AI to Hathor (@v2/hathor-npc-adapter), and compliance
to the shared residency/age/consent packages above. The honest caveat stays
attached: the proto package is not yet in the tree, so the gRPC method
signatures are the designed contract, while the event-bus substrate is the real,
shipped one.
Channel C — out-of-game surfaces. The marketing tile, companion app, public
API, frame-data wiki, and esports portal become tiles under apps/oshun/web
(/v2/...) and apps/oshun/mobile/v2/, consuming the @oshun/shell-core
umbrella plus specific surface clients (@oshun/auth-client,
@oshun/trust-safety, @iris/agents, @sophia/client). There is deliberately
no monolithic @oshun/sdk; consumers import the narrow surfaces they need.
Cross-product entitlements (a Lilith meditation streak unlocking a V2 card
border) become trivial because account state already lives in @oshun/identity.
Optional Web3 cosmetic ownership (@v2/aje-web3-cosmetic-ownership) and the
racing-ecosystem bridge (@v2/racing-ecosystem-bridge) ride the same
off-rollback discipline, each cert-gated by V2/legal/platform-cert-bans.json
and the real check-platform-cert-bans.py gate.
Edge cases & failure modes#
- Cloud-save identity mismatch is a hard refusal. A slot, cloud policy, or
control-scheme profile whose account differs from the header account is
rejected at load (
V2SaveGame.cpp:39,51,65) — two identities never merge by accident. - DSR without 2FA fails loud.
buildV2ThemisPrivacyDsrWorkflowthrows whenauthenticatedWith2Fais false (v2-data-subject-rights.ts:172); a rectification with no fields throws too (:179). No silent no-op. - GDPR optional telemetry with no consent is dropped, not retained. The
allowance evaluator rejects it with
gdpr-explicit-opt-in-required(telemetry-privacy-compliance.ts:400); CCPA behavioral telemetry under a Do-Not-Sell opt-out is rejected withccpa-do-not-sell-share(:387). - Anti-cheat can never auto-discipline.
validateSuite(nous-anti-cheat-classifiers.ts:143) refuses any suite flagged as able to influence rollback, andautomatedDisciplineAllowedisfalseby construction — classifier output only prioritises human review. - Aje/Web3 surfaces fail CI where banned.
check-platform-cert-bans.pyfails the build if a cosmetic-ownership or governance surface is reachable on a platform whosebannedSurfaces[]prohibits it (V2/legal/platform-cert-bans.json). - Missing region content data defaults to most-restrictive. Unknown regions fall through to the China profile rather than shipping uncut gore.
Where this connects#
- Sideways: Online Backbone & Competitive Integrity (the anti-cheat + ban path and the gRPC/proto honesty), Telemetry, Performance, Testing & Release Gates (PII strip at ingest, cert as a release gate), and Live-Ops, Store, Progression & Community (store compliance, spend limits, community governance).
- Platform foundations it composes:
Auth & Identity,
Persistence & Data, and the
shared libraries
(
@oshun/data-residency,@oshun/audit-platform,@oshun/region-rules). - The section hub: ../V2_ARCHITECTURE.md.