Test #464

Merged
klaus merged 8 commits from test into main 2026-11-01 06:23:24 +00:00
Showing only changes of commit 1a544ddd45 - Show all commits
@@ -4,9 +4,9 @@
| 항목 | 내용 |
|---|---|
| 문서 상태 | Phase 16 및 수령확인 시각 정밀도 회귀 수정·검증 완료 |
| 문서 상태 | Phase 17 통합 개인정보 정리 구현·검증 완료 |
| 작성일 | 2026-09-29 |
| 최종 수정일 | 2026-10-26 |
| 최종 수정일 | 2026-11-01 |
| 대상 제품 | 크리에이터 선물하기 |
| 작성자·결정권자 | 사용자 |
| 관련 API Contract | 별도 문서 없음. 이 문서의 `8. API 계약`을 기준으로 사용 |
@@ -258,9 +258,9 @@ INSPECTION_COMPLETED -> UNDELIVERABLE
| `GIFT-015` | 확정 | 관리자는 전체 선물함 목록과 상세를 조회하고 현재 상태에서 가능한 운영 액션을 확인할 수 있다 | 목록은 상태/신청번호/닉네임 검색과 페이징을 지원하고, 상세는 발송인/수취인 개인정보와 상품/배송/상태 정보를 내려준다 | `P10` |
| `GIFT-016` | 확정 | 관리자는 운송장 등록 전 접수된 선물을 취소할 수 있다 | 관리자만 호출할 수 있고 `RECEIVED`에서만 `CANCELED`로 전이하며 `GiftDelivery.canceledAt`을 저장하고 발신자에게 사용 캔 전액을 리워드캔으로 환불한다 | `P14-T2` |
| `GIFT-017` | 확정 | 관리자 선물함 목록과 상세는 현재 가능한 액션으로 취소 가능 여부를 함께 제공한다 | `RECEIVED`에서만 `availableActions=["CANCEL"]`; 다른 상태의 기존 액션은 유지한다 | `P14-T3` |
| `GIFT-018` | 확정 | 전달 완료 후 7일이 지난 선물의 배송 개인정보를 지운다 | 실행 시 기존 업무 시각 관례의 `now`에 대해 `status=DELIVERED`, `deliveredAt != null`, `deliveredAt <= now.minusDays(7)`인 과거 누적 건과 이후 조건을 충족하는 모든 건의 `GiftDelivery` 이름/연락처/우편번호/주소/상세주소, 택배사명/운송장번호 12개 필드를 null 처리한다. 식별자·상태·회원 닉네임·결제·동의·리뷰와 업무 lifecycle/deadline/reminder 시각은 보존하며 재실행은 추가 변경 없이 끝난다 | `P15-T2`, `P15-T3` |
| `GIFT-018` | 확정 | 전달 완료 또는 전달불가 후 7일이 지난 선물의 배송 개인정보를 지운다 | 기존 업무 시각 관례의 `now`에서 `cutoff=now.minusDays(7)`로 판단한다. `DELIVERED`와 non-null `deliveredAt <= cutoff` 또는 `UNDELIVERABLE`과 non-null `undeliverableAt <= cutoff`가 짝을 이루는 과거 누적 및 이후 적격 건의 기존 배송 개인정보 12개만 null 처리한다. 다른 시각 fallback 없이 `CANCELED`와 비대상 상태는 제외하며 식별자·상태·회원·결제·동의·리뷰·업무 시각·전달불가 사유는 보존한다. 재실행은 추가 변경 0건이다 | `P15-T2`, `P15-T3`, `P17-T1`, `P17-GATE` |
| `GIFT-019` | 확정 | 배송 개인정보를 nullable persistence로 저장한다 | `GiftDelivery`의 sender 다섯 Kotlin property는 모두 `var String?`; 현행 NOT NULL sender DB 열 네 개만 nullable로 완화한다. `sender_address_detail`은 이미 nullable이며 ALTER하지 않는다. 기존 CREATE 정의는 유지하고 `gift-schema.sql` 끝에 추가된 ALTER가 fresh install 최종 schema에도 적용된다 | `P15-T1`, `P15-T2` |
| `GIFT-020` | 확정 | 개인정보 null 상태에서도 기존 사용자/관리자 API 계약을 유지한다 | 기존 request 필수 타입/validation, response schema/바깥 객체/visibility와 flags/deadlines 계산 규칙을 유지한다. 데이터 없는 non-null 문자열은 `""`; 주소 전체가 null이면 주소 문자열은 정확히 `""`. nullable 바깥 객체와 tracking/admin courier/tracking은 기존 null 정책 유지 | `P15-T2`, `P15-GATE` |
| `GIFT-020` | 확정 | 개인정보 null 상태에서도 기존 사용자/관리자 API 계약을 유지한다 | 기존 request 필수 타입/validation, response schema/바깥 객체/visibility와 flags/deadlines 계산 규칙을 유지한다. 데이터 없는 non-null 문자열은 `""`; 주소 전체가 null이면 주소 문자열은 정확히 `""`. nullable 바깥 객체와 tracking/admin courier/tracking은 기존 null 정책 유지 | `P15-T2`, `P15-GATE`, `P17-T1`, `P17-GATE` |
| `GIFT-021` | 확정 | 배송지 기한 만료와 deadline reminder 업무 메서드를 시간별 스케줄로 등록한다 | 기존 매시 00분 자동취소 등록은 유지한다. Asia/Seoul 기준 매시 10분은 기존 recipient expiry, 20분은 신규 erasure, 40분은 기존 reminders를 각자 독립 `@Scheduled`/lock/transaction wrapper로 위임한다. 기존 업무 로직은 바꾸지 않고 작업 간 비중첩을 보장하지 않는다 | `P15-T3`, `P15-T4` |
## 8. API 계약
@@ -932,19 +932,21 @@ Response `data`는 기존 `AdminGiftOperationStatusResponse`를 사용한다.
스케줄러는 중복 실행되어도 같은 선물에 중복 환불 또는 중복 푸시가 발생하지 않아야 한다.
배송지 입력 기한과 현재 시각이 같으면 아직 기한 초과가 아니며, `RECEIVED`, `DELIVERED`, `UNDELIVERABLE`, `CANCELED` 상태는 배송지 미입력 전달불가 대상에서 제외한다.
### 8.16 전달 완료 배송 개인정보 정리
### 8.16 통합 배송 개인정보 정리
- 매 실행 시각 `now` 기준 `Gift.status=DELIVERED`, `GiftDelivery.deliveredAt != null`, `deliveredAt <= now.minusDays(7)`인 모든 건을 처리한다. 7일 경계와 그 이전의 과거 누적 건을 포함한다.
- 매 실행 시각 `now`에서 `cutoff=now.minusDays(7)`로 판단한다. `(Gift.status=DELIVERED AND deliveredAt != null AND deliveredAt <= cutoff) OR (Gift.status=UNDELIVERABLE AND undeliverableAt != null AND undeliverableAt <= cutoff)`인 모든 건을 처리한다. 두 상태 모두 정확한 7일 경계와 과거 누적 건을 포함한다.
- 각 상태와 짝인 시각만 사용한다. 짝인 시각이 null이거나 아직 7일 미경과면 다른 시각이 오래됐어도 처리하지 않으며 `COALESCE` fallback을 두지 않는다. `CANCELED`를 비롯한 다른 상태는 제외한다.
- `GiftDelivery`의 다음 12개 값만 null로 지운다: `senderName`, `senderPhoneNumber`, `senderZipCode`, `senderAddress`, `senderAddressDetail`, `recipientName`, `recipientPhoneNumber`, `recipientZipCode`, `recipientAddress`, `recipientAddressDetail`, `courierCompanyName`, `trackingNumber`.
- 기존 입력 request의 필수/non-null 계약과 검증은 유지한다. 공개 DTO 스키마와 바깥 nullable 객체는 유지하고, 기존 non-null 응답 문자열에서 데이터가 없으면 `""`로 표현한다. 주소는 완전 삭제 상태에서 정확히 빈 문자열이며 `"() "`, `"(null) null"`, 문자열 `"null"`을 만들지 않는다.
- `SENT`의 `senderInfo` 객체는 유지하고 각 문자열은 빈 문자열로 응답한다. `RECEIVED`의 `senderInfo`는 계속 null이다. 모든 받는 주소 값이 null이면 `recipientAddress`는 null이다. 사용자 tracking과 관리자 courier/tracking nullable 값은 null이다.
- 기존 `recipientAddressRegistered`는 실제 필드 완비 여부를 따르므로 삭제 뒤 false다. 별도 등록 이력을 만들지 않는다. `DELIVERED` 상태, 가시성, 기존 derived flags/deadlines, 관리자 액션(빈 목록)을 유지해 재입력/운영 액션을 요구하지 않는다.
- 발신/수신 배송 개인정보만 지운다. Member nickname/profile, Gift 및 GiftDelivery 식별자·status, 결제, 동의 여부/시각, 리뷰, `createdAt`, 업무 lifecycle/deadline/reminder 시각은 보존한다. `updatedAt`은 기존 `BaseEntity @PreUpdate` 동작을 따른다.
- 비대상 상태, null `deliveredAt`, 아직 7일이 안 된 건은 유지한다. 부분 삭제된 적격 건은 나머지를 지우며 완전 null 건의 재실행 변경 수는 0이다.
- 기존 `recipientAddressRegistered`는 실제 필드 완비 여부를 따르므로 삭제 뒤 false다. 별도 등록 이력을 만들지 않는다. `DELIVERED`/`UNDELIVERABLE` 상태, 가시성, 기존 derived flags/deadlines, 관리자 액션(빈 목록)을 유지해 재입력/운영 액션을 요구하지 않는다.
- 발신/수신 배송 개인정보만 지운다. Member nickname/profile, Gift 및 GiftDelivery 식별자·status, 결제, 동의 여부/시각, 리뷰, `createdAt`, 업무 lifecycle/deadline/reminder 시각 및 `undeliverableReason`은 보존한다. `updatedAt`은 기존 `BaseEntity @PreUpdate` 동작을 따른다.
- 비대상 상태, 상태와 짝인 null 시각, 아직 7일이 안 된 건은 유지한다. 부분 삭제된 적격 건은 나머지를 지우며 완전 null 건의 재실행 변경 수는 0이다.
- 이 작업은 푸시/환불/상태 전이를 추가하지 않는다. 앱 서버 내 DB 배송정보 정리이며 백업·로그·캐시의 삭제를 뜻하지 않는다.
- 기존 자동취소만 이미 `:00`에 예약되어 있다. 다음 3개 업무 메서드를 별도 Asia/Seoul 시간별 wrapper로 등록한다. 배송지 만료 `0 10 * * * *`, 개인정보 정리 `0 20 * * * *`, 기한 안내 `0 40 * * * *`. 업무 로직은 기존 메서드 또는 새 정리 메서드로 위임한다.
- 세 신규 wrapper는 `tryLock(0, -1, SECONDS)`, `@Transactional`과 서로 다른 lock key를 사용한다. 획득한 lock은 `finally`에서 해제를 예약하고 DB commit/rollback 이후 `afterCompletion`에서 소유권 확인 후 해제한다. transaction 없는 직접 호출에서는 업무 종료 후 해제한다. 기존 `:00` 취소 wrapper 및 key는 변경하지 않는다. 다른 작업 간 비중첩은 보장하지 않는다.
- `LocalDateTime`에 KST를 자동 변환하지 않는다. 기존 작업의 clock convention을 따르고 고정 `now`를 전달해 판단한다.
- Phase 17은 기존 Asia/Seoul `0 20 * * * *` 작업 하나를 확장한다. 내부 이름은 `runGiftPersonalDataErasure()`, `eraseGiftPersonalData(now: LocalDateTime): Int`, `findGiftsWithPersonalDataRetentionExpired(cutoff: LocalDateTime): List<Gift>`로 통합한다. 상수 symbol은 `GIFT_PERSONAL_DATA_ERASURE_LOCK_KEY`로 바꾸되 Redis literal `lock:gift-delivered-personal-data-erasure`와 transaction/unlock 처리는 유지한다. 새 스케줄·DDL·API 계약은 추가하지 않는다.
## 9. 푸시 알림 정책
@@ -1070,11 +1072,12 @@ Response `data`는 기존 `AdminGiftOperationStatusResponse`를 사용한다.
- 내부 운영 화면에서는 필요한 권한을 가진 사용자에게만 개인정보를 노출한다.
- 약관 동의 여부와 동의시각은 선물 신청 건 감사 근거로 보존한다.
### 11.1 전달 완료 배송 개인정보 수명주기
### 11.1 통합 배송 개인정보 수명주기
- 정리 범위는 선물 도메인 DB의 지정된 `GiftDelivery` 열 12개다. 배송지 입력 당시 사용한 요청 필드와 검증 규칙은 그대로 필수다.
- 보존기간은 `DELIVERED`의 `deliveredAt`, `UNDELIVERABLE`의 `undeliverableAt` 각각으로부터 7일이다. 정확한 경계를 포함하며 짝인 시각이 null인 이력은 다른 시각으로 대체하지 않는다. `CANCELED`는 이번 확장의 대상이 아니다.
- 이 정리는 회원 계정, `Member` nickname/profile, Gift 신청/상태/ID, 캔 결제 내역, 약관 동의 snapshot, 리뷰를 삭제하지 않는다.
- `GiftDelivery`의 업무 시각 및 기한은 운영 이력으로 보존한다. `createdAt`은 유지하고 `updatedAt`은 공통 entity auditing 규칙대로 갱신될 수 있다.
- `GiftDelivery`의 업무 시각 및 기한과 `undeliverableReason`은 운영 이력으로 보존한다. `createdAt`은 유지하고 `updatedAt`은 공통 entity auditing 규칙대로 갱신될 수 있다.
- 이 정책은 백업, 애플리케이션/인프라 로그, 외부 관측 시스템, 캐시의 삭제나 보존기간을 정의하지 않는다. 전체 개인정보 삭제 완료로 설명하지 않는다.
- 정리 작업 자체는 상태 전이·환불·알림을 일으키지 않는다. 기존 업무 정책이 정리 task를 통해 달라지지 않도록 한다.
- `GiftDelivery`의 sender 다섯 Kotlin property를 nullable로 선언한다. `sender_address_detail`은 이미 nullable이다. 현재 NOT NULL인 `sender_name`, `sender_phone_number`, `sender_zip_code`, `sender_address`만 schema에서 nullable로 바꾼다.
@@ -1098,11 +1101,11 @@ Response `data`는 기존 `AdminGiftOperationStatusResponse`를 사용한다.
- 관리자 목록과 상세 모두 `RECEIVED`에서만 `CANCEL` 액션을 제공하며 기존 `COMPLETE_DELIVERY` 조건부 액션을 포함한 나머지 상태 매핑은 유지한다.
- 카테고리 `classificationNumber`, `receiptCode`, `representativeItem`의 관리자 등록/수정/조회와 사용자 폼 옵션의 `분류명/대표품목` 조합을 테스트한다.
- 신청번호는 카테고리+날짜별 sequence, 4자리 padding, `9999` 초과 자리수 증가, 병렬 unique를 테스트한다.
- 개인정보 정리는 7일 경계 포함/1초 미경과 제외, 더 오래된 이력 포함, null 완료시각 및 비전달완료 상태 제외, 부분 삭제/완전 삭제 재실행 0건을 실제 JPA 저장·flush/clear/reload로 검증한다. 보호할 결제/동의/리뷰/회원/업무 시각도 재조회해 불변임을 확인한다.
- 개인정보 정리는 `DELIVERED`/`UNDELIVERABLE` 각각의 7일 경계 포함/1초 미경과 제외, 오래된 이력 포함, 짝인 null 시각 제외, 다른 시각이 오래돼도 짝인 null/최근 시각을 대체하지 않음, `CANCELED`와 다른 비대상 상태 보존, 부분 삭제/완전 삭제 재실행 0건을 실제 JPA 저장·flush/clear/reload로 검증한다. 결제/동의/리뷰/회원/업무 시각과 `undeliverableReason`도 재조회해 불변임을 확인한다.
- `GiftDelivery` 매핑은 sender 다섯 property 모두 null 저장을 허용해야 한다. DDL review는 네 nonnull sender columns만 변경하고 기존 `gift-schema.sql` trailing ALTER를 확인한다.
- 사용자 및 관리자 query/controller는 삭제 후 기존 DTO 형태, 빈 non-null 문자열의 정확한 주소 형식, nullable 필드, nickname, `DELIVERED`의 액션/CTA 미노출을 검증한다.
- 사용자 및 관리자 query/controller는 두 대상 상태의 삭제 후 기존 DTO 형태, 빈 non-null 문자열의 정확한 주소 형식, nullable 필드, nickname, 액션/CTA 미노출 및 전달불가 시각/사유 보존을 검증한다. 기존 실제 JWT HTTP의 인증/권한과 완전 일치 assertion을 유지한다.
- 실제 `GiftRepository` query를 기존 `@DataJpaTest`의 embedded H2에서 실행한다. 실제 datasource replacement를 확인해 MySQL-mode/운영 MySQL 검증과 구분한다. 고정 `now`에서 cutoff 동일 시각은 포함하고 cutoff보다 1초 늦은 시각은 제외한다.
- 오래된 과거 이력, null `deliveredAt`, 다른 Gift status도 각 fixture에서 독립 확인한다. 미래에 cutoff를 지나는 데이터도 매번 조회되어야 하며 첫 정리에서만 처리되는 신규 데이터 제한을 두지 않는다.
- 오래된 과거 이력, null `deliveredAt`/`undeliverableAt`, 다른 Gift status도 각 fixture에서 독립 확인한다. 미래에 cutoff를 지나는 데이터도 매번 조회되어야 하며 첫 정리에서만 처리되는 신규 데이터 제한을 두지 않는다.
- 정리 전후 DB를 flush, clear, reload해 null 저장이 실제 반영됐는지 확인한다. 재실행 후 반환 변경 수가 0인지 확인한다.
- 매핑 test는 12개 필드를 모두 null로 저장할 수 있음을 확인하되 지정 외 상태 시각 필드는 그대로 남는지 검증한다.
- controller 테스트는 기존 JSON object shape를 비교하고 사용자 요청 DTO 필수 validation이 유지되는지 확인한다.
@@ -1123,6 +1126,7 @@ Response `data`는 기존 `AdminGiftOperationStatusResponse`를 사용한다.
- [ ] 카테고리 추가 필드와 새 신청번호 정책이 관리자 API, 사용자 폼 옵션, 선물 신청에 반영된다.
- [x] 관리자 취소는 `RECEIVED`에서만 전액 환불과 함께 성공하고, 목록/상세의 `CANCEL` 액션은 상태에 맞게 제공된다.
- [x] 7일 이상 지난 전달 완료 건의 지정된 배송 개인정보만 DB에서 지워지고 기존 업무 데이터와 API 계약은 유지된다. 격리 DB 및 실제 HTTP로 검증했다. (`GIFT-018`, `P15-GATE`)
- [x] 전달불가 후 7일인 건도 같은 작업에서 정리하며 상태별 짝인 시각·12개 필드·보존 계약과 사용자/관리자 응답을 검증했다. (`GIFT-018`, `DEC-023`, `P17-T1`, `P17-GATE`)
- [x] 다섯 sender property nullable mapping 및 네 DB 열의 ALTER 문서가 schema 요구를 충족한다. 운영 적용은 별도다. (`GIFT-019`, `P15-T1`, `P15-T2`)
- [x] 삭제 후 빈 문자열/null 표현으로 기존 API schema와 사용자 입력 검증을 유지한다. (`GIFT-020`, `P15-T2`, `P15-GATE`)
- [x] 만료/정리/reminder 업무 메서드가 각각 Asia/Seoul `:10`/`:20`/`:40` schedule wrapper로 등록된다. 실제 Spring 등록을 확인했다. (`GIFT-021`, `P15-T3`, `P15-T4`)
@@ -1145,9 +1149,9 @@ Phase 15 서버 구현 완료 판정에는 `P15-T2`~`P15-T4` focused test와 격
| `GIFT-011` | 6 | 푸시 이벤트 테스트 | 딥링크와 수신자 확인 |
| `GIFT-014` | 8 | 선물 사용자/관리자 controller 날짜 응답 테스트 | UTC ISO 문자열 확인 |
| `GIFT-016`, `GIFT-017` | 14 | 관리자 취소 service/controller 및 query service 테스트 | `RECEIVED` 액션과 취소/환불 결과 확인 |
| `GIFT-018` | 15 | repository predicate 및 erasure method test | 격리 fixture 이후 삭제 필드와 보존 데이터 재조회 |
| `GIFT-018` | 15, 17 | 두 상태별 repository predicate 및 통합 erasure method test | 격리 fixture 이후 삭제 필드와 보존 데이터 재조회 |
| `GIFT-019` | 15 | GiftEntityMappingTest, SQL section review | no production DB execution in Gate |
| `GIFT-020` | 15 | query/controller contract regression | fixture erase 후 실제 socket HTTP SENT/RECEIVED/admin 조회 |
| `GIFT-020` | 15, 17 | 두 상태의 query/controller contract regression | fixture erase 후 실제 socket HTTP SENT/RECEIVED/admin 조회 |
| `GIFT-021` | 15 | scheduler reflection/lock/delegation tests | schedule/zone/lock key 대조 |
`GIFT-019` rollout은 운영 schema 확인, DDL review 및 별도 실행 승인, nullable schema와 호환 reader 준비를 거친다. Gate는 운영 DB에서 SQL 또는 정리 작업을 실행하지 않는다.
@@ -1178,9 +1182,14 @@ Phase 15 서버 구현 완료 판정에는 `P15-T2`~`P15-T4` focused test와 격
| 2026-10-23 | `DEC-020` | 확정 | sender 다섯 Kotlin property 중 `senderAddressDetail` 포함 다섯 모두 nullable, DB상 현재 NOT NULL인 sender 열 네 개만 변경한다 | entity 필드와 DDL nullable 경계의 구별 | `GIFT-019`, `P15-T2` |
| 2026-10-23 | `DEC-021` | 확정 | 신규 시간별 scheduler wrapper는 기존 만료/안내 업무 메서드에 위임하며, 기존 예약은 자동취소 `:00`뿐이다. 신규 등록은 `:10`, `:20`, `:40`이다 | 현재 scheduler 코드와 승인 일정 | `GIFT-021`, `P15-T3`~`P15-T4` |
| 2026-10-26 | `DEC-022` | 확정 | 기존 PRD의 수령확인·배송지 만료 발신자 알림 호출 누락 두 곳을 보완하고 관리자 가격 변경/카테고리 비활성화 후 기존 신청 스냅샷 보존 통합 테스트를 추가한다 | 사용자 후속 작업 승인 | `GIFT-008`, `GIFT-011`, `GIFT-013`, `P16-T1`~`P16-GATE` |
| 2026-11-01 | `DEC-023` | 확정 | 기존 :20 파기를 `DELIVERED`+non-null `deliveredAt <= now.minusDays(7)` 또는 `UNDELIVERABLE`+non-null `undeliverableAt <= cutoff`로 확장한다. `COALESCE`/다른 시각 fallback 및 `CANCELED`는 제외한다. 기존 12개 필드와 API/업무 데이터/전달불가 사유, Redis literal·transaction/unlock을 보존하고 내부 명칭만 통합한다 | 사용자 승인된 통합 개인정보 파기 정책. `DEC-017`의 당시 전달완료 구현 기록은 보존한다 | `GIFT-018`, `GIFT-020`, `8.16`, `11.1`, `P17-T1`, `P17-GATE` |
## 문서 상태 안내
Phase 14의 당시 전체 테스트 제외 및 검증 이력은 `plan-task.md`의 P14-GATE 기록을 보존한다. Phase 15는 후속 실행 승인에 따라 서버 구현과 최종 전체 테스트 2,396개·lint·build·실소켓 QA·리뷰를 완료했다. 운영 MySQL DDL 실행 및 배포는 별도 승인/조건이며 이번 완료 범위에 포함하지 않는다.
Phase 16은 2026-10-26 후속 승인 범위의 서버 구현·검증을 완료했다. 최종 전체 테스트 2,400개·lint·build 통과 및 실제 localhost HTTP/DB 시나리오와 독립 리뷰 승인을 확인했다. Phase15의 과거 기록과 달리 이번 두 성공 기준은 새 검증 근거로 완료 표시했으며, 나머지 과거 성공 기준은 일괄 체크하지 않았다.
Phase 17은 2026-11-01 기존 문서를 먼저 갱신한 후 UNDELIVERABLE 미처리 assertion RED와 통합 구현 GREEN을 확인했다. 관련 5개 클래스 165개와 lint가 통과했으며 두 상태의 실제 JWT HTTP 파기 전후 검증을 포함한다. 전체 suite·독립 review·최종 QA는 parent의 `P17-GATE` 대기이며 이번 focused 결과로 Gate나 운영 반영 완료를 주장하지 않는다.
Phase 17 최종 판정: 위 focused 인계 이후 전체 2,904개 테스트(실패·오류·skip 0), build·ktlintCheck와 단일 독립 리뷰 APPROVE를 확인해 `P17-GATE`를 완료했다. lead가 별도로 실행한 실제 JWT HTTP/DB 두 상태 검증과 scheduler 59개도 통과했다. 운영 MySQL·배포·실제 Redis 다중 인스턴스는 미검증이며 커밋·푸시는 하지 않았다.