feat: 설계 문서 추가

This commit is contained in:
DongHyeonka
2026-08-13 21:09:52 +09:00
parent 0a6dd0e419
commit 3b5aee50e3
33 changed files with 45861 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
72113b04239cd397787fec5cdc9ac5aa309e85c53767042fc7019338afb884d0 ./README.md
2ae49f02d38b32dbd660ae3957f97912f469c0ff404e0e665d4986fe21e33963 ./VALIDATION.md
1ac376309b161cf95b6b3def89284e509ce0191131a268833ca13bd50814ff09 ./docs/superpowers/plans/2026-08-11-jpa-persistence-experimental-expansion-plan.md
bb5d0d876e9a3232a3661cd50b3a4c0da0ea4c68f820526d8b34cb3bdcb43d86 ./docs/superpowers/plans/2026-08-11-jpa-persistence-platform-implementation-plan.md
a28046eb1451d87e3ac9c3b1922c134b99fa9155d32c09c0f5bddf80f588aaec ./docs/superpowers/specs/2026-08-11-jpa-persistence-platform-design.md
4e53da920430d5a82a744475a30b8036d2412627c2488d182daf6d3c1b013eb6 ./validate_jpa_docs.py
+68
View File
@@ -0,0 +1,68 @@
# JPA 관계형 영속성 플랫폼 Superpowers 패키지
이 패키지는 Java/Spring Backend Skeleton의 JPA 관계형 영속성 플랫폼을 구현하기 위한 설계서, Stable 구현 계획서, Experimental 확장 계획서와 정적 검증 도구를 포함한다.
## 기준
- 기준일: 2026-08-11
- Java 21
- Gradle Kotlin DSL
- Spring Boot 4.1 dependency management
- Spring Data JPA 4.1
- Jakarta Persistence 3.2
- Hibernate ORM 7.4
- PostgreSQL 16·17·18 Stable Matrix
- H2는 Local Convenience이며 PostgreSQL 호환성 증거로 사용하지 않는다.
## 문서
```text
docs/superpowers/specs/2026-08-11-jpa-persistence-platform-design.md
docs/superpowers/plans/2026-08-11-jpa-persistence-platform-implementation-plan.md
docs/superpowers/plans/2026-08-11-jpa-persistence-experimental-expansion-plan.md
```
## 구현 경계
Stable 계획은 다음 원칙을 고정한다.
```text
도메인이 Entity·Embeddable·Repository·Query를 소유한다.
GenericRepository 또는 Spring Data CRUD 재포장을 만들지 않는다.
Application Service가 Transaction 경계를 소유한다.
Retry는 새 Persistence Context에서 전체 Use Case를 재실행한다.
Commit 결과 불명은 자동 Retry하지 않고 reconciliation으로 처리한다.
Flyway가 운영 Schema 변경의 Source of Truth다.
PostgreSQL 16·17·18 실제 계약 테스트를 Release Gate로 사용한다.
```
Experimental 계획은 다음 범위를 Stable과 분리한다.
```text
Shared-schema Multi-tenancy
PostgreSQL RLS
Schema-per-tenant
Database-per-tenant
Consistency-aware Read Replica
Jakarta Persistence 4.0
Hibernate ORM 8
PostgreSQL 19
```
## 실행 순서
1. Stable 구현 계획 Task 1~53을 순서대로 수행한다.
2. 각 Task에서 실패 테스트 → 실패 확인 → 최소 구현 → 통과 확인 → 커밋 순서를 따른다.
3. Stable Release Gate가 통과한 뒤 Experimental Task 1~9를 시작한다.
4. Experimental 기능은 별도 feature flag와 승격 ADR 없이 Stable Starter에 포함하지 않는다.
## 검증
압축을 해제한 패키지 루트에서 실행한다.
```bash
python validate_jpa_docs.py
sha256sum -c MANIFEST.sha256
```
현재 `VALIDATION.md`는 문서 구조·Task 완결성·타입 일관성·경로 충돌·핵심 계약을 대상으로 한 정적 검증 결과다. 실제 Backend Skeleton 저장소의 Gradle compile, PostgreSQL integration, Flyway upgrade, failure injection, 성능 및 보안 테스트 결과는 아니다.
+829
View File
@@ -0,0 +1,829 @@
# JPA Superpowers 문서 정적 검증
- 결과: **PASS**
- 실행 검사: **808개**
- 통과: **808개**
- 실패: **0개**
- 설계서: **3,276행**
- Stable 구현 계획서: **4,716행**
- Experimental 확장 계획서: **771행**
- Stable Task: **53개**
- Experimental Task: **9개**
- Stable Create 경로: **235개**
- Experimental Create 경로: **37개**
- 설계 SHA-256: `a28046eb1451d87e3ac9c3b1922c134b99fa9155d32c09c0f5bddf80f588aaec`
- Stable 계획 SHA-256: `bb5d0d876e9a3232a3661cd50b3a4c0da0ea4c68f820526d8b34cb3bdcb43d86`
- Experimental 계획 SHA-256: `1ac376309b161cf95b6b3def89284e509ce0191131a268833ca13bd50814ff09`
## 검사 결과
| 검사 | 결과 | 상세 |
|---|---:|---|
| 설계서 존재 | PASS | /mnt/data/jpa-superpowers-package/docs/superpowers/specs/2026-08-11-jpa-persistence-platform-design.md |
| Stable 계획서 존재 | PASS | /mnt/data/jpa-superpowers-package/docs/superpowers/plans/2026-08-11-jpa-persistence-platform-implementation-plan.md |
| Experimental 계획서 존재 | PASS | /mnt/data/jpa-superpowers-package/docs/superpowers/plans/2026-08-11-jpa-persistence-experimental-expansion-plan.md |
| 설계서 최소 상세도 | PASS | 3276 lines |
| Stable 계획서 최소 상세도 | PASS | 4716 lines |
| Experimental 계획서 최소 상세도 | PASS | 771 lines |
| 설계서 코드 fence 균형 | PASS | 262 |
| 설계서 placeholder 부재 | PASS | |
| Stable 계획서 코드 fence 균형 | PASS | 540 |
| Stable 계획서 placeholder 부재 | PASS | |
| Experimental 계획서 코드 fence 균형 | PASS | 94 |
| Experimental 계획서 placeholder 부재 | PASS | |
| 설계 핵심 계약: GenericRepository | PASS | |
| 설계 핵심 계약: TransactionCompletionUnknownException | PASS | |
| 설계 핵심 계약: EvidenceAwareJpaTransactionManager | PASS | |
| 설계 핵심 계약: Application Service | PASS | |
| 설계 핵심 계약: OSIV | PASS | |
| 설계 핵심 계약: PostgreSQL 16·17·18 | PASS | |
| 설계 핵심 계약: Hibernate 7.4 Collection Fetch Pagination | PASS | |
| 설계 핵심 계약: FOR UPDATE SKIP LOCKED | PASS | |
| 설계 핵심 계약: CREATE INDEX CONCURRENTLY | PASS | |
| 설계 핵심 계약: Flyway | PASS | |
| 설계 핵심 계약: Runtime·Migration·Admin | PASS | |
| 설계 핵심 계약: H2는 Local Convenience | PASS | |
| 설계 핵심 계약: 전체 Transaction Retry | PASS | |
| 설계 핵심 계약: J1 Standard Persistence | PASS | |
| 설계 핵심 계약: J4 Admin / Operations | PASS | |
| 설계 핵심 계약: 완료 정의 | PASS | |
| GenericRepository 실제 선언 부재 | PASS | |
| 운영 ddl auto update 금지 | PASS | |
| Completion Unknown 자동 Retry 금지 | PASS | |
| OSIV false 강제 | PASS | |
| PG16·17·18 Matrix | PASS | |
| Hibernate 7.4 fetch pagination gate | PASS | |
| Flyway snapshot upgrade gate | PASS | |
| Runtime role no DDL gate | PASS | |
| Stable 계획에 Experimental create 경로 부재 | PASS | |
| Stable Task 1~53 연속성 | PASS | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53] |
| Experimental Task 1~9 연속성 | PASS | [1, 2, 3, 4, 5, 6, 7, 8, 9] |
| Stable Task chunk 수 | PASS | 53 |
| Experimental Task chunk 수 | PASS | 9 |
| Stable Task 1 필수 항목: **Files:** | PASS | |
| Stable Task 1 필수 항목: **Interfaces:** | PASS | |
| Stable Task 1 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 1 필수 항목: **Step 1: | PASS | |
| Stable Task 1 필수 항목: **Step 2: | PASS | |
| Stable Task 1 필수 항목: **Step 3: | PASS | |
| Stable Task 1 필수 항목: **Step 4: | PASS | |
| Stable Task 1 필수 항목: **Step 5: | PASS | |
| Stable Task 1 필수 항목: Expected: | PASS | |
| Stable Task 1 필수 항목: git commit -m | PASS | |
| Stable Task 1 Gradle focused command | PASS | |
| Stable Task 1 exact path | PASS | |
| Stable Task 2 필수 항목: **Files:** | PASS | |
| Stable Task 2 필수 항목: **Interfaces:** | PASS | |
| Stable Task 2 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 2 필수 항목: **Step 1: | PASS | |
| Stable Task 2 필수 항목: **Step 2: | PASS | |
| Stable Task 2 필수 항목: **Step 3: | PASS | |
| Stable Task 2 필수 항목: **Step 4: | PASS | |
| Stable Task 2 필수 항목: **Step 5: | PASS | |
| Stable Task 2 필수 항목: Expected: | PASS | |
| Stable Task 2 필수 항목: git commit -m | PASS | |
| Stable Task 2 Gradle focused command | PASS | |
| Stable Task 2 exact path | PASS | |
| Stable Task 3 필수 항목: **Files:** | PASS | |
| Stable Task 3 필수 항목: **Interfaces:** | PASS | |
| Stable Task 3 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 3 필수 항목: **Step 1: | PASS | |
| Stable Task 3 필수 항목: **Step 2: | PASS | |
| Stable Task 3 필수 항목: **Step 3: | PASS | |
| Stable Task 3 필수 항목: **Step 4: | PASS | |
| Stable Task 3 필수 항목: **Step 5: | PASS | |
| Stable Task 3 필수 항목: Expected: | PASS | |
| Stable Task 3 필수 항목: git commit -m | PASS | |
| Stable Task 3 Gradle focused command | PASS | |
| Stable Task 3 exact path | PASS | |
| Stable Task 4 필수 항목: **Files:** | PASS | |
| Stable Task 4 필수 항목: **Interfaces:** | PASS | |
| Stable Task 4 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 4 필수 항목: **Step 1: | PASS | |
| Stable Task 4 필수 항목: **Step 2: | PASS | |
| Stable Task 4 필수 항목: **Step 3: | PASS | |
| Stable Task 4 필수 항목: **Step 4: | PASS | |
| Stable Task 4 필수 항목: **Step 5: | PASS | |
| Stable Task 4 필수 항목: Expected: | PASS | |
| Stable Task 4 필수 항목: git commit -m | PASS | |
| Stable Task 4 Gradle focused command | PASS | |
| Stable Task 4 exact path | PASS | |
| Stable Task 5 필수 항목: **Files:** | PASS | |
| Stable Task 5 필수 항목: **Interfaces:** | PASS | |
| Stable Task 5 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 5 필수 항목: **Step 1: | PASS | |
| Stable Task 5 필수 항목: **Step 2: | PASS | |
| Stable Task 5 필수 항목: **Step 3: | PASS | |
| Stable Task 5 필수 항목: **Step 4: | PASS | |
| Stable Task 5 필수 항목: **Step 5: | PASS | |
| Stable Task 5 필수 항목: Expected: | PASS | |
| Stable Task 5 필수 항목: git commit -m | PASS | |
| Stable Task 5 Gradle focused command | PASS | |
| Stable Task 5 exact path | PASS | |
| Stable Task 6 필수 항목: **Files:** | PASS | |
| Stable Task 6 필수 항목: **Interfaces:** | PASS | |
| Stable Task 6 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 6 필수 항목: **Step 1: | PASS | |
| Stable Task 6 필수 항목: **Step 2: | PASS | |
| Stable Task 6 필수 항목: **Step 3: | PASS | |
| Stable Task 6 필수 항목: **Step 4: | PASS | |
| Stable Task 6 필수 항목: **Step 5: | PASS | |
| Stable Task 6 필수 항목: Expected: | PASS | |
| Stable Task 6 필수 항목: git commit -m | PASS | |
| Stable Task 6 Gradle focused command | PASS | |
| Stable Task 6 exact path | PASS | |
| Stable Task 7 필수 항목: **Files:** | PASS | |
| Stable Task 7 필수 항목: **Interfaces:** | PASS | |
| Stable Task 7 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 7 필수 항목: **Step 1: | PASS | |
| Stable Task 7 필수 항목: **Step 2: | PASS | |
| Stable Task 7 필수 항목: **Step 3: | PASS | |
| Stable Task 7 필수 항목: **Step 4: | PASS | |
| Stable Task 7 필수 항목: **Step 5: | PASS | |
| Stable Task 7 필수 항목: Expected: | PASS | |
| Stable Task 7 필수 항목: git commit -m | PASS | |
| Stable Task 7 Gradle focused command | PASS | |
| Stable Task 7 exact path | PASS | |
| Stable Task 8 필수 항목: **Files:** | PASS | |
| Stable Task 8 필수 항목: **Interfaces:** | PASS | |
| Stable Task 8 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 8 필수 항목: **Step 1: | PASS | |
| Stable Task 8 필수 항목: **Step 2: | PASS | |
| Stable Task 8 필수 항목: **Step 3: | PASS | |
| Stable Task 8 필수 항목: **Step 4: | PASS | |
| Stable Task 8 필수 항목: **Step 5: | PASS | |
| Stable Task 8 필수 항목: Expected: | PASS | |
| Stable Task 8 필수 항목: git commit -m | PASS | |
| Stable Task 8 Gradle focused command | PASS | |
| Stable Task 8 exact path | PASS | |
| Stable Task 9 필수 항목: **Files:** | PASS | |
| Stable Task 9 필수 항목: **Interfaces:** | PASS | |
| Stable Task 9 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 9 필수 항목: **Step 1: | PASS | |
| Stable Task 9 필수 항목: **Step 2: | PASS | |
| Stable Task 9 필수 항목: **Step 3: | PASS | |
| Stable Task 9 필수 항목: **Step 4: | PASS | |
| Stable Task 9 필수 항목: **Step 5: | PASS | |
| Stable Task 9 필수 항목: Expected: | PASS | |
| Stable Task 9 필수 항목: git commit -m | PASS | |
| Stable Task 9 Gradle focused command | PASS | |
| Stable Task 9 exact path | PASS | |
| Stable Task 10 필수 항목: **Files:** | PASS | |
| Stable Task 10 필수 항목: **Interfaces:** | PASS | |
| Stable Task 10 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 10 필수 항목: **Step 1: | PASS | |
| Stable Task 10 필수 항목: **Step 2: | PASS | |
| Stable Task 10 필수 항목: **Step 3: | PASS | |
| Stable Task 10 필수 항목: **Step 4: | PASS | |
| Stable Task 10 필수 항목: **Step 5: | PASS | |
| Stable Task 10 필수 항목: Expected: | PASS | |
| Stable Task 10 필수 항목: git commit -m | PASS | |
| Stable Task 10 Gradle focused command | PASS | |
| Stable Task 10 exact path | PASS | |
| Stable Task 11 필수 항목: **Files:** | PASS | |
| Stable Task 11 필수 항목: **Interfaces:** | PASS | |
| Stable Task 11 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 11 필수 항목: **Step 1: | PASS | |
| Stable Task 11 필수 항목: **Step 2: | PASS | |
| Stable Task 11 필수 항목: **Step 3: | PASS | |
| Stable Task 11 필수 항목: **Step 4: | PASS | |
| Stable Task 11 필수 항목: **Step 5: | PASS | |
| Stable Task 11 필수 항목: Expected: | PASS | |
| Stable Task 11 필수 항목: git commit -m | PASS | |
| Stable Task 11 Gradle focused command | PASS | |
| Stable Task 11 exact path | PASS | |
| Stable Task 12 필수 항목: **Files:** | PASS | |
| Stable Task 12 필수 항목: **Interfaces:** | PASS | |
| Stable Task 12 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 12 필수 항목: **Step 1: | PASS | |
| Stable Task 12 필수 항목: **Step 2: | PASS | |
| Stable Task 12 필수 항목: **Step 3: | PASS | |
| Stable Task 12 필수 항목: **Step 4: | PASS | |
| Stable Task 12 필수 항목: **Step 5: | PASS | |
| Stable Task 12 필수 항목: Expected: | PASS | |
| Stable Task 12 필수 항목: git commit -m | PASS | |
| Stable Task 12 Gradle focused command | PASS | |
| Stable Task 12 exact path | PASS | |
| Stable Task 13 필수 항목: **Files:** | PASS | |
| Stable Task 13 필수 항목: **Interfaces:** | PASS | |
| Stable Task 13 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 13 필수 항목: **Step 1: | PASS | |
| Stable Task 13 필수 항목: **Step 2: | PASS | |
| Stable Task 13 필수 항목: **Step 3: | PASS | |
| Stable Task 13 필수 항목: **Step 4: | PASS | |
| Stable Task 13 필수 항목: **Step 5: | PASS | |
| Stable Task 13 필수 항목: Expected: | PASS | |
| Stable Task 13 필수 항목: git commit -m | PASS | |
| Stable Task 13 Gradle focused command | PASS | |
| Stable Task 13 exact path | PASS | |
| Stable Task 14 필수 항목: **Files:** | PASS | |
| Stable Task 14 필수 항목: **Interfaces:** | PASS | |
| Stable Task 14 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 14 필수 항목: **Step 1: | PASS | |
| Stable Task 14 필수 항목: **Step 2: | PASS | |
| Stable Task 14 필수 항목: **Step 3: | PASS | |
| Stable Task 14 필수 항목: **Step 4: | PASS | |
| Stable Task 14 필수 항목: **Step 5: | PASS | |
| Stable Task 14 필수 항목: Expected: | PASS | |
| Stable Task 14 필수 항목: git commit -m | PASS | |
| Stable Task 14 Gradle focused command | PASS | |
| Stable Task 14 exact path | PASS | |
| Stable Task 15 필수 항목: **Files:** | PASS | |
| Stable Task 15 필수 항목: **Interfaces:** | PASS | |
| Stable Task 15 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 15 필수 항목: **Step 1: | PASS | |
| Stable Task 15 필수 항목: **Step 2: | PASS | |
| Stable Task 15 필수 항목: **Step 3: | PASS | |
| Stable Task 15 필수 항목: **Step 4: | PASS | |
| Stable Task 15 필수 항목: **Step 5: | PASS | |
| Stable Task 15 필수 항목: Expected: | PASS | |
| Stable Task 15 필수 항목: git commit -m | PASS | |
| Stable Task 15 Gradle focused command | PASS | |
| Stable Task 15 exact path | PASS | |
| Stable Task 16 필수 항목: **Files:** | PASS | |
| Stable Task 16 필수 항목: **Interfaces:** | PASS | |
| Stable Task 16 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 16 필수 항목: **Step 1: | PASS | |
| Stable Task 16 필수 항목: **Step 2: | PASS | |
| Stable Task 16 필수 항목: **Step 3: | PASS | |
| Stable Task 16 필수 항목: **Step 4: | PASS | |
| Stable Task 16 필수 항목: **Step 5: | PASS | |
| Stable Task 16 필수 항목: Expected: | PASS | |
| Stable Task 16 필수 항목: git commit -m | PASS | |
| Stable Task 16 Gradle focused command | PASS | |
| Stable Task 16 exact path | PASS | |
| Stable Task 17 필수 항목: **Files:** | PASS | |
| Stable Task 17 필수 항목: **Interfaces:** | PASS | |
| Stable Task 17 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 17 필수 항목: **Step 1: | PASS | |
| Stable Task 17 필수 항목: **Step 2: | PASS | |
| Stable Task 17 필수 항목: **Step 3: | PASS | |
| Stable Task 17 필수 항목: **Step 4: | PASS | |
| Stable Task 17 필수 항목: **Step 5: | PASS | |
| Stable Task 17 필수 항목: Expected: | PASS | |
| Stable Task 17 필수 항목: git commit -m | PASS | |
| Stable Task 17 Gradle focused command | PASS | |
| Stable Task 17 exact path | PASS | |
| Stable Task 18 필수 항목: **Files:** | PASS | |
| Stable Task 18 필수 항목: **Interfaces:** | PASS | |
| Stable Task 18 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 18 필수 항목: **Step 1: | PASS | |
| Stable Task 18 필수 항목: **Step 2: | PASS | |
| Stable Task 18 필수 항목: **Step 3: | PASS | |
| Stable Task 18 필수 항목: **Step 4: | PASS | |
| Stable Task 18 필수 항목: **Step 5: | PASS | |
| Stable Task 18 필수 항목: Expected: | PASS | |
| Stable Task 18 필수 항목: git commit -m | PASS | |
| Stable Task 18 Gradle focused command | PASS | |
| Stable Task 18 exact path | PASS | |
| Stable Task 19 필수 항목: **Files:** | PASS | |
| Stable Task 19 필수 항목: **Interfaces:** | PASS | |
| Stable Task 19 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 19 필수 항목: **Step 1: | PASS | |
| Stable Task 19 필수 항목: **Step 2: | PASS | |
| Stable Task 19 필수 항목: **Step 3: | PASS | |
| Stable Task 19 필수 항목: **Step 4: | PASS | |
| Stable Task 19 필수 항목: **Step 5: | PASS | |
| Stable Task 19 필수 항목: Expected: | PASS | |
| Stable Task 19 필수 항목: git commit -m | PASS | |
| Stable Task 19 Gradle focused command | PASS | |
| Stable Task 19 exact path | PASS | |
| Stable Task 20 필수 항목: **Files:** | PASS | |
| Stable Task 20 필수 항목: **Interfaces:** | PASS | |
| Stable Task 20 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 20 필수 항목: **Step 1: | PASS | |
| Stable Task 20 필수 항목: **Step 2: | PASS | |
| Stable Task 20 필수 항목: **Step 3: | PASS | |
| Stable Task 20 필수 항목: **Step 4: | PASS | |
| Stable Task 20 필수 항목: **Step 5: | PASS | |
| Stable Task 20 필수 항목: Expected: | PASS | |
| Stable Task 20 필수 항목: git commit -m | PASS | |
| Stable Task 20 Gradle focused command | PASS | |
| Stable Task 20 exact path | PASS | |
| Stable Task 21 필수 항목: **Files:** | PASS | |
| Stable Task 21 필수 항목: **Interfaces:** | PASS | |
| Stable Task 21 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 21 필수 항목: **Step 1: | PASS | |
| Stable Task 21 필수 항목: **Step 2: | PASS | |
| Stable Task 21 필수 항목: **Step 3: | PASS | |
| Stable Task 21 필수 항목: **Step 4: | PASS | |
| Stable Task 21 필수 항목: **Step 5: | PASS | |
| Stable Task 21 필수 항목: Expected: | PASS | |
| Stable Task 21 필수 항목: git commit -m | PASS | |
| Stable Task 21 Gradle focused command | PASS | |
| Stable Task 21 exact path | PASS | |
| Stable Task 22 필수 항목: **Files:** | PASS | |
| Stable Task 22 필수 항목: **Interfaces:** | PASS | |
| Stable Task 22 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 22 필수 항목: **Step 1: | PASS | |
| Stable Task 22 필수 항목: **Step 2: | PASS | |
| Stable Task 22 필수 항목: **Step 3: | PASS | |
| Stable Task 22 필수 항목: **Step 4: | PASS | |
| Stable Task 22 필수 항목: **Step 5: | PASS | |
| Stable Task 22 필수 항목: Expected: | PASS | |
| Stable Task 22 필수 항목: git commit -m | PASS | |
| Stable Task 22 Gradle focused command | PASS | |
| Stable Task 22 exact path | PASS | |
| Stable Task 23 필수 항목: **Files:** | PASS | |
| Stable Task 23 필수 항목: **Interfaces:** | PASS | |
| Stable Task 23 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 23 필수 항목: **Step 1: | PASS | |
| Stable Task 23 필수 항목: **Step 2: | PASS | |
| Stable Task 23 필수 항목: **Step 3: | PASS | |
| Stable Task 23 필수 항목: **Step 4: | PASS | |
| Stable Task 23 필수 항목: **Step 5: | PASS | |
| Stable Task 23 필수 항목: Expected: | PASS | |
| Stable Task 23 필수 항목: git commit -m | PASS | |
| Stable Task 23 Gradle focused command | PASS | |
| Stable Task 23 exact path | PASS | |
| Stable Task 24 필수 항목: **Files:** | PASS | |
| Stable Task 24 필수 항목: **Interfaces:** | PASS | |
| Stable Task 24 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 24 필수 항목: **Step 1: | PASS | |
| Stable Task 24 필수 항목: **Step 2: | PASS | |
| Stable Task 24 필수 항목: **Step 3: | PASS | |
| Stable Task 24 필수 항목: **Step 4: | PASS | |
| Stable Task 24 필수 항목: **Step 5: | PASS | |
| Stable Task 24 필수 항목: Expected: | PASS | |
| Stable Task 24 필수 항목: git commit -m | PASS | |
| Stable Task 24 Gradle focused command | PASS | |
| Stable Task 24 exact path | PASS | |
| Stable Task 25 필수 항목: **Files:** | PASS | |
| Stable Task 25 필수 항목: **Interfaces:** | PASS | |
| Stable Task 25 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 25 필수 항목: **Step 1: | PASS | |
| Stable Task 25 필수 항목: **Step 2: | PASS | |
| Stable Task 25 필수 항목: **Step 3: | PASS | |
| Stable Task 25 필수 항목: **Step 4: | PASS | |
| Stable Task 25 필수 항목: **Step 5: | PASS | |
| Stable Task 25 필수 항목: Expected: | PASS | |
| Stable Task 25 필수 항목: git commit -m | PASS | |
| Stable Task 25 Gradle focused command | PASS | |
| Stable Task 25 exact path | PASS | |
| Stable Task 26 필수 항목: **Files:** | PASS | |
| Stable Task 26 필수 항목: **Interfaces:** | PASS | |
| Stable Task 26 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 26 필수 항목: **Step 1: | PASS | |
| Stable Task 26 필수 항목: **Step 2: | PASS | |
| Stable Task 26 필수 항목: **Step 3: | PASS | |
| Stable Task 26 필수 항목: **Step 4: | PASS | |
| Stable Task 26 필수 항목: **Step 5: | PASS | |
| Stable Task 26 필수 항목: Expected: | PASS | |
| Stable Task 26 필수 항목: git commit -m | PASS | |
| Stable Task 26 Gradle focused command | PASS | |
| Stable Task 26 exact path | PASS | |
| Stable Task 27 필수 항목: **Files:** | PASS | |
| Stable Task 27 필수 항목: **Interfaces:** | PASS | |
| Stable Task 27 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 27 필수 항목: **Step 1: | PASS | |
| Stable Task 27 필수 항목: **Step 2: | PASS | |
| Stable Task 27 필수 항목: **Step 3: | PASS | |
| Stable Task 27 필수 항목: **Step 4: | PASS | |
| Stable Task 27 필수 항목: **Step 5: | PASS | |
| Stable Task 27 필수 항목: Expected: | PASS | |
| Stable Task 27 필수 항목: git commit -m | PASS | |
| Stable Task 27 Gradle focused command | PASS | |
| Stable Task 27 exact path | PASS | |
| Stable Task 28 필수 항목: **Files:** | PASS | |
| Stable Task 28 필수 항목: **Interfaces:** | PASS | |
| Stable Task 28 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 28 필수 항목: **Step 1: | PASS | |
| Stable Task 28 필수 항목: **Step 2: | PASS | |
| Stable Task 28 필수 항목: **Step 3: | PASS | |
| Stable Task 28 필수 항목: **Step 4: | PASS | |
| Stable Task 28 필수 항목: **Step 5: | PASS | |
| Stable Task 28 필수 항목: Expected: | PASS | |
| Stable Task 28 필수 항목: git commit -m | PASS | |
| Stable Task 28 Gradle focused command | PASS | |
| Stable Task 28 exact path | PASS | |
| Stable Task 29 필수 항목: **Files:** | PASS | |
| Stable Task 29 필수 항목: **Interfaces:** | PASS | |
| Stable Task 29 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 29 필수 항목: **Step 1: | PASS | |
| Stable Task 29 필수 항목: **Step 2: | PASS | |
| Stable Task 29 필수 항목: **Step 3: | PASS | |
| Stable Task 29 필수 항목: **Step 4: | PASS | |
| Stable Task 29 필수 항목: **Step 5: | PASS | |
| Stable Task 29 필수 항목: Expected: | PASS | |
| Stable Task 29 필수 항목: git commit -m | PASS | |
| Stable Task 29 Gradle focused command | PASS | |
| Stable Task 29 exact path | PASS | |
| Stable Task 30 필수 항목: **Files:** | PASS | |
| Stable Task 30 필수 항목: **Interfaces:** | PASS | |
| Stable Task 30 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 30 필수 항목: **Step 1: | PASS | |
| Stable Task 30 필수 항목: **Step 2: | PASS | |
| Stable Task 30 필수 항목: **Step 3: | PASS | |
| Stable Task 30 필수 항목: **Step 4: | PASS | |
| Stable Task 30 필수 항목: **Step 5: | PASS | |
| Stable Task 30 필수 항목: Expected: | PASS | |
| Stable Task 30 필수 항목: git commit -m | PASS | |
| Stable Task 30 Gradle focused command | PASS | |
| Stable Task 30 exact path | PASS | |
| Stable Task 31 필수 항목: **Files:** | PASS | |
| Stable Task 31 필수 항목: **Interfaces:** | PASS | |
| Stable Task 31 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 31 필수 항목: **Step 1: | PASS | |
| Stable Task 31 필수 항목: **Step 2: | PASS | |
| Stable Task 31 필수 항목: **Step 3: | PASS | |
| Stable Task 31 필수 항목: **Step 4: | PASS | |
| Stable Task 31 필수 항목: **Step 5: | PASS | |
| Stable Task 31 필수 항목: Expected: | PASS | |
| Stable Task 31 필수 항목: git commit -m | PASS | |
| Stable Task 31 Gradle focused command | PASS | |
| Stable Task 31 exact path | PASS | |
| Stable Task 32 필수 항목: **Files:** | PASS | |
| Stable Task 32 필수 항목: **Interfaces:** | PASS | |
| Stable Task 32 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 32 필수 항목: **Step 1: | PASS | |
| Stable Task 32 필수 항목: **Step 2: | PASS | |
| Stable Task 32 필수 항목: **Step 3: | PASS | |
| Stable Task 32 필수 항목: **Step 4: | PASS | |
| Stable Task 32 필수 항목: **Step 5: | PASS | |
| Stable Task 32 필수 항목: Expected: | PASS | |
| Stable Task 32 필수 항목: git commit -m | PASS | |
| Stable Task 32 Gradle focused command | PASS | |
| Stable Task 32 exact path | PASS | |
| Stable Task 33 필수 항목: **Files:** | PASS | |
| Stable Task 33 필수 항목: **Interfaces:** | PASS | |
| Stable Task 33 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 33 필수 항목: **Step 1: | PASS | |
| Stable Task 33 필수 항목: **Step 2: | PASS | |
| Stable Task 33 필수 항목: **Step 3: | PASS | |
| Stable Task 33 필수 항목: **Step 4: | PASS | |
| Stable Task 33 필수 항목: **Step 5: | PASS | |
| Stable Task 33 필수 항목: Expected: | PASS | |
| Stable Task 33 필수 항목: git commit -m | PASS | |
| Stable Task 33 Gradle focused command | PASS | |
| Stable Task 33 exact path | PASS | |
| Stable Task 34 필수 항목: **Files:** | PASS | |
| Stable Task 34 필수 항목: **Interfaces:** | PASS | |
| Stable Task 34 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 34 필수 항목: **Step 1: | PASS | |
| Stable Task 34 필수 항목: **Step 2: | PASS | |
| Stable Task 34 필수 항목: **Step 3: | PASS | |
| Stable Task 34 필수 항목: **Step 4: | PASS | |
| Stable Task 34 필수 항목: **Step 5: | PASS | |
| Stable Task 34 필수 항목: Expected: | PASS | |
| Stable Task 34 필수 항목: git commit -m | PASS | |
| Stable Task 34 Gradle focused command | PASS | |
| Stable Task 34 exact path | PASS | |
| Stable Task 35 필수 항목: **Files:** | PASS | |
| Stable Task 35 필수 항목: **Interfaces:** | PASS | |
| Stable Task 35 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 35 필수 항목: **Step 1: | PASS | |
| Stable Task 35 필수 항목: **Step 2: | PASS | |
| Stable Task 35 필수 항목: **Step 3: | PASS | |
| Stable Task 35 필수 항목: **Step 4: | PASS | |
| Stable Task 35 필수 항목: **Step 5: | PASS | |
| Stable Task 35 필수 항목: Expected: | PASS | |
| Stable Task 35 필수 항목: git commit -m | PASS | |
| Stable Task 35 Gradle focused command | PASS | |
| Stable Task 35 exact path | PASS | |
| Stable Task 36 필수 항목: **Files:** | PASS | |
| Stable Task 36 필수 항목: **Interfaces:** | PASS | |
| Stable Task 36 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 36 필수 항목: **Step 1: | PASS | |
| Stable Task 36 필수 항목: **Step 2: | PASS | |
| Stable Task 36 필수 항목: **Step 3: | PASS | |
| Stable Task 36 필수 항목: **Step 4: | PASS | |
| Stable Task 36 필수 항목: **Step 5: | PASS | |
| Stable Task 36 필수 항목: Expected: | PASS | |
| Stable Task 36 필수 항목: git commit -m | PASS | |
| Stable Task 36 Gradle focused command | PASS | |
| Stable Task 36 exact path | PASS | |
| Stable Task 37 필수 항목: **Files:** | PASS | |
| Stable Task 37 필수 항목: **Interfaces:** | PASS | |
| Stable Task 37 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 37 필수 항목: **Step 1: | PASS | |
| Stable Task 37 필수 항목: **Step 2: | PASS | |
| Stable Task 37 필수 항목: **Step 3: | PASS | |
| Stable Task 37 필수 항목: **Step 4: | PASS | |
| Stable Task 37 필수 항목: **Step 5: | PASS | |
| Stable Task 37 필수 항목: Expected: | PASS | |
| Stable Task 37 필수 항목: git commit -m | PASS | |
| Stable Task 37 Gradle focused command | PASS | |
| Stable Task 37 exact path | PASS | |
| Stable Task 38 필수 항목: **Files:** | PASS | |
| Stable Task 38 필수 항목: **Interfaces:** | PASS | |
| Stable Task 38 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 38 필수 항목: **Step 1: | PASS | |
| Stable Task 38 필수 항목: **Step 2: | PASS | |
| Stable Task 38 필수 항목: **Step 3: | PASS | |
| Stable Task 38 필수 항목: **Step 4: | PASS | |
| Stable Task 38 필수 항목: **Step 5: | PASS | |
| Stable Task 38 필수 항목: Expected: | PASS | |
| Stable Task 38 필수 항목: git commit -m | PASS | |
| Stable Task 38 Gradle focused command | PASS | |
| Stable Task 38 exact path | PASS | |
| Stable Task 39 필수 항목: **Files:** | PASS | |
| Stable Task 39 필수 항목: **Interfaces:** | PASS | |
| Stable Task 39 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 39 필수 항목: **Step 1: | PASS | |
| Stable Task 39 필수 항목: **Step 2: | PASS | |
| Stable Task 39 필수 항목: **Step 3: | PASS | |
| Stable Task 39 필수 항목: **Step 4: | PASS | |
| Stable Task 39 필수 항목: **Step 5: | PASS | |
| Stable Task 39 필수 항목: Expected: | PASS | |
| Stable Task 39 필수 항목: git commit -m | PASS | |
| Stable Task 39 Gradle focused command | PASS | |
| Stable Task 39 exact path | PASS | |
| Stable Task 40 필수 항목: **Files:** | PASS | |
| Stable Task 40 필수 항목: **Interfaces:** | PASS | |
| Stable Task 40 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 40 필수 항목: **Step 1: | PASS | |
| Stable Task 40 필수 항목: **Step 2: | PASS | |
| Stable Task 40 필수 항목: **Step 3: | PASS | |
| Stable Task 40 필수 항목: **Step 4: | PASS | |
| Stable Task 40 필수 항목: **Step 5: | PASS | |
| Stable Task 40 필수 항목: Expected: | PASS | |
| Stable Task 40 필수 항목: git commit -m | PASS | |
| Stable Task 40 Gradle focused command | PASS | |
| Stable Task 40 exact path | PASS | |
| Stable Task 41 필수 항목: **Files:** | PASS | |
| Stable Task 41 필수 항목: **Interfaces:** | PASS | |
| Stable Task 41 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 41 필수 항목: **Step 1: | PASS | |
| Stable Task 41 필수 항목: **Step 2: | PASS | |
| Stable Task 41 필수 항목: **Step 3: | PASS | |
| Stable Task 41 필수 항목: **Step 4: | PASS | |
| Stable Task 41 필수 항목: **Step 5: | PASS | |
| Stable Task 41 필수 항목: Expected: | PASS | |
| Stable Task 41 필수 항목: git commit -m | PASS | |
| Stable Task 41 Gradle focused command | PASS | |
| Stable Task 41 exact path | PASS | |
| Stable Task 42 필수 항목: **Files:** | PASS | |
| Stable Task 42 필수 항목: **Interfaces:** | PASS | |
| Stable Task 42 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 42 필수 항목: **Step 1: | PASS | |
| Stable Task 42 필수 항목: **Step 2: | PASS | |
| Stable Task 42 필수 항목: **Step 3: | PASS | |
| Stable Task 42 필수 항목: **Step 4: | PASS | |
| Stable Task 42 필수 항목: **Step 5: | PASS | |
| Stable Task 42 필수 항목: Expected: | PASS | |
| Stable Task 42 필수 항목: git commit -m | PASS | |
| Stable Task 42 Gradle focused command | PASS | |
| Stable Task 42 exact path | PASS | |
| Stable Task 43 필수 항목: **Files:** | PASS | |
| Stable Task 43 필수 항목: **Interfaces:** | PASS | |
| Stable Task 43 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 43 필수 항목: **Step 1: | PASS | |
| Stable Task 43 필수 항목: **Step 2: | PASS | |
| Stable Task 43 필수 항목: **Step 3: | PASS | |
| Stable Task 43 필수 항목: **Step 4: | PASS | |
| Stable Task 43 필수 항목: **Step 5: | PASS | |
| Stable Task 43 필수 항목: Expected: | PASS | |
| Stable Task 43 필수 항목: git commit -m | PASS | |
| Stable Task 43 Gradle focused command | PASS | |
| Stable Task 43 exact path | PASS | |
| Stable Task 44 필수 항목: **Files:** | PASS | |
| Stable Task 44 필수 항목: **Interfaces:** | PASS | |
| Stable Task 44 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 44 필수 항목: **Step 1: | PASS | |
| Stable Task 44 필수 항목: **Step 2: | PASS | |
| Stable Task 44 필수 항목: **Step 3: | PASS | |
| Stable Task 44 필수 항목: **Step 4: | PASS | |
| Stable Task 44 필수 항목: **Step 5: | PASS | |
| Stable Task 44 필수 항목: Expected: | PASS | |
| Stable Task 44 필수 항목: git commit -m | PASS | |
| Stable Task 44 Gradle focused command | PASS | |
| Stable Task 44 exact path | PASS | |
| Stable Task 45 필수 항목: **Files:** | PASS | |
| Stable Task 45 필수 항목: **Interfaces:** | PASS | |
| Stable Task 45 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 45 필수 항목: **Step 1: | PASS | |
| Stable Task 45 필수 항목: **Step 2: | PASS | |
| Stable Task 45 필수 항목: **Step 3: | PASS | |
| Stable Task 45 필수 항목: **Step 4: | PASS | |
| Stable Task 45 필수 항목: **Step 5: | PASS | |
| Stable Task 45 필수 항목: Expected: | PASS | |
| Stable Task 45 필수 항목: git commit -m | PASS | |
| Stable Task 45 Gradle focused command | PASS | |
| Stable Task 45 exact path | PASS | |
| Stable Task 46 필수 항목: **Files:** | PASS | |
| Stable Task 46 필수 항목: **Interfaces:** | PASS | |
| Stable Task 46 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 46 필수 항목: **Step 1: | PASS | |
| Stable Task 46 필수 항목: **Step 2: | PASS | |
| Stable Task 46 필수 항목: **Step 3: | PASS | |
| Stable Task 46 필수 항목: **Step 4: | PASS | |
| Stable Task 46 필수 항목: **Step 5: | PASS | |
| Stable Task 46 필수 항목: Expected: | PASS | |
| Stable Task 46 필수 항목: git commit -m | PASS | |
| Stable Task 46 Gradle focused command | PASS | |
| Stable Task 46 exact path | PASS | |
| Stable Task 47 필수 항목: **Files:** | PASS | |
| Stable Task 47 필수 항목: **Interfaces:** | PASS | |
| Stable Task 47 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 47 필수 항목: **Step 1: | PASS | |
| Stable Task 47 필수 항목: **Step 2: | PASS | |
| Stable Task 47 필수 항목: **Step 3: | PASS | |
| Stable Task 47 필수 항목: **Step 4: | PASS | |
| Stable Task 47 필수 항목: **Step 5: | PASS | |
| Stable Task 47 필수 항목: Expected: | PASS | |
| Stable Task 47 필수 항목: git commit -m | PASS | |
| Stable Task 47 Gradle focused command | PASS | |
| Stable Task 47 exact path | PASS | |
| Stable Task 48 필수 항목: **Files:** | PASS | |
| Stable Task 48 필수 항목: **Interfaces:** | PASS | |
| Stable Task 48 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 48 필수 항목: **Step 1: | PASS | |
| Stable Task 48 필수 항목: **Step 2: | PASS | |
| Stable Task 48 필수 항목: **Step 3: | PASS | |
| Stable Task 48 필수 항목: **Step 4: | PASS | |
| Stable Task 48 필수 항목: **Step 5: | PASS | |
| Stable Task 48 필수 항목: Expected: | PASS | |
| Stable Task 48 필수 항목: git commit -m | PASS | |
| Stable Task 48 Gradle focused command | PASS | |
| Stable Task 48 exact path | PASS | |
| Stable Task 49 필수 항목: **Files:** | PASS | |
| Stable Task 49 필수 항목: **Interfaces:** | PASS | |
| Stable Task 49 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 49 필수 항목: **Step 1: | PASS | |
| Stable Task 49 필수 항목: **Step 2: | PASS | |
| Stable Task 49 필수 항목: **Step 3: | PASS | |
| Stable Task 49 필수 항목: **Step 4: | PASS | |
| Stable Task 49 필수 항목: **Step 5: | PASS | |
| Stable Task 49 필수 항목: Expected: | PASS | |
| Stable Task 49 필수 항목: git commit -m | PASS | |
| Stable Task 49 Gradle focused command | PASS | |
| Stable Task 49 exact path | PASS | |
| Stable Task 50 필수 항목: **Files:** | PASS | |
| Stable Task 50 필수 항목: **Interfaces:** | PASS | |
| Stable Task 50 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 50 필수 항목: **Step 1: | PASS | |
| Stable Task 50 필수 항목: **Step 2: | PASS | |
| Stable Task 50 필수 항목: **Step 3: | PASS | |
| Stable Task 50 필수 항목: **Step 4: | PASS | |
| Stable Task 50 필수 항목: **Step 5: | PASS | |
| Stable Task 50 필수 항목: Expected: | PASS | |
| Stable Task 50 필수 항목: git commit -m | PASS | |
| Stable Task 50 Gradle focused command | PASS | |
| Stable Task 50 exact path | PASS | |
| Stable Task 51 필수 항목: **Files:** | PASS | |
| Stable Task 51 필수 항목: **Interfaces:** | PASS | |
| Stable Task 51 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 51 필수 항목: **Step 1: | PASS | |
| Stable Task 51 필수 항목: **Step 2: | PASS | |
| Stable Task 51 필수 항목: **Step 3: | PASS | |
| Stable Task 51 필수 항목: **Step 4: | PASS | |
| Stable Task 51 필수 항목: **Step 5: | PASS | |
| Stable Task 51 필수 항목: Expected: | PASS | |
| Stable Task 51 필수 항목: git commit -m | PASS | |
| Stable Task 51 Gradle focused command | PASS | |
| Stable Task 51 exact path | PASS | |
| Stable Task 52 필수 항목: **Files:** | PASS | |
| Stable Task 52 필수 항목: **Interfaces:** | PASS | |
| Stable Task 52 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 52 필수 항목: **Step 1: | PASS | |
| Stable Task 52 필수 항목: **Step 2: | PASS | |
| Stable Task 52 필수 항목: **Step 3: | PASS | |
| Stable Task 52 필수 항목: **Step 4: | PASS | |
| Stable Task 52 필수 항목: **Step 5: | PASS | |
| Stable Task 52 필수 항목: Expected: | PASS | |
| Stable Task 52 필수 항목: git commit -m | PASS | |
| Stable Task 52 Gradle focused command | PASS | |
| Stable Task 52 exact path | PASS | |
| Stable Task 53 필수 항목: **Files:** | PASS | |
| Stable Task 53 필수 항목: **Interfaces:** | PASS | |
| Stable Task 53 필수 항목: **Implementation requirements:** | PASS | |
| Stable Task 53 필수 항목: **Step 1: | PASS | |
| Stable Task 53 필수 항목: **Step 2: | PASS | |
| Stable Task 53 필수 항목: **Step 3: | PASS | |
| Stable Task 53 필수 항목: **Step 4: | PASS | |
| Stable Task 53 필수 항목: **Step 5: | PASS | |
| Stable Task 53 필수 항목: Expected: | PASS | |
| Stable Task 53 필수 항목: git commit -m | PASS | |
| Stable Task 53 Gradle focused command | PASS | |
| Stable Task 53 exact path | PASS | |
| Experimental Task 1 필수 항목: **Files:** | PASS | |
| Experimental Task 1 필수 항목: **Interfaces:** | PASS | |
| Experimental Task 1 필수 항목: **Implementation requirements:** | PASS | |
| Experimental Task 1 필수 항목: **Step 1: | PASS | |
| Experimental Task 1 필수 항목: **Step 2: | PASS | |
| Experimental Task 1 필수 항목: **Step 3: | PASS | |
| Experimental Task 1 필수 항목: **Step 4: | PASS | |
| Experimental Task 1 필수 항목: **Step 5: | PASS | |
| Experimental Task 1 필수 항목: Expected: | PASS | |
| Experimental Task 1 필수 항목: git commit -m | PASS | |
| Experimental Task 1 Gradle focused command | PASS | |
| Experimental Task 1 exact path | PASS | |
| Experimental Task 2 필수 항목: **Files:** | PASS | |
| Experimental Task 2 필수 항목: **Interfaces:** | PASS | |
| Experimental Task 2 필수 항목: **Implementation requirements:** | PASS | |
| Experimental Task 2 필수 항목: **Step 1: | PASS | |
| Experimental Task 2 필수 항목: **Step 2: | PASS | |
| Experimental Task 2 필수 항목: **Step 3: | PASS | |
| Experimental Task 2 필수 항목: **Step 4: | PASS | |
| Experimental Task 2 필수 항목: **Step 5: | PASS | |
| Experimental Task 2 필수 항목: Expected: | PASS | |
| Experimental Task 2 필수 항목: git commit -m | PASS | |
| Experimental Task 2 Gradle focused command | PASS | |
| Experimental Task 2 exact path | PASS | |
| Experimental Task 3 필수 항목: **Files:** | PASS | |
| Experimental Task 3 필수 항목: **Interfaces:** | PASS | |
| Experimental Task 3 필수 항목: **Implementation requirements:** | PASS | |
| Experimental Task 3 필수 항목: **Step 1: | PASS | |
| Experimental Task 3 필수 항목: **Step 2: | PASS | |
| Experimental Task 3 필수 항목: **Step 3: | PASS | |
| Experimental Task 3 필수 항목: **Step 4: | PASS | |
| Experimental Task 3 필수 항목: **Step 5: | PASS | |
| Experimental Task 3 필수 항목: Expected: | PASS | |
| Experimental Task 3 필수 항목: git commit -m | PASS | |
| Experimental Task 3 Gradle focused command | PASS | |
| Experimental Task 3 exact path | PASS | |
| Experimental Task 4 필수 항목: **Files:** | PASS | |
| Experimental Task 4 필수 항목: **Interfaces:** | PASS | |
| Experimental Task 4 필수 항목: **Implementation requirements:** | PASS | |
| Experimental Task 4 필수 항목: **Step 1: | PASS | |
| Experimental Task 4 필수 항목: **Step 2: | PASS | |
| Experimental Task 4 필수 항목: **Step 3: | PASS | |
| Experimental Task 4 필수 항목: **Step 4: | PASS | |
| Experimental Task 4 필수 항목: **Step 5: | PASS | |
| Experimental Task 4 필수 항목: Expected: | PASS | |
| Experimental Task 4 필수 항목: git commit -m | PASS | |
| Experimental Task 4 Gradle focused command | PASS | |
| Experimental Task 4 exact path | PASS | |
| Experimental Task 5 필수 항목: **Files:** | PASS | |
| Experimental Task 5 필수 항목: **Interfaces:** | PASS | |
| Experimental Task 5 필수 항목: **Implementation requirements:** | PASS | |
| Experimental Task 5 필수 항목: **Step 1: | PASS | |
| Experimental Task 5 필수 항목: **Step 2: | PASS | |
| Experimental Task 5 필수 항목: **Step 3: | PASS | |
| Experimental Task 5 필수 항목: **Step 4: | PASS | |
| Experimental Task 5 필수 항목: **Step 5: | PASS | |
| Experimental Task 5 필수 항목: Expected: | PASS | |
| Experimental Task 5 필수 항목: git commit -m | PASS | |
| Experimental Task 5 Gradle focused command | PASS | |
| Experimental Task 5 exact path | PASS | |
| Experimental Task 6 필수 항목: **Files:** | PASS | |
| Experimental Task 6 필수 항목: **Interfaces:** | PASS | |
| Experimental Task 6 필수 항목: **Implementation requirements:** | PASS | |
| Experimental Task 6 필수 항목: **Step 1: | PASS | |
| Experimental Task 6 필수 항목: **Step 2: | PASS | |
| Experimental Task 6 필수 항목: **Step 3: | PASS | |
| Experimental Task 6 필수 항목: **Step 4: | PASS | |
| Experimental Task 6 필수 항목: **Step 5: | PASS | |
| Experimental Task 6 필수 항목: Expected: | PASS | |
| Experimental Task 6 필수 항목: git commit -m | PASS | |
| Experimental Task 6 Gradle focused command | PASS | |
| Experimental Task 6 exact path | PASS | |
| Experimental Task 7 필수 항목: **Files:** | PASS | |
| Experimental Task 7 필수 항목: **Interfaces:** | PASS | |
| Experimental Task 7 필수 항목: **Implementation requirements:** | PASS | |
| Experimental Task 7 필수 항목: **Step 1: | PASS | |
| Experimental Task 7 필수 항목: **Step 2: | PASS | |
| Experimental Task 7 필수 항목: **Step 3: | PASS | |
| Experimental Task 7 필수 항목: **Step 4: | PASS | |
| Experimental Task 7 필수 항목: **Step 5: | PASS | |
| Experimental Task 7 필수 항목: Expected: | PASS | |
| Experimental Task 7 필수 항목: git commit -m | PASS | |
| Experimental Task 7 Gradle focused command | PASS | |
| Experimental Task 7 exact path | PASS | |
| Experimental Task 8 필수 항목: **Files:** | PASS | |
| Experimental Task 8 필수 항목: **Interfaces:** | PASS | |
| Experimental Task 8 필수 항목: **Implementation requirements:** | PASS | |
| Experimental Task 8 필수 항목: **Step 1: | PASS | |
| Experimental Task 8 필수 항목: **Step 2: | PASS | |
| Experimental Task 8 필수 항목: **Step 3: | PASS | |
| Experimental Task 8 필수 항목: **Step 4: | PASS | |
| Experimental Task 8 필수 항목: **Step 5: | PASS | |
| Experimental Task 8 필수 항목: Expected: | PASS | |
| Experimental Task 8 필수 항목: git commit -m | PASS | |
| Experimental Task 8 Gradle focused command | PASS | |
| Experimental Task 8 exact path | PASS | |
| Experimental Task 9 필수 항목: **Files:** | PASS | |
| Experimental Task 9 필수 항목: **Interfaces:** | PASS | |
| Experimental Task 9 필수 항목: **Implementation requirements:** | PASS | |
| Experimental Task 9 필수 항목: **Step 1: | PASS | |
| Experimental Task 9 필수 항목: **Step 2: | PASS | |
| Experimental Task 9 필수 항목: **Step 3: | PASS | |
| Experimental Task 9 필수 항목: **Step 4: | PASS | |
| Experimental Task 9 필수 항목: **Step 5: | PASS | |
| Experimental Task 9 필수 항목: Expected: | PASS | |
| Experimental Task 9 필수 항목: git commit -m | PASS | |
| Experimental Task 9 Gradle focused command | PASS | |
| Experimental Task 9 exact path | PASS | |
| Stable Create 경로 중복 부재 | PASS | 235 |
| Experimental Create 경로 중복 부재 | PASS | 37 |
| Stable·Experimental Create 경로 충돌 부재 | PASS | set() |
| Experimental 계획은 Stable Task 1~53 이후 시작 | PASS | |
| 공통 타입 일관성: PersistenceOperationName | PASS | 21 |
| 공통 타입 일관성: TransactionProfile | PASS | 14 |
| 공통 타입 일관성: RetryProfile | PASS | 5 |
| 공통 타입 일관성: JpaTransactionExecutor | PASS | 14 |
| 공통 타입 일관성: JpaRetryPolicy | PASS | 7 |
| 공통 타입 일관성: RetryDecision | PASS | 8 |
| 공통 타입 일관성: JpaFailureContext | PASS | 11 |
| 공통 타입 일관성: QueryName | PASS | 22 |
| 공통 타입 일관성: KeysetPageRequest | PASS | 4 |
| 공통 타입 일관성: KeysetSlice | PASS | 7 |
| 공통 타입 일관성: TransactionCompletionUnknownException | PASS | 7 |
| 공통 타입 일관성: PostgreSqlWorkClaimExecutor | PASS | 2 |
| 공통 타입 일관성: FlywayValidationGate | PASS | 8 |
| 공통 타입 일관성: JpaPlatformEndpoint | PASS | 3 |
| Experimental Gradle 경로 정확성 | PASS | |
| Replica annotation-only routing 금지 | PASS | |
| RLS connection reuse 검증 | PASS | |
| Stable 승격 ADR gate | PASS | |
| 심층 리서치 원문 부록 포함 | PASS | |
@@ -0,0 +1,771 @@
# JPA Experimental Expansion Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Stable JPA 플랫폼을 변경하지 않고 Multi-tenancy, PostgreSQL RLS, schema/database tenant 분리, consistency-aware Read Replica, Jakarta Persistence 4.0, Hibernate ORM 8, PostgreSQL 19 호환성을 독립 Experimental 모듈과 승격 Gate로 검증한다.
**Architecture:** Experimental module은 Stable `jpa-core-api` 계약만 소비하며 Stable starter에 자동 포함되지 않는다. 각 기능은 명시적 feature flag와 별도 compatibility/failure suite를 요구한다. 실험 결과가 Stable 의미론과 충돌하면 Core를 왜곡하지 않고 capability 또는 별도 profile로 유지한다.
**Tech Stack:** Stable 계획의 Java 21·Spring Boot 4.1·PostgreSQL Testcontainers 기반, PostgreSQL RLS, AbstractRoutingDataSource, tenant-specific DataSource registry, Jakarta Persistence 4.0 preview/final compatibility lane, Hibernate ORM 8 compatibility lane, PostgreSQL 19 compatibility lane.
## Global Constraints
- Stable 계획 Task 1~53이 완료되고 Release Gate가 통과한 뒤 시작한다.
- 모듈 루트는 `modules/jpa-experimental`이다.
- Experimental module은 `jpa-spring-boot-starter`의 기본 dependency가 아니다.
- 모든 기능은 `backend.jpa.experimental.*` feature flag를 요구한다.
- Tenant ID와 consistency token은 metric label에 기록하지 않는다.
- Tenant context 누락은 fail-closed다.
- `readOnly=true`만으로 replica routing하지 않는다.
- Lock query, write transaction, read-after-write pin은 primary를 사용한다.
- JPA4/Hibernate8/PG19 결과로 Stable 3.2/7.4/PG16~18 contract를 수정하지 않는다.
- 승격 전 별도 security, failure, migration and compatibility evidence가 필요하다.
---
## 1. Experimental 파일 구조
```text
modules/jpa-experimental/
├── jpa-experimental-core/
├── jpa-multitenancy-column/
├── jpa-multitenancy-rls/
├── jpa-multitenancy-schema/
├── jpa-multitenancy-database/
├── jpa-read-replica/
└── jpa-next-compatibility/
```
---
### Task 1: Experimental Module·Feature Gate·Dependency Isolation 구성
**Files:**
- Create: `modules/jpa-experimental/jpa-experimental-core/build.gradle.kts`
- Create: `modules/jpa-experimental/jpa-multitenancy-column/build.gradle.kts`
- Create: `modules/jpa-experimental/jpa-multitenancy-rls/build.gradle.kts`
- Create: `modules/jpa-experimental/jpa-multitenancy-schema/build.gradle.kts`
- Create: `modules/jpa-experimental/jpa-multitenancy-database/build.gradle.kts`
- Create: `modules/jpa-experimental/jpa-read-replica/build.gradle.kts`
- Create: `modules/jpa-experimental/jpa-next-compatibility/build.gradle.kts`
- Create: `modules/jpa-experimental/jpa-experimental-core/src/main/java/io/backend/skeleton/jpa/experimental/ExperimentalFeature.java`
- Create: `modules/jpa-experimental/jpa-experimental-core/src/main/java/io/backend/skeleton/jpa/experimental/ExperimentalFeatureGate.java`
- Modify: `settings.gradle.kts`
- Test: `modules/jpa-experimental/jpa-experimental-core/src/test/java/io/backend/skeleton/jpa/experimental/ExperimentalFeatureGateTest.java`
**Interfaces:**
- Consumes: Stable `jpa-core-api` and explicit environment feature flags.
- Produces: Isolated experimental projects that cannot enter the Stable starter transitively.
**Implementation requirements:**
- Every module depends only on Stable public contracts, never on Stable internal packages.
- Feature gate fails startup when module is present but flag is absent.
- Add a dependency graph test proving the Stable starter has no experimental dependency.
- [ ] **Step 1: Write the failing test**
```java
package io.backend.skeleton.jpa.experimental;
class ExperimentalFeatureGateTest {
@Test
void featureIsDisabledUnlessExplicitlyEnabled() {
assertThatThrownBy(() -> gate.requireEnabled(MULTITENANCY_COLUMN, Map.of()))
.hasMessageContaining("backend.jpa.experimental.multitenancy-column=true");
}
}
```
- [ ] **Step 2: Run the focused test and verify the failure**
Run:
```bash
./gradlew :modules:jpa-experimental:jpa-experimental-core:test --tests 'io.backend.skeleton.jpa.experimental.ExperimentalFeatureGateTest'
```
Expected: FAIL because the production type or behavior does not exist yet.
- [ ] **Step 3: Implement the smallest complete production contract**
```java
package io.backend.skeleton.jpa.experimental;
public final class ExperimentalFeatureGate {
public void requireEnabled(
ExperimentalFeature feature,
Map<String, Boolean> flags) {
if (!Boolean.TRUE.equals(flags.get(feature.property()))) {
throw new IllegalStateException(feature.property() + "=true is required");
}
}
}
```
Implement every file and invariant listed under **Implementation requirements**; the snippet fixes the public names and central behavior rather than replacing those requirements.
- [ ] **Step 4: Run the focused test and the module test suite**
Run:
```bash
./gradlew :modules:jpa-experimental:jpa-experimental-core:test --tests 'io.backend.skeleton.jpa.experimental.ExperimentalFeatureGateTest'
./gradlew :modules:jpa-experimental:jpa-experimental-core:test
```
Expected: PASS with all assertions green.
- [ ] **Step 5: Commit the independently reviewable change**
```bash
git add 'modules/jpa-experimental/jpa-experimental-core/build.gradle.kts' 'modules/jpa-experimental/jpa-multitenancy-column/build.gradle.kts' 'modules/jpa-experimental/jpa-multitenancy-rls/build.gradle.kts' 'modules/jpa-experimental/jpa-multitenancy-schema/build.gradle.kts' 'modules/jpa-experimental/jpa-multitenancy-database/build.gradle.kts' 'modules/jpa-experimental/jpa-read-replica/build.gradle.kts' 'modules/jpa-experimental/jpa-next-compatibility/build.gradle.kts' 'modules/jpa-experimental/jpa-experimental-core/src/main/java/io/backend/skeleton/jpa/experimental/ExperimentalFeature.java' 'modules/jpa-experimental/jpa-experimental-core/src/main/java/io/backend/skeleton/jpa/experimental/ExperimentalFeatureGate.java' 'settings.gradle.kts' 'modules/jpa-experimental/jpa-experimental-core/src/test/java/io/backend/skeleton/jpa/experimental/ExperimentalFeatureGateTest.java'
git commit -m "build: isolate jpa experimental modules"
```
### Task 2: Shared-schema Tenant Context와 Column Guard 구현
**Files:**
- Create: `modules/jpa-experimental/jpa-multitenancy-column/src/main/java/io/backend/skeleton/jpa/experimental/tenant/TenantId.java`
- Create: `modules/jpa-experimental/jpa-multitenancy-column/src/main/java/io/backend/skeleton/jpa/experimental/tenant/TenantContext.java`
- Create: `modules/jpa-experimental/jpa-multitenancy-column/src/main/java/io/backend/skeleton/jpa/experimental/tenant/TenantAwareRepositoryGuard.java`
- Create: `modules/jpa-experimental/jpa-multitenancy-column/src/main/java/io/backend/skeleton/jpa/experimental/tenant/TenantEntityListenerGuard.java`
- Test: `modules/jpa-experimental/jpa-multitenancy-column/src/integrationTest/java/io/backend/skeleton/jpa/experimental/tenant/TenantColumnIsolationTest.java`
**Interfaces:**
- Consumes: Explicit request/job tenant context and domain Entity tenant-column contracts.
- Produces: Fail-closed tenant context propagation and query/write isolation evidence.
**Implementation requirements:**
- Reject Repository access when tenant context is absent outside an audited admin scope.
- Require tenant column in unique/index requirements where isolation depends on it.
- Test async job context propagation and cleanup.
- Do not rely on Hibernate filter alone as the final security boundary.
- [ ] **Step 1: Write the failing test**
```java
package io.backend.skeleton.jpa.experimental.tenant;
class TenantColumnIsolationTest {
@Test
void tenantARepositoryCannotReadTenantBRows() {
insertFor(TENANT_A, "a");
insertFor(TENANT_B, "b");
assertThat(withTenant(TENANT_A, repository::findAll))
.extracting(Item::value)
.containsExactly("a");
}
}
```
- [ ] **Step 2: Run the focused test and verify the failure**
Run:
```bash
./gradlew :modules:jpa-experimental:jpa-multitenancy-column:integrationTest --tests 'io.backend.skeleton.jpa.experimental.tenant.TenantColumnIsolationTest'
```
Expected: FAIL because the production type or behavior does not exist yet.
- [ ] **Step 3: Implement the smallest complete production contract**
```java
package io.backend.skeleton.jpa.experimental.tenant;
public final class TenantContext {
private static final ThreadLocal<TenantId> CURRENT = new ThreadLocal<>();
public static TenantId require() {
TenantId tenant = CURRENT.get();
if (tenant == null) throw new IllegalStateException("tenant context is required");
return tenant;
}
public static void clear() { CURRENT.remove(); }
}
```
Implement every file and invariant listed under **Implementation requirements**; the snippet fixes the public names and central behavior rather than replacing those requirements.
- [ ] **Step 4: Run the focused test and the module test suite**
Run:
```bash
./gradlew :modules:jpa-experimental:jpa-multitenancy-column:integrationTest --tests 'io.backend.skeleton.jpa.experimental.tenant.TenantColumnIsolationTest'
./gradlew :modules:jpa-experimental:jpa-multitenancy-column:test
```
Expected: PASS with all assertions green.
- [ ] **Step 5: Commit the independently reviewable change**
```bash
git add 'modules/jpa-experimental/jpa-multitenancy-column/src/main/java/io/backend/skeleton/jpa/experimental/tenant/TenantId.java' 'modules/jpa-experimental/jpa-multitenancy-column/src/main/java/io/backend/skeleton/jpa/experimental/tenant/TenantContext.java' 'modules/jpa-experimental/jpa-multitenancy-column/src/main/java/io/backend/skeleton/jpa/experimental/tenant/TenantAwareRepositoryGuard.java' 'modules/jpa-experimental/jpa-multitenancy-column/src/main/java/io/backend/skeleton/jpa/experimental/tenant/TenantEntityListenerGuard.java' 'modules/jpa-experimental/jpa-multitenancy-column/src/integrationTest/java/io/backend/skeleton/jpa/experimental/tenant/TenantColumnIsolationTest.java'
git commit -m "feat: add experimental tenant column isolation"
```
### Task 3: PostgreSQL RLS Tenant Policy와 Connection Reuse Guard 구현
**Files:**
- Create: `modules/jpa-experimental/jpa-multitenancy-rls/src/main/java/io/backend/skeleton/jpa/experimental/rls/RlsTenantSessionBinder.java`
- Create: `modules/jpa-experimental/jpa-multitenancy-rls/src/main/java/io/backend/skeleton/jpa/experimental/rls/RlsPolicyVerifier.java`
- Create: `modules/jpa-experimental/jpa-multitenancy-rls/src/main/java/io/backend/skeleton/jpa/experimental/rls/RlsAdminBypassToken.java`
- Create: `modules/jpa-experimental/jpa-multitenancy-rls/src/main/resources/db/experimental-rls/V1__tenant_rls.sql`
- Test: `modules/jpa-experimental/jpa-multitenancy-rls/src/failureTest/java/io/backend/skeleton/jpa/experimental/rls/RlsIsolationFailureTest.java`
**Interfaces:**
- Consumes: TenantContext, PostgreSQL transaction-local settings and restricted runtime role.
- Produces: Database-enforced tenant isolation that resets safely across pooled connections.
**Implementation requirements:**
- Set tenant context with transaction-local `set_config` before tenant queries.
- Prove a pooled connection cannot leak the prior tenant into the next transaction.
- Runtime role must not own tables or bypass RLS.
- Admin bypass requires a separate DataSource and audit token.
- [ ] **Step 1: Write the failing test**
```java
package io.backend.skeleton.jpa.experimental.rls;
class RlsIsolationFailureTest {
@Test
void pooledConnectionDoesNotLeakPriorTenantSetting() {
withTenant(TENANT_A, () -> assertThat(repository.count()).isEqualTo(1));
withTenant(TENANT_B, () -> assertThat(repository.count()).isEqualTo(1));
withoutTenant(() -> assertThatThrownBy(repository::count).isInstanceOf(DataAccessException.class));
}
}
```
- [ ] **Step 2: Run the focused test and verify the failure**
Run:
```bash
./gradlew :modules:jpa-experimental:jpa-multitenancy-rls:failureTest --tests 'io.backend.skeleton.jpa.experimental.rls.RlsIsolationFailureTest'
```
Expected: FAIL because the production type or behavior does not exist yet.
- [ ] **Step 3: Implement the smallest complete production contract**
```java
package io.backend.skeleton.jpa.experimental.rls;
public final class RlsTenantSessionBinder {
public void bind(EntityManager entityManager, TenantId tenant) {
entityManager.createNativeQuery(
"select set_config('app.tenant_id', :tenant, true)")
.setParameter("tenant", tenant.value())
.getSingleResult();
}
}
```
Implement every file and invariant listed under **Implementation requirements**; the snippet fixes the public names and central behavior rather than replacing those requirements.
- [ ] **Step 4: Run the focused test and the module test suite**
Run:
```bash
./gradlew :modules:jpa-experimental:jpa-multitenancy-rls:failureTest --tests 'io.backend.skeleton.jpa.experimental.rls.RlsIsolationFailureTest'
./gradlew :modules:jpa-experimental:jpa-multitenancy-rls:test
```
Expected: PASS with all assertions green.
- [ ] **Step 5: Commit the independently reviewable change**
```bash
git add 'modules/jpa-experimental/jpa-multitenancy-rls/src/main/java/io/backend/skeleton/jpa/experimental/rls/RlsTenantSessionBinder.java' 'modules/jpa-experimental/jpa-multitenancy-rls/src/main/java/io/backend/skeleton/jpa/experimental/rls/RlsPolicyVerifier.java' 'modules/jpa-experimental/jpa-multitenancy-rls/src/main/java/io/backend/skeleton/jpa/experimental/rls/RlsAdminBypassToken.java' 'modules/jpa-experimental/jpa-multitenancy-rls/src/main/resources/db/experimental-rls/V1__tenant_rls.sql' 'modules/jpa-experimental/jpa-multitenancy-rls/src/failureTest/java/io/backend/skeleton/jpa/experimental/rls/RlsIsolationFailureTest.java'
git commit -m "feat: add experimental postgresql rls isolation"
```
### Task 4: Schema-per-tenant Connection Provider와 Migration Orchestrator 구현
**Files:**
- Create: `modules/jpa-experimental/jpa-multitenancy-schema/src/main/java/io/backend/skeleton/jpa/experimental/schema/SchemaTenantRegistry.java`
- Create: `modules/jpa-experimental/jpa-multitenancy-schema/src/main/java/io/backend/skeleton/jpa/experimental/schema/SchemaMultiTenantConnectionProvider.java`
- Create: `modules/jpa-experimental/jpa-multitenancy-schema/src/main/java/io/backend/skeleton/jpa/experimental/schema/SchemaTenantMigrationOrchestrator.java`
- Test: `modules/jpa-experimental/jpa-multitenancy-schema/src/migrationTest/java/io/backend/skeleton/jpa/experimental/schema/SchemaTenantMigrationContractTest.java`
**Interfaces:**
- Consumes: Validated tenant→schema catalog and Flyway migration gate.
- Produces: Bounded schema selection and per-tenant migration status without accepting raw schema names.
**Implementation requirements:**
- Map TenantId to a pre-registered schema identifier; no user-provided SQL identifier.
- Reset schema/search_path when returning pooled connections.
- Track migration version and failure per tenant.
- Rate-limit tenant migrations and support resume without auto-repair.
- [ ] **Step 1: Write the failing test**
```java
package io.backend.skeleton.jpa.experimental.schema;
class SchemaTenantMigrationContractTest {
@Test
void migratesOnlyRegisteredSchemasAndResumesAfterFailure() {
orchestrator.migrateAll(List.of(TENANT_A, TENANT_B));
assertThat(status(TENANT_A).version()).isEqualTo(LATEST);
assertThatThrownBy(() -> orchestrator.migrate(new TenantId("../public")))
.isInstanceOf(IllegalArgumentException.class);
}
}
```
- [ ] **Step 2: Run the focused test and verify the failure**
Run:
```bash
./gradlew :modules:jpa-experimental:jpa-multitenancy-schema:migrationTest --tests 'io.backend.skeleton.jpa.experimental.schema.SchemaTenantMigrationContractTest'
```
Expected: FAIL because the production type or behavior does not exist yet.
- [ ] **Step 3: Implement the smallest complete production contract**
```java
package io.backend.skeleton.jpa.experimental.schema;
public final class SchemaTenantRegistry {
public String requireSchema(TenantId tenant) {
return Optional.ofNullable(schemaByTenant.get(tenant))
.orElseThrow(() -> new IllegalArgumentException("unregistered tenant schema"));
}
}
```
Implement every file and invariant listed under **Implementation requirements**; the snippet fixes the public names and central behavior rather than replacing those requirements.
- [ ] **Step 4: Run the focused test and the module test suite**
Run:
```bash
./gradlew :modules:jpa-experimental:jpa-multitenancy-schema:migrationTest --tests 'io.backend.skeleton.jpa.experimental.schema.SchemaTenantMigrationContractTest'
./gradlew :modules:jpa-experimental:jpa-multitenancy-schema:test
```
Expected: PASS with all assertions green.
- [ ] **Step 5: Commit the independently reviewable change**
```bash
git add 'modules/jpa-experimental/jpa-multitenancy-schema/src/main/java/io/backend/skeleton/jpa/experimental/schema/SchemaTenantRegistry.java' 'modules/jpa-experimental/jpa-multitenancy-schema/src/main/java/io/backend/skeleton/jpa/experimental/schema/SchemaMultiTenantConnectionProvider.java' 'modules/jpa-experimental/jpa-multitenancy-schema/src/main/java/io/backend/skeleton/jpa/experimental/schema/SchemaTenantMigrationOrchestrator.java' 'modules/jpa-experimental/jpa-multitenancy-schema/src/migrationTest/java/io/backend/skeleton/jpa/experimental/schema/SchemaTenantMigrationContractTest.java'
git commit -m "feat: add experimental schema per tenant persistence"
```
### Task 5: Database-per-tenant DataSource Registry와 Capacity Guard 구현
**Files:**
- Create: `modules/jpa-experimental/jpa-multitenancy-database/src/main/java/io/backend/skeleton/jpa/experimental/database/TenantDataSourceRegistry.java`
- Create: `modules/jpa-experimental/jpa-multitenancy-database/src/main/java/io/backend/skeleton/jpa/experimental/database/TenantEntityManagerFactoryRegistry.java`
- Create: `modules/jpa-experimental/jpa-multitenancy-database/src/main/java/io/backend/skeleton/jpa/experimental/database/TenantPoolBudget.java`
- Create: `modules/jpa-experimental/jpa-multitenancy-database/src/main/java/io/backend/skeleton/jpa/experimental/database/TenantDataSourceLifecycle.java`
- Test: `modules/jpa-experimental/jpa-multitenancy-database/src/performanceTest/java/io/backend/skeleton/jpa/experimental/database/TenantPoolCapacityContractTest.java`
**Interfaces:**
- Consumes: Secret-backed tenant connection profiles and global DB connection budget.
- Produces: Lazy bounded per-tenant pools with eviction, credential rotation and migration status.
**Implementation requirements:**
- Never create an unbounded Hikari pool per tenant.
- Enforce global maximum pools and connections before creating a DataSource.
- Drain and close pools on tenant removal or credential rotation.
- Do not expose tenant JDBC URLs or credentials in diagnostics.
- [ ] **Step 1: Write the failing test**
```java
package io.backend.skeleton.jpa.experimental.database;
class TenantPoolCapacityContractTest {
@Test
void refusesNewTenantPoolWhenGlobalConnectionBudgetIsExhausted() {
registry.openTenants(globalBudget().maxTenants());
assertThatThrownBy(() -> registry.require(ANOTHER_TENANT))
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining("tenant pool budget");
}
}
```
- [ ] **Step 2: Run the focused test and verify the failure**
Run:
```bash
./gradlew :modules:jpa-experimental:jpa-multitenancy-database:performanceTest --tests 'io.backend.skeleton.jpa.experimental.database.TenantPoolCapacityContractTest'
```
Expected: FAIL because the production type or behavior does not exist yet.
- [ ] **Step 3: Implement the smallest complete production contract**
```java
package io.backend.skeleton.jpa.experimental.database;
public record TenantPoolBudget(
int maxOpenPools,
int maxConnectionsAcrossPools) {
public void requireCapacity(int openPools, int allocatedConnections) {
if (openPools >= maxOpenPools || allocatedConnections >= maxConnectionsAcrossPools) {
throw new IllegalStateException("tenant pool budget exhausted");
}
}
}
```
Implement every file and invariant listed under **Implementation requirements**; the snippet fixes the public names and central behavior rather than replacing those requirements.
- [ ] **Step 4: Run the focused test and the module test suite**
Run:
```bash
./gradlew :modules:jpa-experimental:jpa-multitenancy-database:performanceTest --tests 'io.backend.skeleton.jpa.experimental.database.TenantPoolCapacityContractTest'
./gradlew :modules:jpa-experimental:jpa-multitenancy-database:test
```
Expected: PASS with all assertions green.
- [ ] **Step 5: Commit the independently reviewable change**
```bash
git add 'modules/jpa-experimental/jpa-multitenancy-database/src/main/java/io/backend/skeleton/jpa/experimental/database/TenantDataSourceRegistry.java' 'modules/jpa-experimental/jpa-multitenancy-database/src/main/java/io/backend/skeleton/jpa/experimental/database/TenantEntityManagerFactoryRegistry.java' 'modules/jpa-experimental/jpa-multitenancy-database/src/main/java/io/backend/skeleton/jpa/experimental/database/TenantPoolBudget.java' 'modules/jpa-experimental/jpa-multitenancy-database/src/main/java/io/backend/skeleton/jpa/experimental/database/TenantDataSourceLifecycle.java' 'modules/jpa-experimental/jpa-multitenancy-database/src/performanceTest/java/io/backend/skeleton/jpa/experimental/database/TenantPoolCapacityContractTest.java'
git commit -m "feat: add experimental database per tenant registry"
```
### Task 6: Consistency-aware Read Replica Routing 구현
**Files:**
- Create: `modules/jpa-experimental/jpa-read-replica/src/main/java/io/backend/skeleton/jpa/experimental/replica/ReadConsistency.java`
- Create: `modules/jpa-experimental/jpa-read-replica/src/main/java/io/backend/skeleton/jpa/experimental/replica/ConsistencyToken.java`
- Create: `modules/jpa-experimental/jpa-read-replica/src/main/java/io/backend/skeleton/jpa/experimental/replica/ReplicaRoutingDecision.java`
- Create: `modules/jpa-experimental/jpa-read-replica/src/main/java/io/backend/skeleton/jpa/experimental/replica/ConsistencyAwareDataSourceRouter.java`
- Create: `modules/jpa-experimental/jpa-read-replica/src/main/java/io/backend/skeleton/jpa/experimental/replica/ReplicaLagMonitor.java`
- Test: `modules/jpa-experimental/jpa-read-replica/src/failureTest/java/io/backend/skeleton/jpa/experimental/replica/ReadAfterWriteRoutingContractTest.java`
**Interfaces:**
- Consumes: Primary/replica DataSources, transaction state, lock intent and replica lag evidence.
- Produces: Routing decisions for PRIMARY_REQUIRED, BOUNDED_STALENESS and EVENTUAL reads.
**Implementation requirements:**
- Writes, lock queries, REQUIRES_NEW writes and active write transactions always use primary.
- Read-after-write uses a consistency token or primary pin, not `readOnly=true` alone.
- Fallback to primary when replica lag exceeds policy or evidence is unavailable.
- Keep routing fixed for the life of one transaction.
- [ ] **Step 1: Write the failing test**
```java
package io.backend.skeleton.jpa.experimental.replica;
class ReadAfterWriteRoutingContractTest {
@Test
void immediateReadAfterWriteUsesPrimaryUntilConsistencyTokenIsSatisfied() {
var token = service.writeAndReturnConsistencyToken();
var decision = router.route(readOnlyTransaction(), ReadConsistency.after(token));
assertThat(decision.target()).isEqualTo(PRIMARY);
}
}
```
- [ ] **Step 2: Run the focused test and verify the failure**
Run:
```bash
./gradlew :modules:jpa-experimental:jpa-read-replica:failureTest --tests 'io.backend.skeleton.jpa.experimental.replica.ReadAfterWriteRoutingContractTest'
```
Expected: FAIL because the production type or behavior does not exist yet.
- [ ] **Step 3: Implement the smallest complete production contract**
```java
package io.backend.skeleton.jpa.experimental.replica;
public final class ConsistencyAwareDataSourceRouter {
public ReplicaRoutingDecision route(
TransactionContext transaction,
ReadConsistency consistency) {
if (transaction.write() || transaction.locking() ||
!lagMonitor.satisfies(consistency)) {
return ReplicaRoutingDecision.primary();
}
return ReplicaRoutingDecision.replica();
}
}
```
Implement every file and invariant listed under **Implementation requirements**; the snippet fixes the public names and central behavior rather than replacing those requirements.
- [ ] **Step 4: Run the focused test and the module test suite**
Run:
```bash
./gradlew :modules:jpa-experimental:jpa-read-replica:failureTest --tests 'io.backend.skeleton.jpa.experimental.replica.ReadAfterWriteRoutingContractTest'
./gradlew :modules:jpa-experimental:jpa-read-replica:test
```
Expected: PASS with all assertions green.
- [ ] **Step 5: Commit the independently reviewable change**
```bash
git add 'modules/jpa-experimental/jpa-read-replica/src/main/java/io/backend/skeleton/jpa/experimental/replica/ReadConsistency.java' 'modules/jpa-experimental/jpa-read-replica/src/main/java/io/backend/skeleton/jpa/experimental/replica/ConsistencyToken.java' 'modules/jpa-experimental/jpa-read-replica/src/main/java/io/backend/skeleton/jpa/experimental/replica/ReplicaRoutingDecision.java' 'modules/jpa-experimental/jpa-read-replica/src/main/java/io/backend/skeleton/jpa/experimental/replica/ConsistencyAwareDataSourceRouter.java' 'modules/jpa-experimental/jpa-read-replica/src/main/java/io/backend/skeleton/jpa/experimental/replica/ReplicaLagMonitor.java' 'modules/jpa-experimental/jpa-read-replica/src/failureTest/java/io/backend/skeleton/jpa/experimental/replica/ReadAfterWriteRoutingContractTest.java'
git commit -m "feat: add experimental consistency aware replica routing"
```
### Task 7: Jakarta Persistence 4.0 Compatibility Lane 구현
**Files:**
- Create: `modules/jpa-experimental/jpa-next-compatibility/src/compatibilityJpa4/java/io/backend/skeleton/jpa/experimental/next/Jpa4CompatibilityTest.java`
- Create: `.github/workflows/jpa-next-jpa4.yml`
- Modify: `modules/jpa-experimental/jpa-next-compatibility/build.gradle.kts`
- Test: `modules/jpa-experimental/jpa-next-compatibility/src/test/java/io/backend/skeleton/jpa/experimental/next/CompatibilityLaneDefinitionTest.java`
**Interfaces:**
- Consumes: Published Jakarta Persistence 4.0 milestone/final artifact when available and the Stable contract suite.
- Produces: A non-blocking compatibility report that does not alter Stable JPA 3.2 APIs.
**Implementation requirements:**
- Run the Stable public API compilation and selected mapping contracts against JPA 4.
- Record removed/changed APIs and provider support separately.
- Do not publish JPA4 compiled artifacts under Stable coordinates.
- [ ] **Step 1: Write the failing test**
```kotlin
package io.backend.skeleton.jpa.experimental.next;
class CompatibilityLaneDefinitionTest {
@Test
void jpaFourLaneIsExperimentalAndSeparateFromStablePublication() {
assertThat(lane("jpa4").publicationEnabled()).isFalse();
assertThat(lane("jpa4").supportLevel()).isEqualTo(EXPERIMENTAL);
}
}
```
- [ ] **Step 2: Run the focused test and verify the failure**
Run:
```bash
./gradlew :modules:jpa-experimental:jpa-next-compatibility:test --tests 'io.backend.skeleton.jpa.experimental.next.CompatibilityLaneDefinitionTest'
```
Expected: FAIL because the production type or behavior does not exist yet.
- [ ] **Step 3: Implement the smallest complete production contract**
```kotlin
testing {
suites {
register<JvmTestSuite>("compatibilityJpa4") {
useJUnitJupiter()
dependencies {
implementation(project(":modules:jpa:jpa-core-api"))
implementation(libs.jakarta.persistence.next)
}
}
}
}
```
Implement every file and invariant listed under **Implementation requirements**; the snippet fixes the public names and central behavior rather than replacing those requirements.
- [ ] **Step 4: Run the focused test and the module test suite**
Run:
```bash
./gradlew :modules:jpa-experimental:jpa-next-compatibility:test --tests 'io.backend.skeleton.jpa.experimental.next.CompatibilityLaneDefinitionTest'
./gradlew :modules:jpa-experimental:jpa-next-compatibility:test
```
Expected: PASS with all assertions green.
- [ ] **Step 5: Commit the independently reviewable change**
```bash
git add 'modules/jpa-experimental/jpa-next-compatibility/src/compatibilityJpa4/java/io/backend/skeleton/jpa/experimental/next/Jpa4CompatibilityTest.java' '.github/workflows/jpa-next-jpa4.yml' 'modules/jpa-experimental/jpa-next-compatibility/build.gradle.kts' 'modules/jpa-experimental/jpa-next-compatibility/src/test/java/io/backend/skeleton/jpa/experimental/next/CompatibilityLaneDefinitionTest.java'
git commit -m "test: add jakarta persistence four compatibility lane"
```
### Task 8: Hibernate ORM 8 Compatibility Lane 구현
**Files:**
- Create: `modules/jpa-experimental/jpa-next-compatibility/src/compatibilityHibernate8/java/io/backend/skeleton/jpa/experimental/next/Hibernate8CompatibilityTest.java`
- Create: `.github/workflows/jpa-next-hibernate8.yml`
- Modify: `modules/jpa-experimental/jpa-next-compatibility/build.gradle.kts`
- Test: `modules/jpa-experimental/jpa-next-compatibility/src/test/java/io/backend/skeleton/jpa/experimental/next/HibernateCompatibilityPolicyTest.java`
**Interfaces:**
- Consumes: Hibernate ORM 8 milestone/final artifact and Stable Hibernate 7.4 regression suites.
- Produces: Generated SQL, fetch pagination, statistics, batch and extension compatibility evidence.
**Implementation requirements:**
- Re-run collection fetch pagination, StatementInspector, Statistics, JSONB, Batch and StatelessSession contracts.
- Record SQL and performance differences without weakening the 7.4 Stable gate.
- Do not allow Hibernate 8 dependencies in Stable published modules.
- [ ] **Step 1: Write the failing test**
```kotlin
package io.backend.skeleton.jpa.experimental.next;
class HibernateCompatibilityPolicyTest {
@Test
void hibernateEightCannotReplaceStableProviderWithoutPromotion() {
assertThat(policy.stableProvider()).isEqualTo("7.4");
assertThat(policy.experimentalProviders()).contains("8");
}
}
```
- [ ] **Step 2: Run the focused test and verify the failure**
Run:
```bash
./gradlew :modules:jpa-experimental:jpa-next-compatibility:test --tests 'io.backend.skeleton.jpa.experimental.next.HibernateCompatibilityPolicyTest'
```
Expected: FAIL because the production type or behavior does not exist yet.
- [ ] **Step 3: Implement the smallest complete production contract**
```kotlin
testing {
suites {
register<JvmTestSuite>("compatibilityHibernate8") {
useJUnitJupiter()
dependencies {
implementation(project(":modules:jpa:jpa-testkit-postgresql"))
implementation(libs.hibernate.orm.next)
}
}
}
}
```
Implement every file and invariant listed under **Implementation requirements**; the snippet fixes the public names and central behavior rather than replacing those requirements.
- [ ] **Step 4: Run the focused test and the module test suite**
Run:
```bash
./gradlew :modules:jpa-experimental:jpa-next-compatibility:test --tests 'io.backend.skeleton.jpa.experimental.next.HibernateCompatibilityPolicyTest'
./gradlew :modules:jpa-experimental:jpa-next-compatibility:test
```
Expected: PASS with all assertions green.
- [ ] **Step 5: Commit the independently reviewable change**
```bash
git add 'modules/jpa-experimental/jpa-next-compatibility/src/compatibilityHibernate8/java/io/backend/skeleton/jpa/experimental/next/Hibernate8CompatibilityTest.java' '.github/workflows/jpa-next-hibernate8.yml' 'modules/jpa-experimental/jpa-next-compatibility/build.gradle.kts' 'modules/jpa-experimental/jpa-next-compatibility/src/test/java/io/backend/skeleton/jpa/experimental/next/HibernateCompatibilityPolicyTest.java'
git commit -m "test: add hibernate eight compatibility lane"
```
### Task 9: PostgreSQL 19 Compatibility와 Stable 승격 Gate 구현
**Files:**
- Create: `modules/jpa-experimental/jpa-next-compatibility/src/compatibilityPostgresql19/java/io/backend/skeleton/jpa/experimental/next/PostgreSql19CompatibilityTest.java`
- Create: `docs/jpa/experimental-support-matrix.md`
- Create: `docs/jpa/experimental-promotion-checklist.md`
- Create: `.github/workflows/jpa-next-postgresql19.yml`
- Modify: `modules/jpa-experimental/jpa-next-compatibility/build.gradle.kts`
- Test: `modules/jpa-experimental/jpa-next-compatibility/src/test/java/io/backend/skeleton/jpa/experimental/next/ExperimentalPromotionGateTest.java`
**Interfaces:**
- Consumes: PG19 image when GA, all Stable contracts, experimental security/failure/migration/performance reports.
- Produces: A promotion decision that requires evidence rather than version availability alone.
**Implementation requirements:**
- Run mapping, SQLSTATE, lock, batch, Flyway, plan and native extension contracts on PG19.
- Promotion requires two supported patch runs and no unresolved semantic regression.
- Multi-tenancy/replica promotion requires tenant leakage, failover, lag and pool-capacity evidence.
- Update Stable support matrix only through a reviewed ADR.
- [ ] **Step 1: Write the failing test**
```java
package io.backend.skeleton.jpa.experimental.next;
class ExperimentalPromotionGateTest {
@Test
void promotionRequiresAllEvidenceAndReviewedAdr() {
var evidence = evidence().withCompatibility(true).withSecurity(true).withFailure(true)
.withMigration(true).withPerformance(true).withReviewedAdr(false);
assertThat(gate.evaluate(evidence)).isEqualTo(BLOCKED_MISSING_ADR);
}
}
```
- [ ] **Step 2: Run the focused test and verify the failure**
Run:
```bash
./gradlew :modules:jpa-experimental:jpa-next-compatibility:test --tests 'io.backend.skeleton.jpa.experimental.next.ExperimentalPromotionGateTest'
```
Expected: FAIL because the production type or behavior does not exist yet.
- [ ] **Step 3: Implement the smallest complete production contract**
```java
package io.backend.skeleton.jpa.experimental.next;
public final class ExperimentalPromotionGate {
public PromotionDecision evaluate(PromotionEvidence evidence) {
if (!evidence.allTechnicalGatesPassed()) return BLOCKED_TECHNICAL;
if (!evidence.reviewedAdr()) return BLOCKED_MISSING_ADR;
return ELIGIBLE_FOR_STABLE_REVIEW;
}
}
```
Implement every file and invariant listed under **Implementation requirements**; the snippet fixes the public names and central behavior rather than replacing those requirements.
- [ ] **Step 4: Run the focused test and the module test suite**
Run:
```bash
./gradlew :modules:jpa-experimental:jpa-next-compatibility:test --tests 'io.backend.skeleton.jpa.experimental.next.ExperimentalPromotionGateTest'
./gradlew :modules:jpa-experimental:jpa-next-compatibility:test
```
Expected: PASS with all assertions green.
- [ ] **Step 5: Commit the independently reviewable change**
```bash
git add 'modules/jpa-experimental/jpa-next-compatibility/src/compatibilityPostgresql19/java/io/backend/skeleton/jpa/experimental/next/PostgreSql19CompatibilityTest.java' 'docs/jpa/experimental-support-matrix.md' 'docs/jpa/experimental-promotion-checklist.md' '.github/workflows/jpa-next-postgresql19.yml' 'modules/jpa-experimental/jpa-next-compatibility/build.gradle.kts' 'modules/jpa-experimental/jpa-next-compatibility/src/test/java/io/backend/skeleton/jpa/experimental/next/ExperimentalPromotionGateTest.java'
git commit -m "docs: add jpa experimental promotion gates"
```
## 2. Experimental 완료 조건
```text
Stable starter가 Experimental module에 의존하지 않는다.
Tenant context 누락과 connection reuse에서 fail-closed다.
RLS runtime role이 policy를 bypass하지 못한다.
Schema/database tenant migration과 pool capacity가 bounded다.
Replica routing이 read-after-write와 lock query를 primary에 고정한다.
JPA4/Hibernate8/PG19 lane이 Stable artifacts를 변경하지 않는다.
승격은 ADR와 compatibility/security/failure/migration/performance 증거를 요구한다.
```
@@ -0,0 +1,210 @@
from __future__ import annotations
from pathlib import Path
import hashlib
import re
import sys
import zipfile
SCRIPT_DIR = Path(__file__).resolve().parent
PACKAGE_DESIGN = SCRIPT_DIR / 'docs/superpowers/specs/2026-08-11-jpa-persistence-platform-design.md'
if PACKAGE_DESIGN.exists():
ROOT = SCRIPT_DIR
DESIGN = PACKAGE_DESIGN
PLAN = ROOT / 'docs/superpowers/plans/2026-08-11-jpa-persistence-platform-implementation-plan.md'
EXPANSION = ROOT / 'docs/superpowers/plans/2026-08-11-jpa-persistence-experimental-expansion-plan.md'
VALIDATION = ROOT / 'VALIDATION.md'
ZIP_PATH = ROOT.parent / 'jpa-superpowers-package.zip'
else:
ROOT = Path('/mnt/data')
DESIGN = ROOT / 'jpa-persistence-platform-design.md'
PLAN = ROOT / 'jpa-persistence-platform-implementation-plan.md'
EXPANSION = ROOT / 'jpa-persistence-experimental-expansion-plan.md'
VALIDATION = ROOT / 'jpa-superpowers-validation.md'
ZIP_PATH = ROOT / 'jpa-superpowers-package.zip'
checks: list[tuple[str, bool, str]] = []
def check(name: str, condition: bool, detail: str = '') -> None:
checks.append((name, bool(condition), detail))
def sha256(path: Path) -> str:
h = hashlib.sha256()
with path.open('rb') as f:
for chunk in iter(lambda: f.read(1024 * 1024), b''):
h.update(chunk)
return h.hexdigest()
def task_chunks(text: str) -> list[tuple[int, str]]:
matches = list(re.finditer(r'^### Task (\d+):', text, re.MULTILINE))
chunks: list[tuple[int, str]] = []
for i, match in enumerate(matches):
end = matches[i + 1].start() if i + 1 < len(matches) else len(text)
chunks.append((int(match.group(1)), text[match.start():end]))
return chunks
for path, label in [(DESIGN, '설계서'), (PLAN, 'Stable 계획서'), (EXPANSION, 'Experimental 계획서')]:
check(f'{label} 존재', path.exists(), str(path))
if not all(path.exists() for path in [DESIGN, PLAN, EXPANSION]):
print('missing required documents', file=sys.stderr)
sys.exit(2)
design = DESIGN.read_text(encoding='utf-8')
plan = PLAN.read_text(encoding='utf-8')
expansion = EXPANSION.read_text(encoding='utf-8')
line_counts = {
'design': len(design.splitlines()),
'plan': len(plan.splitlines()),
'expansion': len(expansion.splitlines()),
}
check('설계서 최소 상세도', line_counts['design'] >= 2500, f"{line_counts['design']} lines")
check('Stable 계획서 최소 상세도', line_counts['plan'] >= 4000, f"{line_counts['plan']} lines")
check('Experimental 계획서 최소 상세도', line_counts['expansion'] >= 650, f"{line_counts['expansion']} lines")
for text, label in [(design, '설계서'), (plan, 'Stable 계획서'), (expansion, 'Experimental 계획서')]:
fences = len(re.findall(r'^```', text, re.MULTILINE))
check(f'{label} 코드 fence 균형', fences % 2 == 0, str(fences))
bad = re.findall(r'\b(?:TODO|TBD|FIXME|implement later|fill in details)\b', text, re.IGNORECASE)
check(f'{label} placeholder 부재', not bad, ', '.join(sorted(set(bad))))
required_design_terms = [
'GenericRepository',
'TransactionCompletionUnknownException',
'EvidenceAwareJpaTransactionManager',
'Application Service',
'OSIV',
'PostgreSQL 16·17·18',
'Hibernate 7.4 Collection Fetch Pagination',
'FOR UPDATE SKIP LOCKED',
'CREATE INDEX CONCURRENTLY',
'Flyway',
'Runtime·Migration·Admin',
'H2는 Local Convenience',
'전체 Transaction Retry',
'J1 Standard Persistence',
'J4 Admin / Operations',
'완료 정의',
]
for term in required_design_terms:
check(f'설계 핵심 계약: {term}', term in design)
check('GenericRepository 실제 선언 부재',
'public interface GenericRepository' not in design + plan and
'interface GenericRepository<' not in design + plan)
check('운영 ddl auto update 금지', '운영에서 `ddl-auto=update`' in plan)
check('Completion Unknown 자동 Retry 금지',
'TransactionCompletionUnknownException' in plan and
'RetryDecision.reconcile' in plan and
'Never retry completion unknown' in plan)
check('OSIV false 강제', 'spring.jpa.open-in-view must be false' in plan)
check('PG16·17·18 Matrix', 'PG_16' in plan and 'PG_17' in plan and 'PG_18' in plan)
check('Hibernate 7.4 fetch pagination gate', 'HibernateCollectionFetchPaginationContractTest' in plan)
check('Flyway snapshot upgrade gate', 'FlywayUpgradeContractTest' in plan)
check('Runtime role no DDL gate', 'runtimeRoleCanWriteRowsButCannotCreateTable' in plan)
check('Stable 계획에 Experimental create 경로 부재', '- Create: `modules/jpa-experimental/' not in plan)
stable_chunks = task_chunks(plan)
exp_chunks = task_chunks(expansion)
check('Stable Task 1~53 연속성', [n for n, _ in stable_chunks] == list(range(1, 54)), str([n for n, _ in stable_chunks]))
check('Experimental Task 1~9 연속성', [n for n, _ in exp_chunks] == list(range(1, 10)), str([n for n, _ in exp_chunks]))
check('Stable Task chunk 수', len(stable_chunks) == 53, str(len(stable_chunks)))
check('Experimental Task chunk 수', len(exp_chunks) == 9, str(len(exp_chunks)))
required_markers = [
'**Files:**',
'**Interfaces:**',
'**Implementation requirements:**',
'**Step 1:',
'**Step 2:',
'**Step 3:',
'**Step 4:',
'**Step 5:',
'Expected:',
'git commit -m',
]
for group_name, chunks in [('Stable', stable_chunks), ('Experimental', exp_chunks)]:
for number, chunk in chunks:
for marker in required_markers:
check(f'{group_name} Task {number} 필수 항목: {marker}', marker in chunk)
check(f'{group_name} Task {number} Gradle focused command', './gradlew ' in chunk and '--tests' in chunk)
check(f'{group_name} Task {number} exact path', '*' not in '\n'.join(
line for line in chunk.splitlines() if line.startswith(('- Create:', '- Modify:', '- Test:'))))
create_pattern = re.compile(r'^- Create: `([^`]+)`', re.MULTILINE)
stable_creates = create_pattern.findall(plan)
exp_creates = create_pattern.findall(expansion)
check('Stable Create 경로 중복 부재', len(stable_creates) == len(set(stable_creates)), str(len(stable_creates)))
check('Experimental Create 경로 중복 부재', len(exp_creates) == len(set(exp_creates)), str(len(exp_creates)))
check('Stable·Experimental Create 경로 충돌 부재', not (set(stable_creates) & set(exp_creates)), str(set(stable_creates) & set(exp_creates)))
check('Experimental 계획은 Stable Task 1~53 이후 시작', 'Stable 계획 Task 1~53이 완료되고' in expansion, '')
# Type/name consistency checks for high-risk cross-task contracts.
for term in [
'PersistenceOperationName',
'TransactionProfile',
'RetryProfile',
'JpaTransactionExecutor',
'JpaRetryPolicy',
'RetryDecision',
'JpaFailureContext',
'QueryName',
'KeysetPageRequest',
'KeysetSlice',
'TransactionCompletionUnknownException',
'PostgreSqlWorkClaimExecutor',
'FlywayValidationGate',
'JpaPlatformEndpoint',
]:
check(f'공통 타입 일관성: {term}', plan.count(term) >= 2, str(plan.count(term)))
check('Experimental Gradle 경로 정확성', ':modules:jpa-experimental:' in expansion)
check('Replica annotation-only routing 금지', 'readOnly=true`만으로 replica routing하지 않는다' in expansion)
check('RLS connection reuse 검증', 'pooledConnectionDoesNotLeakPriorTenantSetting' in expansion)
check('Stable 승격 ADR gate', 'BLOCKED_MISSING_ADR' in expansion)
# Source preservation check.
check('심층 리서치 원문 부록 포함', '# 부록 A. 심층 리서치 원문 보존본' in design and '# JPA 관계형 영속성 플랫폼 심층 리서치' in design)
passed = sum(1 for _, ok, _ in checks if ok)
failed = len(checks) - passed
status = 'PASS' if failed == 0 else 'FAIL'
lines = [
'# JPA Superpowers 문서 정적 검증',
'',
f'- 결과: **{status}**',
f'- 실행 검사: **{len(checks)}개**',
f'- 통과: **{passed}개**',
f'- 실패: **{failed}개**',
f'- 설계서: **{line_counts["design"]:,}행**',
f'- Stable 구현 계획서: **{line_counts["plan"]:,}행**',
f'- Experimental 확장 계획서: **{line_counts["expansion"]:,}행**',
f'- Stable Task: **{len(stable_chunks)}개**',
f'- Experimental Task: **{len(exp_chunks)}개**',
f'- Stable Create 경로: **{len(stable_creates)}개**',
f'- Experimental Create 경로: **{len(exp_creates)}개**',
f'- 설계 SHA-256: `{sha256(DESIGN)}`',
f'- Stable 계획 SHA-256: `{sha256(PLAN)}`',
f'- Experimental 계획 SHA-256: `{sha256(EXPANSION)}`',
'',
'## 검사 결과',
'',
'| 검사 | 결과 | 상세 |',
'|---|---:|---|',
]
for name, ok, detail in checks:
safe_detail = detail.replace('|', '\\|').replace('\n', ' ')[:500]
lines.append(f'| {name} | {"PASS" if ok else "FAIL"} | {safe_detail} |')
VALIDATION.write_text('\n'.join(lines) + '\n', encoding='utf-8')
print(f'{status}: {passed}/{len(checks)} checks passed')
if failed:
for name, ok, detail in checks:
if not ok:
print(f'FAIL: {name}: {detail}')
sys.exit(1)