diff --git a/docs/release/2026-08-19-release-gate-verification.md b/docs/release/2026-08-19-release-gate-verification.md index df217cd..3fa0f94 100644 --- a/docs/release/2026-08-19-release-gate-verification.md +++ b/docs/release/2026-08-19-release-gate-verification.md @@ -214,3 +214,181 @@ SchemaManagementException: Schema-validation: missing table [fs_cleanup_item] 5. **나머지 16개 오퍼레이션** — 백엔드 HANDOFF.md가 지적한 생성 union 5종의 Jackson 파손 전략 결정이 선행. 6. 배포 레이어 (P1-5·6·7) — nginx/CDN에 보안 헤더·캐시 정책 적용, 프론트엔드 이미지. 7. robots.txt로 Studio 차단 (P1-8). + +--- + +# 2차 검증 — 로컬에서 가능한 항목 완주 (2026-08-19) + +1차에서 "물리적으로 불가능"이라 분류했던 항목 중 상당수가 실제로는 검증 가능했다. +Studio는 mock 게이트웨이가 18개 오퍼레이션을 **전부** 구현하고 있고(낙관적 락·검증 +staleness·미리보기 만료·경고 승인·멱등성 포함), Public은 정적 콘텐츠지만 UI 동작 +항목은 그대로 검증된다. 아래는 그 재검증 결과다. + +## 1차 판정 정정 + +### 정정 1 — P0-6 은 결함이 아니다 + +`prod` 프로파일이 커밋된 `src/.env`로 부팅하지 않는 것은 **의도된 설계**다. +`application-prod.yml`이 문서화한 5개 startup validator가 개발용 값을 거부한다: + +``` +JpaSchemaSafetyValidator ddl-auto must be none|validate (exit 71) +FlywayProdSafetyValidator baseline-on-migrate / out-of-order / clean 비활성 +StartupSafetyValidator error-detail 노출 · body-capture 로깅 off +PostgreSqlTransportSecurityValidator pgJDBC sslmode=verify-full +PersistenceVendorProdSafetyValidator vendor·URL 모두 H2 금지 +``` + +실측으로 2개가 순서대로 발화하는 것을 확인했다: + +``` +exit=71 error.code=PROFILE_MISMATCH + "prod profile requires APP_DATASOURCE_DDL_AUTO ... to be none or validate, + but was update; Flyway is the production schema writer" + +ddl-auto=none 으로 넘긴 뒤: + "prod PostgreSQL transport requires pgJDBC sslmode=verify-full" +``` + +즉 **§9 "운영 환경에서 개발용 설정이 활성화되지 않는다"는 PASS**다. +남는 진짜 갭은 별개다 — **운영 값 세트가 저장소에도 배포 시스템에도 아직 없다**(P1로 이동). + +### 정정 2 — P0-7 의 심각도 하향 + +`ddl-auto=validate`가 `fs_cleanup_item` 누락으로 실패하는 것은 사실이나, +prod는 `none|validate` **둘 다** 허용하므로 `none`으로 부팅할 수 있다(실제로 그렇게 기동해 검증했다). +따라서 출시 차단은 아니고, **스키마 검증을 포기해야 한다는 제약**으로 남는다 → P1. + +### 정정 3 — 1차의 오탐 2건 + +- **"Public UI에 Studio 노출"** — 오탐. 매칭된 "Studio"는 전부 게시된 릴리스 노트의 본문 + 텍스트였다("TechLog Public·Studio 경계를 확정했습니다"). 실제 `a[href^="/studio"]`는 + 모든 Public 화면에서 **0건**. §1.3 PASS. +- **"코드 블록 미표시"** — 오탐. `code-block.tsx`가 `
` + + `
`을 렌더하고 CSS가 `overflow-x:auto`·`max-width:100%`를
+  준다. 정적 공개 문서에 CODE_BLOCK이 0건이라 발견하지 못한 것이며, Studio 편집기에
+  직접 넣어 확인하니 정상 렌더되고 페이지 가로 오버플로도 없었다. §4 PASS.
+
+## 새로 발견한 결함
+
+| # | 항목 | 근거 |
+|---|---|---|
+| **N-1** | **로그아웃할 방법이 없다** | `signOut` 포트와 `app-shell.tsx`의 세션 버튼(`action.signOut`="로그아웃")은 존재하지만, **TechLog는 자체 셸(`public-shell.tsx` + Studio 셸)을 쓰고 `AppShell`을 렌더하지 않는다.** 로그인 후 Public·Studio 어느 화면에서도 로그아웃 버튼이 없다. §1.4 "로그아웃", "로그아웃 후 보호된 데이터가 UI 상태에 남지 않는다" 미충족 |
+| **N-2** | **중복 관계 생성이 방지되지 않는다** | 같은 대상을 두 번 연결해 저장해도 경고가 없다. 계약에 `uniqueItems` 제약이 없고(`relations: maxItems 20`뿐), `validate-working-copy.ts`도 slug 중복만 검사한다(`SLUG_DUPLICATE`). **백엔드를 구현해도 계약이 허용하므로 같은 결과가 난다.** §3.2 미충족 |
+| **N-3** | **CLS 0.192 (기준 0.1)** | 원인 단일: `FOOTER.site-footer`가 t=538ms에 0.1922 이동. 나머지 shift는 0.0001. 세 라우트 모두 동일 값 → 앱 셸 마운트 시점의 footer 점프. §4 "주요 화면의 Layout Shift가 없다" 미충족 |
+| **N-4** | **`navigation_path`(slug 조회)에 인덱스가 없다** | 20,000행 기준 `Seq Scan`, `Rows Removed by Filter: 19999`, **236ms**. `enable_seqscan=off`로도 인덱스를 못 쓴다 → 존재하지 않는다. 체크리스트가 명시한 "Slug 조회" 쿼리 패턴 |
+| **N-5** | 검색 trgm 인덱스가 플래너에 선택되지 않음 | GIN trgm 인덱스는 존재하고 강제하면 3.96ms로 동작하나, 20k 규모에서 플래너가 Seq Scan(10.3ms)을 고른다. 운영 규모에서 재확인 필요 |
+| **N-6** | `/api/v3/api-docs`가 500 | `/swagger-ui`·`/v3/api-docs`는 404로 미배포(정상)인데, path prefix가 붙은 `/api/v3/api-docs`만 500 INTERNAL_ERROR |
+
+## 검증 결과 — 절별
+
+### §1.2 Routing · §1.3 경계 · §2 기능 — 27/27 PASS
+
+```
+§1.2  존재하지 않는 Case / 잘못된 explore kind / 없는 프로젝트 / 없는 릴리스
+      → 전부 "페이지를 찾을 수 없습니다."
+§1.2  Not Found 화면, Back/Forward (/explore→/projects→back→forward) 정상
+§1.3  Public 6개 화면에 studio 링크 0건, Draft 표식 0건
+§2.1  탐색 목록 6건 · 중복 0 · 필터 적용 6→2건
+§2.2  검색창 열림 / Focus 이동 / Overlay 겹침 없음 / 입력 중 과요청 0
+      결과 없음 UI / ESC 닫기 / 빈 검색어 정책 / 결과 클릭 → 상세 이동
+§2.3  프로젝트 목록 2건 · 상세("Backend Skeleton") · 포함 문서 6건
+§2.4  변경 기록 목록·상세, 연결 문서 7건, 시간순 정렬 일관
+```
+
+### §3 Studio — 20/22 PASS (mock 기준)
+
+```
+§3.1  새 문서(유형 4종) → 편집 진입 → 저장 → 상태 전달        PASS
+§3.1  저장 버튼 3연타 → 문서 수 8→9 (증가 1)                 PASS ← 멱등성 실동작
+§3.1  미저장 변경 이동 경고 [머무르기/변경 버리기/저장 후 이동]   PASS
+§3.1  머무르기 후 입력값 보존                                PASS
+§3.1  검증 화면("저장본 검증") / 게시 화면("게시 준비")         PASS
+§3.2  관계 추가·순서 이동·삭제, 대상 카탈로그 4건               PASS
+§3.2  중복 관계 방지                                       FAIL (N-2)
+§3.3  즉시 미리보기 렌더 / Public Preview 화면               PASS
+§3.3  게시 기록 8건 · 게시 취소 버튼 3개                      PASS
+§20   저장 충돌(409) 사용자 안내                             PASS
+콘솔 오류 0건
+```
+
+문서 **삭제**는 계약에 오퍼레이션 자체가 없다(`deleteStudioAsset`만 존재). §3.1의 "삭제"는 설계 범위 밖.
+
+### §4 UX/UI · §5 접근성 · §6 성능 — 15/18 PASS
+
+```
+§4  Layout Shift                    FAIL  CLS=0.1924 (N-3)
+§4  Header가 콘텐츠를 가리지 않음      PASS
+§4  긴 제목(150자)/긴 본문/긴 URL      PASS  scrollWidth==clientWidth 1440
+§4  코드 블록 (pre overflow-x:auto)   PASS
+§5  Modal Focus 이동 / role=dialog / Focus Trap / 닫은 뒤 복귀   PASS
+§5  키보드 순회 19개 요소 · Focus 표시 전부 존재                  PASS
+§6  긴 문서 렌더링 296ms                                      PASS
+§6  이미지 lazy loading · width/height 명시                    PASS
+§6  동일 요청 중복 0 · 2초간 DOM 변경 0건(render loop 없음)       PASS
+§6  검색 21자 입력+반영 847ms                                  PASS
+```
+
+### §14 데이터베이스
+
+```
+Constraint  PK 33 · FK 36 · UNIQUE 18 · CHECK 89 · NOT NULL 267 · PK 없는 테이블 0   PASS
+Index 실행계획 (20,000행 기준)
+  Public 목록(최신순)   Index Scan  idx_public_latest        0.113ms  PASS
+  유형별 조회           Index Scan  idx_public_type          0.129ms  PASS
+  Topic별 조회          Bitmap Index Scan idx_public_topic    0.229ms  PASS
+  검색(trgm)            Seq Scan (인덱스 미선택)              10.3ms   주의 (N-5)
+  slug 조회             Seq Scan (인덱스 부재)               236ms    FAIL (N-4)
+```
+
+`public_resource_projection`의 인덱스들이 `WHERE publication_state='ACTIVE' AND
+visibility='PUBLIC'` 부분 인덱스로 정의되어 있다 — Public/Private 경계를 인덱스 수준에서
+강제하는 좋은 설계다(§12를 구현할 때 그대로 활용 가능).
+
+### §19 악용 방지 · §28 Swagger
+
+```
+pagination 최대 크기 (limit=1000)     422 REQUEST_VALIDATION_FAILED   PASS
+q 길이 제한 (500자)                   422 REQUEST_VALIDATION_FAILED   PASS
+Rate Limit                            APP_RATE_LIMIT_ENABLED=false    미적용
+대용량 Body                           쓰기 엔드포인트 부재로 검증 불가
+/swagger-ui, /v3/api-docs             404 (미배포)                    PASS
+/api/v3/api-docs                      500                             주의 (N-6)
+```
+
+### §26 의존성 장애
+
+```
+PostgreSQL Down   catalog 503 DB_UNAVAILABLE(retryable) 30s · readiness 503 DOWN
+                  liveness 200 UP 유지 · 복구 후 27ms 정상                  PASS
+Keycloak  Down    JWKS 캐시로 기존 토큰 32ms/200 · 잘못된 서명 21ms/401
+                  readiness 200 UP 유지(외부 IdP를 readiness에 걸지 않음)
+                  복구 후 정상                                            PASS
+Backend   단절    Public 화면 정상 유지(정적 소스)                          PASS
+MinIO / Redis     해당 없음(미배선)
+```
+
+### §0 · §9 설정
+
+```
+src/.env 가 git에 커밋되어 있다 — 값은 local 프로파일용이지만 .gitignore에 .env가 없어
+구조적으로 막혀 있지 않다. Redis HMAC은 secret://environment/... 간접 참조를 쓴다(좋은 패턴).
+prod 5개 validator 실동작 확인 (정정 1)
+show-sql=false · 로그에 토큰/쿠키/비밀번호 0건 · user= 는 가명화 해시
+```
+
+## 남은 것 — 로컬에서 불가능
+
+| 절 | 이유 |
+|---|---|
+| §12 Public/Private 경계 | Public 엔드포인트·문서 엔드포인트 부재 |
+| §15 N+1 / JPA Query | Tech Log에 JPA 리포지토리 0건 (catalog는 raw JDBC 단일 쿼리) |
+| §16 Transaction | 쓰기 유스케이스 부재 |
+| §17 파일/Object Storage | 업로드 엔드포인트·스토리지 배선 부재 |
+| §18 HTTPS/HSTS/Redirect | TLS 종단 필요 |
+| §22 Grafana·Loki 대시보드 | 관측 스택 필요 (수집 측 127개 메트릭은 확인 완료) |
+| §24 Kubernetes | 매니페스트·오케스트레이터 부재 |
+| §25 Ingress 라우팅 · X-Forwarded-* | 리버스 프록시 필요 |
+| §27 Backup / Restore | 실제 볼륨·운영 DB 필요 |
+| §30 Production Smoke Test | 운영 환경 부재 |
+| §1.4 세션 만료 · 토큰 만료 후 프론트 동작 | demo 어댑터에 만료 개념이 없음 (외부 IdP 연동 필요) |