[P9] 토너먼트 매치 브래킷 서버 이관 - 페어 조합 강제와 2의 거듭제곱 정규화 - #835
Conversation
- 브래킷을 표준 싱글 엘리미네이션으로 정규화하기로 결정. 현행 computeExpectedRound 는 라운드마다 인원을 절반씩 깎아 25명이 13강·7강 같은 어중간한 라운드를 만들고, 클라 getRoundLabel 이 인원수를 그대로 써서 화면에 "13강"이 뜬다. 첫 라운드에서 2의 거듭제곱으로 맞춰 이후를 16→8→4→2 로 고정 - 진행 순서는 시드 셔플(seed = tournamentUserId + round). 클라 Math.random 은 시드 개념이 없어 재현이 불가능해 "그대로 옮기기"가 성립하지 않았다. DB 저장·무상태 랜덤과 비교해 스키마 변경 0 으로 새로고침 복원까지 얻는 쪽을 택함 - 부전승 선정도 시드 랜덤. 현행은 가격 오름차순 마지막(최고가) 고정이라 가격 편향이 있다 - 페어 조합은 강제 검증하되 진행 순서는 검증하지 않기로. 라운드 내 매치는 서로 독립이라 순서가 최종 결과를 바꾸지 않는 반면, 강제하면 열린 탭·뒤로가기·재전송에서 오탐 400 만 는다 - 설계 도중 구버전 클라 호환이 불가능함을 발견. 브래킷 정규화로 비(2의 거듭제곱) 인원의 첫 라운드 페어 구성이 서버·클라 간 달라져, 당초 예상과 달리 서버 단독 배포가 불가하다. 클라 동반 배포를 전제로 고정하고 문서 8장에 명시
- 루프 조건이 `players > FINAL_ROUND_SIZE` 라 결승(2) 자체를 검사하지 않았다 - 4강 2매치를 치른 뒤 players 가 2로 줄면 `2 > 2` 가 false 여서 결승을 반환하지 못하고 루프를 빠져나가 error() 로 떨어진다 - `>=` 로 결승을 검사 범위에 넣고, 결승까지 완료된 경우만 break 로 빠져 기존과 같이 "모든 라운드 완료인데 IN_PROGRESS" 불변식 위반을 알리게 고침
- TournamentService 가 이미 1131줄이라 서비스에 얹지 않고 RoundBracket 순수 도메인으로 분리 - Spring·DB 없이 단위 테스트로 분기를 망라한다 - 파생 순서: 가격 정렬 -> 시드 부전승 -> 인접 페어 -> 시드 셔플. 부전승을 먼저 빼고 남은 것을 묶어야 가격이 가까운 것끼리 붙는 성질이 유지되고, 같은 Random 인스턴스를 순차 사용해 부전승 선정과 진행 순서가 함께 결정론이다 - seed = tournamentUserId + round 로 저장 없이 재구성한다 - 참여자마다 순서가 다르고 같은 참여자의 같은 라운드는 항상 같은 순서라 새로고침해도 복원된다 - 매치 수 공식에 2의 거듭제곱 분기를 반드시 둔다 - 없으면 n - highestOneBit(n) 이 0 이 되어 아무도 안 싸우고 라운드가 넘어간다 - isSamePair 로 좌/우가 뒤집힌 조합도 같은 매치로 본다 - 조합이 브래킷의 본질이고 좌/우는 표시 순서일 뿐이다 - 단위 테스트 86건: 인원 2~32 전수 배정 불변식(모든 아이템이 매치 또는 부전승에 정확히 한 번), 스펙 공식 표 10행, 비(2의 거듭제곱) 인원의 첫 라운드 통과 인원이 2의 거듭제곱임, 부전승 제외 후 인접 페어 유지, 시드 재현성·participant/round 별 상이, 가격 동률 시 id 정렬, price null-first, 입력 순서 무관, firstUnplayed
- TOURNAMENT-034 INVALID_MATCH_PAIR (INVALID_INPUT, 400) - 서버가 파생한 페어 집합에 없는 조합. 최신 클라는 서버가 준 currentMatch·nextMatch 를 그대로 되돌려주므로, 여기 닿는 건 조합을 임의로 구성한 요청 = 계약 위반 - TOURNAMENT-035 MATCH_ALREADY_RECORDED (CONFLICT, 409) - 이미 기록된 매치에 다른 승자 전송. 같은 승자면 멱등 성공이고 다른 승자는 결과 뒤집기라 409 - 번호는 append-only 로 033 뒤에 붙였다 (재사용·재배치 금지, code 가 클라 계약) - message 는 사용자 대면 고정 문구 - 내부 식별자·구체 검증 사유를 담지 않는다. status 는 ErrorCategory 에서 파생돼 예외가 직접 들지 않는다
- recordMatch 가 소속·미탈락·라운드만 봐서 클라가 임의 조합([0]vs[3])을 보내도 통과했다 (브래킷 무결성 미보장). 서버가 파생한 페어 집합에 없는 조합을 400 으로 거부한다
- 진행 순서는 검증하지 않는다 - 라운드 내 매치는 서로 독립이라 순서가 최종 결과를 바꾸지 않고, 강제하면 열린 탭·뒤로가기·재전송에서 오탐 400 만 는다
- 멱등 검사를 탈락 검사보다 앞에 둔다 - 이미 기록된 매치의 패자가 탈락 집합에 있어 순서를 어기면 정상 재시도가 409 ELIMINATED 로 오인된다. 같은 승자는 성공(그 라운드로 nextMatch 재파생), 다른 승자는 409
- computeExpectedRound 를 RoundBracket.matchCountOf 로 교체 - 브래킷 파생과 라운드 수학이 같은 공식을 써야 "서버가 지정한 currentMatch" 와 "서버가 기대하는 라운드" 가 어긋나지 않는다. firstRoundMatchCount 파라미터는 파생 가능해져 제거
- 브래킷 파생 입력은 라운드 시작 시점 집합이어야 한다 - 이미 싸운 아이템이 빠진 축소 집합으로 매번 파생하면 인원이 달라져 부전승 대상과 진행 순서가 흔들린다. 이전 라운드 탈락만 제외하고 현재 라운드에서 진 아이템은 남긴다
- 응답: GET inProgress.currentMatch 추가(additive), POST 는 nextMatch 를 실을 자리가 없어 {nextMatch, completed} 래퍼로 전환. currentMatch 는 ID 대신 아이템을 통째로 담는다 - remainingItems 에서 다시 찾게 하면 클라에 조합 로직이 남는다
- snapshot 조회 범위를 전체 아이템으로 넓힘 - 브래킷 파생이 remainingItems 의 상위집합인 라운드 시작 시점 집합의 가격을 필요로 한다
- 스키마 변경 0 (전부 tournament_histories 에서 파생), *Api @ApiResponses·description 과 *ApiExamples 동반 갱신 (새 예외는 add(exception, name) 으로 detail 이 예외에서 자동 추종)
- 기존 통합 테스트 1건 교정 - 삽입 인접 페어(10k,40k)를 보내던 테스트가 새 브래킷(가격 인접 (10k,20k)·(30k,40k))에서 400 이 된다. 이관이 막으려던 구멍이라 유효 페어로 고치고, remainingItems 가격 오름차순 단언은 30k·40k 로 갱신
- 이 엔드포인트 쌍의 핵심 계약은 "GET 이 내려준 currentMatch 를 POST 에 그대로 되돌려주면 통과한다" 다. 클라이언트가 하는 일과 같은 방식으로(조회해서 되돌려주기) 8건을 검증 - currentMatch 배선(아이템 정보까지 포함), 5명 시작 시 1매치 후 currentRound 가 4 로 정규화(정규화 전 로직은 절반씩 깎아 "3강" 이었다), 위조 페어 400 TOURNAMENT-034, 멱등 성공 + 기록 미증가, 승자 뒤집기 409 TOURNAMENT-035, 좌우 뒤집어도 200, 라운드 내 순서 바꿔도 둘 다 200, 결승 시 nextMatch=null + completed 순위 - 응답 계약(code·detail·data 모양)을 단언에 포함하고, 실패 detail 은 TournamentErrorCode 엔트리에서 끌어와 손으로 박지 않는다 - 분기 망라는 RoundBracketTest(단위 86건)로 내리고 통합은 시나리오·계약 수준으로 유지
|
Discord 스레드 연동용 메타데이터입니다. discord-pr-bot 워크플로가 자동 생성하며, 수정·삭제하면 PR 과 Discord 알림 연동이 끊깁니다. |
Walkthrough서버가 결정론적 싱글 엘리미네이션 브래킷을 생성하고 현재·다음 매치를 제공하도록 변경했습니다. 매치 기록에는 페어 무결성 검증과 멱등 처리가 추가되었으며, 관련 DTO·OpenAPI 문서·통합 및 단위 테스트가 갱신되었습니다. Changes토너먼트 매치 로직 백엔드 이관
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant TournamentController
participant TournamentService
participant RoundBracket
Client->>TournamentController: GET 토너먼트 상세
TournamentController->>TournamentService: getTournamentById
TournamentService->>RoundBracket: 현재 브래킷 파생
RoundBracket-->>TournamentService: currentMatch
TournamentService-->>Client: currentMatch 포함 응답
Client->>TournamentController: POST 매치 결과
TournamentController->>TournamentService: recordMatch
TournamentService->>RoundBracket: 페어 검증 및 nextMatch 계산
RoundBracket-->>TournamentService: 검증 결과
TournamentService-->>Client: nextMatch 또는 completed
Assessment against linked issues
🚥 Pre-merge checks | ✅ 1 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (1 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/test/kotlin/com/depromeet/piki/tournament/controller/TournamentIntegrationTest.kt (1)
1073-1088: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win브래킷 무결성의 실패 경로도 검증하세요.
Line 1073의 설명과 달리 현재는 유효 페어의 200만 확인합니다. 페어 검증이 제거돼도 통과하므로, 먼저
(10k, 40k)요청이TOURNAMENT-034과 함께 400인지 단언한 뒤 유효 요청을 보내세요.검증 추가 예시
+ mockMvc + .perform( + post("/api/v1/tournaments/$tournamentId/matches") + .header(HttpHeaders.AUTHORIZATION, authHeader(userId)) + .contentType(MediaType.APPLICATION_JSON) + .content( + """{"currentRound":4,"firstTournamentItemId":$ti10k,"secondTournamentItemId":$ti40k,"selectedTournamentItemId":$ti10k}""", + ), + ).andExpect(status().isBadRequest) + .andExpect(jsonPath("$.code").value("TOURNAMENT-034")) + mockMvc .perform(🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/kotlin/com/depromeet/piki/tournament/controller/TournamentIntegrationTest.kt` around lines 1073 - 1088, Update the tournament match integration test around the existing items and mockMvc request to first submit the invalid adjacent pair using ti10k and ti40k, asserting HTTP 400 and the TOURNAMENT-034 error code, then submit the valid ti10k/ti20k pair and retain the HTTP 200 assertion.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/main/kotlin/com/depromeet/piki/tournament/controller/dto/RecordMatchResponse.kt`:
- Around line 7-13: API 문서와 예제가 실제 NON_NULL 직렬화 계약인 필드 생략을 명시하도록 일관되게 수정하세요.
RecordMatchResponse.kt 7-13의 nextMatch·completed, TournamentDetailResponse.kt
94-100의 currentMatch는 직접 변경 없이 생략 계약을 기준으로 문서화하고, TournamentApi.kt 106-110 및
578-580의 null 설명을 필드 미포함 표현으로 바꾸며, TournamentApiExamples.kt 191-253의 예제명과 소비자
안내를 실제 JSON payload와 일치시키세요.
In `@src/main/kotlin/com/depromeet/piki/tournament/service/TournamentService.kt`:
- Around line 587-626: Update the early tournament-state validation before the
existing isInProgress check so a completed tournament can recognize a previously
recorded final match by matching the pair in histories. For the same winner,
reconstruct and return the completed result; for a different winner, reject it
with the existing duplicate-match error. Preserve the current
notInProgressTournament behavior for unrelated requests and leave the existing
in-progress idempotency flow unchanged.
---
Nitpick comments:
In
`@src/test/kotlin/com/depromeet/piki/tournament/controller/TournamentIntegrationTest.kt`:
- Around line 1073-1088: Update the tournament match integration test around the
existing items and mockMvc request to first submit the invalid adjacent pair
using ti10k and ti40k, asserting HTTP 400 and the TOURNAMENT-034 error code,
then submit the valid ti10k/ti20k pair and retain the HTTP 200 assertion.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro Plus
Run ID: 8f36e48d-02fc-4d80-8bcb-b77a05e6a8a1
📒 Files selected for processing (15)
src/main/kotlin/com/depromeet/piki/tournament/controller/TournamentApi.ktsrc/main/kotlin/com/depromeet/piki/tournament/controller/TournamentApiExamples.ktsrc/main/kotlin/com/depromeet/piki/tournament/controller/TournamentController.ktsrc/main/kotlin/com/depromeet/piki/tournament/controller/dto/RecordMatchResponse.ktsrc/main/kotlin/com/depromeet/piki/tournament/controller/dto/TournamentDetailResponse.ktsrc/main/kotlin/com/depromeet/piki/tournament/domain/RoundBracket.ktsrc/main/kotlin/com/depromeet/piki/tournament/service/TournamentErrorCode.ktsrc/main/kotlin/com/depromeet/piki/tournament/service/TournamentException.ktsrc/main/kotlin/com/depromeet/piki/tournament/service/TournamentService.ktsrc/main/kotlin/com/depromeet/piki/tournament/service/dto/RecordMatchResult.ktsrc/main/kotlin/com/depromeet/piki/tournament/service/dto/TournamentDetail.ktsrc/test/kotlin/com/depromeet/piki/tournament/controller/TournamentIntegrationTest.ktsrc/test/kotlin/com/depromeet/piki/tournament/controller/TournamentMatchIntegrationTest.ktsrc/test/kotlin/com/depromeet/piki/tournament/domain/RoundBracketTest.kttournament-match-bracket-spec.md
- 진행 중 검사(isInProgress)가 멱등 판정보다 앞에 있어, 결승 재전송만 409 TOURNAMENT-006 으로 떨어졌다. 결승을 기록하면 토너먼트가 즉시 COMPLETED 로 바뀌기 때문이다 - 결승 응답을 못 받고 재전송하는 경우(타임아웃·앱 재실행·중복 탭)가 가장 흔한 재시도인데, 하필 그 케이스만 멱등 계약에서 빠져 있었다. 클라는 최종 순위를 못 받고, 방금 선택을 마친 사용자에게 "토너먼트가 진행 중일 때만 할 수 있어요" 가 뜬다 - 진행 중 검사를 멱등 판정 뒤로 옮겼다 - 그 검사가 묻는 건 "새 매치를 기록해도 되나" 이므로 재시도 판정 뒤가 제자리다. 완료된 토너먼트의 같은 매치·같은 승자 재전송은 최초와 같은 순위 결과를 재구성해 돌려주고, 다른 승자는 그대로 409 TOURNAMENT-035 다 - 부수 효과로 미참여자가 완료된 토너먼트에 요청하면 409 대신 403 이 나간다 - 권한 검사가 상태 검사보다 앞서므로 더 정확한 응답이다 - 기존 통합 테스트 1건 재구성 - "COMPLETED 이면 409" 를 검증하던 테스트가 실은 결승 재전송 케이스라 이제 200 이다. 4아이템으로 완주시킨 뒤 한 번도 치르지 않은 조합을 보내 원래 의도(완료 상태 충돌)를 그대로 지켰다 - 통합 2건 추가 - 결승 재전송 시 같은 순위 결과 + 기록 미증가, 완료 후 다른 승자 재전송 시 409 TOURNAMENT-035
- NON_NULL 규약이라 nextMatch·completed·currentMatch 는 값이 없을 때 `"nextMatch": null` 이 아니라 키째 생략된다. 두 필드가 다 비는 라운드 종료 응답은 `"data": {}` 다
- 문서·example 이름은 "null 이다" 로 안내하고 있어 실제 직렬화와 어긋났다. 통합 테스트는 doesNotExist() 로 이미 생략을 단언하고 있었으니 계약이 아니라 설명만 틀린 상태였다
- 레포 전체가 NON_NULL 이고 클라 타입도 optional 로 선언돼 있어, null 을 명시적으로 내리는 대신 생략을 계약으로 확정하고 문서를 맞췄다
- 결승 재전송이 409 가 아니라 같은 completed 를 돌려준다는 점도 매치 기록 API 설명·409 사유에 반영
- 원문에는 "재요청 → 멱등" 만 있어 결승 예외가 없다는 게 암묵적이었고, 그 탓에 구현이 상태 검사를 멱등 판정보다 앞에 두는 실수를 했다 (CodeRabbit 지적, 51600e9 에서 수정) - 진행 중 검사가 멱등 판정보다 뒤에 와야 하는 이유와 판정 순서표, 참여자 조회가 앞당겨지며 생기는 403/409 순서 변화를 함께 기록
- 가격 오름차순 응답을 검증하는 테스트가 "(10k,40k) 를 보내면 400" 이라고 주석만 달고 단언하지 않아, 페어 검증이 사라져도 통과했다 (CodeRabbit nitpick) - 전용 위조 페어 테스트는 삽입 순서와 가격 순서가 같아 "삽입 인접이지만 가격 인접이 아닌" 구분을 못 잡는다. 이관이 막은 구멍 그 자체라 이 자리에서 단언한다
|
@coderabbitai CodeRabbit 리뷰 3건 전부 accept 했습니다. 인라인 thread 2건은 각 자리에 답변하고 resolve 했고, nitpick 은 resolve 대상이 아니라 여기에 처리 사실을 남깁니다.
추가로 두 건을 더 손봤습니다.
전체 1430건 통과. |
|
Tip For best results, initiate chat on the files or code changes.
미참여자 요청을 상태 검사보다 먼저 권한 검사하여 ✏️ Learnings added
If you found this review helpful, would you consider giving us a shout-out on X? Thank you for using CodeRabbit! You are interacting with an AI system. |
draft 로 두는 이유 - 서버 단독 배포가 불가하다구현·테스트는 끝났고 CI 도 초록입니다. 리뷰는 지금 받고 싶지만, 머지 자체를 보류합니다.
브래킷 정규화로 2의 거듭제곱이 아닌 인원의 첫 라운드 페어 구성이 구버전 클라와 달라지므로, 클라가 함께 나가지 않으면 dev 환경이 이렇게 됩니다.
프론트 작업(TeamPiKi/client#404)이 아직 착수 전이라, 지금 머지하면 dev 에서 비(2의 거듭제곱) 인원 토너먼트가 전부 막힙니다. QA·기획이 dev 로 확인 중이면 바로 걸립니다. 머지 조건 (셋 다 충족되면 draft 해제)
배포 시 주의
배포 순간 |
Situation
매치 진행은 두 축으로 갈린다: 페어 구성("누가 누구와 붙나")과 진행 순서("지금 어느 페어 차례냐"). 이관 전에는 둘 다 프론트가 소유했고, 그래서 세 가지가 동시에 깨져 있었다.
Math.random으로 페어 순서를 셔플하고 그게 매치마다 재실행됐다. 시드 개념이 없어 재현이 불가능하고, 새로고침하면 다음에 뜰 매치가 바뀌었다. SSR 첫 렌더는 셔플을 건너뛰어야 해서 하이드레이션 방어 코드까지 붙어 있었다.여기에 web, app, 향후 플랫폼이 각자 페어링과 셔플과 부전승을 재구현하는 중복 비용이 겹쳤다.
Task
생존 아이템에서 서버가 매치를 파생해 내려주고 클라이언트는 그리기만 하게 만든다. 스키마는 건드리지 않는다 (기존 매치 기록 테이블에서 전부 파생 가능).
착수 전에 저울질한 결정이 셋 있었다.
1. 진행 순서를 어떻게 재현 가능하게 만드나
사용자 눈에는 시드 랜덤도 그냥 랜덤이다. 가격순도 아니고 예측도 안 된다. 차이는 서버가 같은 시드로 순서를 다시 만들 수 있다는 것뿐이라, 저장 없이 새로고침 복원이 따라온다. 세 번째는 서버가 내려준 매치를 다음 요청에서 스스로 뒤집게 되어 이관의 의미가 사라진다.
2. 검증을 어디까지 강제하나
3. 부전승을 누가 먹나. 기존은 가격 최고가 고정이었다. 가격 편향을 없애려 시드 랜덤(중립)으로 바꿨다. 사용자에게 보이는 동작 변경이라 기획 확인 항목이다.
Action
브래킷 정규화
첫 라운드에서 인원을 2의 거듭제곱으로 맞춘 뒤 진행한다. 이후 라운드는 항상 2의 거듭제곱이라 부전승이 다시 생기지 않고, 라운드 인원수를 라벨에 쓰는 클라이언트가 "16강, 8강, 4강, 결승" 으로 저절로 맞아떨어진다.
2의 거듭제곱 분기가 반드시 필요하다.
n - highestOneBit(n)은 n 이 이미 2의 거듭제곱일 때 0 이라, 분기가 없으면 매치 수가 0 이 되어 아무도 안 싸우고 라운드가 넘어간다.구현
RoundBracket). 토너먼트 서비스가 이미 1100줄을 넘어 서비스에 얹지 않았다. Spring 과 DB 없이 단위 테스트로 분기를 망라할 수 있다.멱등 판정이 다른 두 검사보다 앞에 와야 한다
재시도 판정을 어디에 두는지가 이 PR 에서 가장 미묘했던 자리다. 검사 순서를 잘못 두면 정상 재시도가 엉뚱한 409 로 오인된다.
두 번째는 CodeRabbit 리뷰에서 잡혔다. 구현 중 인지하고도 "이관 전에도 같은 409 였다" 는 이유로 넘겼는데, 이슈와 스펙의 멱등 계약에 결승 예외가 없으므로 스코프 밖이 아니라 빠뜨린 것이었다. 게다가 응답을 못 받고 재전송하는 상황은 결승에서 가장 흔하다 (타임아웃, 앱 재실행, 중복 탭). 하필 그 케이스만 새고 있었다.
진행 중 검사가 묻는 것은 "새 매치를 기록해도 되나" 이므로 재시도 판정 뒤가 제자리다. 분기를 새로 만들지 않고 검사를 옮기기만 해서, 같은 조건을 두 곳에서 판정하지 않는다.
행 락을 이미 잡은 상태라 이 분기 안에서 추가 조회를 해도 레이스는 없다.
응답 계약
inProgress.currentMatch추가. 기존remainingItems,lastHistory,currentRound는 유지{nextMatch, completed}로 감쌌다매치에는 id 대신 아이템을 통째로 담는다. 생존 목록에서 다시 찾게 하면 클라이언트에 조합 로직이 남는다.
nextMatch가 있으면 클라이언트는 재조회 없이 다음 매치를 그린다. 없으면 라운드가 끝났다는 뜻이고 조회를 다시 불러 다음 라운드를 받는다.nullable 필드는
null로 내려가지 않고 키째 생략된다. 이 레포 응답 DTO 가 전부NON_NULL규약이라nextMatch,completed,currentMatch는 값이 없으면 키 자체가 빠지고, 두 필드가 다 비는 라운드 종료 응답은"data": {}다. 문서와 예제가 "null 이다" 로 안내하던 것을 실제 직렬화에 맞췄다 (통합 테스트는 처음부터 생략을 단언하고 있었으니 계약이 아니라 설명만 틀린 상태였다). 클라 타입도 optional 로 두도록 client#404 에 반영했다.새 에러 코드 둘을 append-only 로 붙였다 (번호 재사용과 재배치 금지, 코드가 클라 계약이다).
TOURNAMENT-034TOURNAMENT-035문서도 함께 갱신했다. 조회 응답 설명에서 "생존 목록 순서가 클라이언트 페어링 순서를 결정한다" 는 서술을 걷어내고, 기록 API 에 조합 강제와 멱등 규칙을 명시했다. example 은 새 예외에서 detail 을 자동 추종하는 오버로드로 등록해 문구를 손으로 박지 않았다.
설계 문서 교정
정본 스펙에 두 곳을 고쳤다.
players > 2라, 4강 2매치를 치른 뒤 인원이 2로 줄면2 > 2가 false 여서 결승을 반환하지 못하고 불변식 위반 에러로 떨어진다. 문서와 구현 양쪽을>=와 break 구조로 고쳤다.기존 테스트 2건 교정
새 계약과 충돌하는 기존 테스트가 둘 있었다. 둘 다 이관이 바꾸려던 동작을 인코딩하고 있었다.
검증
Result
연관 이슈
Summary by CodeRabbit
nextMatch) 또는 라운드 종료/결승 완료 결과(completed)를 즉시 반환합니다.