mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-10 16:04:04 +00:00
feat: add durable matchmaking metadata schema
This commit is contained in:
+1
-1
@@ -1172,7 +1172,7 @@ the local/CI/community transport, not a silent production fallback.
|
||||
| 8.2 `[D:8.1]` | **DONE.** Encode the launch SLOs from `docs/MATCHMAKING.md`: RTT, allocation/connect latency, 99.9% allocation/result success, API latency and tick health | [`docs/MATCHMAKING-SLOs.md`](docs/MATCHMAKING-SLOs.md) defines each metric, denominator, percentile/window, owner, alert threshold and release evidence |
|
||||
| 8.3 `[D:8.1]` | **DONE.** Publish versioned OpenAPI + WebSocket contracts for Steam login/session, profile/rating, queue create/heartbeat/cancel/resume, proposal accept/decline, assignment/status, server registration/roster/result/shutdown | [`server/contracts/v1/`](server/contracts/v1/) contains machine-readable REST/events contracts and dependency-free structural tests; REST resync is specified by the contract |
|
||||
| 8.4 `[D:8.3]` | **DONE.** Define opaque IDs, legal queue/match state transitions, revisions and idempotency keys | [`server/contracts/v1/state-transitions.json`](server/contracts/v1/state-transitions.json) locks terminal states, legal edges, stale-revision handling and same-key replay/conflict behavior; contract tests cover the invariants |
|
||||
| 8.5 `[D:8.4]` | Add PostgreSQL migrations for durable queue ownership, active-participation fencing, identities, sessions/revocations, seasons, ratings/events, matches/participants, penalties, results, audits and outbox; document Redis caches/TTLs | A blank DB migrates up; lost Redis writes cannot resurrect revocation, split a proposal or corrupt durable state; rollback/forward compatibility is tested |
|
||||
| 8.5 `[D:8.4]` | **IN PROGRESS.** Initial PostgreSQL migration now defines durable idempotency keys, queue ownership/active-participation fencing, identities, sessions/revocations, ranked seasons, ratings/events, matches/participants, penalties, results, audits and outbox | `server/migrations/0001_initial.sql` and static checks cover the durable tables, uniqueness/check constraints and Redis-as-cache boundary; live PostgreSQL up/rollback/forward migration, serializable adapters and cache-loss repair remain |
|
||||
| 8.6 `[D:8.3,8.4]` | **IN PROGRESS.** Add allocated-mode `ServerConfig` compatibility fields as opt-in defaults | `ServerConfig` now validates allocation mode, match/server IDs, playlist version, image digest, transport and EU/NA region; client-build/expiry/signed-authorisation admission and full manifest tests remain |
|
||||
|
||||
#### 8B — Authentication and secure control plane
|
||||
|
||||
@@ -19,6 +19,15 @@ CREATE TABLE sessions (
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE idempotency_keys (
|
||||
scope TEXT NOT NULL,
|
||||
idempotency_key TEXT NOT NULL CHECK (char_length(idempotency_key) BETWEEN 16 AND 128),
|
||||
payload_digest BYTEA NOT NULL,
|
||||
result JSONB NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (scope, idempotency_key)
|
||||
);
|
||||
|
||||
CREATE TABLE queue_tickets (
|
||||
ticket_id TEXT PRIMARY KEY,
|
||||
player_id TEXT NOT NULL REFERENCES identities(player_id),
|
||||
@@ -95,6 +104,25 @@ CREATE TABLE ratings (
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE seasons (
|
||||
season_id TEXT PRIMARY KEY,
|
||||
playlist TEXT NOT NULL CHECK (playlist = 'ranked'),
|
||||
starts_at TIMESTAMPTZ NOT NULL,
|
||||
ends_at TIMESTAMPTZ NOT NULL CHECK (ends_at > starts_at),
|
||||
rolled_over_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE TABLE penalties (
|
||||
penalty_id TEXT PRIMARY KEY,
|
||||
player_id TEXT NOT NULL REFERENCES identities(player_id),
|
||||
match_id TEXT REFERENCES matches(match_id),
|
||||
playlist TEXT NOT NULL CHECK (playlist IN ('casual', 'ranked')),
|
||||
kind TEXT NOT NULL,
|
||||
starts_at TIMESTAMPTZ NOT NULL,
|
||||
ends_at TIMESTAMPTZ NOT NULL CHECK (ends_at > starts_at),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE result_receipts (
|
||||
result_id TEXT PRIMARY KEY,
|
||||
match_id TEXT NOT NULL UNIQUE REFERENCES matches(match_id),
|
||||
|
||||
@@ -10,15 +10,16 @@ SQL = (Path(__file__).parent / "0001_initial.sql").read_text()
|
||||
class MigrationTest(unittest.TestCase):
|
||||
def test_durable_domains_and_fences_exist(self):
|
||||
required_tables = {
|
||||
"identities", "sessions", "queue_tickets", "proposals",
|
||||
"identities", "sessions", "idempotency_keys", "queue_tickets", "proposals",
|
||||
"proposal_participants", "matches", "match_participants",
|
||||
"ratings", "result_receipts", "outbox", "audit_events",
|
||||
"ratings", "seasons", "penalties", "result_receipts", "outbox", "audit_events",
|
||||
}
|
||||
for table in required_tables:
|
||||
self.assertIn(f"CREATE TABLE {table}", SQL)
|
||||
self.assertIn("queue_tickets_one_active_per_player", SQL)
|
||||
self.assertIn("match_participants_one_active_match", SQL)
|
||||
self.assertIn("UNIQUE (aggregate_type, aggregate_id, revision)", SQL)
|
||||
self.assertIn("PRIMARY KEY (scope, idempotency_key)", SQL)
|
||||
|
||||
def test_redis_is_not_a_durable_dependency(self):
|
||||
self.assertNotIn("CREATE TABLE redis", SQL.lower())
|
||||
@@ -34,6 +35,12 @@ class MigrationTest(unittest.TestCase):
|
||||
self.assertIn("participation_active BOOLEAN NOT NULL DEFAULT TRUE", SQL)
|
||||
self.assertIn("WHERE participation_active", SQL)
|
||||
|
||||
def test_seasons_are_ranked_only_and_penalties_are_durable(self):
|
||||
self.assertIn("CHECK (playlist = 'ranked')", SQL)
|
||||
self.assertIn("CREATE TABLE penalties", SQL)
|
||||
self.assertIn("REFERENCES identities(player_id)", SQL)
|
||||
self.assertIn("REFERENCES matches(match_id)", SQL)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user