Commit Graph

152 Commits

Author SHA1 Message Date
Josh Creek 4eaa3304c3 feat(multiplayer): extend event logging to queue and proposal mutations
Wire the same Service.Log hook added for the server register/result
routes into queue create/heartbeat/cancel and proposal accept/decline:
log the resulting state on success (queue_create, queue_heartbeat,
queue_cancel, proposal_response) or 'rejected' on a domain error,
using only the ticket/proposal ID and outcome -- never the domain
error text itself, which isn't documented as credential-free.

Read-only routes (queue GET, proposal GET, assignment fetch) and the
early availability/not-found rejections that return before reaching
the domain call are deliberately not logged in this pass.

Covered by a new end-to-end test driving real create/heartbeat/cancel
and an accept followed by a stale-revision accept (fenced for real by
the domain layer behind proposalBackendSpy, unlike the dumb queue
spy), asserting the exact sequence of events logged.
2026-09-01 13:10:08 +01:00
Josh Creek 3817df2a12 feat(multiplayer): wire structured event logging into server routes
server/observability existed fully unit-tested but was imported by
nothing outside its own package -- no HTTP handler ever called it, so
its credential redaction protected zero real log output. Wire it into
Service via an optional Log field (nil-safe, so every existing Service
literal keeps compiling unchanged) and call it from the two
workload-authenticated server routes -- register and result -- at
every outcome: unauthorized, rejected, conflict and success. Wire
cmd/control-plane to actually emit those events as JSON lines on
stderr.

Add a secret canary test that drives both routes end to end with
realistic bearer-token and result-nonce values and asserts neither
literal secret appears anywhere in what Service.Log actually received
-- a stronger claim than the existing observability unit test, which
only proves redact() strips a synthetic value under a denylisted key
name. redact() is still key-name-based, not content-based: a future
call site that logs a secret under an unlisted key name would not be
caught by this test or by redact() itself, only by the same discipline
applied here of never putting raw request/token bytes into Fields.

Queue, proposal and assignment mutation routes are not wired yet.
2026-09-01 13:07:23 +01:00
Josh Creek 66d114bfe3 test(server): seed a seasons row for the ranked rollover integration test
ranked_season_rollovers.season_id has a foreign key into seasons, but
the integration test never inserted a seasons row for 'season-1' --
ApplyRankedSeasonRollover failed on the FK constraint before the
rollover logic itself ran at all. Insert a matching seasons row,
mirroring how a real 12-week season would already exist when
maintenance's rollover sweep runs. Verified against a real
PostgreSQL instance.
2026-09-01 12:44:48 +01:00
Josh Creek 6d3490da14 fix(server): gate proposal participant timeout on actual expiry
ProposalParticipantExpireSQL marked every PENDING participant on a
proposal TIMED_OUT unconditionally -- it took a proposal_id and 'now'
but never actually compared 'now' against the proposal's expires_at,
unlike its sibling ProposalExpireSQL (which does gate on
'expires_at <= $2'). Both GetProposal and RespondToProposal run this
statement on every call as a recovery step, so the very first
RespondToProposal for any proposal timed out every participant
(including the one about to respond) before checking their response,
then rejected the real accept/decline with ErrConflict. Add the same
expiry gate via an EXISTS against proposals.expires_at, matching
ProposalExpireSQL's own condition, and update the SQL-fragment test to
assert the gate is present. Verified end to end against a real
PostgreSQL instance: TestPostgreSQLProposalClaimAndResponseAreAtomic
now passes a two-participant accept/accept sequence that previously
failed on the first response.
2026-09-01 12:44:43 +01:00
Josh Creek e23243ff56 fix(server): drop extra unused argument in queue ticket insert
CreateQueueTicket passed 9 arguments to QueueTicketInsertSQL, which
only has 8 placeholders (state is a hardcoded 'QUEUED' literal in the
SQL, not $4) -- every real queue-ticket creation against PostgreSQL
failed with 'mismatched param and argument count'. Found by actually
running the opt-in Postgres integration suite (previously never
exercised locally, per its own gating) rather than trusting the unit
tests, which mock the driver and can't catch a placeholder-count
mismatch. Verified fixed against a real postgres:17-alpine container.
2026-09-01 12:44:36 +01:00
Josh Creek 67609d71c0 feat(multiplayer): add down migrations and a rollback runner
Add migrations.Rollback(ctx, db, dir, steps): reverses the N most
recently applied migrations, newest first, each in its own committed
transaction under the same advisory lock Apply uses. Down SQL lives in
migrations/down/<version>.sql (a subdirectory, so Apply's *.sql glob
over the main directory is untouched); a missing down file for a
migration being rolled back is a hard error rather than a silent
partial reversal. Wire it into cmd/migrate as --rollback=N.

Add down files for all six existing migrations, each dropping objects
in FK-safe reverse dependency order.

Adversarial review: could not run the new integration test
(TestPostgreSQLMigrationsRollBackAndReapplyCleanly, gated behind
COSMIC_CLASH_POSTGRES_DSN / scripts/run_postgres_integration.sh)
against a real database in this sandbox - Docker Desktop's own
overlayfs ran out of space pulling postgres:17-alpine, unrelated to
this change. Verified instead by hand-tracing every DROP against its
forward migration's FK graph, confirming Apply's directory glob does
not pick up the down/ subdirectory, and a clean go build/vet/test
-tags integration. Worth an explicit real run before this is trusted
in CI.
2026-09-01 12:41:30 +01:00
Josh Creek 7e5cfdeceb style(server): gofmt allocation_match_sql_test.go 2026-09-01 12:41:18 +01:00
Josh Creek d937cb153c feat(multiplayer): add server process/assignment-ready registration API
Add POST /v1/servers/{id}/register (and its /api/v1 contract alias),
authenticated by the same workload binding as the result route. A
game server reports its protocol version and image digest and asks
to advance ALLOCATING -> PROCESS_READY -> ASSIGNMENT_READY; the store
boundary (AdvanceServerRegistration) does this as one idempotent
SERIALIZABLE transaction that also advances every participant's queue
ticket, and gates the final transition on every participant having a
live, unexpired assignment.

Adversarial review of the surrounding routing turned up a pre-existing
bug: contractServerMutation rejected any path containing '/', so the
already-documented /api/v1/servers/{id}/result route (and this new
/register route) 404'd for every real caller despite being declared
in the OpenAPI contract. Fix it to delegate shape validation to
serverMutation, matching how contractQueueMutation handles its own
two-segment paths, and add a regression test covering both contract
routes end to end.
2026-09-01 12:35:30 +01:00
Josh Creek 9dc1cc2d6f feat(multiplayer): refresh allocator ready servers 2026-09-01 10:44:43 +01:00
Josh Creek 013eb0778b fix(multiplayer): advance tickets on allocation bind 2026-09-01 10:42:10 +01:00
Josh Creek 25fdc2c2c8 fix(multiplayer): recover recorded allocations 2026-09-01 10:40:10 +01:00
Josh Creek 55c46f56ec feat(multiplayer): run leased allocator worker 2026-09-01 10:38:45 +01:00
Josh Creek 6f7d61eafb feat(multiplayer): lease allocating match claims 2026-09-01 10:36:13 +01:00
Josh Creek 03ff8e485e feat: promote accepted proposals from API 2026-09-01 10:29:50 +01:00
Josh Creek e0ba6c6ead fix: enforce ranked match promotion size 2026-09-01 10:22:26 +01:00
Josh Creek 7807b9706b feat: promote accepted proposals into matches 2026-09-01 10:21:41 +01:00
Josh Creek 11599889fa feat: gate allocation on accepted proposals 2026-09-01 10:18:22 +01:00
Josh Creek 2d750cbcab feat: enable guarded ranked matcher role 2026-09-01 10:14:22 +01:00
Josh Creek d882469c79 feat: repair matcher candidates through redis projection 2026-09-01 10:11:54 +01:00
Josh Creek 7a3d520608 fix: bind rated result to durable receipt 2026-09-01 10:09:40 +01:00
Josh Creek dd80b52a11 feat: apply certified ratings during result completion 2026-09-01 10:08:11 +01:00
Josh Creek 388300c553 fix: validate durable result submissions 2026-09-01 10:02:29 +01:00
Josh Creek eebab1bc19 feat: add workload-authenticated result API 2026-09-01 10:01:04 +01:00
Josh Creek a70a0ebc74 feat: add projected workload JWT adapter 2026-09-01 09:57:33 +01:00
Josh Creek febc69bdef feat: gate allocator roster publication 2026-09-01 09:55:09 +01:00
Josh Creek 8b5b5333c6 fix: verify signed assignment rosters 2026-09-01 09:53:45 +01:00
Josh Creek 55e07648cf test: cover provider allocation reconciliation SQL 2026-09-01 09:52:00 +01:00
Josh Creek 0023bdab6e feat: reconcile Agones allocations durably 2026-09-01 09:51:52 +01:00
Josh Creek 931e51a647 feat: add Agones allocation client 2026-09-01 09:49:09 +01:00
Josh Creek 4bbaf0976f feat: add ranked season maintenance role 2026-09-01 09:46:57 +01:00
Josh Creek e729570010 test: cover allocator PostgreSQL claims 2026-09-01 09:45:02 +01:00
Josh Creek b1966a3423 feat: add durable allocator claim boundary 2026-09-01 09:43:33 +01:00
Josh Creek 7803e1ec7c feat: wire control plane probe recorder 2026-09-01 09:40:35 +01:00
Josh Creek def60169a8 feat: persist authenticated queue probe RTT 2026-09-01 09:40:25 +01:00
Josh Creek 25cc182793 feat: persist queue probe metadata 2026-09-01 09:37:47 +01:00
Josh Creek e170bcf0ef fix: bind matcher source to playlist 2026-09-01 09:35:00 +01:00
Josh Creek d0952adaf9 feat: add runnable casual matcher role 2026-09-01 09:32:22 +01:00
Josh Creek bdc89303b4 feat: add durable matcher worker orchestration 2026-09-01 09:30:28 +01:00
Josh Creek 18538e833b feat: wire API queue projection to Redis 2026-09-01 09:28:49 +01:00
Josh Creek fe9f6f3cb5 feat: add runnable control-plane API role 2026-09-01 09:24:05 +01:00
Josh Creek 18888ed520 feat: add PostgreSQL migration runner 2026-09-01 09:21:26 +01:00
Josh Creek 4d83ec1525 feat: add signal-bound server supervisor command 2026-09-01 09:19:25 +01:00
Josh Creek 58508bd87c feat: bound supervisor drain termination 2026-09-01 09:18:19 +01:00
Josh Creek 5b80c97337 test: cover PostgreSQL season rollover 2026-09-01 09:06:10 +01:00
Josh Creek bd26aa3dc4 test: cover PostgreSQL result and outbox flow 2026-09-01 09:05:15 +01:00
Josh Creek 9d210d254a test: cover PostgreSQL proposal transactions 2026-09-01 09:04:16 +01:00
Josh Creek c8a542a3af feat: repair Redis candidates from durable source 2026-09-01 09:02:55 +01:00
Josh Creek eadaa7b54e feat: add rebuildable Redis candidate index 2026-09-01 09:01:32 +01:00
Josh Creek 414df530f0 test: expand PostgreSQL adapter coverage 2026-09-01 08:59:39 +01:00
Josh Creek 5234390931 test: add PostgreSQL integration harness 2026-09-01 08:58:28 +01:00