docs(banner): 언어 필터 계약과 검증 결과를 기록한다

This commit is contained in:
2026-11-03 00:33:39 +09:00
parent c4c61e3615
commit 5666802f61
8 changed files with 262 additions and 29 deletions
@@ -2,12 +2,13 @@
| 문서 항목 | 내용 |
|---|---|
| 상태 | P5 소프트 삭제·P6 추천 통합 구현 및 전체 검증 완료, 미배포 |
| 상태 | P7 언어 후속 구현·전체 검증·독립 리뷰 완료, 미배포 |
| 작성일 | 2026-10-29 |
| 요구사항 기준 | [prd.md](prd.md) |
| API 기준 | [api-contract.md](api-contract.md), 실제 구현·HTTP 검증 계약 |
| 현재 Phase | Phase 2~6 완료 |
| 현재 Phase | Phase 2~7 완료 |
| 현재 활성 Goal | 없음 |
| 다음 Goal | 없음 |
## 목표
@@ -31,6 +32,12 @@
- 신규 dependency, global exception rewrite, 공통 utility wrapper
- 앱/client network 통합은 backend 구현 후 별도 계약 검토와 제공이 끝날 때까지 차단한다. backend를 알 수 없는 외부 block으로 취급하지 않는다.
### 승인된 언어 후속 범위
- [2026-11-02 승인 후속] 재사용 `Lang`의 KO/EN/JA로 서브 배너를 저장한다. 관리자 생성 누락은 KO, 수정 누락은 유지, explicit null/non-text/invalid는 upload/write 전 HTTP 400이다. Public 조회는 member 국가로 JP→JA, 그 외→KO를 선택하며 관리자 목록은 언어 미필터다.
- public `SubBannerResponse`는 기존 필드를 유지하고, 운영 DB/DDL 실행·UI·event link parsing·event detail/adult 변경·새 dependency는 금지한다.
- 신규 설치용 `create-sub-banner.sql`과 `20261102_add_sub_banner_lang.sql`을 함께 갱신한다. KO backfill은 소재의 JA 여부와 연결 이벤트 언어를 자동 추정하지 않으며, 운영 담당자의 수동 분류·확인을 적용 전에 요구한다.
## 문서 변환 Goal
### `DOC-T5` 백엔드 계획 문서 변환
@@ -545,3 +552,67 @@ backend checkbox는 실제 검증 후 갱신한다. 문서 작성/부모 검토
- 결과: 종료 코드 0, `BUILD SUCCESSFUL in 5m 5s`. 최신 HTML/XML에서 161 tests, failures/errors/skipped 0을 확인했다. 테스트는 재실행됐으며 lint·컴파일·bootJar는 입력 변경이 없어 UP-TO-DATE였다.
- 전체 회귀 생략: 이전 최종 Gate의 전체 2,682개 통과 이후 제품 코드 변경이 없어 전체 suite를 반복하지 않았다. 이번 영향 범위 실행으로 `build/reports/tests/test/index.html`과 XML은 161개 결과로 갱신됐으며, 과거 전체 결과와 구분한다.
- 운영 한계: 운영 DB·native MySQL·실제 AWS·앱/관리자 UI·배포는 이번에도 검증하지 않았다. 서버 배포 전에 현재 DDL 준비가 필요하다.
## Phase 7: 서브 배너 언어 후속
2026-11-02 사용자 승인으로 새 Phase를 추가한다. Phase 2~6의 완료 상태와 검증 기록은 당시 계약의 이력이며 수정하지 않는다.
### Task 7.1 저장·관리자 계약·공개 국가별 조회
**Goal `P7-T1`:** 서브 배너가 기존 `Lang`을 저장하고 관리자가 안전하게 언어를 관리하며, 두 공개 추천 API가 회원 국가에 맞는 언어만 반환한다.
- 요구사항: PRD `SB-013`, `DEC-012`.
- 시작 조건: `DEC-012` 승인과 기존 Phase 2~6 완료.
- 완료 증거: baseline 기록, language 저장/기본값/수정 보존 persistence 및 실제 관리자/public HTTP 테스트의 RED→GREEN 결과, DDL 두 경로 검토, 독립 코드 리뷰.
- 범위 밖: 앱·관리자 UI, public `SubBannerResponse` 변경, event link parsing, event detail/adult 정책, dependency, 운영 DB/DDL 실행, 기존 언어 자동 추론.
- **Files:**
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/subbanner/domain/SubBanner.kt`
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/subbanner/adapter/out/persistence/SubBannerEntity.kt`, `src/main/kotlin/kr/co/vividnext/sodalive/v2/subbanner/adapter/out/persistence/DefaultSubBannerRepository.kt`, `src/main/kotlin/kr/co/vividnext/sodalive/v2/subbanner/port/out/SubBannerRepository.kt`
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/subbanner/dto/SubBannerRequest.kt`, `src/main/kotlin/kr/co/vividnext/sodalive/v2/subbanner/dto/AdminSubBannerResponse.kt`, `src/main/kotlin/kr/co/vividnext/sodalive/v2/subbanner/application/SubBannerService.kt`
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/application/HomeRecommendationFacade.kt`, `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/content/recommendation/application/AudioRecommendationFacade.kt`
- Modify: `docs/20261029_가로형서브배너/create-sub-banner.sql`
- Create: `docs/20261029_가로형서브배너/20261102_add_sub_banner_lang.sql`
- Modify: `docs/20261029_가로형서브배너/api-contract.md`, `docs/20261029_가로형서브배너/admin-prompt.md`, `docs/20261029_가로형서브배너/app-prompt.md` (실제 언어 계약과 배포 주의사항 동기화; UI 구현 제외)
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/subbanner/domain/SubBannerTest.kt`, `src/test/kotlin/kr/co/vividnext/sodalive/v2/subbanner/adapter/out/persistence/SubBannerPersistenceTest.kt`, `src/test/kotlin/kr/co/vividnext/sodalive/v2/subbanner/adapter/in/web/AdminSubBannerControllerIntegrationTest.kt`, `src/test/kotlin/kr/co/vividnext/sodalive/v2/subbanner/adapter/in/web/RecommendationSubBannerIntegrationTest.kt`, `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/application/HomeRecommendationFacadeTest.kt`, `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/recommendation/application/AudioRecommendationFacadeTest.kt`
- **Interfaces:** `SubBanner.lang: Lang = Lang.KO`; admin request optional `lang` preserving JSON presence/type distinctions; `AdminSubBannerResponse.lang: Lang`; repository `findVisibleByPlacement(placement: SubBannerPlacement, lang: Lang, now: LocalDateTime): List<SubBanner>`. Public service accepts `Member?`, resolves through existing `MemberContentPreferenceService.resolveCountryCode(member)`, then passes required `Lang`. Both existing facades pass their `member`. Admin list receives no language filter. `SubBannerResponse` remains unchanged.
- [x] **Baseline:** 부모의 사전 통과 113개 중 서브 배너 기존 4개 클래스 98개를 확인했다. 원본 `/tmp/soda-banner-ulw.HDkpg4/baseline.log`. 두 facade 클래스는 사전 baseline 범위 밖이며 이번 GREEN에서 10개를 검증했다. 운영 DDL은 실행하지 않았다.
- [x] **RED:** domain KO 기본값, 관리자 9필드·생성 언어·invalid 입력 400 및 공개 두 JP 경로의 KO 제외를 실제 HTTP/응답 assertion으로 먼저 추가했다. invalid 입력은 실제 DB 상태·업로드·repository save 호출 0회를 검증한다.
- [x] **RED 확인:** domain/admin/public 81개 중 언어 관련 assertion 15개 실패, 오류 0을 확인했다. 컴파일·설정 RED가 아니다. `/tmp/soda-banner-impl.CfAkhD/subbanner-red.log`와 `subbanner-*-red.xml`.
- [x] **GREEN:** 기존 `Lang`, 생성 KO·수정 생략 보존, 부작용 전 검증, facade의 member 전달, repository 필수 언어 조건 및 두 SQL artifact를 구현했다. typed persistence 언어 왕복·필터·변경과 기존 호출부도 함께 갱신했다.
- [x] **GREEN 확인:** focused 123개 실패·오류·무시 0. 실제 관리자 KO/JA/EN·수정 생략 보존·invalid 입력, 두 추천 JP/KR/US/누락·강제 회원, 공개 정확히 6필드 및 기존 기간·소프트 삭제·정렬·링크 회귀가 통과했다.
- [x] **REFACTOR:** 전체 2,928개 테스트, 공백 수정 후 영향 26개 HTTP 테스트·전체 ktlint·bootJar 및 독립 리뷰를 확인했다. 운영 DDL과 도구가 없는 LSP 진단은 실행하지 않았다.
```bash
export TMPDIR=/tmp/soda-banner-impl.CfAkhD NODE_COMPILE_CACHE=/tmp/soda-banner-impl.CfAkhD/node-compile-cache BUN_RUNTIME_TRANSPILER_CACHE_PATH=/tmp/soda-banner-impl.CfAkhD/bun-transpiler
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.subbanner.domain.SubBannerTest' --tests 'kr.co.vividnext.sodalive.v2.subbanner.adapter.out.persistence.SubBannerPersistenceTest' --tests 'kr.co.vividnext.sodalive.v2.subbanner.adapter.in.web.AdminSubBannerControllerIntegrationTest' --tests 'kr.co.vividnext.sodalive.v2.subbanner.adapter.in.web.RecommendationSubBannerIntegrationTest' --tests 'kr.co.vividnext.sodalive.v2.api.home.application.HomeRecommendationFacadeTest' --tests 'kr.co.vividnext.sodalive.v2.api.content.recommendation.application.AudioRecommendationFacadeTest' --console=plain --no-daemon -Pkotlin.compiler.execution.strategy=in-process
./gradlew test ktlintCheck bootJar --continue --console=plain
```
**DDL requirements:** both SQL artifacts define `lang VARCHAR(10) NOT NULL DEFAULT 'KO'` with a column comment. The migration safely adds a missing column, backfills NULL rows to KO, then enforces NOT NULL/default, following the existing project SQL pattern. Before production application, warn that KO is only a safe placeholder: operations must manually identify JA banners and verify language consistency against linked events. No event link parser or automatic classification is allowed. Production DB/DDL execution is not part of this goal.
### Phase 7 Gate `P7-GATE`
- 시작 조건: `P7-T1` 완료.
- 완료 증거: full test suite·ktlint·bootJar 성공, 실제 관리자와 두 추천 HTTP 경로 검증, 두 SQL artifact/수동 분류 경고 대조, 독립 리뷰에서 미해결 차단 사항 없음.
- 범위 밖: 운영 DB 적용·배포·UI.
- [x] 국가 resolver 재사용·관리자 전체 언어·public DTO 불변·invalid 부작용 0을 확인하고 이전 Phase 이력을 보존했다. 전체 검증 및 최종 Oracle 승인으로 Gate를 완료했다.
### Phase 7 구현 Progress, 2026-11-02
- RED는 위 명령 중 domain/admin/public 3개 클래스를 실행해 `81 tests completed, 15 failed`를 확인했다. 실패는 KO 기본값/관리자 lang 필드 누락, invalid lang의 200 수락, JP에서 KO 노출이다. 원본 XML은 `/tmp/soda-banner-impl.CfAkhD/subbanner-domain-red.xml`, `subbanner-admin-red.xml`, `subbanner-public-red.xml`이다. 개별 typed persistence 테스트를 별도 RED 실행한 것으로 주장하지 않는다.
- GREEN은 위 6개 클래스 명령이며 `/tmp/soda-banner-impl.CfAkhD/subbanner-green.log`에 `BUILD SUCCESSFUL in 8m 28s`가 남는다. Domain 3, Persistence 32, Admin HTTP 52, Public HTTP 26, Home facade 8, Audio facade 2로 총 123개가 통과했다.
- 실제 HTTP/DB 증거는 `subbanner-http-db-evidence.log`의 HTTP 121건과 `subbanner-green-TEST-*.xml`에 보관했다. 신규 언어 경로는 두 추천 각각 익명 JP/KR/US/누락·강제 JP/KR를 실제 JWT/회원/resolver로 검증한다. EN 행이 있어도 공개 목록에서 제외되고, admin은 KO/JA/EN을 모두 반환한다. invalid 8종 POST/PUT 16건은 400이며 실제 DB 불변·S3 상호작용 없음·repository save 0회가 통과했다.
- H2 flush/clear/재조회에서 모든 언어의 문자열 저장·변경·언어 조건 및 `VARCHAR(10) NOT NULL`을 확인했다. 기존 CREATE SQL 실행 테스트도 H2에서 KO default/NOT NULL/길이 10을 검증했다. migration은 기존 event DDL의 information_schema/PREPARE 패턴과 대조만 했고 native MySQL·운영 DDL은 실행하지 않았다. 기존 초 정밀도·소프트 삭제/동시성·링크 검증은 유지했다.
- API 계약/관리자·앱 프롬프트에 생성 생략 KO·수정 생략 유지·null/type/blank/unknown 400, JP→JA/그 외→KO, admin 전체 언어, EN 비노출, 공개 6필드 및 migration-before-deploy를 반영했다. KO backfill은 자동 분류가 아니므로 JA 소재와 연결 이벤트 언어를 수동 확인해야 한다. 외부 링크 원문은 유지하며 파서를 추가하지 않았다.
- 사용자 지정 종료 경계에 따라 전체 suite·ktlint·bootJar·독립 리뷰와 `P7-GATE`는 부모가 수행하도록 미완료 유지한다. LSP 진단 도구 없음, 실제 AWS/UI/운영 DB/배포 미검증. Gradle 및 embedded Redis 소유 프로세스가 종료됐음을 확인했고 evidence는 남겼다. 모든 변경 파일·명령·제한은 `/tmp/soda-banner-impl.CfAkhD/implementation-report.md`에 기록한다. 기존 Phase 2~6 기록과 부모 PRD 변경은 보존했다.
- 최종 source 상태에서 위 6개 클래스와 Phase 3의 5개 클래스를 한 focused 명령으로 재실행했다. `/tmp/soda-banner-impl.CfAkhD/final-focused.log`: `BUILD SUCCESSFUL in 8m 8s`, 총 145개 실패·오류·무시 0. 전체 suite는 실행하지 않았다. 최종 11개 XML은 `final-TEST-*.xml`, 실제 HTTP 185건과 DB/동시성 증거는 `final-http-db-evidence.log`에 별도 보관했다.
### Phase 7 최종 검증 Progress, 2026-11-03
- 부모가 `./gradlew test ktlintCheck bootJar --continue --console=plain --no-daemon -Pkotlin.compiler.execution.strategy=in-process`를 실행했다. 전체 XML을 집계해 336 suites / 2,928 tests / failures=0 / errors=0 / skipped=0을 확인했고 JAR도 생성됐다. 최초 명령은 `RecommendationSubBannerIntegrationTest.kt`의 생성자 인자 줄바꿈 1곳(5개 lint 진단) 때문에 exit 1, 15m 50s였다.
- 값·순서·동작 변경 없이 인자만 줄별로 배치했다. `./gradlew test --tests 'kr.co.vividnext.sodalive.v2.subbanner.adapter.in.web.RecommendationSubBannerIntegrationTest' ktlintCheck bootJar --continue --console=plain --no-daemon -Pkotlin.compiler.execution.strategy=in-process`: exit 0, `BUILD SUCCESSFUL in 4m 32s`, 26 tests / failures=0 / errors=0 / skipped=0, 전체 lint 통과, bootJar UP-TO-DATE.
- 전체 회귀 뒤 테스트 공백만 달라져 영향 HTTP suite만 재실행했다. 현재 `build/test-results/test`/HTML은 26개 결과이며, 전체 2,928개 수치는 갱신 전 부모의 직접 집계다. 실제 HTTP·DB 검증은 테스트 대체가 아니라 RANDOM_PORT 서버를 대상으로 한 TestRestTemplate 요청과 저장소 재조회다.
- 부모가 production diff, 입력·저장·조회 경로, 테스트 assertion, SQL 두 개와 소비 문서를 직접 대조했다. LSP 도구가 없어 미실행이며 Gradle 컴파일·lint로 대체했다. 운영 DB/native MySQL/AWS/UI/배포는 제외한다.
- 원본 `/tmp/soda-banner-ulw.HDkpg4/full-verification.log`, `final-verification.log` 및 구현자 임시 증거는 작업 종료 시 정리한다. 재현 테스트 소스, 최신 build reports, 본 검증 기록과 [공동 리뷰](reviews/banner-language-review.md)를 남긴다.
- 독립 Oracle 리뷰는 명세·코드·시나리오 APPROVE, 후속 공백 delta 및 최종 검증 Gate도 APPROVE했다. 미해결 발견 사항 없음. `P7-T1/P7-GATE` 완료이며 운영 DDL·배포는 별도다.
- 서브 배너 구현·SQL·테스트 커밋 `c4c61e36`, hook/메시지 검사 통과. 소유 임시 자료·QA 프로세스 정리와 build 산출물 보존은 공동 리뷰의 종료 증거에 기록했다. 운영 DDL·배포·push는 하지 않았다.