Files

60 KiB

AWS 언어 감지·번역 Provider 구현 계획

실행 방식: 사용자 승인 Subagent-driven 실행 완료. 기능 범위 검증과 독립 최종 리뷰를 통과했다. 후속 회귀: 2026-11-10 사용자 보고로 추가한 P4-R1 및 P4-GATE까지 완료했다. 기존 P1~P3 완료·검증 기록은 보존한다. 실행 skill: subagent-driven-development.

Goal: 기존 언어 감지·번역 계약과 캐시를 유지하면서 AWS Comprehend 및 Translate를 Papago 다음 순서로 사용할 수 있게 한다.

Architecture: 공급자에 종속되지 않는 UTF-8/UTF-16 code-point splitter를 공통 패키지에 추가하고 Papago 분할을 해당 helper로 옮긴다. AWS SDK for Java v2의 동기 client는 기존 명시적 AWS 자격 증명과 리전을 쓰는 lifecycle Bean으로 만들며, 두 adapter를 기존 coordinator 순서에 등록한다. 끝으로 실제 adapter를 SDK mock과 H2 저장 흐름에 연결해 캐시·worker·표시 경계를 검증한다.

Tech Stack: Kotlin 1.6.21, Java 17, Spring Boot 2.7.14, Gradle, JUnit 5, AWS SDK for Java v2 BOM 2.57.11, Comprehend 및 Translate 모듈. Spec: prd.md, 승인 결정 DEC-006. 제공 API 근거는 PRD의 공식 Comprehend 및 Translate 문서 링크를 따른다.

Global Constraints

  • SDK v2 BOM은 2.57.11로 고정하고 software.amazon.awssdk:comprehend, software.amazon.awssdk:translate만 새 SDK 모듈로 추가한다. 기존 AWS SDK v1 의존성은 유지한다.
  • 기존 cloud.aws.credentials.access-key, cloud.aws.credentials.secret-key, cloud.aws.region.static 설정을 사용한다. AWS 자격 증명 기본 체인, 환경변수 인증 설계, IAM 설정은 변경하지 않는다.
  • AWS 동기 clients는 Spring @Bean과 destroyMethod = "close"로 관리한다. 기본 동기 HTTP runtime은 SDK의 전이 의존성으로 둔다.
  • 모든 production 변경은 src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/에, 대응 테스트는 src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/ 및 실제 영향받는 기존 테스트 디렉터리에 둔다.
  • splitter는 Unicode code point 경계에서 자른다. Papago 크기는 UTF-16 code unit 5000, AWS Translate 크기는 UTF-8 byte 10000, 감지 prefix는 UTF-8 byte 최대 100000이다.
  • 빈 raw Papago 문자열과 whitespace 보존을 포함한 기존 동작을 유지한다. 감지 prefix는 전체 원문 hash/cache key와 번역 입력을 바꾸지 않는다.
  • 감지 순서 및 번역 순서는 각각 PAPAGO, AWS로 둔다. 지원 번역 언어는 기존 ko/en/ja만 허용한다.
  • 공개 API, DB schema, provider interface, retry 동작, AWS IAM, 실계정 자격 증명, 유료 API 호출은 범위 밖이다.
  • 구현 전에 각 구현 Task의 RED 테스트가 의도된 assertion 실패임을 확인한다. test/compiler setup 오류는 RED 증거가 아니다.
  • 직접 영향받는 테스트만 --tests로 지정한다. 무필터 test, check, build, clean build는 자동 실행하지 않는다. 필요한 최종 산출물 확인은 ./gradlew bootJar로 한다.
  • 이후 build.gradle.kts 변경 시 docs/agent-guides/실행명령어.md의 AWS dependency/build 예시를, 신규 테스트 추가 시 해당 문서의 단일 테스트 예시를 갱신한다.
  • 어떤 응답 원문, 인증정보, 민감 오류 메시지도 로그, fixture 또는 worker 오류 문자열에 노출하지 않는다.

Review Focus

  1. 경계 바로 앞의 한글 및 supplementary-plane 문자는 잘리지 않고 한 조각 안에 남으며, byte/UTF-16 한도는 초과하지 않는다. CodePointTextSplitterTest에서 각 공급자 한도 경계, 재구성 및 100000 byte prefix와 제외 tail을 고정한다.
  2. blank 입력, 짧은 감지 입력, blank/누락 후보와 AWS 오류가 감지 cache에 잘못된 값으로 저장되지 않는다. AwsLanguageDetectionProviderTest와 LanguageProviderCacheEndToEndTest에서 호출 수와 결과를 확인한다.
  3. AWS 번역 일부 조각의 오류 또는 공백 응답이 앞선 조각·원문의 성공값과 섞이거나 후속 호출을 유발하지 않는다. AwsTranslationServiceTest 및 필요 시 TranslationCoordinatorTest에서 전체 요청 폐기와 원문 단위 fallback을 확인한다.
  4. AWS 기본 순서 전환 후 Papago 전용 fixture가 실제 AWS adapter를 가짜 AWS provider와 함께 로드하거나 PAPAGO 전용 검증을 바꾸지 않는다. LanguageProviderSpringConfigurationTest 및 아래 명시한 영향 테스트를 확인한다.
  5. 서로 prefix가 같은 긴 본문도 원문 전체 hash/cache key로 구분되고, 성공 AWS 결과는 aws 및 translate-2017-07-01로 저장되며 재호출되지 않는다. LanguageProviderCacheEndToEndTest에서 H2 row, SDK 호출 수, 화면 결과를 확인한다.

현재 상태

Phase 상태 Task 다음 Goal 차단 조건
1 완료 1/1 없음 없음
2 완료 2/2 없음 없음
3 완료 1/1 없음 없음
4 완료 1/1 없음 없음

P1~P4의 모든 Task/Gate가 완료됐고 활성 Goal은 없다. 실제 IAM·유료 API·운영 DB 및 사용자 전체 테스트 검증은 아래 미검증 범위로 구분한다.

범위와 파일 책임

포함

  • 공통 Unicode code point splitter 및 기존 Papago 분할의 helper 이전.
  • AWS Comprehend 감지 adapter, AWS Translate adapter, AWS SDK v2 client 설정 Bean.
  • 감지/번역 기본 순서를 PAPAGO 다음 AWS로 등록하고 PAPAGO 전용 테스트 fixture를 명시 설정으로 고정.
  • 실제 adapter, cache, worker, H2 persistence, materializer 표시 및 현재 provider guide의 구현 상태 갱신.

제외

  • 공개 interface나 API/DB schema 변경, 기존 AWS SDK v1 마이그레이션, AWS 실계정 호출/IAM 변경.
  • AWS를 이유로 한 지원 언어 확대, confidence threshold, 다중 조각 감지 집계, retry·logging 확장.
  • 일반적인 provider-guide 재작성 또는 역사적 PRD/계획/결정 기록 수정.

파일 맵

책임 파일
공유 code point 분할과 UTF-8 길이 계산 src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/CodePointTextSplitter.kt
Papago splitter 사용 src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/PapagoTranslationService.kt
AWS detection/translation adapter src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/AwsLanguageDetectionProvider.kt, src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/AwsTranslationService.kt
client 생성 및 종료 src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/AwsLanguageProviderConfiguration.kt
adapter 순서와 직접 영향 테스트 src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderOrder.kt, 아래 Task별 명시 파일
의존성과 실행 예시 build.gradle.kts, docs/agent-guides/실행명령어.md
AWS 검증 및 가이드 src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderCacheEndToEndTest.kt, docs/20261109_감지번역공급자공통화/provider-guide.md

요구사항 추적

PRD 요구사항 구현 Goal 검증 핵심
AWS-001 P2-T1 client configuration의 synthetic credentials, Region, 종료
AWS-002, AWS-003, AWS-004 P2-T1 blank/짧은 입력, 100000 byte prefix, 첫 후보만 검사하고 blank면 실패 null
AWS-005, AWS-007 P2-T1 명시 source/target, 순차 chunk, 전체 결과/중단, 안정 version
AWS-006 P1-T1 Papago 5000 UTF-16 회귀와 AWS UTF-8/code point 경계
순서·실제 provider cache 귀속·전체 fallback P2-T2, P3-T1 PAPAGO → AWS, H2 저장 및 재사용, 전체 원문 fallback
보안·기존 API 및 DB 계약 모든 Goal, 특히 P3-T1 cause/error marker 비노출, 기존 hash 및 저장 계약

TDD 및 실행 규칙

  • 구현 Task는 각각 정확히 다섯 체크박스인 RED, RED 확인, GREEN, GREEN 확인, REFACTOR를 가진다.
  • RED 테스트를 작성한 뒤 필요한 production type의 컴파일용 빈 shell만 추가할 수 있다. shell은 요구 동작을 구현하지 않으며, intended assertion failure를 확인한 뒤에만 행동 코드를 쓴다.
  • RED 확인은 해당 focused test가 테스트 누락/compile failure가 아닌 요구 동작 부재 assertion으로 실패했음을 기록한다.
  • Gate는 통합 검증용 대체 절차를 따르고 TDD exception을 명시한다. P3-T1은 test-only wiring 작업이므로 첫 실행이 통과할 수 있다. 실패를 만들기 위해 production이나 응답을 고의 변경하거나 assertion을 약화하지 않는다.
  • 모든 향후 명령은 저장소 루트에서 실행한다. 신규 명령은 아래 Task의 focused 명령 및 Gate 범위에 한정한다.

Phase 1: 공통 code point 분할

Phase 결과: Papago의 기존 동작을 보존하며 공급자별 크기 측정 함수를 받는 내부 splitter 계약을 갖는다.

Task 1.1: 공통 splitter와 Papago 이전

  • Task 1.1: 공통 splitter와 Papago 이전

Goal P1-T1: Papago의 기존 분할 경계를 유지하면서 code point 단위 공통 분할 및 prefix helper를 제공한다.

  • 시작 조건: 사용자 승인된 PRD 및 DEC-004/DEC-005 확인, 계획 검토와 실행 방식 선택 완료. 변경 전 ./gradlew test --tests 'kr.co.vividnext.sodalive.i18n.translation.PapagoTranslationServiceTest'로 기존 동작의 통과를 기록한다. 기존 실패가 있으면 변경 영향과 분리해 먼저 보고한다.

  • 완료 증거: 3개 helper의 단위 테스트, Papago 기존 assertion 보존, focused 테스트 및 lint 성공.

  • 범위 밖: AWS adapter 구현, Papago HTTP 흐름·응답 계약 변경. Files:

  • Create: src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/CodePointTextSplitter.kt

  • Modify: src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/PapagoTranslationService.kt

  • Create: src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/CodePointTextSplitterTest.kt

  • Preserve and verify: src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/PapagoTranslationServiceTest.kt Interfaces:

  • Consumes: 기존 Papago의 MAX_REQUEST_TEXT_LENGTH = 5000 및 String.length 기준.

  • Produces: internal fun splitTextByCodePoint(text: String, maxSize: Int, codePointSize: (Int) -> Int): List<String>; internal fun takeTextPrefixByCodePoint(text: String, maxSize: Int, codePointSize: (Int) -> Int): String; internal fun utf8CodePointSize(codePoint: Int): Int.

  • RED: CodePointTextSplitterTest에 정확 한도/초과, Korean, supplementary character, whitespace 포함 재구성, empty input (listOf("")), 100000 UTF-8 byte prefix와 제외 tail 테스트를 작성한다. Papago 5001/10001 및 경계 surrogate pair 기대도 기존 PapagoTranslationServiceTest에 유지한다.

  • RED 확인: ./gradlew test --tests 'kr.co.vividnext.sodalive.i18n.translation.CodePointTextSplitterTest'를 실행한다. helper 미구현에 따른 compile failure가 아니라 splitter 요구 결과 assertion 실패를 기록한다. 테스트 실행 명령 오류는 RED가 아니다.

  • GREEN: 위 세 internal 함수를 CodePointTextSplitter.kt에 구현한다. UTF-16 계수 callback에는 Character.charCount(codePoint), UTF-8 계수에는 utf8CodePointSize를 쓴다. Prefix는 첫 허용 경계에서 멈춰 전체 입력을 split하지 않는다. 공통 boundary scanning은 중복이 실제 생길 때만 private helper로 공유한다.

  • GREEN 확인: ./gradlew test --tests 'kr.co.vividnext.sodalive.i18n.translation.CodePointTextSplitterTest'를 실행해 경계·재구성 assertion이 통과함을 확인한다.

  • REFACTOR: PapagoTranslationService.splitText를 helper 호출로 바꾸고 5000 UTF-16 callback을 지정한다. ./gradlew test --tests 'kr.co.vividnext.sodalive.i18n.translation.CodePointTextSplitterTest' --tests 'kr.co.vividnext.sodalive.i18n.translation.PapagoTranslationServiceTest'와 ./gradlew ktlintCheck로 Papago 전체 HTTP 회귀를 확인한다.

P1-T1 검증 기록 (2026-11-09):

  • 변경 전 ./gradlew --no-daemon test --tests 'kr.co.vividnext.sodalive.i18n.translation.PapagoTranslationServiceTest': exit 0, 기존 17개 통과. 기존 assertion은 수정하지 않았고 빈 raw 문자열/공백 HTTP 회귀 2개만 추가했다.
  • RED ./gradlew --no-daemon test --tests 'kr.co.vividnext.sodalive.i18n.translation.CodePointTextSplitterTest': 최초 실행은 테스트 컴파일 중 120초 timeout으로 RED에서 제외했다. 300초 제한 재시도로 compile 후 33개 중 32개 assertion 실패를 관찰했고, 같은 명령 재실행에서 COMMAND_EXIT_CODE=1을 확인했다. UTF-8 크기 assertion 예: expected: <1> but was: <0>. 빈 prefix 1개만 기존 shell에서도 통과했다.
  • GREEN 같은 splitter focused 명령: exit 0, 33개 통과. 공통 경계 scanner를 split과 prefix가 공유하며 prefix는 전체 tail을 순회하지 않는다. JVM UTF-8 인코딩과 일치하도록 짝 없는 surrogate의 1바이트 대체 인코딩도 검증했다.
  • Papago 이전 후 ./gradlew --no-daemon test --tests 'kr.co.vividnext.sodalive.i18n.translation.CodePointTextSplitterTest' --tests 'kr.co.vividnext.sodalive.i18n.translation.PapagoTranslationServiceTest': 최초 실행은 테스트 컴파일 중 120초 timeout, 300초 제한 재시도는 exit 0. splitter 33개와 Papago 19개, 합계 52개 통과(실패/오류/skip 모두 0).
  • ./gradlew --no-daemon ktlintCheck --continue: exit 0. main/test/Kotlin script lint 모두 성공. LSP 도구 대신 Kotlin compile 및 lint를 사용했다. 기존 Gradle deprecation, 중복 org.json.JSONObject 및 변경하지 않은 테스트의 compiler warning은 수정하지 않았다.
  • 신규 단일 테스트 예시만 docs/agent-guides/실행명령어.md에 추가했다. 상세 보고서: /var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/aws-implementation.7dG8YS/p1.W0mxDa/report.md. 전체 테스트·빌드·외부 API는 실행하지 않았다. 사용자 전체 테스트 명령은 ./gradlew test다. P1-GATE는 부모 reviewer 판정 전이며 완료 처리하지 않는다.

Phase 1 Gate

  • Phase 1 Gate: Papago 호환성·분할 경계 검증

2026-11-09 Gate: 독립 reviewer 명세·품질 모두 Approved. 부모가 두 focused class 명령을 재실행해 exit 0(UP-TO-DATE)을 확인했다. 직전 실제 실행은 helper 33개 + Papago 19개, 총 52개 통과이며 ktlintCheck도 exit 0이다. LSP 도구 부재는 Kotlin 컴파일·lint로 보완했고 전체 테스트는 미실행이다.

Goal P1-GATE: 공통 helper가 Papago 호환성과 Unicode/byte 경계를 만족하는지 판정한다.

  • 시작 조건: P1-T1의 다섯 체크와 결과 기록 완료.
  • 완료 증거: 두 focused test class와 ktlint 성공, assertion 및 실제 명령 결과가 기록됨.
  • 범위 밖: AWS SDK dependency 또는 adapter 추가.
  • TDD 예외 사유: 구현을 추가하지 않는 단계별 통합 검증이다.
  • 대체 검증 방법: 아래 명령 결과와 splitter 경계 assertion을 대조하고, 기존 Papago 5000/5001/10001 및 surrogate 요청/결합 검증이 유지되는지 확인한다.
./gradlew test --tests 'kr.co.vividnext.sodalive.i18n.translation.CodePointTextSplitterTest' --tests 'kr.co.vividnext.sodalive.i18n.translation.PapagoTranslationServiceTest'
./gradlew ktlintCheck

Expected: 두 테스트 범위와 lint가 exit code 0. 실패가 있으면 해당 Task의 미완료 상태를 보존한다.

Phase 2: AWS adapters와 순서 등록

Phase 결과: Comprehend 및 Translate가 기존 provider contract를 구현하고 production 기본 순서 PAPAGO 다음에 AWS가 온다.

Task 2.1: AWS SDK v2 clients와 adapters

  • Task 2.1: AWS SDK v2 clients와 adapters

Goal P2-T1: 명시된 AWS 설정으로 client lifecycle을 관리하고 기존 provider contract에 맞는 감지·번역 adapter를 추가한다.

  • 시작 조건: P1-GATE 통과 및 CodePointTextSplitter 계약 확정.
  • 완료 증거: SDK dependency, 세 production 클래스, 세 focused test class, 실행 명령 문서 갱신과 해당 테스트 성공.
  • 범위 밖: production provider order 변경, 실 AWS 연결, IAM 권한 검증. Files:
  • Modify: build.gradle.kts
  • Create: src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/AwsLanguageDetectionProvider.kt
  • Create: src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/AwsTranslationService.kt
  • Create: src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/AwsLanguageProviderConfiguration.kt
  • Create: src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/AwsLanguageDetectionProviderTest.kt
  • Create: src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/AwsTranslationServiceTest.kt
  • Create: src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/AwsLanguageProviderConfigurationTest.kt
  • Modify: docs/agent-guides/실행명령어.md Interfaces:
  • Consumes: LanguageDetectionProvider.detect(query: String): String?; TranslationProvider.translate(request: TranslateRequest): TranslateResult; P1 splitter three functions; existing cloud AWS @Value keys.
  • Produces: AwsLanguageDetectionProvider(ComprehendClient) : LanguageDetectionProvider; AwsTranslationService(TranslateClient) : TranslationProvider; @Configuration bean methods comprehendClient(): ComprehendClient, translateClient(): TranslateClient, each with @Bean(destroyMethod = "close").

테스트 실행 준비: RED 테스트를 먼저 작성하고 SDK 타입을 컴파일하는 데 필요한 BOM 2.57.11, comprehend, translate 의존성과 비동작 shell만 준비한다. dependency resolution 및 compile 성공 자체는 RED 증거가 아니다. 생산 동작은 아래 RED 확인 뒤 구현한다.

  • RED: 신규 세 테스트 클래스에 성공·실패 assertion을 작성한다. 감지는 짧은 입력 그대로 전달, blank 무호출, 100000바이트 prefix, 첫 후보의 낮은 score/미지원 코드 허용을 검증한다. 첫 후보가 blank이고 두 번째가 유효해도 결과는 null이며 빈 후보 목록·SDK 예외도 null이다. 번역은 10000바이트 정확 경계와 초과, 한글·보조 평면 문자·공백, source/target, 순서·원문별 결과 하나, 중간 조각 또는 다음 원문의 예외/blank/null 응답 시 전체 결과 폐기와 이후 무호출을 검증한다. 빈 목록·동일 언어·미지원 언어 가드도 검증한다. Configuration은 synthetic credentials/region과 실제 client 생성·종료를 좁은 Spring context에서 검증하고, adapter는 SDK interface만 mock한다.
  • RED 확인: 세 신규 test class를 ./gradlew test --tests 'kr.co.vividnext.sodalive.i18n.translation.AwsLanguageDetectionProviderTest' --tests 'kr.co.vividnext.sodalive.i18n.translation.AwsTranslationServiceTest' --tests 'kr.co.vividnext.sodalive.i18n.translation.AwsLanguageProviderConfigurationTest'로 실행한다. 각 intended assertion 실패를 확인하며 test compile 오류는 거부한다.
  • GREEN: static credentials provider에 기존 access/secret 키를 연결하고 Region.of와 종료 관리 client Bean을 구현한다. 감지는 blank 무호출·짧은 입력·100000바이트 prefix를 적용하고 첫 후보만 검사한다. 첫 후보의 blank/missing 또는 SDK 예외는 null이며 후속 후보를 대신 선택하지 않는다. PRD의 prefix 한계 shortcut: 주석을 남긴다. 번역은 source/target을 명시하고 10000 UTF-8 바이트 이하 조각을 순서대로 번역해 원문별로 결합한다. 예외·null/blank 번역이면 전체 요청 결과를 폐기하고 이후 호출을 중단한다. 기존 언어 가드와 translate-2017-07-01 버전을 적용한다.
  • GREEN 확인: 동일한 세 --tests 대상으로 실행해 client 설정/종료, 감지 boundary 및 응답 매핑, 번역 요청 assertions가 통과함을 확인한다.
  • REFACTOR: 통과한 동작을 바꾸지 않고 이번 Task가 만든 중복만 정리한다. docs/agent-guides/실행명령어.md에 신규 focused 테스트 예시를 추가하고 Papago 및 AWS 공통 focused suite와 ./gradlew ktlintCheck 결과를 기록한다.

P2-T1 검증 기록 (2026-11-09):

  • 아래 명령에 모두 --no-daemon을 추가하고 600000ms timeout 및 전용 TMPDIR/cache 경로를 사용했다. 테스트 세 클래스를 먼저 작성한 뒤 dependency와 비동작 shell만 추가했다. 최초 세 클래스 RED는 main/test compile 성공 후 48개 중 40개 실패(감지 19/16 실패, 번역 27/22 실패, 설정 2/2 실패), exit 1이었다. 실제 assertion은 감지 expected: <ko> but was: <null>, 번역 expected: <[translated]> but was: <[]>, 설정 Bean 수 expected: <1> but was: <0>이며 compiler/setup 오류가 아니다. 빈 입력/언어 가드 8개는 shell에서도 통과했다. 숫자 exit marker만 얻기 위한 동일 RED 재실행은 하지 않았다.
  • 첫 구현 후 세 클래스 실행은 exit 1(48개 중 설정 2개 실패). 실제 client 생성에서 BeanCreationException → BeanInstantiationException → NoClassDefFoundError: org/apache/hc/client5/http/ssl/TlsSocketStrategy를 발견했다. dependencyInsight --dependency httpclient5 및 --dependency httpcore5를 --configuration runtimeClasspath로 실행(각 exit 0)해 Boot의 5.1.4/5.1.5 하향 관리를 확인했다. 공식 SDK POM의 5.6.4/5.4.3으로 build extra 두 값만 정렬했으며 transport 추가/제외와 v1 변경은 없다. 정렬 후 dependencyInsight --dependency org.apache.httpcomponents --configuration runtimeClasspath는 exit 0이며 HTTP4는 기존 4.5.14/4.4.16, SDK v1은 1.12.380이다.
  • HTTP5 정렬 후 실행은 실제 생성/인증/region을 통과했으나 test proxy의 종료 관찰 assertion 2개가 실패해 exit 1이었다. Spring의 원래 Bean destruction 경로에 맞춰 proxy 대신 실제 두 Apache5 pool의 종료 전 false/종료 후 true를 관찰하도록 테스트만 수정했다. 동일 세 클래스 최종 GREEN은 exit 0, 48개 통과(감지 19, 번역 27, 설정 2; failure/error/skip 0). 실제 client는 synthetic credentials와 서울/대체 region으로 생성하며 네트워크 요청 없이 종료를 검증한다.
  • 위 다섯 클래스 공통 focused 명령은 exit 0, 총 100개 통과(splitter 33, Papago 19, 감지 19, 번역 27, 설정 2; failure/error/skip 0). ./gradlew --no-daemon ktlintCheck --continue는 신규 번역 클래스 한 줄 길이로 exit 1, 신규 설정 테스트 개행으로 exit 1, 두 줄만 정리한 최종 실행은 exit 0이다. 실행 문서는 새 세 단일 테스트 및 AWS dependency/build 예시만 추가했다.
  • 원문·합성 민감 오류 marker의 신규 로그 비노출 assertion을 확인했다. LSP 도구가 없어 Kotlin compiler와 lint를 사용했다. 전체 테스트, build/check/clean, bootJar, 실 AWS/Papago 호출, 실제 자격 증명/IAM 및 SDK wire/signing은 미검증이다. 사용자 전체 테스트 명령은 ./gradlew test다. 상세 명령·실패 이력·변경 파일 보고서는 /var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/aws-implementation.7dG8YS/p2-adapters.wy5Eu6/report.md에 저장했다. Provider order와 다른 Task/Gate는 변경하지 않았다.

검증 명령:

./gradlew test --tests 'kr.co.vividnext.sodalive.i18n.translation.AwsLanguageDetectionProviderTest' --tests 'kr.co.vividnext.sodalive.i18n.translation.AwsTranslationServiceTest' --tests 'kr.co.vividnext.sodalive.i18n.translation.AwsLanguageProviderConfigurationTest'
./gradlew test --tests 'kr.co.vividnext.sodalive.i18n.translation.CodePointTextSplitterTest' --tests 'kr.co.vividnext.sodalive.i18n.translation.PapagoTranslationServiceTest' --tests 'kr.co.vividnext.sodalive.i18n.translation.AwsLanguageDetectionProviderTest' --tests 'kr.co.vividnext.sodalive.i18n.translation.AwsTranslationServiceTest' --tests 'kr.co.vividnext.sodalive.i18n.translation.AwsLanguageProviderConfigurationTest'
./gradlew ktlintCheck

Maven Central BOM POM 확인 URL: https://repo.maven.apache.org/maven2/software/amazon/awssdk/bom/2.57.11/bom-2.57.11.pom. SDK 2.57.11 BOM/service POM과 Java 17 호환성은 부모 조사에서 확인된 근거다. 실제 Gradle dependency resolution 및 프로젝트 통합 호환성은 이 Task 실행 시 검증하며, 현재 이미 통과했다고 기록하지 않는다. 전이 기본 HTTP runtime 외의 transport 의존성/제외는 추가하지 않는다.

Task 2.2: AWS 기본 순서와 Papago 전용 fixture

  • Task 2.2: AWS 기본 순서와 Papago 전용 fixture

Goal P2-T2: 실제 두 AWS adapter를 등록하고 Papago 전용 테스트는 의도한 한 provider만 주입하도록 고정한다.

  • 시작 조건: P2-T1의 adapter 테스트와 configuration 성공.

  • 완료 증거: 두 default list의 순서, 영향 테스트의 PAPAGO 전용 설정, 실제 AWS와 fake AWS bean의 중복 없는 Spring wiring을 검증.

  • 범위 밖: 공개 provider ID/code, properties 검증 규칙 및 기존 PAPAGO 우선순위 변경. Files:

  • Modify: src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderOrder.kt

  • Modify: src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderIdTest.kt

  • Modify: src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderSpringConfigurationTest.kt

  • Modify: src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/TranslationJobWorkerTest.kt

  • Modify: src/test/kotlin/kr/co/vividnext/sodalive/content/LanguageDetectionCacheServiceTest.kt

  • Modify: src/test/kotlin/kr/co/vividnext/sodalive/content/CreatorCommunityLanguageDetectTest.kt

  • Modify: src/test/kotlin/kr/co/vividnext/sodalive/content/CreatorCommunityLanguageDetectionCacheConcurrencyTest.kt

  • Modify: src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/translation/CreatorCommunityTranslationServiceTest.kt Interfaces:

  • Consumes: AwsLanguageDetectionProvider 및 AwsTranslationService 실제 Spring components, LanguageProviderProperties(List<LanguageProviderId>, List<LanguageProviderId>).

  • Produces: production detectionOrder와 translationOrder 각각 listOf(PAPAGO, AWS); fake-only test runner와 actual-adapter runner가 구분된 Spring wiring.

  • RED: LanguageProviderIdTest의 기본 순서 assertion을 [PAPAGO, AWS]로 기대하고 production adapter identity 및 안정 code assertion은 보존한다. Spring 기본 순서/legacy override 테스트에 두 provider와 PAPAGO 첫 성공, AWS 미호출 assertion을 추가한다.

  • RED 확인: ./gradlew test --tests 'kr.co.vividnext.sodalive.i18n.translation.LanguageProviderIdTest' --tests 'kr.co.vividnext.sodalive.i18n.translation.LanguageProviderSpringConfigurationTest' 실행 결과가 production default가 아직 [PAPAGO]인 데서 비롯된 intended assertion failure인지 확인한다.

  • GREEN: LanguageProviderOrder.kt의 두 목록에 AWS를 Papago 다음에 추가한다. Spring 테스트의 legacy property/env default 기대를 새 순서로 갱신하며 PAPAGO 우선 성공과 AWS 미호출을 검증한다. actual adapter를 검증할 runner는 SDK mock과 함께 별도로 구성하고 fake AwsProviders를 같은 capability에 로드하지 않는다.

  • GREEN 확인: 위 두 class를 다시 실행한다. startup uniqueness assertion을 유지하고 shouldRejectInvalidOrdersAtStartup의 papagoRunner가 AWS 없는 [AWS] 설정에서 실패하는 검증을 그대로 보존한다.

  • REFACTOR: 아래 영향 테스트 helper만 LanguageProviderProperties(listOf(PAPAGO), listOf(PAPAGO))로 명시해 기존 단일 Papago 기대를 유지한다. LanguageProviderPropertiesTest의 invalid inventory 검증은 변경하지 않는다. 전체 지정 suite와 ktlint 결과를 기록한다.

PAPAGO-only fixture 대상은 TranslationJobWorkerTest의 여섯 coordinator 생성 위치, LanguageDetectionCacheServiceTest.service, CreatorCommunityLanguageDetectTest의 production 기본 properties import 및 ControlledLanguageDetectionCacheService super 생성자, CreatorCommunityLanguageDetectionCacheConcurrencyTest의 imported default properties, CreatorCommunityTranslationServiceTest.materializer helper다. 검증 명령:

./gradlew test --tests 'kr.co.vividnext.sodalive.i18n.translation.*' --tests 'kr.co.vividnext.sodalive.content.LanguageDetectionCacheServiceTest'
./gradlew 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 ktlintCheck

P2-T2 검증 기록 (2026-11-10):

  • 모든 Gradle 명령은 저장소 루트에서 --no-daemon, timeout 600000ms 및 전용 TMPDIR/runtime cache 환경으로 실행했다. 두 default assertion 테스트부터 작성한 최초 RED는 exit 1, 10개 중 5개 실패였다(ID 3/1 실패, Spring 7/4 실패; 오류/skip 0). 실패는 모두 expected: <[PAPAGO, AWS]> but was: <[PAPAGO]>이며 compiler/setup 오류가 아니다. 두 production 목록 변경 후 같은 명령의 GREEN은 exit 0, 10개 전체 통과다.
  • Production 변경은 LanguageProviderOrder.kt 두 목록뿐이다. Spring fake AWS runner와 실제 AwsLanguageDetectionProvider/AwsTranslationService + SDK interface mock runner를 분리했다. 실제 capability별 두 Bean, AWS enum/version, PAPAGO 첫 성공 뒤 AWS 무호출, 명시 AWS 우선 SDK 요청/응답을 확인했다. legacy property/env 무시, PAPAGO adapter identity/code 및 papagoRunner의 AWS 없는 inventory 거부는 유지했다. LanguageProviderPropertiesTest는 수정하지 않았다.
  • 지정 Papago-only fixture만 명시 목록으로 격리했다(worker 여섯 coordinator, content cache helper, community 감지 테스트의 imported properties와 controlled service super 생성자, concurrency imported properties, community translation materializer). 기존 기대와 assertion은 유지했다. i18n.translation.* + content.LanguageDetectionCacheServiceTest 명령은 최초/최종 모두 exit 0, 각 142개 통과다. 별도 세 community class 명령도 최초/최종 모두 exit 0, 각 30개 통과(감지 12, concurrency 2, translation 16)이며 두 최종 실행의 실패/오류/skip은 모두 0이다.
  • ./gradlew --no-daemon ktlintCheck는 최초에 신규 테스트 네 파일의 인자 개행/130자 위반으로 exit 1이었다. 해당 변경 줄만 개행한 최종 실행은 exit 0이며 format 명령은 실행하지 않았다. ./gradlew --no-daemon bootJar는 exit 0이다. 최종 포맷 뒤 두 focused suite를 각각 다시 실제 실행했다. 산출물의 production 클래스와 실제 AWS 번역 adapter를 호출한 Java 드라이버는 exit 0으로 기본 순서, 첫 성공 무호출, AWS fallback, blank 입력 및 AWS 누락 거부를 확인했다. 네트워크 요청은 없다.
  • 기존 Gradle deprecation, 중복 org.json.JSONObject, invalid-order startup 및 worker 실패 시나리오의 예상 WARN은 유지했다. LSP가 없어 Kotlin main/test compile 및 lint로 검증했다. 전체 테스트·실 AWS/Papago·실제 credentials/IAM·운영 DB는 미검증이며 사용자 전체 명령은 ./gradlew test다. 상세 count/명령/로그 및 보고서는 /var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/aws-implementation.7dG8YS/p2-order.NhauNM/report.md에 기록했다. P2-GATE는 부모 독립 review 전이며 체크하지 않았다. 다른 Task/Gate와 현재 상태 표는 변경하지 않았다.

Phase 2 Gate

  • Phase 2 Gate: AWS 동작·순서·직접 영향 회귀 검증

2026-11-10 Gate: 독립 reviewer 명세·품질 Approved. 부모가 모든 변경 diff와 suite/lint/build 증거를 대조했다. translation/cache 142개와 community 30개 모두 실패·오류·skip 0, ktlintCheck 및 bootJar exit 0. 실제 AWS adapter의 localhost SDK 요청 검증도 완료했다. 실 AWS IAM·번역 품질·전체 테스트는 미검증이다.

Goal P2-GATE: AWS adapter 동작, default provider order, Papago 전용 회귀 및 두 capability wiring을 판정한다.

  • 시작 조건: P2-T1, P2-T2 완료 증거와 Progress 기록.
  • 완료 증거: 아래 두 focused suite, ktlint 및 bootJar exit code 0. Gradle에서 실제 dependency resolution도 성공.
  • 범위 밖: 전체 테스트, live cloud/API, IAM 권한 확인.
  • TDD 예외 사유: 구현 결과에 대한 검증 gate다.
  • 대체 검증 방법: focused suites를 각각 실행하고 Papago-only fixtures, 실제 AWS adapter, provider uniqueness와 저장 계약 경계를 대조한다. 실제 AWS 권한은 미검증으로 기록한다.
./gradlew test --tests 'kr.co.vividnext.sodalive.i18n.translation.*' --tests 'kr.co.vividnext.sodalive.content.LanguageDetectionCacheServiceTest'
./gradlew 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 ktlintCheck
./gradlew bootJar

Phase 2 관련 추가 회귀는 coordinator/worker/cache/end-to-end 테스트에 한정한다. 무필터 test나 check/build는 실행하지 않는다.

Phase 3: 실제 adapter의 저장·표시 경로 검증

Phase 결과: SDK mock으로 실제 AWS adapter를 H2 기반 기존 worker/cache/materializer 경로에서 실행하고 현재 확장 가이드를 실제 구현과 일치시킨다.

Task 3.1: adapter end-to-end와 현재 가이드

  • Task 3.1: 실제 adapter 통합 검증과 현재 가이드

Goal P3-T1: 실제 AWS adapter 성공·cache 재사용·fallback·표시를 H2에서 검증하고 현재 provider guide 설명을 갱신한다.

  • 시작 조건: P2-GATE 통과.
  • 완료 증거: actual SDK adapter mock을 이용한 H2 검증, 원문 full hash 및 output/providerVersion 저장 assertion, guide 대조 완료.
  • 범위 밖: 실 AWS 연결, 운영 MySQL claim query의 SKIP LOCKED 동작 증명, 역사적 PRD 변경.

Files:

  • Modify: src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderCacheEndToEndTest.kt
  • Modify if required by a proven gap: src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/TranslationCoordinatorTest.kt
  • Modify if required by a proven gap: src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/TranslationJobWorkerTest.kt
  • Modify: docs/20261109_감지번역공급자공통화/provider-guide.md

Interfaces:

  • Consumes: AwsLanguageDetectionProvider(ComprehendClient), AwsTranslationService(TranslateClient), production order [PAPAGO, AWS], 기존 H2 cache/worker/materializer wiring.

  • Produces: H2 integration evidence for original full-source cache identity, successful AWS provider/version attribution, SDK call count and displayed output; current guide description of AWS adapters, order, splitter and version.

  • TDD (명시적 예외): test-only integration wiring 및 검증이다. 새 전체 경로 검증은 첫 실행에 통과할 수 있으므로 인위적 RED를 만들지 않는다. Production behavior, SDK response 또는 assertion을 sabotage/약화하지 않는다.

  • TDD 예외 대체 검증: LanguageProviderCacheEndToEndTest에서 실제 adapters를 사용하고 AWS SDK interface만 mock한다. 중복 fake AWS beans/casts를 제거하고 assertion을 actual SDK call counts로 바꾼다. Comprehend에 동일 prefix와 서로 다른 tail을 가진 긴 원문 두 개를 보내 full hash/cache row가 구분되는지 확인하고 반복 조회는 AWS 호출이 없는지 확인한다.

  • TDD 예외 대체 검증: 번역 경로에서 Papago 실패 후 실제 AWS adapter의 성공 결과가 aws, translate-2017-07-01, 정확한 joined output으로 저장되고 worker 완료 및 materializer 화면에 보이는지 검증한다. cache 재사용 시 AWS 재호출이 없고, generic Recording provider의 version rewriting이 없음을 확인한다.

  • TDD 예외 대체 검증: 기존 PAPAGO → AWS production order를 보존한다. 실제 AWS adapter의 중간 조각 실패를 명시적 테스트 순서 AWS → PAPAGO로 실행해 다음 공급자에 원래 요청 전체가 전달됨을 검증한다. 모든 공급자 실패 시 cause 없는 고정 오류와 worker 오류 저장을 확인하고, 실패한 AWS의 부분 번역 memory가 저장되지 않음을 H2에서 확인한다. 기존 테스트가 이를 이미 검증하면 재사용하고 없으면 명시된 coordinator/worker 테스트에 추가한다. 합성 민감 오류 marker와 원문은 최종 오류·신규 로그에 없어야 한다.

  • TDD 예외 대체 검증: provider-guide.md의 현재 구현, default order, helper와 provider version 문장을 코드와 대조해 갱신한다. H2의 claim query spy는 기존 FOR UPDATE SKIP LOCKED 한정으로만 설명한다. Progress에 명령, 실제 결과, 운영 DB/live API 미검증을 기록한다.

P3-T1 검증 기록 (2026-11-10):

  • Test-only 예외대로 production 변경이나 인위적 RED 없이 기존 E2E를 실제 AwsLanguageDetectionProvider/AwsTranslationService와 mock ComprehendClient/TranslateClient로 교체했다. Fake AWS 두 Bean과 해당 recording 주입을 제거하고 Papago 실패 fixture, 실제 H2 저장소·worker·materializer·표시 및 기존 claim query 한 곳의 spy를 유지했다. SDK mock은 매 테스트 Mockito.reset, Papago 기록 목록과 cache/job/read model은 매 테스트 비워 context 재사용을 격리한다.
  • 최초 ./gradlew --no-daemon test --tests 'kr.co.vividnext.sodalive.i18n.translation.LanguageProviderCacheEndToEndTest'는 exit 0, 32초, 5개 전체 통과(실패/오류/skip 0)다. 같은 100000 UTF-8 바이트 prefix의 서로 다른 전체 원문 두 개가 같은 SDK prefix 두 번, 서로 다른 전체 hash 및 H2 aws row 두 개를 만들고 반복 조회에는 추가 SDK/Papago 호출이 없다. 첫 후보가 blank이고 둘째가 유효한 응답은 두 번 모두 null이며 H2 row는 0이다.
  • 10000 UTF-8 바이트를 넘는 한글·보조 평면 원문은 Papago 실패 뒤 실제 AWS 두 조각으로 번역된다. H2 memory 한 개에 전체 원문/hash, aws, translate-2017-07-01, 정확한 first tail 결과를 저장하고 worker 완료와 유효 표시를 확인했다. read model/job을 삭제한 뒤 materializer 재생성과 새 job도 같은 memory를 사용하며 총 SDK 2회/Papago 1회가 늘지 않는다. 실제 중간 SDK 오류는 앞선 성공까지 버려 빈 결과를 반환하고 후속 조각/원문을 호출하지 않는다. 전체 실패의 cause는 null, worker 재시도 오류는 all translation providers failed, memory/read model은 0이며 합성 민감 marker와 원문은 출력에 없다. 별도 로컬 AWS → PAPAGO 순서에서는 여러 원문을 포함한 원래 요청 객체 전체가 Papago에 전달된다. 모든 경계를 E2E에서 검증하므로 coordinator/worker 단위 테스트의 중복 추가는 필요하지 않았다.
  • 위 지정 translation/cache 명령에 --no-daemon을 추가한 실행은 exit 0, 25초, 145개 통과다(기존 142개에서 E2E 2→5로 3개 추가). 별도 세 community class 명령은 exit 0, 32초, 30개 통과(감지 12, concurrency 2, translation 16)다. 두 실행 모두 :test가 실제 실행됐고 실패/오류/skip은 0이다. ./gradlew --no-daemon ktlintCheck는 exit 0, 24초(test lint 실제 실행), ./gradlew --no-daemon bootJar는 exit 0, 11초(UP-TO-DATE)다. 모든 Gradle 명령은 timeout 600000ms와 전용 TMPDIR/runtime cache 환경으로 실행했다.
  • 현재 provider-guide.md만 실제 adapter 가용성·기본 순서·공유 splitter·기존 명시 credentials/region·SDK/HTTP5 버전·providerVersion·테스트 경계에 맞췄고 역사적 PRD/계획은 수정하지 않았다. 기존 Gradle deprecation, 중복 org.json.JSONObject, negative startup/worker 시나리오의 예상 WARN은 유지했다. LSP 도구가 없어 Kotlin test compile과 lint로 보완했다. 전체 테스트·실 AWS/Papago·credentials/IAM·운영 MySQL claim 동시성 및 별도 SDK wire QA는 이 Task에서 실행하지 않았다. 사용자 전체 테스트 명령은 ./gradlew test다. 상세 명령/count/파일/로그는 /var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/aws-implementation.7dG8YS/p3-integration.QpAQTJ/report.md에 기록했다. P3-GATE, 상단 상태 표와 다른 Task/Gate는 부모 소유로 남겼다.

검증 명령:

./gradlew test --tests 'kr.co.vividnext.sodalive.i18n.translation.*' --tests 'kr.co.vividnext.sodalive.content.LanguageDetectionCacheServiceTest'
./gradlew 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 ktlintCheck
./gradlew bootJar

H2 검증 경계: repository 저장·별도 transaction 조회·materializer/display는 실제 H2다. Claim query만 H2 호환 spy로 교체한다. 이 경로는 SDK serialization/signing, 실제 AWS 품질·권한, 운영 MySQL SKIP LOCKED 동시성을 증명하지 않는다.

Phase 3 Gate

  • Phase 3 Gate: 기능 범위 최종 검증

2026-11-10 Gate: Oracle이 P3 명세·품질과 전체 변경을 승인했다. 부모가 별도 fresh 실행한 translation/cache 145건, community 30건 모두 통과했고 lint·bootJar·최종 산출물 기반 실제 SDK localhost QA도 성공했다. 실패·오류·skip 0이며 코드·가이드·PRD를 대조했다. 실제 IAM·AWS/Papago 품질·과금·운영 MySQL 및 전체 테스트는 미검증이며 기능 범위 완료와 구분한다.

Goal P3-GATE: 세 Phase의 범위, 실제 adapter 사용자 흐름, 문서 최신성 및 검증 증거를 최종 판정한다.

  • 시작 조건: P3-T1과 모든 이전 Goal 완료 증거 기록.
  • 완료 증거: 아래 focused suite 각각 성공, ktlint와 bootJar 성공, trace table의 요구사항과 실제 결과 대조, 전체 테스트 미실행 표시.
  • 범위 밖: test 전체 실행, 실 AWS 호출 및 PRD의 IAM/live 품질 미검증 상태 변경.
  • TDD 예외 사유: 구현을 새로 하지 않는 final verification gate다.
  • 대체 검증 방법: 아래 명령 결과, H2 assertion, guide/current code/PRD 대조 및 문서 공백 검사를 Progress에 기록한다. 사용자의 전체 검증 명령은 별도로 안내한다.
./gradlew test --tests 'kr.co.vividnext.sodalive.i18n.translation.*' --tests 'kr.co.vividnext.sodalive.content.LanguageDetectionCacheServiceTest'
./gradlew 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 ktlintCheck
./gradlew bootJar

Expected: 각 명령이 exit code 0이고 문서/코드 대조에서 미해결 contract 차이가 없다. 전체 테스트는 사용자가 ./gradlew test로 실행하며, 이 계획의 focused 성공을 전체 성공으로 표현하지 않는다.

Goal 순서와 의존성

Phase 4: 공용 테스트 AWS 초기화 회귀 보정

  • Task 4.1: 테스트 전용 인증정보와 공유 설정 회귀 (P4-R1)
  • 목표: 공용 테스트 YAML로 전체 Spring 앱이 시작되며 운영의 빈 키 거부는 그대로 유지한다.
  • 시작 조건: SpringBootIntegrationSampleTest가 Access key ID cannot be blank 및 AwsLanguageProviderConfiguration.kt:18에서 실패함을 수정 전에 재현.
  • 완료 증거: 아래 RED/GREEN, 실제 H2·Redis 및 로컬 HTTP 통합 검증, 독립 리뷰와 결과 기록.
  • 범위 밖: 운영 코드·인증정보·Provider 순서·lazy/profile 우회·전체 테스트·실 AWS 호출 변경.
  • Files: 수정 src/test/resources/application.yml, src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/AwsLanguageProviderConfigurationTest.kt; 검증 src/test/kotlin/kr/co/vividnext/sodalive/support/SpringBootIntegrationSampleTest.kt, src/test/kotlin/kr/co/vividnext/sodalive/deployment/DeploymentEndpointIntegrationTest.kt.
  • 계약: 공용 test YAML을 실제로 로드하고 인증정보 property를 테스트에서 덮어쓰지 않는다. 두 client와 두 provider가 생성되며 합성 static credentials/서울 설정을 사용한다. 운영 빈 access/secret 키는 계속 실패한다.
  • RED: 공용 YAML 기반 AWS 설정·provider 생성 assertion과 빈 키 거부 회귀를 기존 설정 테스트에 추가한다.
  • RED 확인: ./gradlew --no-daemon test --tests 'kr.co.vividnext.sodalive.i18n.translation.AwsLanguageProviderConfigurationTest'에서 공용 빈 키가 원인인 실패를 확인한다.
  • GREEN: 공용 test YAML의 accessKey/secretKey 두 값만 명백한 테스트 전용 가짜 값으로 교체한다. production 설정·검증은 수정하지 않는다.
  • GREEN 확인: 같은 설정 테스트와 SpringBootIntegrationSampleTest, DeploymentEndpointIntegrationTest를 각각 focused 실행한다.
  • REFACTOR: 불필요한 추가 변경 없이 i18n.translation.* 회귀·ktlintCheck와 변경 공백 검사를 수행하고 실패·성공 증거를 기록한다.

P4-R1 검증 기록 (2026-11-10):

  • ConfigDataApplicationContextInitializer로 실제 공용 application.yml을 읽는 설정 회귀를 기존 클래스에 추가했다. 이 회귀는 인증정보 property override나 SDK/provider mock 없이 실제 configuration·두 client·두 adapter를 로드하고 capability별 Bean 수, AWS ID, static synthetic credentials와 서울 리전을 확인한다. 기존 두 region의 실제 client 생성·종료 테스트는 수정하지 않았다. 별도 parameterized negative 2건은 동일 production configuration에서 blank access/secret 각각의 startup failure와 SDK root cause를 확인한다.

  • YAML 수정 전 ./gradlew --no-daemon test --tests 'kr.co.vividnext.sodalive.i18n.translation.AwsLanguageProviderConfigurationTest': exit 1, 19초, 5건 중 공유 YAML 회귀 1건 실패(오류/skip 0). assertNull(context.startupFailure)가 BeanCreationException → BeanInstantiationException → NullPointerException: Access key ID cannot be blank.로 실패했다. main/test compile은 성공했으며 기존 lifecycle 2건과 빈 키 거부 2건은 통과했다. 원본 RED assertion/count는 아래 보고서 경로의 01-config-red-evidence.log에 보존했다.

  • src/test/resources/application.yml의 두 값만 test-access-key/test-secret-key로 교체했다. 동일 설정 focused 명령은 exit 0, 13초, 5건 전체 통과다. 두 negative는 각각 Access key ID cannot be blank.와 Secret access key cannot be blank.를 계속 확인한다. production 코드·설정·build와 다른 도메인 테스트는 이번 수정에서 변경하지 않았다.

  • ./gradlew --no-daemon test --tests 'kr.co.vividnext.sodalive.support.SpringBootIntegrationSampleTest': exit 0, 35초, 1건 통과. 실제 전체 Spring 앱에서 H2 select 1과 embedded Redis set/get을 확인했다. ./gradlew --no-daemon test --tests 'kr.co.vividnext.sodalive.deployment.DeploymentEndpointIntegrationTest': exit 0, 35초, 4건 통과. 실제 Tomcat 앱/management 포트 65041/65042에서 TestRestTemplate HTTP로 readiness 200 두 경로, deployment 기본 응답 및 앱 포트 비노출을 검증했다. 두 기존 클래스와 credential override는 추가·변경하지 않았다.

  • ./gradlew --no-daemon test --tests 'kr.co.vividnext.sodalive.i18n.translation.*': exit 0, 26초, 15개 클래스 146건 통과(AWS 설정/감지/번역 5/19/27건 포함). GREEN 실행은 모두 실패/오류/skip 0이며 :test가 실제 실행됐다. 중복 없는 검증 범위는 translation 146건과 전체 앱/HTTP 5건, 총 151건이다. ./gradlew --no-daemon ktlintCheck: exit 0, 20초(test lint 실제 실행); git diff --check: exit 0. 불필요한 refactor/format과 bootJar는 실행하지 않았다.

  • 모든 Gradle 명령은 timeout 600000ms와 지정 TMPDIR/cache export로 실행했다. 테스트 후 lsof/ps에서 해당 Spring test workers, Redis 65026, HTTP 65041/65042와 잔존 Redis listener가 없었으며 프로세스를 kill하지 않았다. LSP 도구가 없어 Kotlin compile·ktlint로 보완했다. Gradle deprecation, 기존 중복 JSONObject 경고와 JVM 종료 시 Redis 재접속 WARN은 수정하지 않았다. 전체 테스트·실 AWS/Papago·실계정/IAM은 미검증이며 사용자 전체 명령은 ./gradlew test다.

  • 상세 명령/exit/count/RED 증거 보고서: /var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/aws-test-fix.We4KIU/fix.4xaIEt/report.md. 다섯 실행 체크는 완료했으며 완료 증거의 독립 리뷰 및 Task 최상위 완료 판정은 부모에게 남긴다. P4-GATE, 상단 상태 표, PRD와 기존 P1~P3 기록은 수정하지 않았다.

  • Phase 4 Gate (P4-GATE): 회귀 수정·운영 검증 보존 승인

  • 시작 조건: P4-R1 결과 및 독립 리뷰 확인.

  • 완료 증거: 설정 회귀, H2/Redis 전체 앱, 실제 로컬 HTTP 테스트가 통과하고 production diff가 이번 수정에 없음을 확인한다.

  • 범위 밖: 전체 테스트 성공 또는 실 IAM 검증으로 확대 해석하지 않는다.

  • TDD 예외: Gate는 검증·문서 마감이며 새 동작을 구현하지 않는다.

  • 대체 검증: 위 focused 명령의 실제 실행 결과, runtime context·HTTP 응답, diff·리뷰 대조.

  • 진행 기록: 2026-11-10 수정 전 ./gradlew --no-daemon test --rerun --tests 'kr.co.vividnext.sodalive.support.SpringBootIntegrationSampleTest' 실패. root cause는 공용 test YAML의 빈 accessKey로 SDK v2의 eager credential 생성이 거부되는 것이다. 앞선 전용 AWS 설정 테스트는 합성 property를 직접 주입해 이 경로를 놓쳤다. 전체 테스트는 사용자 실행 ./gradlew test로 남긴다.

  • 부모 fresh 검증: ./gradlew --no-daemon test --rerun --tests 'kr.co.vividnext.sodalive.i18n.translation.*' --tests 'kr.co.vividnext.sodalive.support.SpringBootIntegrationSampleTest' --tests 'kr.co.vividnext.sodalive.deployment.DeploymentEndpointIntegrationTest' exit 0, 실제 재실행 17개 클래스 151건 실패·오류·skip 0을 XML에서 확인했다. ./gradlew --no-daemon ktlintCheck 및 git diff --check exit 0. 실제 H2/Redis와 앱·management HTTP 경로가 포함되며 변경한 테스트 외 credential override는 없다.

  • 독립 리뷰: 명세·품질 및 S1(공용 설정 로딩), S2(빈 운영 키 거부), S3(실제 앱·HTTP) 모두 승인, 차단 사항 없음. H2가 지원하지 않는 background scheduler SKIP LOCKED 오류는 별도 기존 한계로 관찰했으며 이번 통과가 scheduler SQL의 정상 동작을 증명하는 것은 아니다.

  • 실행 경계: test worker PID 70305와 HTTP 포트 65088/65089가 종료됐음을 확인했다. production 코드·운영 설정·빌드에는 이번 수정이 없고, 전체 테스트·실 IAM·외부 API는 미검증이다. 위 임시 보고서의 핵심 RED/GREEN 증거는 이 문서에 보존하고 임시 산출물은 작업 종료 시 정리한다.

  • 정리 영수증: 소유한 회귀 조사·구현·리뷰 임시 root의 열린 파일이 없음을 확인하고 하위 로그·캐시·노트를 제거했다. 경로 부재 검사 exit 0. 공유 캐시·기존 프로세스·저장소 파일은 제거하지 않았다.

순서 Goal 의존성 실행 방식
1 P1-T1 시작 조건 충족 splitter 단일 Task
2 P1-GATE P1-T1 focused 검증
3 P2-T1 P1-GATE SDK dependency 및 adapter
4 P2-T2 P2-T1 order 및 영향 fixture
5 P2-GATE P2-T1, P2-T2 focused suite/lint/bootJar
6 P3-T1 P2-GATE H2 adapter 통합 및 guide
7 P3-GATE P3-T1 focused 최종 검증
8 P4-R1 사용자 후속 오류 재현 공용 테스트 설정 회귀 보정
9 P4-GATE P4-R1 실제 앱·HTTP 및 리뷰 판정

P1-T1 → P1-GATE → P2-T1 → P2-T2 → P2-GATE → P3-T1 → P3-GATE. 한 번에 하나의 goal만 active로 둔다. 선행 goal이 완료되지 않으면 뒤 goal을 시작하지 않는다.

문서 및 실행 기록

계획 작성 기록

  • 상태: 계획 문서만 작성 완료, 구현 대기.
  • 무엇을: 승인 PRD의 확정 요구사항을 세 Phase와 일곱 Goal로 추적하고 정확한 구현/테스트 경로 및 focused 명령을 계획했다.
  • 어떻게: 필수 작업/문서/실행/테스트 가이드, 샘플, 현재 PRD, build 설정, splitter/order 및 직접 영향 테스트를 읽어 파일과 계약을 확인했다.
  • 실행 검증: 문서 전용 변경이므로 Gradle, 설치, AWS/API 호출을 실행하지 않는다. 향후 구현 명령은 계획 예시이며 아직 실행하지 않았다.
  • 다음 행동: 사용자가 계획을 검토하고 실행 방법을 선택한 뒤에만 첫 Goal을 시작한다.

Verification Record

부모 최종 실행 검증 — 2026-11-10

  • ./gradlew --no-daemon test --rerun --tests 'kr.co.vividnext.sodalive.i18n.translation.*' --tests 'kr.co.vividnext.sodalive.content.LanguageDetectionCacheServiceTest': exit 0, 실제 test 재실행. 16개 클래스 145건, 실패·오류·skip 0을 XML에서 확인했다.

  • ./gradlew --no-daemon test --rerun --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': exit 0, 실제 test 재실행. 각각 12·2·16건, 총 30건 모두 통과했다. 두 범위 합계는 중복 없는 175건이다.

  • ./gradlew --no-daemon ktlintCheck bootJar --rerun: exit 0. 변경 없는 lint는 UP-TO-DATE, bootJar는 재생성했다. 앞선 실제 lint 실행도 통과했다. 산출물은 build/libs/sodalive-0.0.1-SNAPSHOT.jar다.

  • 부모가 산출물의 classes/libs를 임시 경로에 추출하고 javac/java로 로컬 HTTP 드라이버를 실행했다. 실제 SDK의 Comprehend JSON, Translate rpc-v2-cbor 요청·응답과 서울 리전 서명, 100000바이트 prefix, 10000바이트 번역 분할·재구성·결합, blank 응답 후 중단이 모두 PASS(exit 0)였다. 합성 인증정보와 localhost만 사용했고 SDK clients와 HTTP 서버 종료를 확인했다.

  • git diff --check: exit 0. LSP 도구가 제공되지 않아 Kotlin 컴파일과 ktlint로 보완했다. 전체 테스트 ./gradlew test, 실제 IAM·AWS/Papago 품질·과금, 운영 MySQL claim 동시성은 미검증이다. Gradle deprecation 및 기존 중복 org.json 경고는 범위 밖으로 유지했다.

  • 전체 변경 최종 리뷰: Oracle이 P3 명세·품질 및 전체 변경을 승인했다. 구체적 차단 사항 없음. SDK lifecycle 테스트의 내부 reflection은 버전 변경 시 유지보수가 필요하다는 비차단 제약으로 기록했다.

  • 실행 중 판단: 재현된 HTTP5 충돌만 두 버전 정렬로 수정했다. 기존 HTTP4·SDK v1은 그대로이며 다른 HTTP5 사용 경로의 전체 회귀는 사용자 전체 테스트 범위다. 자동 worktree·커밋·push 없이 기존 test 브랜치에 변경을 남긴다.

  • 2026-11-10 정리 영수증: 실제 SDK QA 서버·clients가 종료된 뒤 작업 root의 열린 파일이 없음을 lsof로 확인했다. 소유한 임시 root와 모든 하위 작업 캐시·드라이버·추출 jar·임시 로그를 제거하고 경로 부재를 확인했다. 저장소의 구현 코드·문서·최종 build/libs/sodalive-0.0.1-SNAPSHOT.jar는 유지했다. 핵심 명령·결과·실패 이력은 이 계획에 보존했다.

  • P2-T1 부모 검증: 독립 reviewer 명세·품질 Approved. ./gradlew --no-daemon bootJar exit 0. 산출물 라이브러리로 Java 드라이버를 컴파일하고 실제 SDK를 localhost HTTP 서버에 연결해 감지 prefix·서울 서명·번역 바이트 분할·결합·blank 응답 후 중단을 검증했다(exit 0). 서버 종료·client close 영수증을 확인했다. 첫 QA는 Translate가 JSON이라는 드라이버 가정으로 실패했으며 실제 rpc-v2-cbor에 맞춰 드라이버만 수정 후 통과했다. 실 AWS 호출은 없다.

  • P2-T1 통합 결정: 재현된 TlsSocketStrategy 누락을 해결하기 위해 Spring Boot의 HTTP5 관리 버전만 SDK POM 기준 httpclient5=5.6.4, httpcore5=5.4.3으로 정렬했다. 기존 HTTP4·AWS SDK v1 선언은 유지한다.

계획 작성 시 문서 간 정합성 및 경로 대조만 수행한다. 구현 전 테스트 결과나 실행 count는 없다. 구현 시 각 Goal의 실제 명령·결과를 해당 Goal에 누적하고, 공통 회귀·문서 검사는 여기에 추가한다. 전체 테스트는 사용자가 ./gradlew test로 실행한다.

  • 2026-11-09 문서 검토: 첫 후보만 검사하는 감지 계약, SDK 준비와 RED/GREEN 순서, 기존 Papago-only fixture·미등록 공급자 거부 유지의 세 시나리오를 독립 검토해 모두 PASS를 받았다. 중요한 차단 사유 없음.
  • 실제 파일 경로와 PRD 수용 기준을 대조하고 공백 검사를 수행했다. 문서 전용 변경이므로 테스트·빌드·LSP 진단·실제 AWS 호출은 수행하지 않았으며, 이 PASS는 구현 성공을 뜻하지 않는다.

Self-review

  • Coverage: AWS-001 client credentials/region은 P2-T1; AWS-002~004 감지 입력/후보는 P2-T1 및 P3-T1; AWS-005 chunk/atomic fallback 및 AWS-007 version은 P2-T1/P3-T1; AWS-006 Papago 및 code-point 경계는 P1-T1; 순서/cache/schema/security 요구는 P2-T2/P3-T1에 추적했다.
  • Step scan: 모든 구현 Task에 RED, assertion 중심 RED 확인, GREEN, GREEN 확인, REFACTOR가 다섯 개씩 있다. P3 test-only와 Gate는 TDD 예외 사유 및 대체 검증을 둔다.
  • Type consistency: splitter는 String, Int, (Int) -> Int, List<String>/String을 사용한다. AWS client 생성 및 constructor 주입 타입은 AWS SDK v2의 ComprehendClient/TranslateClient다.
  • Review focus: 다섯 항목이 owner test와 task에 각각 매핑돼 있다. 빈 Papago 원문, blank 입력, Unicode 경계, full hash, 에러 민감정보를 포함한다.
  • Proportion: plan은 테스트 대상·contract·실행 명령·dependency 및 영향 fixture를 확정하는 범위다. 별도 subsystem 분할은 필요하지 않다.

실행 승인 기록

요구사항과 SDK v2 방향은 DEC-006으로 승인됐다. 실행 전 이 계획의 Task 경로와 범위를 검토한다. 구현 방식은 두 선택지 중 하나를 정한다.

  • Subagent-driven: 각 Task를 독립 실행하고 단계별 fresh review를 거친다. Goal 사이 의존성과 AWS 설정/실제 provider 등록 회귀 위험이 있어 권장한다.
  • Native: 한 구현자가 모든 Goal을 순서대로 실행하고 마지막에 전체 변경 review를 한다.

2026-11-09 사용자가 "진행"으로 계획과 Subagent-driven 실행을 승인했다. 기존 test 브랜치에서 구현하며 자동 worktree 생성, 커밋·push, 실 AWS 호출은 하지 않는다.

커밋 전 검증 — 2026-11-10

  • 사용자 /commit-with-validation 요청으로 공용 커밋 절차를 적용했다. 기존 구현은 변경하지 않고 의존 순서별 구현·테스트 및 문서 커밋을 준비했다.
  • ./gradlew --no-daemon test --tests 'kr.co.vividnext.sodalive.i18n.translation.*' --tests 'kr.co.vividnext.sodalive.content.LanguageDetectionCacheServiceTest': exit 0, 33초, 16개 클래스 148건 통과.
  • ./gradlew --no-daemon 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': exit 0, 41초, 30건 통과.
  • ./gradlew --no-daemon test --tests 'kr.co.vividnext.sodalive.support.SpringBootIntegrationSampleTest' --tests 'kr.co.vividnext.sodalive.deployment.DeploymentEndpointIntegrationTest' ktlintCheck bootJar: exit 0, 55초, 5건 통과. lint와 bootJar는 UP-TO-DATE다.
  • 세 실행의 :test가 실제 실행됐고 XML 기준 총 183건, 실패·오류·skip 모두 0이다. git diff --check도 exit 0이다. 전체 테스트·실 AWS/Papago·IAM·운영 MySQL은 미검증이며 전체 테스트는 사용자가 ./gradlew test로 실행한다.