286fbfeb36
Closes #2
114 lines
5.8 KiB
Markdown
114 lines
5.8 KiB
Markdown
# 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.
|