Files
tech-log-frontend/docs/release/2026-08-19-release-gate-verification.md
T
DongHyeonkaandClaude Opus 5 5fe355483e docs: complete the locally-verifiable half of the release checklist
Second pass. The first pass called several sections impossible; most were
not. The Studio mock implements all 18 contract operations with real
semantics (optimistic locking, validation staleness, preview expiry,
warning acknowledgement, idempotency), so the whole authoring flow is
exercisable without a backend, and the Public surface's UI behaviour is
testable against its static content.

Corrections to the first pass:
  - prod refusing to boot on the committed .env is the design working,
    not a defect: five startup validators reject development values, two
    of which were observed firing in order. The real gap is that no
    production value set exists anywhere yet.
  - ddl-auto=validate failing is a constraint, not a blocker -- prod
    accepts none as well, which is how this run booted.
  - two first-pass findings were false positives: the "Studio exposure"
    hits were release-note body text (zero /studio links on any public
    page), and the missing code block was a test artifact (no static
    document contains one; injecting one renders correctly).

New defects found:
  - no way to log out: the session button lives in the template's
    AppShell, which TechLog never renders -- it supplies its own shells.
  - duplicate relations are not prevented, at the contract level, so a
    backend implementation would inherit the same hole.
  - CLS 0.192, from a single footer shift at t=538ms.
  - no index on navigation_path: 236ms seq scan over 20k rows for the
    slug lookup the checklist names as a query pattern.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 17:28:05 +09:00

25 KiB
Raw Blame History

Tech Log 운영 출시 전 체크리스트 — 실측 검증 보고서

검증일 2026-08-19 · 방식 두 저장소를 로컬에서 실제 기동해 엔드포인트·브라우저 단위로 실측

대상 위치 리비전
Frontend tech-log-frontend main eb86708fix/release-gate-frontend fff5e6f
Backend tech-log-backend develop ab0447a
Keycloak 로컬 컨테이너 local-keycloak 26.7.0 (:18080)
PostgreSQL 로컬 컨테이너 techlog-pg 16.15 (:5433)

요약 판정: 출시 보류 (P0 미충족)

체크리스트 §31의 P0 항목 중 인증 우회 불가 · 인가 우회 불가 · Studio 주요 기능 정상 · Publish 정상이 현재 충족되지 않는다. 아래 근거는 전부 실행 결과다.

가장 중요한 구조적 사실

백엔드는 계약(studio-v1.yaml)이 선언한 18개 오퍼레이션 중 2개만 구현되어 있다.

상태 오퍼레이션
구현됨 (2) getStudioSession, listStudioCatalog
미구현 (16) getStudioDashboard, listStudioDocuments, createStudioDocument, getStudioDocument, saveStudioDocument, validateStudioDocument, getCurrentStudioPreview, createStudioPreview, publishStudioDocument, listStudioPublications, unpublishStudioPublication, getStudioPublicationSnapshot, listStudioAssets, uploadStudioAsset, getStudioAsset, updateStudioAsset, deleteStudioAsset

또한 Public 읽기 엔드포인트는 계약에 아예 없다. studio-v1.yaml은 Studio 전용이고, 프론트엔드의 Public 화면(/, /explore, /projects, /releases, 문서 상세)은 src/features/tech-log/adapters/static/public-content.ts번들에 컴파일된 정적 콘텐츠를 읽는다.

따라서 체크리스트의 다음 절은 검증 대상 자체가 존재하지 않는다: §2(탐색·검색·프로젝트·변경기록의 백엔드 연동), §3.1~3.3(문서 작성·관계·Publish), §12(Public/Private 데이터 경계), §17(파일/Object Storage), §29(E2E 시나리오).


P0 — 출시 차단 결함

P0-1. Studio 라우트가 인증을 검사하지 않았다 — 수정 완료

TECH_LOG_ROUTE_REGISTRY가 모든 TechLog 라우트를 access: "public"으로 등록하고 있었다. 라우터에 decideRouteAccessForDefinition 가드가 존재하지만 Studio에 대해 무력화된 상태였다.

운영 프로파일 빌드(AUTH_MODE=external)로 실측한 수정 전:

/studio            http=200  h1="작업 흐름"      ← 비로그인 상태에서 Studio UI 렌더링
/studio/documents  http=200  h1="작업본"
/studio/assets     http=200  h1="Asset"

spec.layoutGroup === "STUDIO"에서 access를 유도하도록 수정한 뒤:

/studio            http=200  h1="로그인 연동이 필요합니다."
/studio/documents  http=200  h1="로그인 연동이 필요합니다."
/  , /explore      변화 없음

로그인 후 원래 요청 화면으로 복귀하는 것도 확인했다(/studio/documents → 로그인 → 작업본).

커밋 fff5e6f.

P0-2. 백엔드 Studio API에 인가 검사가 없다 — 미해결

SecurityConfiganyRequest().authenticated()로 끝나고, Studio 컨트롤러에 @RequiresPermission 계열 애노테이션이 하나도 없다.

Keycloak에 Studio 권한이 없는 사용자(plain, realm role plain-user)를 만들어 확인:

GET /api/v1/studio/catalog?type=TOPIC
  studio 사용자 (studio-author) → HTTP 200
  plain  사용자 (권한 없음)      → HTTP 200   ← 인가 우회

체크리스트 §11 "인증된 사용자라고 해서 무조건 Studio API를 호출할 수 있지 않다", §31 P0 "인가 우회 불가" 미충족.

P0-3. 모든 Studio 경로가 /api/api/v1/...에 매핑된다 (Double Prefix) — 미해결

PresentationWebConfigconfigurer.addPathPrefix("/api", c -> true)로 전 컨트롤러에 /api를 붙이는데, Studio 컨트롤러는 @GetMapping("/api/v1/studio/...")로 이미 /api를 포함해 선언한다.

GET /api/v1/studio/catalog       → 404 ROUTE_NOT_FOUND
GET /api/api/v1/studio/catalog   → 200
GET /api/v1/studio/session       → 404 ROUTE_NOT_FOUND
GET /api/api/v1/studio/session   → 503

프론트엔드는 계약대로 /api/v1/studio/...를 호출하므로 현재 상태로는 단 한 건도 연결되지 않는다. 체크리스트 §25 "/api Prefix 처리에서 Double Prefix가 발생하지 않는다" 미충족.

P0-4. getStudioSession이 항상 503을 반환한다 — 미해결

auth-mode: jwt(저장소 기본값, src/.env:115)에서 SecurityConfigcsrf.disable()CsrfFilter를 제거하므로 CsrfToken 파라미터가 항상 null이고, 컨트롤러는 이를 STUDIO_UNAVAILABLE(503)로 정직하게 보고한다.

GET /api/api/v1/studio/session  (유효한 studio 토큰)
→ 503 {"code":"STUDIO_UNAVAILABLE","category":"TRANSIENT_DEPENDENCY","retryable":true}
   로그: "CSRF token unavailable: CSRF protection is disabled for the active auth-mode"

프론트엔드 HTTP 모드는 getStudioSession으로 CSRF 토큰을 받아 부트스트랩하므로, 이 한 건 때문에 Studio HTTP 경로 전체가 시작조차 못 한다. auth-mode: redis-session에 필요한 세션 빈이 저장소에 없다는 점은 백엔드 HANDOFF.md도 명시하고 있다.

P0-5. main 브랜치의 dev 부팅이 깨져 있었다 — 수정 완료

eb86708(계약 3.0.0 머지) 이후 public/release-manifest.json이 2.0.0으로 남아 부팅 시 contract-set 검증이 fail-closed → 빈 화면. 이전에 한 번 겪은 것과 같은 실패 양식이다.

setDigest drift: manifest sha256:e0da7765…, build sha256:261ac630…
package drift: manifest 2.0.0 / ce2e748  vs  build 3.0.0 / b20d7a2

generate:dev-release-manifest로 재생성하고, 같은 값을 하드코딩하던 tests/runtime-schema/release-manifest.test.ts도 함께 갱신했다. 커밋 fff5e6f.

P0-6. 커밋된 .env로는 prod 프로파일이 부팅하지 않는다 — 미해결

src/.env:140APP_DATASOURCE_DDL_AUTO=update인데, application-prod.yml이 문서화한 JpaSchemaSafetyValidator는 prod에서 none|validate만 허용하고 위반 시 exit 71로 종료한다.

P0-7. ddl-auto=validate로는 PostgreSQL에서 부팅하지 않는다 — 미해결

SchemaManagementException: Schema-validation: missing table [fs_cleanup_item]

PostgreSqlPersistenceConfig가 Flyway 위치를 classpath:db/migration/postgresql로 고정해 db/migration/jpa/fileserver 트리가 한 번도 적용되지 않는데, 해당 JPA 엔티티는 스캔된다. ddl-auto=update가 이 사실을 가려 온 것이고, prod가 요구하는 validate로 바꾸는 순간 드러난다. (본 검증은 ddl-auto=none으로 우회해 진행했다.)


P1 — 출시 전 해결 권장

# 항목 실측 근거
P1-1 Keycloak realm 구성이 두 저장소 어디에도 없다 compose에 keycloak 서비스 없음, realm export 파일 없음. 검증을 위해 ca-skeleton realm·클라이언트·audience 매퍼·테스트 사용자를 수기로 생성해야 했다. §27 "Keycloak Realm 설정을 복원할 수 있다" 미충족
P1-2 프론트엔드에 로그인 구현이 없다 OIDC/Keycloak 클라이언트 코드 0건. AUTH_MODE=external은 호스팅 페이지가 window.__CA_FRONTEND_AUTH_OWNER__를 주입하기를 기대하며, 없으면 createUnavailableSessionAdapter가 "로그인 연동이 필요합니다"를 띄운다. §1.4 인증 항목 전부 검증 불가
P1-3 production 런타임 설정이 플레이스홀더 API_BASE_URL: https://api.example.com/, TELEMETRY_ENDPOINT: https://telemetry.example.com/v1/events
P1-4 Rate Limit 비활성 APP_RATE_LIMIT_ENABLED=false, APP_RATE_LIMIT_PROVIDER=disabled. 60회 연속 호출 전부 200
P1-5 보안 헤더를 적용하는 주체가 없다 config/hosting/security-headers.json에 CSP·HSTS·X-Frame-Options 등이 정의돼 있으나 dist/server.mjs하나도 적용하지 않는다. verify:hosting-headers는 기본적으로 fixture 모드로 동작해 실 서버를 검사하지 않는다
P1-6 캐시 정책도 미적용 cache-policy.json/assets/*public, max-age=31536000, immutable을 요구하나 실제 응답은 전부 no-cache
P1-7 프론트엔드 배포 아티팩트 부재 Dockerfile·nginx conf·compose 없음. dist/server.mjs는 프리뷰용이지 운영 파일 서버가 아니다
P1-8 robots.txt / sitemap.xml 없음 Studio 경로가 검색 엔진에 차단되지 않는다. §7 미충족
P1-9 Open Graph·canonical 메타데이터 없음 dist/index.htmlog:*·canonical 없음. <title>은 라우트별로 정상 동작하나 런타임에 설정되므로 JS를 실행하지 않는 공유 미리보기 크롤러에는 "Tech Log" 고정값만 노출된다
P1-10 DB 타임아웃 30초 APP_DATASOURCE_CONNECTION_TIMEOUT=30000. application.yml이 문서화한 D2 fail-fast 의도(기본 5s)와 어긋난다. 프론트엔드 REQUEST_TIMEOUT_MS=10000이므로 DB 장애 시 프론트가 항상 먼저 끊겨 DB_UNAVAILABLE 503을 보지 못한다
P1-11 Tech Log Asset의 Object Storage 배선 없음 objectstorage 어댑터는 템플릿 자산으로 존재하나 techlog 참조 0건, MinIO/S3 환경변수 0건, uploadStudioAsset 엔드포인트 미구현

검증되어 통과한 항목

Frontend

항목 결과
Production Build PASS (local·production 프로파일 모두)
TypeScript compile PASS (check:types 6개 프로젝트)
ESLint PASS (수정 후 0 error)
전체 테스트 1,818 passed / 16 skipped / 1 기존 flake (provider-guardian-transaction — 단독 실행 2회 모두 PASS, 부하 의존)
architecture / contract / dev-release-manifest / browser-security 게이트 PASS
Production 번들에 dev·localhost URL 없음 PASS (localhost·127.0.0.1 0건, .local 매치는 전부 locale/localeCompare)
Production 번들에 Mock API 미포함 PASS (createMockStudioGateway 0건)
Source Map 비공개 PASS (.map 0개)
Route 단위 Lazy Loading PASS (30 청크, 총 954 KB / 최대 569 KB)
SPA 라우팅·새로고침 PASS (열거형 allowlist 방식. 존재하지 않는 문서 경로는 의도적으로 404)
Route별 <title> PASS (탐색 · Tech Log, 프로젝트 · Tech Log …)
반응형 PASS — 360/414/768/1440 × 6개 Public 라우트 24개 조합 전부 가로 스크롤 없음
접근성 PASS — axe(wcag2a/2aa/21a/21aa) serious+critical 0건 (Public 6 + Studio 4 라우트). h1 정확히 1개, heading 건너뜀 없음, alt 누락 0, 레이블 없는 icon button 0
로그인 흐름 PASS (게이트 → 로그인 → 원래 화면 복귀)

Backend

항목 결과
Production Profile Build PASS — :app-bootstrap:bootJar 성공
전체 테스트 PASS — 3,530 tests / 0 failures / 7 skipped (BUILD SUCCESSFUL 8m 9s). app-bootstrap 797 · application-core 568 · cache-redis 423 · fileserver 398 · inbound-web 341 · httpclient 283 · shared-contract 224 · objectstorage 140 · persistence-jpa 122 · 그 외
Docker Image Build PASS — 623MB. BUILD_VERSION/GIT_SHA/SOURCE_URL build-arg를 강제하는 provenance 게이트가 있어 인자 없이는 의도적으로 실패한다
Production Image 실제 실행 PASS — 컨테이너에서 14.5초 기동, healthcheck 200 · readiness 200 · catalog 200(실데이터 2건)
Flyway 마이그레이션 (신규 DB, 처음부터) PASS — 6개 적용, V7 techlog core 포함, 테이블 33개 생성
응답 봉투 일관성 PASS — {success,data,error,meta} 전 경로 동일
HTTP 상태 코드 PASS — 401 / 404 / 405 / 422 / 500 / 503 모두 적절
인증 오류 코드 분리 PASS — AUTH_TOKEN_MISSING / AUTH_TOKEN_MALFORMED / AUTH_TOKEN_INVALID_SIGNATURE / AUTH_TOKEN_EXPIRED
Validation PASS — 잘못된 enum·필수 누락은 422 + fieldErrors, limit 상·하한 강제
SQL Injection PASS — ' OR 1=1-- 파라미터 바인딩되어 빈 결과
Visibility 필터 PASS — ARCHIVED 토픽이 catalog 결과에서 제외됨
CORS PASS — 허용 origin 200 + Allow-Credentials: true, 미허용 origin 403, 와일드카드 없음
보안 헤더 PASS — X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Cache-Control: no-store
오류 정보 노출 PASS — Stack trace·SQL·내부 클래스명 모두 미노출 (details: null)
로그 위생 PASS — 토큰·Authorization·쿠키·비밀번호 0건. user=는 가명화 해시
추적성 PASS — 모든 요청 로그에 req=·trace=, http_request method= uri_template= status= duration_ms=
Metrics PASS — Prometheus 127개 메트릭 패밀리 (http_server_requests_seconds_bucket, jvm_gc_*, jvm_memory_*, hikaricp_connections_*)
Liveness / Readiness 분리 PASS — DB 중단 시 readiness 503 DOWN, liveness 200 UP 유지
의존성 장애 대응 PASS(동작) — DB 중단 시 무한 대기 없이 503 DB_UNAVAILABLE (retryable) 반환, DB 복구 후 27ms/정상 데이터로 자동 회복. 단 응답까지 30초 소요(P1-10)

다음 단계 (권장 순서)

  1. Double Prefix 해소 (P0-3) — 컨트롤러 매핑에서 /api를 제거하거나 addPathPrefix 대상에서 제외. 이걸 고치기 전에는 프론트-백엔드가 한 건도 연결되지 않으므로 최우선.
  2. 세션 인프라 (P0-4) — redis-session 배선. 백엔드 HANDOFF.md도 Plan 02보다 앞선 선행 작업으로 지목하고 있다.
  3. Studio 인가 (P0-2) — Studio 컨트롤러에 권한 검사 추가 + 권한 없는 사용자 403 회귀 테스트.
  4. prod 부팅 설정 (P0-6, P0-7) — .envddl-auto, fileserver 마이그레이션 위치.
  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=validatefs_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<figure class="code-block"> + <pre role="region" tabindex=0>을 렌더하고 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 연동 필요)