Commit Graph

48 Commits

Author SHA1 Message Date
Josh Creek ce17a45afb feat(multiplayer): alert on workload server-mutation conflicts
Closes the 'live duplicate/conflict alerting also remains' gap noted
in §8.10: a durable domain.ErrConflict/ErrResultConflict rejection on
/v1/servers/{id}/{register,connect,disconnect,shutdown,result} was
already logged as a structured 'conflict' stage event, but had no
Prometheus signal distinct from the generic 4xx-class counter, which
also catches ordinary client noise (malformed bodies, expired
tokens). A real duplicate registration, raced reconnect, or replayed
result would have been invisible to alerting until someone went
looking through logs.

observability.Metrics gains ObserveServerConflict(kind), a bounded
counter keyed to serverMutation's own five routes (an unrecognized
kind folds into "other", so a caller mistake can't grow the label
set), exported as cosmic_clash_api_server_conflicts_total. Wired at
each of serverMutation's four conflict branches in server/api/service.go.
deploy/observability/prometheus-rules.yaml adds
CosmicClashControlPlaneServerConflicts, mirroring the existing
allocator quota-denial alert shape, firing on >3 conflicts of one
kind in 15 minutes.

Verified: go build/vet/test -race clean across every server package;
new unit tests cover per-kind counting, the bounded 'other' fallback,
the counter's absence until first observed, and a nil-receiver no-op;
a service-level test proves a real register conflict is exported
through the live /metrics endpoint. scripts/verify_observability_manifests.py
passes against the edited rules file.

Remaining, and explicitly out of scope here: this alert has only been
validated statically, never against a live Prometheus/Alertmanager
firing on real traffic — that requires the same live cluster this
sandbox has never had.
2026-09-04 17:24:09 +01:00
Josh Creek aac81c89b6 feat(multiplayer): bind admissions to durable leases 2026-09-03 13:45:39 +01:00
Josh Creek 3e0022ce9c feat(multiplayer): persist connection generation leases 2026-09-03 13:38:13 +01:00
Josh Creek aa446cfbfe fix(multiplayer): reconcile authoritative initial connections 2026-09-03 00:02:04 +01:00
Josh Creek 51f8008a38 fix(multiplayer): gate API readiness on database 2026-09-02 19:14:21 +01:00
Josh Creek 55b88a3aa5 fix(multiplayer): converge events through REST 2026-09-02 18:54:10 +01:00
Josh Creek 91658fbc13 fix(multiplayer): recover assignment handoff 2026-09-02 18:47:00 +01:00
Josh Creek 6a9b269798 fix(multiplayer): validate contract ticket input 2026-09-01 22:59:57 +01:00
Josh Creek badd0b1b47 fix(multiplayer): validate contract route ids 2026-09-01 22:58:28 +01:00
Josh Creek e376e1e80d fix(multiplayer): align websocket assignment ids 2026-09-01 22:42:04 +01:00
Josh Creek 429fb87c08 fix(multiplayer): validate server event resource ids 2026-09-01 22:25:04 +01:00
Josh Creek 51f6e19339 feat(multiplayer): expose ranked season countdown 2026-09-01 21:49:48 +01:00
Josh Creek eb3b685af0 feat(multiplayer): expose active ranked season 2026-09-01 21:34:57 +01:00
Josh Creek f1b8366531 feat(multiplayer): export bounded API metrics 2026-09-01 17:06:38 +01:00
Josh Creek cc12260225 feat(multiplayer): expose server shutdown acknowledgement 2026-09-01 16:50:26 +01:00
Josh Creek 5812386676 feat(observability): log authenticated read routes 2026-09-01 16:14:20 +01:00
Josh Creek c0861bfcad feat(multiplayer): wire allocated fleet runtime 2026-09-01 16:00:03 +01:00
Josh Creek 68e76b5feb feat(multiplayer): deliver allocated server rosters 2026-09-01 15:54:07 +01:00
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 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 03ff8e485e feat: promote accepted proposals from API 2026-09-01 10:29:50 +01:00
Josh Creek eebab1bc19 feat: add workload-authenticated result API 2026-09-01 10:01:04 +01:00
Josh Creek def60169a8 feat: persist authenticated queue probe RTT 2026-09-01 09:40:25 +01:00
Josh Creek 18538e833b feat: wire API queue projection to Redis 2026-09-01 09:28:49 +01:00
Josh Creek 467c25b20c feat: expose validated assignment endpoints 2026-09-01 08:17:39 +01:00
Josh Creek 0cdb60c0d9 fix: preserve assignment event revisions 2026-09-01 08:13:35 +01:00
Josh Creek eef7cf28da feat: make proposal responses durable 2026-09-01 07:52:52 +01:00
Josh Creek 30b4560bd5 feat: persist participant-scoped proposal recovery 2026-09-01 07:50:11 +01:00
Josh Creek 60fe2caf8f feat: publish matchmaking state events 2026-08-31 23:12:20 +01:00
Josh Creek 161d2cdceb fix: validate matchmaking event vocabulary 2026-08-31 23:10:31 +01:00
Josh Creek a4bc8cdac8 fix: close slow event subscribers safely 2026-08-31 23:09:16 +01:00
Josh Creek d258f17852 feat: add authenticated matchmaking event stream 2026-08-31 23:05:04 +01:00
Josh Creek fcc5d82763 feat: expose documented control plane routes 2026-08-31 23:00:22 +01:00
Josh Creek 69f1ef6be1 feat: add player-scoped assignment recovery 2026-08-31 22:54:16 +01:00
Josh Creek 99680dbf6f fix: bind queue idempotency to compatibility 2026-08-31 22:51:00 +01:00
Josh Creek 66a67c931d feat: add participant-scoped proposal recovery 2026-08-31 22:41:26 +01:00
Josh Creek 8253d772cb feat: add verified Steam session endpoint 2026-08-31 22:26:27 +01:00
Josh Creek a2a7107dd7 feat: wire durable session authentication into API 2026-08-31 22:24:51 +01:00
Josh Creek 1a43a342ea test: verify persistent queue API delegation 2026-08-31 22:18:59 +01:00
Josh Creek 59cf29949f feat: wire persistent queue backend into API 2026-08-31 22:17:32 +01:00
Josh Creek b2ee9ec92d feat: validate queue compatibility metadata 2026-08-31 21:57:40 +01:00
Josh Creek 02a11704ce feat: add authenticated probe evidence boundary 2026-08-31 21:34:27 +01:00
Josh Creek 0f1cc17af6 feat: add authenticated queue recovery 2026-08-31 21:29:33 +01:00
Josh Creek 236cca30ba feat: expose authoritative ranked profile 2026-08-31 21:27:21 +01:00
Josh Creek 8d1b407bb0 feat: add authenticated proposal API 2026-08-31 21:17:15 +01:00
Josh Creek 77c01dc531 fix: reject ambiguous queue JSON bodies 2026-08-31 21:13:47 +01:00
Josh Creek cd30098eca feat: add authenticated queue HTTP API 2026-08-31 21:13:10 +01:00