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
Add Step 5b to fix-defect and implement-feature commands: after applying
review comments, rerun compile, test, spotlessScalaApply, and lint, then
re-spawn reviewer to confirm findings are resolved.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pass discard_changes: true so cleanup succeeds after branch is pushed.
Add .claude/worktrees/ to .gitignore.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Run spotlessScalaApply then lint synchronously (never background) in
fix-defect and implement-feature workflows. Hard gate: do not proceed
to review/commit until lint exits 0.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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
Adds x86-64-v2 microarchitecture as a second build target alongside
default. Images tagged with -x86_v2 suffix; GHA cache scoped per arch.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
Track whether subscribeGame is being called and completing successfully
to diagnose empty game-writeback messages.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
When instance stream drops, immediately force delete the K8s pod (grace period 0). No waiting for health check or pod watch events.
Reduces failover latency and ensures stale pods don't linger.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Scale up: only if resource constrained (CPU/memory)
Scale down: only if NOT resource constrained AND game load low
Remove: triggering scale-up on unexpected instance failures
Keep: health monitoring (mark dead, delete pod, failover games) but no scaling
Prevents cascade scaling from transient health check failures.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>