From 5eee580aaf51369285a0c5242b4bc524cbad16e1 Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 11 Nov 2026 10:58:20 +0900 Subject: [PATCH] =?UTF-8?q?docs(subtitle):=20=EC=BB=AC=EB=9F=BC=20?= =?UTF-8?q?=EB=A7=A4=ED=95=91=20=EA=B2=80=EC=A6=9D=20=EA=B2=B0=EA=B3=BC?= =?UTF-8?q?=EC=99=80=20=EB=B0=B0=ED=8F=AC=20=EC=A1=B0=EA=B1=B4=EC=9D=84=20?= =?UTF-8?q?=EC=A0=95=EC=A0=95=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../deployment-gates.md | 5 ++ docs/20261110_자막Phase1연동/final-report.md | 8 ++ .../reviews/schema-validation-review.md | 15 ++++ .../schema-validation-report.md | 86 +++++++++++++++++++ 4 files changed, 114 insertions(+) create mode 100644 docs/20261110_자막Phase1연동/reviews/schema-validation-review.md create mode 100644 docs/20261110_자막Phase1연동/schema-validation-report.md diff --git a/docs/20261110_자막Phase1연동/deployment-gates.md b/docs/20261110_자막Phase1연동/deployment-gates.md index de4eddb5..a140a345 100644 --- a/docs/20261110_자막Phase1연동/deployment-gates.md +++ b/docs/20261110_자막Phase1연동/deployment-gates.md @@ -37,6 +37,11 @@ | `format` | `format VARCHAR(16)` | N | | `s3Bucket`, `s3Key` | `s3_bucket`, `s3_key VARCHAR(255)` | N | +2026-11-11 정정: 이전 정적 대조는 `s3Bucket`의 숫자 경계 명명 규칙을 놓쳤다. 기본 naming strategy가 +`s3bucket`/`s3key`로 추론하므로 두 필드에 명시적 `@Column(name = "s3_bucket"/"s3_key")`가 필요하다. +`P3-R1`에서 실제 `validate` 실패를 재현한 뒤 두 이름을 명시해 H2 validation/저장/재조회를 통과했다. +독립 리뷰도 승인했다. 운영 DB에 `s3bucket` 컬럼을 추가한 것이 아니며 실 MySQL 기동은 별도 확인 대상이다. + | 기존 엔티티 속성 | DDL 컬럼·타입 | NULL | |---|---|---| | `SubtitleCallbackReceipt.eventId` | `event_id VARCHAR(255)` PK; UUID 제한 없음 | N | diff --git a/docs/20261110_자막Phase1연동/final-report.md b/docs/20261110_자막Phase1연동/final-report.md index e6b8faa2..40c1ac2c 100644 --- a/docs/20261110_자막Phase1연동/final-report.md +++ b/docs/20261110_자막Phase1연동/final-report.md @@ -111,6 +111,14 @@ additive SQL만 작성했다. 실제 DB 적용은 하지 않았다. 미래 언 이 결과를 기존15개를 포함한 클래스 전체 재실행 성공으로 표현하지 않는다. 상세 명령/선택자/응답 예시/잔여 게이트는 [generate-url-report.md](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](schema-validation-report.md)에 기록했다. + ## 커밋 검증 후속 기록 2026-11-11 별도 사용자 `/commit-with-validation` 요청으로 변경을 커밋했다. diff --git a/docs/20261110_자막Phase1연동/reviews/schema-validation-review.md b/docs/20261110_자막Phase1연동/reviews/schema-validation-review.md new file mode 100644 index 00000000..599d37b4 --- /dev/null +++ b/docs/20261110_자막Phase1연동/reviews/schema-validation-review.md @@ -0,0 +1,15 @@ +# Subtitle 물리 컬럼명 회귀 리뷰 + +- 일자: 2026-11-11 +- 범위: `P3-R1`, Subtitle의 두 Column annotation과 신규 ORM schema-validation 테스트. +- 기준: PRD §8, plan-task Phase3, 실제 RED/GREEN 로그와 production diff. +- 리뷰어: `ses_e37b4db2bffeUyLw5vO2EtHHuG`, read-only. +- 판정: **APPROVE**, 범위 내 확정 finding/blocker 없음. + +검토 결과: `s3Bucket`/`s3Key`의 기본 추론 이름을 기존 DDL의 `s3_bucket`/`s3_key`로 명시했다. +Kotlin/JSON 이름·타입/길이/nullable, DDL과 전역 전략은 그대로다. 신규 테스트는 독립 테이블에서 실제 +Hibernate validate, commit 후 새 Session 및 직접 JDBC 컬럼 조회를 수행한다. RED missing[s3bucket]과 +GREEN1/0/0/0, bootJar 및 변경 lint 성공을 확인했다. 기존 create-drop 테스트의 한계를 반복하지 않는다. + +미검증: 실제 MySQL 기동/collation/FK, 전체 Spring 연동, 기존 테스트/전체 테스트, LSP. +추가 수정 Task 없음. 운영 DB 변경이나 배포 승인을 의미하지 않는다. diff --git a/docs/20261110_자막Phase1연동/schema-validation-report.md b/docs/20261110_자막Phase1연동/schema-validation-report.md new file mode 100644 index 00000000..98125544 --- /dev/null +++ b/docs/20261110_자막Phase1연동/schema-validation-report.md @@ -0,0 +1,86 @@ +# Subtitle 컬럼명 schema-validation 회귀 + +## 원인과 범위 + +사용자가 보고한 `Schema-validation: missing column [s3bucket] in table [subtitle]`를 실제 Hibernate로 재현했다. +`Subtitle.s3Bucket`/`s3Key`에 물리 컬럼 이름이 없었고, 기본 naming strategy는 숫자 `3` 다음 대문자 앞에 +underscore를 넣지 않아 `s3bucket`/`s3key`로 변환한다. 기존 DDL은 `s3_bucket`/`s3_key`이므로 불일치했다. + +production 수정은 `Subtitle.kt`의 두 annotation뿐이다: + +```kotlin +@Column(name = "s3_bucket", nullable = false, length = 255) +val s3Bucket: String, +@Column(name = "s3_key", nullable = false, length = 255) +val s3Key: String +``` + +Kotlin/JSON 필드명, 기존 DDL, 전역 naming strategy, 운영 DB는 변경하지 않는다. +잘못 추론된 이름의 DB 컬럼을 새로 만들거나 `ddl-auto`를 update/none으로 바꿔 오류를 숨기지 않는다. + +## 기존 검증의 공백 + +기존 HTTP 테스트는 `ddl-auto:create-drop`으로 엔티티 기준 테이블을 자동 생성했으므로 잘못된 컬럼명으로 +테이블과 ORM이 동시에 일치했다. 앞선 정적 DDL/JPA 대조도 숫자-대문자 경계의 이름 변환을 놓쳤다. +해당 완료 이력은 보존하되 이번 재현으로 그 검증 한계를 명확히 정정한다. + +## 신규 회귀 테스트 + +`SubtitleSchemaValidationTest.shouldValidateAndUseExistingS3Columns` 한 개만 실행한다. + +1. 엔티티와 독립적으로 H2에 `s3_bucket`/`s3_key`를 포함한 테이블을 직접 생성한다. +2. `HibernateProperties`/`HibernateSettings`로 Boot 기본 설정을 얻고 실제 Hibernate의 `hbm2ddl.auto=validate`를 실행한다. +3. `Subtitle`을 저장하고 새 Session 및 직접 JDBC SELECT로 두 컬럼의 값을 확인한다. + +전체 Spring context/Redis/AWS 및 mock 없이 ORM 저장 표면을 검증한다. DB는 테스트 전용 메모리 H2이며 +실제 MySQL collation/FK/운영 기동 전부를 검증하는 테스트는 아니다. + +## 실행 기록 + +- RED: 새 테스트1개/실패1개/오류0/skip0, XML UTC `2026-11-11T01:42:01`. +- 실제 실패: `SchemaManagementException: Schema-validation: missing column [s3bucket] in table [subtitle]`. +- RED 빌드 결과: `BUILD FAILED in 3m 49s`. 컴파일 오류가 아닌 보고된 운영 오류와 동일한 validation 실패다. +- GREEN: 새 테스트1개/실패0/오류0/skip0, XML UTC `2026-11-11T01:46:49`. +- 실제 Hibernate validation 성공 후 저장/new Session 조회/직접 JDBC 두 컬럼 조회가 통과했다. +- `bootJar` 및 변경 엔티티/신규 테스트에 한정한 `ktlintCheck` 성공, `BUILD SUCCESSFUL in 3m 29s`. +- LSP 도구 미제공으로 Kotlin 컴파일·변경 파일 lint를 사용했다. +- 독립 한정 리뷰 `ses_e37b4db2bffeUyLw5vO2EtHHuG`: **APPROVE**. 두 annotation만 변경됐고 독립 DDL/실제 ORM roundtrip 테스트가 원인에 맞는 것을 확인했다. 신규 blocker 없음. + +일반 환경의 신규 테스트 실행 명령: + +```bash +./gradlew test --tests 'kr.co.vividnext.sodalive.v2.subtitle.SubtitleSchemaValidationTest' +``` + +격리 실행은 작업 소유 root `subtitle-schema-fix.XcftjN`의 init script로 build/project-cache와 테스트 compile/lint +입력을 이 변경에 한정한다. production source는 필터링하지 않는다. 기존 테스트와 전체 테스트는 실행하지 않는다. + +실제 격리 실행 명령(RED는 `test`까지만, GREEN은 아래 전체): + +```bash +export VERIFY_ROOT=/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/subtitle-schema-fix.XcftjN +export TMPDIR="$VERIFY_ROOT" NODE_COMPILE_CACHE="$VERIFY_ROOT/node-compile-cache" +export BUN_RUNTIME_TRANSPILER_CACHE_PATH="$VERIFY_ROOT/bun-transpiler" +./gradlew --offline --no-daemon --max-workers=2 \ + -Pkotlin.compiler.execution.strategy=in-process -Dorg.gradle.jvmargs=-Xmx4g \ + --project-cache-dir "$VERIFY_ROOT/project-cache" --init-script "$VERIFY_ROOT/verification.init.gradle" \ + test --tests 'kr.co.vividnext.sodalive.v2.subtitle.SubtitleSchemaValidationTest' bootJar ktlintCheck +``` + +로그는 위 root의 `red.log`, `green.log`다. 실제 운영 DB 접속·DDL 변경·서버 배포/재시작·기존 테스트 실행은 하지 않았다. + +## 정리 + +검증·리뷰 후 작업 root를 참조하는 프로세스의 부재를 확인하고 소유 root/build/cache/log/JAR를 제거했다. +`test ! -e` exit0으로 확인했으며 위 임시 경로는 과거 검증 위치다. 공유 캐시/다른 작업은 변경하지 않았다. +새 테스트/원인/명령/결과와 리뷰 기록은 저장소에 남겼고 이번 수정은 커밋하지 않았다. + +## 후속 커밋 검증 + +2026-11-11 별도 `/commit-with-validation` 요청으로 요구사항·계획은 `cf31c570`, 수정·회귀 테스트는 +`c3e5f19c`에 커밋했다. 두 커밋 모두 저장소 메시지 검증 스크립트의 직전·직후 검증을 통과했다. +수정 커밋의 pre-commit hook에서 변경 Kotlin 2개 파일의 `ktlintCheck`도 통과했다. + +`./gradlew --no-daemon test --tests 'kr.co.vividnext.sodalive.v2.subtitle.SubtitleSchemaValidationTest'`를 +재실행해 `BUILD SUCCESSFUL in 46s`, 테스트 1개/실패 0/오류 0/건너뜀 0을 확인했다. +전체 테스트·실제 MySQL 기동·bootJar는 이번 커밋 작업에서 실행하지 않았다. 전체 테스트 사용자 명령은 `./gradlew test`다.