GraalVM native image fails when scala.util.Random companion object (a static
singleton with cached seed) is reachable from the image heap. UUID.randomUUID()
is always runtime-initialized and safe.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add consumer group official-bots-game-over on {prefix}:game-over stream.
Track pub/sub subscribers per gameId in gameWatches map. On GameOver event,
unsubscribe from the game s2c channel and remove from watch map.
XACK after cleanup; DLQ after maxRetries failures.
Closes NCS-103
https://knockoutwhist.youtrack.cloud/issue/NCS-103
Reviewed-on: #67
Replace pub/sub publish in EventPublisher with XADD to user event stream.
UserWebSocketResource subscribes via XREADGROUP consumer group (per-connection
group, '$' offset). DLQ after maxRetries=3 on delivery failure. Poll loop
uses connection identity to prevent thread leak on reconnect.
Closes NCS-104
https://knockoutwhist.youtrack.cloud/issue/NCS-104
Reviewed-on: #66
Add GameOver to EventType enum and GameOverPayload DTO.
GameRedisPublisher publishes to {prefix}:game-over stream (MAXLEN ~1000)
on game completion. NativeReflectionConfig updated for core module.
Closes NCS-102
https://knockoutwhist.youtrack.cloud/issue/NCS-102
Reviewed-on: #64
Replace pub/sub subscribe with XREADGROUP on bot game-start stream.
Remove dual-write from EventPublisher.publishGameStart.
Consumer group: bot-platform-consumer, XACK after forwarding.
Poll loop uses emitter identity to prevent thread leak on re-registration.
Group created with '$' offset — no historical replay on first connect.
Closes NCS-101
https://knockoutwhist.youtrack.cloud/issue/NCS-101
Reviewed-on: #63
## Summary
- Implements the full tournament lifecycle (create, join, withdraw, start,
round progression, finish) as a standalone Quarkus module
- All 11 endpoints from the OpenAPI spec (`docs/tournament-openapi.yaml`) are covered
- Swiss pairing algorithm with Buchholz tiebreak and bye support
- Per-bot NDJSON event stream with targeted `gameStart` events carrying
the correct `color` field
- Game results ingested via Redis writeback stream (`GameResultStreamListener`)
## Known gaps (deferred)
- `GET /results` `nb` param defaults to 100 instead of all
- `PairingDto` exposes an internal `id` field not in the spec
- `GameExport.moves` emits PGN instead of UCI (upstream `GameWritebackEventDto`
does not carry UCI moves)
- `Pairing.white` can be `null` for bye rounds (spec has no bye concept)
## Test plan
- [x] 23 `TournamentResourceTest` integration tests (H2, mocked core client) — all pass
- [x] 5 `SwissPairingServiceTest` unit tests — all pass
- [x] Redis listener excluded in test/dev profiles; no Docker required to run tests
---------
Co-authored-by: LQ63 <lkhermann@web.de>
Co-authored-by: Lala, Shahd <Shahd.Lala@sybit.de>
Reviewed-on: #55
Replace synchronous account→core game-creation HTTP call and plain
pub/sub bot game-start events with Redis Streams using consumer groups,
XACK, retry, and a Dead Letter Queue for at-least-once delivery and
observability.
- account: GameCreationStreamClient publishes game-creation requests and
correlates responses via a per-instance consumer group (NCS-91)
- core: GameCreationStreamListener consumes requests, calls
GameCreationService, publishes response events, retries, and routes
exhausted/unparseable events to the DLQ (NCS-91, NCS-93, NCS-94)
- official-bots: bot game-start events migrated from pub/sub to Streams
with consumer group, XACK, retry, and DLQ (NCS-92)
- account EventPublisher dual-writes to the stream and legacy pub/sub
channel for backward compatibility
- all flows use the typed EventEnvelope (eventId/type/payload/timestamp/
correlationId) with DLQ error context (eventType, error, attempt)
- register new DTOs and EventEnvelope/EventType for native reflection
Closes NCS-91, NCS-92, NCS-93, NCS-94
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Janis Eccarius <eccariusjanis@gmail.com>
Reviewed-on: #62
- Add EventEnvelope case class (eventId, type, payload, timestamp, correlationId)
- Add EventType enum with all known event types
- Update account EventPublisher to use EventEnvelope instead of raw string interpolation
- Add EventEnvelope/EventType to account NativeReflectionConfig
- Add Jackson Scala and JSR310 modules to api dependencies
- Add api module dependency to account module
- Add NativeReflectionConfig rule to CLAUDE.md
Closes NCS-90
https://knockoutwhist.youtrack.cloud/issue/NCS-90
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reviewed-on: #61
Two bugs in move notation causing PGN import failures in LiChess:
1. Disambiguation: when two pieces of same type can reach same square,
SAN requires file/rank/full-square prefix (e.g. "Ndf3" not "Nf3").
Added disambiguate() in PgnExporter and disambiguatePiece() in
GameEngine, both querying allLegalMoves to find competing pieces.
2. Check/checkmate suffix: "+" and "#" were never appended.
PgnExporter now threads ctxAfter through moveToAlgebraic and
calls DefaultRules.isCheck/isCheckmate. GameEngine passes
PostMoveStatus to translateMoveToNotation for the same result.
Also removes dead notation code in executeMoveBody (result was never
used — not passed to MoveExecutedEvent).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Janis Eccarius <eccariusjanis@gmail.com>
Reviewed-on: #56
Default Agroal pool (max-size=20) exhausted under concurrent logins due to
bcrypt latency per request. Fixes NCS-87.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>