Files
sodalive-backend-spring-boot/docs/20261110_자막Phase1연동/final-report.md
T

11 KiB

자막 Phase 1 Spring 구현 검증 기록

상태: 재생성 제외 로컬 backend 구현·신규 테스트 15개·독립 리뷰 완료. 운영 배포 완료가 아니다.

범위와 결정

  • 이번 범위: 기존 업로드의 PENDING/source 선등록, 네 BOT 내부 endpoint, raw-body receipt, 기존 상세의 언어·권한별 자막 제공.
  • 재생성은 사용자 결정으로 제외했다. 새 공개 재생성 API, 직접 START 호출, Scheduler/outbox를 만들지 않는다.
  • 후속 요구로 언어별 결과는 Subtitle 행에 저장한다. 이번 callback 허용 언어/완료 판정은 ko/ja만 유지하며 다른 언어를 활성화하지 않는다.
  • 테스트 실행은 사용자 후속 지시 이후 이번 작업에서 새로 작성한 테스트만 대상으로 한다. 기존 클래스에 추가한 사례도 메서드 selector로 한정한다.

실제 조사와 보존

  • 현재 Spring에 subtitle/recovery/자막 HMAC/재생성 action이 발견되지 않았다. 제거할 해당 코드가 없으므로 실제 삭제 없음.
  • 공유 member login/JWT/MemberRole.BOT, Agora HMAC, 텍스트 TranslationJob/worker, S3Uploader와 기존 MediaConvert 관련 외부 코드/설정은 보존 대상이다.
  • 기존 MediaConvert 완료 API의 ADMIN/BOT 권한은 유지한다. 새 원본 UUID에 대한 source-bound 경로 검증과 기존 파일 검증을 구분한다. 실제 consumer ID 추출·출력 규칙 호환성은 운영 확인 전 배포 BLOCKED다.
  • Flyway/Liquibase 의존성 및 자막 migration은 현재 프로젝트에서 발견되지 않았다. 운영/공유 환경의 적용 schema/data 상태는 조회하지 않았다. additive SQL 문서만 준비하며 DB에는 적용하지 않는다.

Lambda와 운영 전환

명시된 aws-subtitle-service/docs/FINAL_REPORT.md와 실제 로컬 ZIP을 대조했다.

산출물 SHA-256
start-subtitle-transcription.zip a0d0b19b2ecb24c0f89caf528bf09fd039558563a99063f082ac64418704abfd
generate-subtitle.zip bfa8742ca436bace853ec2540fc5f70daa24cbab49e75315565d980189fbf209

파일 hash 일치는 AWS 배포 증거가 아니다. 운영 legacy consumer/caller/in-flight/Scheduler/배포 버전은 미확인이다. AWS 호출·설정·배포·중지·삭제·재개를 수행하지 않았다. 운영자는 inventory 후 실제 가동 중인 경우에만 pause/drain하고 Phase 1 backend/Lambda를 조정 배포해야 한다. S3/DB purge나 legacy fake work API는 금지한다.

실행 기록

시점 명령/검증 관찰 결과
테스트 범위 변경 지시 전 기존 AudioContentServiceTest, MemberLoginResponseContractTest, TokenProviderTest focused baseline 부모 실행은 kapt 단계 200초 제한 종료, 테스트 판정 없음
테스트 범위 변경 지시 전 구현 담당자의 격리 focused 시도 red-focused.log: 41 cases 중 새 사례의 JWT key setup 1건 실패. 기능 RED로 인정하지 않음. 기존 테스트 실행은 이후 중단
RED 새 SubtitleHttpIntegrationTest.shouldRejectActualNonBotLoginJwt red-http.log: 실제 로그인한 non-BOT의 자막 접근 차단 실패를 재현. 최종 GREEN 아님
1차 새 테스트/HTTP 부모가 신규 SubtitleHttpIntegrationTest 직접 재실행 13 passed, failures/errors/skipped 0. XML timestamp 2026-11-10T14:33:59, BUILD SUCCESSFUL in 2m 21s
bootJar/변경 범위 lint 구현 담당 실제 실행 및 부모 같은 코드 재확인 build-lint.log: BUILD SUCCESSFUL in 1m 28s; 부모 재확인은 UP-TO-DATE
리뷰 수정 RED 신규 HTTP 2개 빈 body 422 기대/200 실제, DB 실패 500 기대/200 실제 확인
최종 새 테스트/빌드/lint P1-R1 수정 후 신규 자막 클래스 전체 15 passed, failures/errors/skipped 0, bootJar/변경 lint 실제 실행 성공, 7m44s
부모 최종 HTTP 재검증 추가된 새 2개 메서드만 직접 실행 2 passed, 422/500 및 전체 rollback 확인. XML timestamp 2026-11-10T15:34:43, 4m55s
독립 리뷰 1차 REQUEST_CHANGES 1건 수정 후 delta 재검토 REV-P1-001 ADDRESSED / 로컬 backend APPROVE, 추가 blocker 없음

테스트 fixture는 로컬 합성 데이터와 H2, 실제 localhost HTTP, 외부 S3/서명 경계 대체를 사용한다. 실제 음성 또는 live S3 품질 검증이 아니다. LSP diagnostics 도구가 제공되지 않아 실제 Kotlin compile/lint 결과를 대체 증거로 기록했으며 LSP 성공을 주장하지 않는다.

실제 검증한 HTTP 결과: BOT 허용 및 non-BOT 403/익명 401, unknown 404, callback 수락/replay 200, 같은 eventId의 변경 body 422, terminal 다른 결과 409, 동시 receipt 경합 200/422 및 loser rollback. 기존 콘텐츠 상세에서 preview-only의 자막 null, 구매/소유자/무료 및 공개 시점별 ko/ja 선택을 검증했다. 동일 job 결과는 언어별 행 ID를 보존하며, 이전 job 완료가 최신 선택값을 바꾸지 않는 것을 확인했다.

실제 HTTP 증거는 테스트 JVM PID 23390, Tomcat 127.0.0.1:59074, XML timestamp 2026-11-10T14:33:59다. 실행 후 ps -p 23390,20950,22207, lsof -iTCP:59074 -sTCP:LISTEN, 작업 root를 대상으로 한 pgrep에서 남은 프로세스/listener가 없음을 확인했다. 임시 산출물은 리뷰 후 정리했다.

후속 HTTP 검증은 PID 44707, Tomcat port 59977에서 실행했다. 빈 callback/started는 422이고, receipt 저장 실패는 {"error":"Subtitle request failed"}와 500을 반환하며 상태/오류/결과/receipt가 rollback된다. 구현 담당의 첫 GREEN은 컴파일 600초 제한으로 종료됐으나 캐시 재사용 후 성공했다. 실패 이력은 삭제하지 않았다. 후속 PID 44707/28638/37727/39291, port 59977, 작업 root 참조 프로세스의 부재도 확인했다.

내부 API와 저장 구조

Method Path
GET /internal/api/subtitle-jobs/resolve
GET /internal/api/subtitle-jobs/{jobId}
POST /internal/api/subtitle-jobs/{jobId}/started
POST /internal/api/subtitle-jobs/callback

네 API는 기존 로그인 JWT와 활성 DB BOT role을 요구한다. 조회는 최소 DTO이며 callback은 원문 bytes hash를 사용한다. subtitle_job, 언어별 subtitle, subtitle_callback_receipt와 content.latest_subtitle_job_id의 additive SQL만 작성했다. 실제 DB 적용은 하지 않았다. 미래 언어는 결과 행으로 저장할 수 있지만 현재 허용 언어는 ko/ja뿐이고 향후 Lambda·API·조회 정책 변경 없이 새 언어가 자동 활성화되지는 않는다.

공유 로그인/JWT/업로드/MediaConvert는 보존했다. 현재 저장소에는 삭제할 자막 recovery code가 없어서 삭제 파일은 0개다. 외부 프로젝트, AWS 리소스, 적용 migration 및 운영 데이터는 변경하지 않았다. commit/push도 수행하지 않았다.

전체 변경 경로·신규 case 이름·재현 명령·실패/수정 이력은 implementation-report.md에 있다. 로컬 mapping의 업로드 오류 시각 TIMESTAMP 불일치는 수정·대조했으나 운영 MySQL 실행 증거는 아니다. 테스트 전체 Spring context에서 기존 worker의 H2/MySQL 구문 차이 로그가 발생했다. 해당 worker 성공을 주장하지 않는다.

외부 차단 항목

항목 상태와 재개 조건
운영 DB BLOCKED: 기존 schema 확인 및 additive DDL 승인·적용 필요
old consumer coordination BLOCKED: 원격 배포/사용/in-flight 조사와 운영자 조정 필요
MediaConvert/CloudFront BLOCKED: UUID ID 추출·출력 key 규칙 및 자막 private origin/서명 접근 확인 필요
live ko/ja/mixed BLOCKED: 승인된 실제 S3 입력부터 callback/콘텐츠/앱 표시 검증 필요. mixed는 실제 word/item 언어 evidence 필수
2시간 입력 목표 BLOCKED: 실제 처리 시간/예산 미측정. 상한을 내리거나 SLA/지원 성공을 주장하지 않음

전체 테스트는 실행하지 않는다. 사용자가 별도로 전체 검증을 원하면 ./gradlew test를 실행할 수 있다. 기존 회귀 미실행에 따른 잔여 위험은 새 기능 테스트 성공과 구분한다.

종료 정리와 재현

2026-11-11 리뷰 승인 후 이번 작업 소유 임시 root subtitle-phase1.wiRcAJ와 그 안의 build/cache/log/JAR를 제거했다. test ! -e exit0으로 부재를 확인했고 공유 캐시·기존 worker는 변경하지 않았다. 위 임시 경로는 과거 검증 위치이며, 검증 결과와 실패 이력은 이 문서 및 구현/리뷰 보고서에 보존했다.

정상 개발 환경에서 이번에 작성한 테스트만 다시 실행하는 명령:

./gradlew test --tests 'kr.co.vividnext.sodalive.v2.subtitle.SubtitleHttpIntegrationTest'

후속 완료: generate-url 자막 응답

2026-11-11 요청에 따라 GET /audio-content/{id}/generate-url에도 상세와 동일한 nullable subtitle을 추가했다. 기존 contentUrl과 활성 구매 검사/음원 URL 동작은 유지하며 자막만 요청 언어·완료 상태·공개 시점 조건을 따른다. 이번 후속에서 새로 작성한 HTTP3개만 실행했고 부모 재실행도 3/0/0/0으로 통과했다. 독립 리뷰 APPROVE다. 컴파일/bootJar/테스트 lint는 성공했으나 production lint는 시작 전부터 있던 서비스 EOF 공백1건을 지적해 보존했다. 이 결과를 기존15개를 포함한 클래스 전체 재실행 성공으로 표현하지 않는다. 상세 명령/선택자/응답 예시/잔여 게이트는 generate-url-report.md에 있다.

후속 회귀: s3 컬럼 schema-validation

2026-11-11 실제 기동의 missing [s3bucket] 오류를 독립 H2 table과 Hibernate validate로 재현했다. 기본 명명전략이 숫자 경계를 분리하지 않는 것을 기존 정적 대조와 create-drop 테스트가 놓친 문제다. Subtitle의 두 Column 이름만 s3_bucket, s3_key로 명시했고 DDL/운영 DB/JSON 계약은 바꾸지 않았다. 신규 회귀 테스트1개에서 validation과 Hibernate/JDBC 저장·재조회가 통과했으며 bootJar/변경 lint 및 독립 리뷰 APPROVE다. 실제 운영 MySQL 재기동은 수행하지 않았다. 상세 원인·검증은 schema-validation-report.md에 기록했다.

커밋 검증 후속 기록

2026-11-11 별도 사용자 /commit-with-validation 요청으로 변경을 커밋했다. 기능 연동 커밋은 pre-commit ktlintCheck에서 위에 기록된 서비스 EOF 빈 줄 1건으로 처음 거부됐다. 해당 빈 줄만 제거한 뒤 재시도하여 staged Kotlin 대상 lint, git diff --staged --check 및 work/scripts/check-commit-message-rules.sh의 커밋 직전·직후 검증이 통과했다. 기존 EOF 미해결 기록은 당시 상태이며 이 커밋 준비 과정에서 해소됐다.

이번 커밋 작업에서는 기능 테스트·전체 테스트·bootJar를 재실행하지 않았다. 기존 테스트 결과는 위 실행 시점의 기록이며 운영 배포 BLOCKED는 유지한다. 사용자가 전체 테스트를 실행할 때의 명령은 ./gradlew test다.