feat: add bounded compatible session browser (#8)
quality-gate / quality (push) Successful in 57s

Closes #8
This commit is contained in:
KyuubiYoru
2026-07-16 06:06:29 +02:00
parent 49564c7e7e
commit a9a2b3db35
14 changed files with 786 additions and 10 deletions
+75 -4
View File
@@ -207,6 +207,13 @@
"format": "int32"
}
},
{
"name": "excludeFull",
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "cursor",
"in": "query",
@@ -226,8 +233,18 @@
}
}
},
"501": {
"description": "Not Implemented",
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
}
}
}
},
"503": {
"description": "Service Unavailable",
"content": {
"application/json": {
"schema": {
@@ -530,6 +547,40 @@
"schema": {
"type": "string"
}
},
{
"name": "contractVersion",
"in": "query",
"required": true,
"schema": {
"type": "integer",
"format": "int32"
}
},
{
"name": "gameId",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "environmentId",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "protocolVersion",
"in": "query",
"required": true,
"schema": {
"type": "integer",
"format": "uint32"
}
}
],
"responses": {
@@ -543,8 +594,28 @@
}
}
},
"501": {
"description": "Not Implemented",
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
}
}
}
},
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
}
}
}
},
"503": {
"description": "Service Unavailable",
"content": {
"application/json": {
"schema": {
@@ -0,0 +1,51 @@
# ADR 0006: bounded compatible session browser
- Status: Accepted
- Date: 2026-07-16
- Tracking: #8
## Decision
The public list endpoint requires game, environment, and exact gameplay protocol.
Region is optional, page size is 1100, and callers may exclude sessions whose
advisory current-player count has reached the advertised maximum. Lists contain
public sessions only and only while both lease and authenticated host presence are
fresh. Unlisted sessions never appear in a list; they may be retrieved directly by
their 128-bit unguessable listing ID only when the caller also supplies the exact
game, environment, and protocol scope.
Results use ascending opaque listing ID as a deterministic keyset. A cursor carries
the last ID plus every compatibility/filter field, a five-minute expiry, and an
HMAC-SHA256 signature under a per-process key. Tampering, expiry, or reuse with a
different tenant/protocol/region/full filter returns `InvalidRequest`. Restart
rotates the key, matching the loss of ephemeral listings.
Pagination is a bounded live view, not a database snapshot. A record that remains
eligible and whose ID is greater than the cursor is returned exactly once. Records
removed or made stale disappear immediately. A record created after a page whose ID
sorts before that page's cursor is outside that traversal; callers refresh from the
first page to discover new sessions. This avoids skips or duplicates among stable
eligible records without retaining per-browser snapshot state.
The store reads at most page size plus one record. The service serializes against
the 256 KiB response ceiling and shortens a page before returning it when metadata
makes the requested count too large. A continuation cursor is emitted whenever an
extra or byte-trimmed record remains. All cursor, page, metadata, property, scalar,
and collection sizes are bounded before untrusted allocation can grow without a
ceiling.
Browser DTOs are fresh copies containing only opaque listing ID, exact compatibility,
region, visibility/trust presentation, advisory capacity, build/display labels, and
policy-validated string metadata. They contain no observed endpoint, lease,
capability, ticket, credential fingerprint, derivation salt, principal subject, or
store key. Metadata is display text: JSON encoding escapes markup, but game UI must
still render values as text and must never execute markup, interpret endpoints, or
use metadata for authorization.
## Consequences
- Cross-game, cross-environment, incompatible, stale, revoked, expired, unlisted,
and optionally full sessions are removed before response construction.
- Direct unlisted lookup is suitable for an out-of-band invite carrying the opaque
ID; human join codes remain future work and require their own bounded abuse model.
- Host capacity remains advisory. The host makes the final admission decision.
+1
View File
@@ -8,6 +8,7 @@ decision requires a superseding ADR and corresponding contract/test updates.
- [ADR 0003: state, privacy, availability, and safety budgets](0003-state-privacy-availability-and-budgets.md)
- [ADR 0004: atomic ephemeral state and single-active availability](0004-atomic-ephemeral-state.md)
- [ADR 0005: authenticated session lease and presence lifecycle](0005-session-lease-lifecycle.md)
- [ADR 0006: bounded compatible session browser](0006-compatible-session-browser.md)
- [Threat model](../security/threat-model.md)
- [Security promise and test matrix](../security/control-matrix.md)
- [Versioned HTTP and UDP contracts](../contracts/README.md)