4.7 KiB
ADR 0005: authenticated session lease and presence lifecycle
- Status: Accepted
- Date: 2026-07-16
- Tracking: #7
Context
A host needs to publish a player-facing session without letting an HTTP request claim a public endpoint or remain visible after the gameplay socket disappears. Registration retries must be safe, credentials must remain opaque, and policy or ownership checks cannot race state mutation.
Decision
The four host HTTP operations require Authorization: Bearer <publisher credential>.
The signed principal supplies the authoritative game, environment, publisher trust
mode, subject, and allowed regions. Request fields never widen that scope. Creation
and update apply the enabled GamePolicy to exact protocol, region, visibility,
bounded display/build/capacity values, and the allowlisted metadata schema.
Capacity reported by a host is advisory directory information. Rendezvous bounds and publishes it but never treats it as final admission authority; the game host still decides identity, bans, reserved slots, and whether a connection may join.
stateDiagram-v2
[*] --> AwaitingPresence: authorized register
AwaitingPresence --> Listed: valid host UDP presence
Listed --> AwaitingPresence: presence deadline passes
AwaitingPresence --> AwaitingPresence: lease renew or data update
Listed --> Listed: lease renew, data update, or presence refresh
AwaitingPresence --> Removed: lease expiry or delete
Listed --> Removed: lease expiry or delete
Removed --> [*]
Registration returns a listing ID, lease ID/token, host-presence handle/capability, lease expiry, a 30-second renewal suggestion, and a 10-second presence-refresh suggestion. The authoritative ceilings remain 60 seconds for the lease and 20 seconds for presence. Timing suggestions are server-controlled, not client-selected.
The lease token and presence capability are 256-bit opaque values derived with HMAC-SHA256 from an in-memory per-process secret, a purpose label, the publisher subject, the idempotency key, a canonical request fingerprint, and a random per-registration derivation salt. Opaque IDs use separate purpose labels. Exact retries read the retained non-secret salt and therefore reproduce the original response without retaining plaintext credentials. Once the bounded idempotency record expires, a new salt rotates IDs and capabilities so an old token cannot regain authority. Metadata order is canonicalized before fingerprinting. The store retains the salt and only a second keyed fingerprint of each token. Restart rotates the derivation secret while the matching ephemeral state disappears.
Renew, update, and delete require both the same publisher subject and the lease capability. Cross-owner or wrong-capability access returns the same not-found shape. Update may change display name, build label, advisory capacity, and metadata only; game, environment, region, protocol, visibility, trust mode, and opaque IDs remain canonical. Delete is idempotent and does not reveal whether another publisher owns the supplied ID.
UDP presence
Only a structurally valid HostPresence datagram with the issued capability can
refresh presence. The public endpoint is the UDP packet's observed source on the
host's gameplay socket; the HTTP API never accepts one. The bounded local candidate
comes from the authenticated datagram. Invalid, unknown, or client-presence packets
receive no response. Presence expiry demotes public visibility but keeps the lease,
so the same handle can restore visibility without changing session identity.
Public listing responses contain bounded listing data only. They never contain public/local endpoints, lease tokens, presence capabilities, fingerprints, store keys, or canonical player identity.
Failure semantics
- malformed or policy-invalid fields return a stable typed
InvalidRequest; - an unsupported gameplay protocol returns
IncompatibleProtocol; - missing/invalid publisher authentication returns
AuthenticationRequired; - cross-scope authorization returns
Forbiddenwithout resource disclosure; - wrong owner/capability or expired state returns the tenant-hidden
NotFound; - idempotency reuse with changed input returns
Conflict; - publisher/global exhaustion returns
CapacityExceeded; and - drain or loss of atomic state returns
ServiceUnavailableand authorizes no join.
Consequences
- HTTP registration alone can never make a public session browseable.
- Plaintext session capabilities are returned to the intended host but are not retained, logged, included in public listing DTOs, or exported as metrics.
- Re-registration after restart is the recovery path; there is no durable session identity or gameplay state in Rendezvous.