@@ -0,0 +1,113 @@
|
||||
# ADR 0001: v1 control-plane boundaries and domain
|
||||
|
||||
- Status: Accepted
|
||||
- Date: 2026-07-16
|
||||
- Tracking: #2
|
||||
|
||||
## Context
|
||||
|
||||
Rendezvous must help two game peers discover and attempt an authenticated direct
|
||||
connection without becoming a game server, an identity provider, or a gameplay
|
||||
traffic service. The HTTP API and UDP mediator share short-lived state and must
|
||||
agree on authorization, endpoint freshness, and tenant scope.
|
||||
|
||||
## Decision
|
||||
|
||||
V1 is one ASP.NET Core deployable with separable directory, join-authorization,
|
||||
endpoint-registry, NAT-mediator, and operations modules. Modules communicate
|
||||
through application interfaces, not through transport DTOs or one another's
|
||||
storage implementation. Contracts and the client SDK remain independently
|
||||
packageable.
|
||||
|
||||
The service is a connection control plane. A successful join authorization only
|
||||
grants permission to attempt a direct connection. The game host remains the
|
||||
final authority for player identity, capacity, bans, admission, and gameplay.
|
||||
Rendezvous success is reported only after the host accepts a valid connection
|
||||
ticket and LiteNetLib establishes the authenticated peer connection.
|
||||
|
||||
## Domain glossary
|
||||
|
||||
| Term | Definition | Lifetime and exposure |
|
||||
| --- | --- | --- |
|
||||
| `SessionListing` | Bounded public discovery data for one hosted game session. | Visible only while its lease and host presence are fresh. Never contains endpoints or credentials. |
|
||||
| `Lease` | Renewable capability controlling the lifetime of a listing. | Secret, host-only, expires unless renewed. |
|
||||
| `HostPresence` | Authenticated observation of the host's local and public UDP endpoints from its gameplay socket. | Internal, short-lived, never returned by browsing. |
|
||||
| `JoinAttempt` | Authorization linking one client attempt to one compatible session. | Internal and short-lived; it is not authoritative game admission. |
|
||||
| `PunchCapability` | Opaque, one-time credential scoped to attempt, role, tenant, and expiry. | Sent only to its intended peer; consumed at the UDP mediator. |
|
||||
| `ConnectionTicket` | Compact signed credential presented to the host during the direct connection. | One-time, short-lived, and scoped to the attempt and host. |
|
||||
|
||||
IDs are opaque and tenant-scoped. They are never canonical player, entity, or
|
||||
world identities.
|
||||
|
||||
## Trust boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Browser["Untrusted browser/client"] -->|"HTTPS: browse/join"| Proxy["Reverse proxy"]
|
||||
Host["Game host"] -->|"HTTPS: register/renew"| Proxy
|
||||
Operator["Privileged operator"] -->|"separate authenticated route"| Proxy
|
||||
Proxy -->|"normalized HTTP + trusted forwarding metadata"| Service["Rendezvous service"]
|
||||
Host -->|"host gameplay UDP socket"| Mediator["UDP mediator module"]
|
||||
Browser -->|"client gameplay UDP socket"| Mediator
|
||||
Mediator <--> Service
|
||||
Service -->|"read keys; never list or log values"| Secrets["Secret provider"]
|
||||
Service -.->|"future authenticated state protocol"| Store["Future shared store"]
|
||||
Service -->|"redacted events and aggregate metrics"| Ops["Observability systems"]
|
||||
```
|
||||
|
||||
- Public HTTP input is hostile even after TLS termination. The proxy may be
|
||||
trusted to terminate TLS and supply forwarding metadata only when its source
|
||||
address is allowlisted; forwarded headers from other sources are discarded.
|
||||
- Public UDP input is hostile even when structurally valid. HTTP-supplied
|
||||
endpoints are claims, never proof. Public response targets come only from an
|
||||
authenticated UDP packet's observed source. A private local candidate may be
|
||||
carried inside that packet only under ADR 0002's bounded same-LAN rules.
|
||||
- Operator routes use a separate authentication policy and network exposure.
|
||||
Operator access does not bypass tenant scoping, audit, or secret redaction.
|
||||
- The client SDK is convenience code in an untrusted process. Server decisions
|
||||
never rely on client-side validation or secrecy.
|
||||
- Game hosts are authoritative only for their own gameplay admission. A host
|
||||
cannot enumerate or mutate another game/environment tenant.
|
||||
- The secret provider is trusted with long-lived key material. The application
|
||||
receives only the minimum named key version it needs.
|
||||
- A future shared store is a distinct authenticated boundary. Moving state to it
|
||||
does not make stored input trusted and requires a new availability ADR.
|
||||
|
||||
## Connection data flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant H as Game host
|
||||
participant R as Rendezvous HTTP
|
||||
participant M as Rendezvous UDP mediator
|
||||
participant C as Game client
|
||||
H->>R: Register listing (publisher authorization)
|
||||
R-->>H: Lease + host-presence capability
|
||||
H->>M: Presence from gameplay UDP socket
|
||||
M->>R: Store observed endpoint and freshness
|
||||
H->>R: Renew lease
|
||||
C->>R: Browse compatible visible listings
|
||||
C->>R: Request join attempt
|
||||
R-->>C: Client punch capability
|
||||
R-->>H: Host attempt/capability via authenticated poll or stream
|
||||
H->>M: Host capability from gameplay UDP socket
|
||||
C->>M: Client capability from gameplay UDP socket
|
||||
M->>M: Validate scope, freshness, expiry, and replay state
|
||||
M-->>H: Introduce verified client endpoints + connection ticket
|
||||
M-->>C: Introduce verified host endpoints + connection ticket
|
||||
C->>H: Direct LiteNetLib connect + ticket
|
||||
H->>H: Validate and consume ticket; apply game admission
|
||||
H-->>C: Authenticated peer connection or rejection
|
||||
```
|
||||
|
||||
The mediator does not forward normal gameplay packets. A connection attempt
|
||||
that times out or is rejected returns a typed outcome to the caller.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Directory and mediator can ship together without erasing their module boundary.
|
||||
- Contracts cannot expose server storage or LiteNetLib implementation types.
|
||||
- Tests must cover the three-party host/service/client flow; an HTTP-only test is
|
||||
insufficient evidence of a successful connection.
|
||||
- Splitting modules into processes requires an explicit protocol, shared-state
|
||||
ownership, deterministic mediator routing, and a superseding ADR.
|
||||
Reference in New Issue
Block a user