docs(i18n): 코드 기반 공급자 순서 설정과 검증을 기록한다
This commit is contained in:
@@ -3,8 +3,8 @@
|
||||
| 항목 | 내용 |
|
||||
|---|---|
|
||||
| 작성일 | 2026-11-09 |
|
||||
| 상태 | 구현·검증·독립 리뷰·리소스 정리 완료 |
|
||||
| 요구사항 | [prd.md](prd.md), LP-001~010 및 확정 구현 계약 |
|
||||
| 상태 | Phase 1·2 구현·검증·리뷰·리소스 정리 완료 |
|
||||
| 요구사항 | [prd.md](prd.md), LP-001~012 및 확정 구현 계약 |
|
||||
| 활성 Goal | 없음 |
|
||||
|
||||
## 목표와 제약
|
||||
@@ -339,3 +339,160 @@ M v2/creator/channel/community/translation/CreatorCommunityTranslationServiceTes
|
||||
출력: `CLEANUP PASS: owned language-providers.06uAbV removed; shared caches untouched`.
|
||||
임시 Java/class/추출 라이브러리/로그가 함께 제거됐으며 필요한 증거는 이 문서에 보존했다.
|
||||
작업 임시 노트도 문서에 이관 후 삭제한다. 다른 작업의 파일·프로세스·캐시에는 조치하지 않았다.
|
||||
|
||||
### Phase 2: 코드 목록으로 공급자 순서 지정
|
||||
|
||||
- [x] **Task 2.1: 우선순위 환경 설정 제거 (P2-T1)**
|
||||
|
||||
**Objective:** LP-011/012에 따라 코드의 독립 목록만 production 순서를 결정한다.
|
||||
**시작 조건:** 사용자의 상수 목록 방식 승인, 기존 Phase 1 완료.
|
||||
**완료 증거:** 과거 property/env 무시 회귀 RED/GREEN, typed-list 순서/검증, Spring/H2 wiring 회귀.
|
||||
**범위 밖:** 공급자 선택/캐시 로직 변경, 인증 키 환경변수 제거, AWS 구현, 전역 mutable 테스트 설정.
|
||||
|
||||
**Files:**
|
||||
- Add: `src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderOrder.kt`.
|
||||
- Modify: 같은 패키지 `LanguageProviderProperties.kt`, `src/main/resources/application.yml`의 우선순위 항목만.
|
||||
- Update tests: `LanguageProviderPropertiesTest.kt`, `LanguageProviderSpringConfigurationTest.kt`,
|
||||
`LanguageProviderCacheEndToEndTest.kt`, `LanguageDetectionProviderTest.kt`, `TranslationCoordinatorTest.kt`.
|
||||
- Update docs: 이 PRD/plan과 `provider-guide.md`. 이전 완료 이력·리뷰는 삭제하지 않는다.
|
||||
|
||||
**계약:** `LanguageProviderOrder` object에 두 `val` 목록. `LanguageProviderProperties`의 주 생성자는
|
||||
두 `List<String>`을 받고 `@Autowired`가 붙은 noarg 보조 생성자가 production object 목록으로 위임한다.
|
||||
기존 `orderedProviders` 검증은 그대로 유지한다. 기본 생성자를 사용하는 호출자와 coordinator는 변경하지 않는다.
|
||||
|
||||
- [x] **RED:** 실제 Spring 기본 생성에서 과거 priority property/env override를 무시해야 한다는 테스트를 추가하고 기존 binding 동작으로 실패함을 확인한다.
|
||||
- [x] **GREEN:** 코드 목록·typed 생성자·Spring 기본 생성자를 적용하고 우선순위 YAML/env 연결을 삭제한다.
|
||||
- [x] **GREEN 확인:** default/legacy override 무시/테스트별 독립 순서/잘못된 목록·공급자 등록 검증이 통과한다.
|
||||
- [x] **REFACTOR:** 실제 H2 fallback 캐시 테스트를 property override 대신 테스트 bean으로 연결하고 기존 assertion을 유지한다.
|
||||
|
||||
**명령:**
|
||||
```bash
|
||||
./gradlew --no-daemon --offline test --tests 'kr.co.vividnext.sodalive.i18n.translation.*' --tests 'kr.co.vividnext.sodalive.content.LanguageDetectionCacheServiceTest'
|
||||
./gradlew --no-daemon --offline test --tests 'kr.co.vividnext.sodalive.content.CreatorCommunityLanguageDetectTest' --tests 'kr.co.vividnext.sodalive.content.CreatorCommunityLanguageDetectionCacheConcurrencyTest' --tests 'kr.co.vividnext.sodalive.v2.creator.channel.community.translation.CreatorCommunityTranslationServiceTest'
|
||||
./gradlew --no-daemon --offline ktlintCheck bootJar
|
||||
```
|
||||
|
||||
- [x] **Phase 2 Gate (P2-GATE): 코드 순서 실행·리뷰·정리**
|
||||
|
||||
**Objective:** production bean이 코드 목록으로 동작하고 환경값으로 순서가 바뀌지 않음을 직접 실행 검증한다.
|
||||
**시작 조건:** P2-T1 통과.
|
||||
**대체 검증:** 부모 Spring driver에 충돌 property/env를 넣고 코드 목록 유지, 테스트 bean으로 목록 교체·validation을 확인한다.
|
||||
**완료 증거:** focused 통과, build/lint, 독립 scoped 리뷰 승인, 소유 임시 경로 정리.
|
||||
**범위 밖:** 전체 테스트·유료 API·운영 환경 조작. 전체 테스트는 사용자 `./gradlew test`.
|
||||
|
||||
Phase 2 실행 증거는 아래에 누적한다. 코드 변경 전 현재 git status는 깨끗했다.
|
||||
|
||||
### P2-T1 실행 증거 (2026-11-09)
|
||||
|
||||
승인된 Phase 2 계약과 기존 Phase 1 결과를 재사용했다. baseline 85건을 변경 전에 다시 실행하지 않고
|
||||
실제 Spring의 과거 override 무시 회귀부터 작성했다. PRD의 승인 정정과 Phase 1 이력·리뷰는 수정하지 않았다.
|
||||
|
||||
**실행 환경:** parent 존재를 `ls -ld`로 확인하고 아래 명령으로 이 구현자만 소유하는 child를 생성했다.
|
||||
이후 모든 shell 명령에서 아래 세 환경변수를 이 child로 지정했다. Gradle tool timeout은 모두 `600000ms`다.
|
||||
|
||||
```bash
|
||||
mktemp -d /var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/provider-order-code.RRCyJ1/phase2.XXXXXX
|
||||
export TMPDIR=/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/provider-order-code.RRCyJ1/phase2.QPrb3i
|
||||
export NODE_COMPILE_CACHE="$TMPDIR" BUN_RUNTIME_TRANSPILER_CACHE_PATH="$TMPDIR"
|
||||
```
|
||||
|
||||
**RED (2건, 2실패, errors/skipped 0, exit 1, 20초):** production 코드를 바꾸기 전에 다음 명령을 실행했다.
|
||||
두 테스트 모두 context는 정상 시작했지만 기대 `[papago]`, 실제 `[secondary, papago]`로 assertion 실패했다.
|
||||
property 직접 지정과 `SystemEnvironmentPropertySource`의 과거 환경변수 → property placeholder 연결을 각각 검증했다.
|
||||
환경변수 테스트는 실제 property가 `secondary,papago`로 해석됐다는 assertion도 통과한 뒤 실패했다.
|
||||
컴파일·unresolved symbol·context 오류를 행동 RED로 계산하지 않았다.
|
||||
|
||||
```bash
|
||||
./gradlew --no-daemon --offline test --tests 'kr.co.vividnext.sodalive.i18n.translation.LanguageProviderSpringConfigurationTest.shouldIgnoreLegacy*Overrides'
|
||||
```
|
||||
|
||||
**GREEN:** 하나의 구현 변경으로 코드 목록·생성자·다섯 테스트의 typed-list fixture·현재 가이드를 전환했다.
|
||||
`LanguageProviderOrder`에는 독립 `val` 두 개만 있으며 각각 `listOf("papago")`다. 전역 mutable 상태는 추가하지 않았다.
|
||||
`LanguageProviderProperties`의 `orderedProviders`는 그대로다. production 기본/negative 테스트는
|
||||
`withUserConfiguration(LanguageProviderProperties::class.java)`로 실제 component를 Spring이 생성하게 한다.
|
||||
독립 순서·잘못된 목록은 테스트 bean으로 주입하고 H2는 테스트 `@Bean`으로 두 목록을 주입한다.
|
||||
기존 캐시·폴백·귀속·materializer·동시성·revision assertion은 삭제하거나 완화하지 않았다.
|
||||
|
||||
**결합 focused 회귀 + lint/산출물 (exit 0, 3분 14초):** 아래 명령 한 번으로 모두 실행했다.
|
||||
`:test`, `:ktlintCheck`, `:bootJar`의 실제 실행과 `BUILD SUCCESSFUL`을 확인했다.
|
||||
|
||||
```bash
|
||||
./gradlew --no-daemon --offline test \
|
||||
--tests 'kr.co.vividnext.sodalive.i18n.translation.*' \
|
||||
--tests 'kr.co.vividnext.sodalive.content.LanguageDetectionCacheServiceTest' \
|
||||
--tests 'kr.co.vividnext.sodalive.content.CreatorCommunityLanguageDetectTest' \
|
||||
--tests 'kr.co.vividnext.sodalive.content.CreatorCommunityLanguageDetectionCacheConcurrencyTest' \
|
||||
--tests 'kr.co.vividnext.sodalive.v2.creator.channel.community.translation.CreatorCommunityTranslationServiceTest' \
|
||||
--tests 'kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunityTranslationEndToEndTest' \
|
||||
ktlintCheck bootJar
|
||||
```
|
||||
|
||||
| XML로 확인한 범위 | 결과 |
|
||||
|---|---|
|
||||
| 번역 package + 감지 cache | 54/54 |
|
||||
| 커뮤니티 감지 + cache 경합 | 14/14 (12 + 2) |
|
||||
| 커뮤니티 번역 service + 기존 end-to-end | 20/20 (16 + 4) |
|
||||
| 합계 | 15개 XML, 88/88, failures/errors/skipped 모두 0 |
|
||||
|
||||
새 override 무시 회귀 2/2가 GREEN이며 Spring 클래스 전체는 5/5다.
|
||||
85건 대비 증가는 회귀 2건과 기존 default/invalid 검증을 서로 다른 테스트로 분리한 1건이다.
|
||||
기존 Papago chunk 17건과 H2 secondary 귀속·재사용 2건도 통과했다.
|
||||
변경하지 않은 테스트의 Kotlin 경고 7개(불필요한 `!!` 4개, unchecked cast 3개)와 Gradle 9 deprecation 경고는 남아 있다.
|
||||
|
||||
**부모 QA용 실제 생성자:** 아래 `javap` 명령은 exit 0이었다. 문자열 생성자와 setter는 없다.
|
||||
|
||||
```bash
|
||||
javap -classpath build/classes/kotlin/main kr.co.vividnext.sodalive.i18n.translation.LanguageProviderProperties kr.co.vividnext.sodalive.i18n.translation.LanguageProviderOrder
|
||||
```
|
||||
|
||||
```text
|
||||
LanguageProviderProperties(java.util.List<java.lang.String>, java.util.List<java.lang.String>)
|
||||
LanguageProviderProperties() // @Autowired, LanguageProviderOrder의 두 목록으로 위임
|
||||
getDetectionOrder(): List<String>
|
||||
getTranslationOrder(): List<String>
|
||||
LanguageProviderOrder.INSTANCE.getDetectionOrder(): List<String>
|
||||
LanguageProviderOrder.INSTANCE.getTranslationOrder(): List<String>
|
||||
```
|
||||
|
||||
**이 구현자의 변경 파일:** production은 `i18n/translation/LanguageProviderOrder.kt` 추가,
|
||||
`LanguageProviderProperties.kt` 수정과 `application.yml` 우선순위 블록 5줄 삭제뿐이다.
|
||||
테스트는 같은 패키지의 `LanguageProviderPropertiesTest.kt`, `LanguageProviderSpringConfigurationTest.kt`,
|
||||
`LanguageProviderCacheEndToEndTest.kt`, `LanguageDetectionProviderTest.kt`, `TranslationCoordinatorTest.kt`만 수정했다.
|
||||
문서는 `provider-guide.md`와 이 `plan-task.md`를 갱신했다. 합계 10개다.
|
||||
기본 생성자 기존 호출자·인증 환경변수·기타 설정·AWS/SDK·DB/schema·domain·캐시·폴백 코드는 변경하지 않았다.
|
||||
|
||||
**한계/소유권:** LSP/diagnostics 도구가 제공되지 않아 Kotlin 컴파일과 ktlint로 보완했다.
|
||||
H2의 기존 claim SQL test spy 한계는 유지된다. 실제 API·운영 DB·전체 테스트는 실행하지 않았다.
|
||||
전체 테스트는 사용자 명령 `./gradlew test`이며 무필터 test/check/build는 실행하지 않았다.
|
||||
별도 수동 Spring driver와 ONE reviewer 및 P2-GATE 완료 판정은 부모가 맡는다.
|
||||
commit/staging/push/worktree/하위 에이전트 작업은 하지 않았다.
|
||||
|
||||
**소유 임시 자원 정리 영수증 (2026-11-09 18:13:46 KST):** 모든 Gradle/test 실행이 종료된 뒤
|
||||
`lsof -nP +D "$TMPDIR"` 출력이 없고 Java process 목록에 이 child를 사용하는 프로세스가 없음을 확인했다.
|
||||
정확한 child 경로와 존재를 `test`로 대조한 뒤 이 구현자 소유 `phase2.QPrb3i`만 `rm -rf -- "$TMPDIR"`로 삭제했다.
|
||||
`test ! -e "$TMPDIR"`와 parent `ls -ld`가 포함된 정리 명령은 exit 0이었다.
|
||||
parent `provider-order-code.RRCyJ1`, 부모 QA의 형제 경로, 기존 공유 daemon/cache는 건드리지 않았다.
|
||||
마지막 `git diff --check`는 exit 0이었다. P2-T1만 완료 처리하고 P2-GATE는 부모 검증을 위해 미완료로 남겼다.
|
||||
|
||||
### P2-GATE 부모 검증
|
||||
|
||||
- 부모가 최종 production/test/guide diff와 신규 `LanguageProviderOrder.kt`를 직접 읽었다.
|
||||
구현자 결과 XML 15개를 대조해 88건, failures/errors/skipped 0을 확인했다.
|
||||
- 별도 Java 17 `CodeOrderQa`로 실제 Spring component를 생성했다. 충돌하는 과거 Spring property와
|
||||
`SystemEnvironmentPropertySource`를 주입해도 두 기본 순서는 코드의 `papago` 목록을 유지했다.
|
||||
typed-list 생성자로 감지 B/A/C, 번역 C/B/A를 독립 지정했고 빈 목록·빈 이름·중복·미등록 순서 및
|
||||
중복 공급자 이름 거부를 확인했다. 전역 목록은 변경하지 않았다.
|
||||
출력: `QA PASS: production code defaults, ignored legacy configuration, independent lists and validation; context closed`.
|
||||
driver exit 0, Spring context 종료, 외부 API 요청 없음.
|
||||
- 부모 fresh 명령:
|
||||
`./gradlew --no-daemon --offline test --rerun --tests 'kr.co.vividnext.sodalive.i18n.translation.*' --tests 'kr.co.vividnext.sodalive.content.LanguageDetectionCacheServiceTest' ktlintCheck bootJar`
|
||||
→ exit 0, `BUILD SUCCESSFUL in 36s`, `:test` 실제 실행. lint/build는 검증된 현재 산출물과 일치해 UP-TO-DATE였다.
|
||||
- 이후 사용자가 enum의 적합성을 물어 검토 의견만 제공했다. enum 변경은 별도 구현 승인 전이므로 Phase 2에 포함하지 않는다.
|
||||
- 부모 fresh XML 11개 합계 54건, failures/errors/skipped 0을 확인했다.
|
||||
- [독립 Phase 2 리뷰](reviews/phase-2-review.md): **APPROVE**, 차단/수정 요구 사항 없음.
|
||||
이전 Phase 1 리뷰는 그대로 보존한다. 코드·테스트와 문서는 각각 원자적으로 커밋한다.
|
||||
- 코드·테스트 커밋: `9136b0e3`. pre-commit ktlint와 커밋 메시지 사전/사후 검증을 통과했다.
|
||||
- 부모 리소스 정리: `lsof -nP +D` 출력 없음으로 열린 파일이 없음을 확인하고, 소유 root
|
||||
`provider-order-code.RRCyJ1`(driver/class/라이브러리/로그)만 제거했다. `test ! -e` exit 0.
|
||||
출력: `CLEANUP PASS: owned provider-order-code.RRCyJ1 removed; shared caches untouched`.
|
||||
필요한 증거는 이 문서에 이관했으며 임시 노트도 삭제한다. 다른 작업의 리소스는 건드리지 않았다.
|
||||
|
||||
Reference in New Issue
Block a user