Skip to content

Releases: beyondwin/ReadMates

ReadMates v1.9.0

Choose a tag to compare

@beyondwin beyondwin released this 14 May 19:34

Highlights

v1.8.3 이후 누적된 변경은 호스트 알림 운영을 "상태 조회/재처리"에서 "세션별 수동 발송 계획, 미리보기, 확정, 감사"까지 확장하고, 호스트 세션 편집기에 외부에서 정리한 세션 기록 JSON 가져오기를 더하며, 테스트/빌드 runtime을 빠른 피드백 lane과 release baseline으로 분리하는 데 초점을 둡니다. DB migration V27, V28이 포함됩니다.

Added

  • 호스트 수동 알림 발송 워크벤치: /app/host/notifications에서 세션을 선택하고 NEXT_BOOK_PUBLISHED, SESSION_REMINDER_DUE, FEEDBACK_DOCUMENT_PUBLISHED 템플릿을 고른 뒤 대상 그룹(ALL_ACTIVE_MEMBERS, SESSION_PARTICIPANTS, CONFIRMED_ATTENDEES), 채널(IN_APP, EMAIL, BOTH), 멤버별 포함/제외를 조합해 알림을 발송할 수 있습니다.
  • 수동 발송 preview/confirm 계약: GET /api/host/notifications/manual/options, POST /api/host/notifications/manual/preview, POST /api/host/notifications/manual, GET /api/host/notifications/manual/dispatches를 추가했습니다. Preview는 대상 수, in-app/email 예상 건수, 이메일 선호도 skip, 이메일 누락, 중복 발송 여부를 반환합니다.
  • 수동 발송 감사 원장: notification_manual_dispatch_previews, notification_manual_dispatches 테이블을 추가했습니다. Confirm은 preview selection hash와 10분 TTL을 확인하고, 같은 preview로 중복 confirm해도 기존 dispatch 결과를 반환하도록 hardened path를 둡니다.
  • 수동 발송 E2E 커버리지: Playwright manual-notifications.spec.ts가 세션 선택, preview/confirm, 중복 재발송 확인, 피드백 문서 알림 조건, in-app 수신 확인을 검증합니다.
  • Backend test fast lanes: Gradle unitTest, integrationTest, architectureTest task를 추가하고, Gradle test JVM을 Java 21 toolchain으로 고정했습니다.
  • 프런트 Vitest v8 coverage gate: @vitest/coverage-v8를 dev dep으로 추가하고 pnpm --dir front test:coverage(= vitest run --coverage) 스크립트를 도입했습니다. Coverage thresholds는 현재 baseline에서 -2pp 정수 floor로 고정(lines/statements 87, functions 83, branches 84)했고, CI front job은 pnpm test:coverage로 전환되어 front-coverage 아티팩트를 always upload(14일 보존)합니다. 기존 실패시 frontend-reports 업로드는 유지합니다.
  • 서버 ktlint baseline gate: org.jlleitschuh.gradle.ktlint 12.1.1 Gradle plugin과 ktlint tool 1.7.1(Kotlin 2.2 호환을 위해 spec의 1.3.1에서 상향)을 도입했습니다. 기존 위반은 server/config/ktlint/baseline.xml(336 lines)로 grandfather 처리하고 신규 위반만 차단합니다. 1회성 auto-format으로 server/src 하위 329 파일이 line wrap, trailing comma, parameter newline, package decl 포맷을 baseline 기준으로 정렬됐습니다. ./gradlew check에 wiring되어 PR 회귀를 막습니다.
  • 서버 detekt baseline gate: io.gitlab.arturbosch.detekt 1.23.7과 default server/config/detekt/detekt.yml(785 lines), server/config/detekt/baseline.xml(540 lines)을 도입했습니다. detekt 1.23.x가 호스트 JDK 25에서 동작하도록 server/gradle/gradle-daemon-jvm.propertiestoolchainVersion=21 daemon JVM pin을 추가하고, detekt classpath는 Kotlin 2.0.10으로 고정합니다(Detekt* task는 jvmTarget=21). 기존 위반은 baseline grandfather, check에 wiring되어 신규 위반을 차단합니다.
  • 서버 JaCoCo line coverage gate: jacoco 플러그인(toolVersion 0.8.12)을 적용해 unitTest task에 JacocoTaskExtension을 붙여 build/jacoco/unitTest.exec를 생성하고, jacocoTestReportApplication/dto/config 클래스 디렉터리를 제외합니다. jacocoTestCoverageVerification은 LINE COVEREDRATIO minimum 0.23(현재 측정치 0.2504 − 2pp baseline)으로 고정했고, check가 verification에 의존합니다.
  • CI 서버 quality gate 통합: backend job의 기존 Test step을 ./gradlew check(ktlint + detekt + tests + JaCoCo verify) 단일 호출로 교체하고, architectureTest step을 별도 실행합니다. detekt/jacoco/ktlint report 아티팩트는 if: always()로 항상 업로드하고, 기존 실패시 backend-reports(tests/test-results) 업로드는 유지합니다.
  • 서버 CQRS read/write 패키지 분리 컨벤션 + ArchUnit 강제: note/publication/archive 의 read-side application service에 @ReadOnlyApplicationService 마커(com.readmates.shared.architecture)를 부착하고, ServerArchitectureBoundaryTest 에 두 ArchUnit 규칙을 추가했습니다 — read-only application services must not depend on mutation ports(*SavePort/*UpdatePort/*DeletePort/*WriterPort/*StorePort/*WritePort suffix 의 *.port.out.* 의존 차단) 와 read-only application services must not be Transactional. archive/application/service/MemberArchiveReviewService 는 write-side(write port 의존 + @Transactional)이므로 marker 미부착. feedback 은 mixed(upload mutation + 조회)로 분류되어 marker 미부착, 향후 분리 후보. 컨벤션 전체는 docs/development/architecture.md 의 "CQRS Read vs Write Package Split" 섹션 참고.
  • 세션 기록 JSON 가져오기: 호스트 세션 편집기에서 readmates-session-import:v1 JSON을 preview한 뒤 저장할 수 있습니다. 저장은 해당 회차의 공개 요약, 하이라이트, 한줄평, 피드백 문서를 한 번에 교체하고, HOST_ONLY 공개 범위나 세션 metadata/참석자 매칭 오류는 저장 전에 막습니다.

Changed

  • 알림 event ledger metadata 확장: host event/delivery 목록에서 source=AUTOMATIC|MANUAL과 manual dispatch metadata를 구분합니다. Host-facing 응답은 요청자 표시명, 대상 수, 예상 채널별 건수, 재발송 여부만 노출하고 raw email body는 계속 노출하지 않습니다.
  • 프런트 테스트 runtime 분리: Vitest config를 Node project와 jsdom project로 나눠 순수 model/contract 테스트가 DOM 환경을 불필요하게 띄우지 않도록 했습니다.
  • 프런트 빌드 타겟 ES2022: front/tsconfig.jsoncompilerOptions.targetES2017ES2022로 올렸습니다. Vite 8 + 모던 브라우저 대상이므로 Object.hasOwn, Error.cause, top-level await, private class fields의 불필요한 downlevel을 제거합니다. lib는 그대로 유지하여 타입 표면 변화는 없습니다.
  • Playwright worker opt-in: 기본 worker는 seeded DB 공유 때문에 1로 유지하고, PLAYWRIGHT_WORKERS 환경 변수로만 병렬 실행을 실험할 수 있게 했습니다.
  • E2E DB reset 통합: E2E helper가 generated session, invited member, Google login, manual notification artifact cleanup을 한 번의 SQL batch로 묶을 수 있게 했습니다.
  • 서버 Docker image layer 최적화: server/Dockerfileserver/Dockerfile.release가 Spring Boot layertools로 dependency/application layer를 분리하고 JarLauncher entrypoint를 사용합니다.
  • 서버 Docker build 메모리 제한: local Dockerfile builder 단계에서 Gradle max worker와 JVM 메모리 옵션을 명시해 작은 빌드 환경의 OOM 가능성을 낮췄습니다.
  • .env.example 관심사별 재그룹화: 9개 번호 섹션(Spring 서버, Auth & BFF, Frontend / Cloudflare Pages, Local MySQL, Redis, Kafka & 알림, SMTP, OCI Compose stack, Legacy / rollback)으로 정리하고 인라인 주석을 추가했습니다. 키 이름·기본값·placeholder 포맷 변경 없음, 중복 표기되던 READMATES_BFF_SECRET(S) 라인은 Auth & BFF 섹션으로 통합했습니다.
  • 프런트 테스트 co-location 컨벤션: 신규 테스트는 대상 소스 옆 *.test.ts(x)로 두는 컨벤션을 front/AGENTS.md에 명시했습니다. Vitest config include 글롭을 src/**/*.test.{ts,tsx}, features/**/*.test.{ts,tsx}, shared/**/*.test.{ts,tsx}로 확장하고 기존 tests/unit/** 패턴과 node/jsdom project 분리는 그대로 유지합니다. 기존 테스트는 server testcontainer fixture 호환성을 위해 이동하지 않습니다.
  • 프런트 server state를 TanStack Query v5로 이관 시작: 앱 루트(front/src/main.tsx)에 단일 QueryClientQueryClientProvider로 주입하고, 라우터(front/src/app/router.tsx)는 createReadmatesRouter()에서 {router, queryClient}를 반환하도록 바뀌어 loader와 컴포넌트가 같은 cache를 공유합니다. 첫 reference migration은 호스트 초대 목록(/app/host/invitations)으로, list query는 host-invitation-queries.tsqueryOptions를 통해 useQuery로 읽고, create/revoke는 useMutation + targeted invalidation으로 처리하며, loader는 setQueryData로 SSR 친화적 hand-off를 수행합니다. 마이그레이션 진행 상황과 다음 단계는 docs/development/server-state-migration.md에 정리합니다.
  • 프런트 router.tsx variant별 모듈 분리: 단일 파일에 모여 있던 route 정의를 variant별 모듈로 분리했습니다. front/src/app/router.tsx는 24-line composition root로 축소되어 createReadmatesRouter()에서 QueryClient를 생성하고 sub-route 모듈을 조합하는 역할만 합니다. 실제 route 정의는 front/src/app/routes/{public,auth,member,host}.tsx로 옮겨져 각각 publicRoutes(), authRoutes(), memberRoutes(), hostRoutes(queryClient)로 export됩니다. URL 라우팅 매트릭스, guard(RequireAuth/RequireHost/RequireMemberApp/RequirePlatformAdmin), errorElement, lazy() 경계는 그대로 유지됩니다.

Deployment Notes

  • DB migration: V27(notification_manual_dispatch_previews, notification_manual_dispatches), V28(preview consumed 상태와 preview-dispatch 1:1 제약) 적용 필요.
  • 새 환경 변수: 없음.
  • 배포 순서: 서버 먼저 배포해 새 API와 migration을 적용한 뒤 프론트엔드를 배포합니다. 구버전 프론트는 새 수동 발송 API를 호출하지 않으므로 서버 선배포가 안전합니다.
  • 운영 확인: 배포 후 호스트 알림 운영 페이지에서 수동 발송 preview가 대상 수와 경고를 반환하는지, confirm 후 event ledger에 source=MANUAL row가 생기는지, Kafka/SMTP가 꺼진 환경이면 notification_event_outboxPENDING row가 남는지 확인합니다.

Verification

  • pnpm --dir front lint — exit 0. 로컬 ignored/generated front/coverage report의 unused eslint-disable warning 1건이 출력됐지만 error는 없었습니다.
  • pnpm --dir front test — 60 files / 761 tests passed.
  • pnpm --dir front build — exit 0.
  • pnpm --dir front test:e2e — 28 tests passed.
  • ./server/gradlew -p server clean test — BUILD SUCCESSFUL.
  • pnpm --dir front zod:export-fixtures && git diff --exit-code front/tests/unit/__fixtures__/zod-schemas/ — fixture diff 없음.
  • ./scripts/build-public-release-candidate.sh && ./scripts/public-release-check.sh .tmp/public-release-candidate — passed, gitleaks found no leaks.
  • git diff --check -- CHANGELOG.md README.md docs/deploy/README.md docs/deploy/release-publish-runbook.md docs/development/release-management.md — 출력 없음.
  • 변경 문서 대상 공개 안전 스캔(rg 정규식) — 실제 secret, private host, 로컬 절대 경로 없음. 일반 path와 placeholder만 확인됨.

ReadMates v1.8.3

Choose a tag to compare

@beyondwin beyondwin released this 13 May 06:45

Highlights

ReadMates v1.8.3은 v1.8.2 서버 이미지 Trivy 스캔에서 남은 Netty DNS codec 취약점 한 건을 해소하는 패치 릴리스입니다. DB migration 없음. 사용자 기능 변경 없음.

Fixed

  • 서버 이미지 Trivy 스캔 Netty 잔여 항목 복구: io.netty:netty-codec-dns를 4.2.13.Final로 고정해 CVE-2026-42579가 포함된 4.2.12.Final 런타임 jar가 이미지에 들어가지 않도록 했습니다.

Deployment Notes

  • DB migration: 없음.
  • 배포 순서: v1.8.3 태그 이미지의 Trivy 스캔과 release tag promotion이 성공한 뒤 OCI compose stack에 반영하고, 같은 태그의 Cloudflare Pages 프론트엔드 배포 상태를 확인합니다.
  • v1.8.2 주의: v1.8.2 main CI와 프론트엔드 배포는 통과했지만, 서버 이미지 promotion은 Netty DNS codec 스캔 실패로 완료되지 않았습니다. 운영 서버에는 v1.8.3 이미지를 사용하세요.

Verification

  • ./server/gradlew -p server dependencyInsight --dependency netty-codec-dns --configuration runtimeClasspath — Netty DNS codec 4.2.13.Final 확인.
  • ./server/gradlew -p server clean test bootJar — BUILD SUCCESSFUL.
  • ./scripts/build-public-release-candidate.sh && ./scripts/public-release-check.sh .tmp/public-release-candidate — passed, gitleaks found no leaks.
  • v1.8.2 Deploy Server Image workflow의 Trivy 로그에서 CVE-2026-42579 단일 HIGH 실패 원인 확인 후 반영.

ReadMates v1.8.2

Choose a tag to compare

@beyondwin beyondwin released this 13 May 06:33

Highlights

ReadMates v1.8.2는 GitHub Actions의 ShellCheck 0.9.0 기준에서 v1.8.1 main CI Scripts 잡이 실패한 문제를 해소하는 패치 릴리스입니다. DB migration 없음. 사용자 기능 변경 없음.

Fixed

  • Scripts CI ShellCheck 0.9.0 호환성 복구: public release fixture 검증 스크립트의 cat | sort 파이프를 직접 sort "$file" 호출로 바꿔 CI ShellCheck 경고를 제거했습니다.

Deployment Notes

  • DB migration: 없음.
  • 배포 순서: v1.8.2 태그 이미지의 서버 스캔/promotion이 성공한 뒤 OCI compose stack에 반영하고, 같은 태그의 Cloudflare Pages 프론트엔드 배포 상태를 확인합니다.

Verification

  • shellcheck scripts/*.sh deploy/oci/*.sh — exit 0.
  • ./scripts/build-public-release-candidate.sh && ./scripts/public-release-check.sh .tmp/public-release-candidate — passed, gitleaks found no leaks.
  • ./server/gradlew -p server clean test bootJar — v1.8.1 서버 의존성 패치 후 BUILD SUCCESSFUL.
  • GitHub Actions main CI에서 v1.8.1 Scripts 잡이 ShellCheck 0.9.0 SC2002로 실패한 로그 확인 후 반영.

ReadMates v1.8.1

Choose a tag to compare

@beyondwin beyondwin released this 13 May 06:30

Highlights

ReadMates v1.8.1은 v1.8.0 태그의 서버 이미지가 Trivy 취약점 스캔에서 중단된 문제를 해소하는 패치 릴리스입니다. DB migration 없음. 사용자 기능 변경 없음. 서버 런타임 의존성, 스크립트 CI 호환성, 공개 release fixture 안전성만 정리했습니다.

Fixed

  • 서버 이미지 Trivy 스캔 복구: Spring Boot를 4.0.6으로 올리고, Trivy가 지적한 Spring Security, Jackson 3, Tomcat, lz4 계열 취약 런타임 의존성이 고정/교체되도록 빌드 설정을 조정했습니다. 확인된 런타임 클래스패스는 Spring Boot 4.0.6, Spring Security web 7.0.5, Jackson 3.1.2, Tomcat 11.0.21, at.yawk.lz4:lz4-java 1.10.1입니다.
  • Scripts CI ShellCheck 복구: 새 ShellCheck CI가 의도적인 SSH client-side expansion과 fixture literal을 실패로 처리하지 않도록 명시하고, mkdir -p -m 사용을 정리했습니다.
  • 공개 release fixture 안전성 보강: public release fixture 검증 스크립트가 정적 secret-shaped literal을 저장소에 남기지 않도록 테스트 값을 런타임에 조립합니다.

Deployment Notes

  • DB migration: 없음.
  • 배포 순서: 서버 이미지 스캔이 통과한 v1.8.1 태그 이미지를 먼저 OCI compose stack에 반영한 뒤, 같은 태그의 Cloudflare Pages 프론트엔드 배포 상태를 확인합니다.
  • v1.8.0 주의: v1.8.0 GitHub Release와 프론트엔드 tag deploy는 생성됐지만, 서버 이미지 promotion은 Trivy 스캔 실패로 완료되지 않았습니다. 운영 서버에는 v1.8.1 이미지를 사용하세요.

Verification

  • ./server/gradlew -p server clean test bootJar — BUILD SUCCESSFUL.
  • ./server/gradlew -p server dependencyInsight --dependency org.springframework.boot:spring-boot --configuration runtimeClasspath — Spring Boot 4.0.6 확인.
  • ./server/gradlew -p server dependencyInsight --dependency spring-security-web --configuration runtimeClasspath — Spring Security web 7.0.5 확인.
  • ./server/gradlew -p server dependencyInsight --dependency jackson-core --configuration runtimeClasspath — Jackson 3.1.2 확인.
  • ./server/gradlew -p server dependencyInsight --dependency lz4-java --configuration runtimeClasspathat.yawk.lz4:lz4-java 1.10.1 확인.
  • ./server/gradlew -p server dependencyInsight --dependency tomcat-embed-core --configuration runtimeClasspath — Tomcat 11.0.21 확인.
  • for f in scripts/*.sh deploy/oci/*.sh; do bash -n "$f"; done && shellcheck scripts/*.sh deploy/oci/*.sh — exit 0.
  • ./scripts/build-public-release-candidate.sh — public release candidate built at .tmp/public-release-candidate.
  • ./scripts/public-release-check.sh .tmp/public-release-candidate — passed, gitleaks found no leaks.
  • git diff --check — 출력 없음.

ReadMates v1.8.0

Choose a tag to compare

@beyondwin beyondwin released this 13 May 06:18

Highlights

ReadMates v1.8.0은 v1.7.0 이후 누적된 운영자/사용자 가시 변경을 한 묶음으로 정리합니다. 핵심은 (1) OCI compose 배포가 attempt-stage ledger와 이미지 verification, post-deploy watch를 자동으로 실행하도록 굳어진 운영 워크플로 채택, (2) read-only 진단/수집 스크립트와 새 운영 런북 셋, (3) 셸 스크립트 syntax/shellcheck·공개 release 안전·release 이미지 Trivy 스캔을 묶은 CI 강화, (4) 공개 저장소에 프론트 테스트 산출물이 흘러 들어가지 않도록 빌더 manifest를 좁히는 위생 강화, (5) BFF 시크릿 감사 적재량을 평상시 0에 수렴시키는 audit-mode 도입과 rate-limit 필터의 다중 시크릿 신뢰 정렬, (6) 플랫폼 관리자 영속 어댑터의 Web/HTTP 의존 제거로 인한 아키텍처 경계 정리입니다. DB migration 없음.

Fixed

  • 공개 release 안전 — 프론트 테스트 산출물 누출 차단: 공개 release 후보 빌더가 front/test-results, front/playwright-report, front/coverage, front/.nyc_output 디렉토리를 후보 tree에서 제외합니다. 같은 회귀를 막기 위한 fixture를 함께 추가했습니다.
  • rate-limit 필터의 BFF 시크릿 신뢰 정렬: RateLimitFilter가 BFF 인증과 동일한 다중 시크릿 설정(READMATES_BFF_SECRETS + 단일 READMATES_BFF_SECRET fallback)을 신뢰하도록 맞췄습니다. rotation 중인 환경에서 정상 BFF 트래픽이 rate-limit 단에서 떨어질 가능성을 닫았습니다.
  • 플랫폼 관리자 영속 어댑터의 Web/HTTP 의존 제거: JdbcPlatformAdminAdapter가 더 이상 HttpStatus/ResponseStatusException을 던지지 않습니다. HTTP 매핑은 PlatformAdminErrorHandler로 이동했고, 영속 어댑터는 도메인 예외만 던집니다.
  • BFF 시크릿 감사 executor graceful shutdown (DEF-001): BffSecretAuditExecutorConfigsetWaitForTasksToCompleteOnShutdown=true + awaitTerminationSeconds=5를 적용해 컨테이너 종료 시 큐에 남은 audit 태스크 손실을 줄입니다. 폐기 발생 시 bff.audit.shutdown.dropped counter가 증가합니다.
  • READMATES_IP_HASH_BASE_SECRET 운영 프로파일 필수화 (DEF-002): ClientIpHashing salt base secret이 운영 프로파일(spring.profiles.active가 비어 있거나 production 포함)에서 비어 있으면 startup이 명시적 메시지와 함께 실패합니다. local/test 등 비운영 프로파일도 기본값은 실패이며, readmates.security.ip-hash.allow-empty-secret=true를 명시한 경우에만 빈 값을 허용하고 WARN을 남깁니다.
  • member-app 라우트 가드의 clubSlug 우회 제거 (DEF-003): RequireMemberApp / RequireHost가 club slug 유무와 무관하게 canUseMemberApp / canUseHostApp을 항상 확인합니다. INACTIVE/non-member 사용자가 다른 club slug URL로 우회 진입하던 가능성을 닫았습니다.
  • AuthContext 401 처리 분리 + 리다이렉트 쿨오프 (DEF-004): 401 응답을 session_expired(만료된 세션)과 미인증으로 분리하고, 새 AuthState variant + ReadMatesSessionExpiredError를 도입했습니다. 1500ms cool-off로 redirect loop 가능성을 차단합니다.
  • 알림 발송 UNKNOWN 상태 재시도 (DEF-005): NotificationDispatchService가 SMTP 결과가 UNKNOWN인 경우를 dead-letter가 아닌 retryable로 처리합니다. 새 counter notification.dispatch.unknown_status로 발생 빈도를 추적합니다.

Documented

  • v1.7 이후 문서/코드 정합성 동기화: README/docs 허브, 배포·운영·public-release 문서, ADR, case study를 현재 구현에 맞췄습니다. 주요 정정 범위는 BFF audit-mode 기본값(rotation-only), SecretComparator 기반 시크릿 비교, /api/bff/__internal/secret-status, ClubContextSource 기반 host fallback 처리, notification UNKNOWN 재시도, public release manifest와 frontend 산출물 제외 규칙입니다.
  • Notification outbox dedupeKey 정책 (LOGIC-001): dedupeKey 생성 규약과 멱등성 보장 범위를 ADR-0015로 명시했습니다. NotificationEventService의 관련 메서드 KDoc도 보강했습니다.
  • MemberAuthoritiesFilter null-context 분기 의도 (LOGIC-004): club context가 null인 경로의 의도된 동작과 fallback 의미를 KDoc + 테스트로 고정했습니다.

Added

  • 신규 메트릭 bff.audit.shutdown.dropped, notification.dispatch.unknown_status: BFF audit executor 종료 시 폐기된 태스크 수와 UNKNOWN 상태로 재시도 분기에 진입한 이메일 발송 횟수를 각각 추적합니다.
  • BFF 진단 라우트 GET /api/bff/__internal/secret-status: configured secret count, rotation stage(stable/staging), primary secret의 SHA-256 첫 6자 fingerprint만 반환합니다 — raw secret 값은 노출하지 않습니다.
  • ADR-0014/0015/0016: BFF secret rotation lifecycle(ADR-0014), notification outbox dedupe policy(ADR-0015), deploy ledger event schema(ADR-0016)를 추가했습니다.
  • OCI compose 배포 attempt ledger: deploy/oci/05-deploy-compose-stack.sh가 시도/스테이지/결과를 ledger 행으로 기록합니다. 각 행의 필드 의미는 docs/operations/runbooks/deploy-attempts.md에 정리되어 있습니다.
  • 이미지 verification + post-deploy watch 자동 실행: 배포 스크립트가 게시된 server image와 실제 실행 중 image의 mismatch를 검출해 ledger에 기록하고, 배포 직후 post-deploy watch 스크립트(deploy/oci/watch-compose-post-deploy.sh)를 자동으로 실행합니다. 환경 제약으로 watch가 생략된 경우에도 그 사실이 ledger에 남습니다.
  • 운영 진단 스크립트: read-only compose 진단 수집기(deploy/oci/readmates-collect.sh), 해당 수집기를 호스트에 설치하는 installer(deploy/oci/install-readmates-collector.sh), 강화된 post-deploy watch 스크립트(deploy/oci/watch-compose-post-deploy.sh)를 추가했습니다.
  • 운영 런북 셋: docs/operations/runbooks/deploy-attempts.md, docs/operations/runbooks/post-deploy-watch.md, docs/operations/runbooks/read-only-diagnostics.md와 인덱스 페이지를 추가했습니다. 공개 release 후보 manifest가 이 운영 런북을 포함하도록 함께 갱신했습니다.
  • CI 강화 잡: 모든 셸 스크립트에 대한 bash -n syntax 검증과 shellcheck 잡, 공개 release 안전 검사 잡, release 이미지 Trivy 스캔 잡을 추가했습니다.
  • BFF 시크릿 감사 audit-mode: BffSecretFilterreadmates.security.bff.audit-mode 설정을 따르도록 변경됐고, 기본값은 rotation-only입니다. 감사 기록은 bounded ThreadPoolTaskExecutor로 비동기 처리됩니다.

Changed

  • 아키텍처 경계: JdbcMemberApprovalStoreAdapter Spring HTTP/Web 미사용 import 제거 및 baseline exception 1건 축소.
  • 아키텍처 경계: JdbcMemberLifecycleStoreAdapter Spring HTTP/Web 미사용 import 제거 및 baseline exception 1건 축소.
  • 아키텍처 경계: JdbcMemberProfileStoreAdapter Spring HTTP/Web 미사용 import 제거 및 baseline exception 1건 축소.
  • 아키텍처 경계: JdbcPendingApprovalStoreAdapter Spring HTTP/Web 미사용 import 제거 및 baseline exception 1건 축소.
  • 아키텍처 경계: JdbcFeedbackDocumentStoreAdapter Spring HTTP/Web 미사용 import 제거 및 baseline exception 1건 축소. baseline 셋이 모두 비어 있음.
  • 공개 release 후보 manifest 정리: 운영 런북을 포함하도록 manifest를 넓히는 한편, 프론트 테스트 산출물 같은 비공개 후보 디렉토리는 명시적으로 좁혔습니다.
  • BFF 시크릿 감사 기본 동작: bff_secret_rotation_audit 테이블에 매 성공 요청을 적재하던 기존 동작 대신, 기본 모드 rotation-only에서는 rotation 확인용 비-primary alias(secondary, index_N)가 사용된 요청만 기록합니다. 모든 요청을 적재하던 기존 행동은 audit-mode=all로 명시 설정해야 활성화됩니다.
  • 배포 ledger 필드 문서화: deploy attempt 모델과 ledger 필드 정의를 운영 런북과 공개 문서에서 일치시켰습니다.
  • 시크릿 비교 유틸리티 통합 (STRUCT-001): BFF 인증과 rate-limit 필터의 alias 매칭에 흩어져 있던 timing-uniform 시크릿 비교 로직을 공통 SecretComparator로 묶었습니다. MessageDigest.isEqual + 모든 후보를 끝까지 비교하는 방식은 그대로이며, 의미 변경은 없습니다. OAuth HMAC 검증 경로는 도메인이 달라 기존 구현을 유지합니다.
  • 멤버 lifecycle 상태 전이 명시화 (LOGIC-002): 멤버 lifecycle 상태 전이를 MemberLifecycleStatus enum과 허용 전이 매트릭스로 명시했습니다. JdbcMemberLifecycleStoreAdapter가 UPDATE 전에 현재 상태를 조회해 허용되지 않은 전이를 IllegalMemberStateTransitionException으로 차단하며, 동시성 race는 기존 SQL WHERE 절이 계속 보호합니다.
  • 권한 합성 로직의 application 계층 분리 (STRUCT-002): 권한 합성 로직을 MemberAuthoritiesFilter(infrastructure)에서 AuthoritySynthesisService(application)로 분리했습니다. filter는 transport 어댑터 역할만 수행하며 ROLE_ 리터럴이 0개입니다. application 계층은 Spring Security GrantedAuthority나 web 어댑터 타입에 의존하지 않는 framework-neutral 시그니처(Set<String>, ClubContextInput)를 사용해 ArchUnit boundary를 준수합니다.
  • BFF 시크릿 rotation lifecycle 정착 (STRUCT-003): front/functions/_shared/proxy.tsgetConfiguredBffSecrets, getRotationStage, secretFingerprint helper를 추가했습니다(ADR-0014 구현). 진단 라우트와 ADR 항목은 ### Added 참고.
  • 배포 ledger NDJSON 스키마 (LOGIC-005): 05-deploy-compose-stack.sh/watch-compose-post-deploy.shremote_ledger_append가 기존 한 줄 포맷과 NDJSON {ts, stage, event, status, detail:{...}}를 동시에 기록합니다(기본 READMATES_LEDGER_FORMAT=both, json/legacy로 단일 포맷 선택 가능). watch 스크립트는 trap watch_on_error ERR로 예기치 못한 실패 경로에서도 ledger 이벤트를 보장하며, 01-vm-setup.shjq를 함께 설치합니다. 운영 런북에 jq 쿼리 예시와 env var 의미를 보강했습니다. 스키마 규약은 ADR-0016 참고.
  • 공개 release candidate 빌더 deny-list 우선 모델 (STRUCT-004): scripts/build-public-release-candidate.sh를 deny-list 우선 모델로 정리하고, top-level 구성을 고정하는 scripts/fixtures/public-release-candidate-coverage.txt fixture와 scripts/verify-public-release-fixtures.sh의 top-level diff 검사를 추가했습니다. .claude/, .cursor/, .windsurf/, .orchestrator/, CLAUDE.md, AGENTS.md, GEMINI.md, .impeccable.md, *.local.md, CHANGELOG.md(의도적으로 internal) 등 내부 자료를 deny-list에 명시했습니다.
  • 아키텍처 경계: persistence 어댑터 Web/HTTP 의존성 5건 정리 완료, ServerArchitectureBoundaryTest baseline exception 리스트 0건.

Deployment Notes

  • DB migration: 없음.
  • 신규 환경 변수: READMATES_LEDGER_FORMAT (기본 both — NDJSON + legacy 두 라인 동시 기록, json/legacy로 단일 포맷 선택 가능), BFF_SECRET_ROTATION_STAGE (기본 stable — BFF 시크릿 rotation 단계 명시). 기존 READMATES_IP_HASH_BASE_SECRET은 운영 프로파일에서 필수(비어 있으면 startup 실패).
  • 새 환경 변수/설정 후보: readmates.security.bff.audit-mode (기본 rotation-only, 명시값 all로 모든 요청 감사 적재 복원 가능).
  • 운영 프로파일 startup 변화 (DEF-002): READMATES_IP_HASH_BASE_SECRET가 비어 있으면 운영 프로파일에서 startup이 실패합니다. 배포 전 /etc/readmates/readmates.env에 값이 설정돼 있는지 확인하세요(openssl rand -base64 32로 생성, 1Password에 저장). 자세한 운영 절차는 docs/deploy/oci-backend.md 참고.
  • 새 메트릭: bff.audit.shutdown.dropped (BFF audit executor 종료 시 폐기된 태스크 — graceful shutdown 시 0이 정상), notification.dispatch.unknown_status (UNKNOWN 상태로 재시도 분기에 진입한 이메일 발송 횟수).
  • 운영 가시성: BFF 시크릿 감사(audit-mode 기본 rotation-only)는 rotation 확인용 비-primary alias 사용만 기록합니다. 평상시 bff_secret_rotation_audit 적재량은 0에 수렴하므로 기존 알람 임계값을 점검하세요.
  • 후속 권장: 30일 이상 행을 정리하는 retention job 예시는 docs/deploy/oci-backend.md를 참고하세요.
  • 배포 순서: 서버 먼저, 그다음 프론트엔드. OCI compose stack 배포는 Deploy Server Image workflow가 scan-candidate digest를 Trivy로 검사하고 같은 digest를 release tag로 promote한 후 ./deploy/oci/05-deploy-compose-stack.sh로 실행합니다. 이번부터는 attempt ledger 행과 이미지 verification 결과, post-deploy watch 실행 여부를 함께 확인하세요.
  • CI 동작 변화: PR/main push에서 셸 스크립트 syntax/shellcheck, 공개 release 안전 검사, release 이미지 Trivy 스캔이 새 실패 신호로 등장할 수 있습니다.

Verification

  • pnpm --dir front lint — exit 0.
  • pnpm --dir front test — 56 files / 729 tests passed.
  • pnpm --dir front build — exit 0.
  • pnpm --dir front test:e2e — 22 tests passed.
  • ./server/gradlew -p server clean test — BUILD SUCCESSFUL.
  • ./scripts/build-public-release-candidate.sh — public release candidate built at .tmp/public-release-candidate.
  • ./scripts/public-release-check.sh .tmp/public-release-candidate — passed, gitleaks found no leaks.
  • git diff --check — 출력 없음.
  • 공개 시크릿/호스트/개인 경로 스캔(rg 정규식) — 정책 문구, placeholder, loopback 예시만 확인됨.

ReadMates v1.7.0

Choose a tag to compare

@beyondwin beyondwin released this 11 May 06:21

Highlights

2026-05-11 production incident(current-session refresh 빈 화면)의 한 줄 fix와 server-side 후속 안전망(ADR-0013)을 함께 묶고, 그간의 portfolio polish — Architecture Decision Records 백필(0001~0010, 0013), Engineering Highlights와 case study deep-dive 3건, observability runbook(메트릭/대시보드/알람/SLO), incident post-mortem 실천(템플릿 + 1차 incident)을 한 릴리즈로 정착시킵니다. 사용자에게 보이는 변화는 (1) 빈 화면 incident 재발 차단, (2) /api/auth/me의 잘못된 club slug 명시 시 새 404 CLUB_NOT_FOUND 응답입니다. DB migration 없음.

Fixed

  • 2026-05-11 production incident — current-session refresh blank screen: clubSlug가 route refresh event에서 누락되어 일부 라우트의 refresh path에서 빈 화면이 발생하던 회귀를 수정합니다. front/features/current-session/route/current-session-route.tsxuseParams()의 값을 refresh handler에 명시적으로 forward하도록 조정했습니다. (post-mortem: docs/operations/postmortems/2026-05-11-current-session-refresh-club-context.md)
  • AuthMeController: slug 명시 누락 vs host fallback unknown 분리 (ADR-0013): BFF는 모든 요청에 X-Readmates-Club-Host를 첨부하지만 server가 host lookup miss를 supplied-with-no-context로 처리해 degraded authenticatedUser 응답을 내던 잠복 경로를 닫았습니다. 이제 RequestedClubContext.source로 분기:
    • SLUG 명시 + club_domains에 미등록 → 404 CLUB_NOT_FOUND (real client bug 명시).
    • HOST_FALLBACK + 미등록 host → unscoped 응답 (dev에서 host 헤더가 strip되어 동작하던 unscoped 경로와 일치).
    • "club 존재 + 사용자 미가입" 케이스는 기존 degraded UX 보존 (의도된 동작).

Added

  • Architecture Decision Records 셋: docs/development/adr/에 backfill 10개(0001~0010)와 신규 0013-bff-host-header-policy.md를 함께 정착. 인덱스(README.md), 작성 규약, 템플릿, 상태 라벨, 후보 ADR 목록을 포함합니다. ADR-0011(jOOQ migration)과 0012(Redis adoption)는 follow-up 후보로 등록.
  • Engineering Highlights + Case studies: README 최상단에 Engineering Highlights 섹션을 추가해 운영 중 풀어낸 비자명한 문제 3건을 case study deep-dive로 연결합니다. docs/case-studies/에 BFF 보안과 secret rotation, notification outbox pipeline, multi-club domain platform 3건의 deep-dive를 추가 — 각 case는 문제 → 접근 → 구현 → 검증 → trade-off → 다시 한다면 흐름을 따르며, case 03은 2026-05-11 incident의 root cause와 영구 수정 서사를 담습니다.
  • Observability runbook (docs/operations/observability/): 진입 README, 19개 custom 메트릭 카탈로그(근원 코드 인용), 22개 권장 dashboard panel(PromQL), 11개 alertmanager rule candidate, 3개 SLO 정의(API availability, read latency, notification delivery latency). docs/operations/README.md 진입점도 함께. 코드 변경 없음 — 현재 배포된 메트릭과 권장 구성만 정리.
  • Incident post-mortem 실천 (docs/operations/postmortems/): 디렉토리, 템플릿, severity 정의(SEV1~SEV4), 첫 incident(2026-05-11 current-session refresh club context degradation, SEV2)를 함께 등록. Post-mortem follow-up 갱신 이력 섹션으로 후속 변경 추적을 영구 보존합니다.
  • 서버 코드: com.readmates.club.adapter.in.web.ClubContextSource enum(SLUG / HOST_FALLBACK / NONE)과 RequestedClubContext.source 필드. 현재 AuthMeController만 분기에 사용; 다른 컨슈머(CurrentMemberArgumentResolver, MemberAuthoritiesFilter, SessionCookieAuthenticationFilter)는 후속 audit 대상으로 ADR-0013 후속 섹션에 명시.
  • 테스트: ResolveClubContextRequestExtensionTest (6 시나리오 — slug/host/neither/both × hit/miss)와 AuthMeControllerTest의 HOST_FALLBACK 시나리오 2건.

Changed

  • 2026-05-11 post-mortem의 Action items 표가 라운드 후속 평가 결과를 반영합니다 — #3 Closed (READMATES_ROUTE_REFRESH_EVENT grep audit 단일 사용처 확인), #2 Closed (ADR-0013 머지), #1 Deferred (parity test 시급성 재평가).
  • 공개 저장소 위생 기준을 정리해 .orchestrator/**.claude/settings.json을 Git 추적 대상에서 제거하고, .gitignore.claude/.orchestrator/를 명시했습니다.
  • docs/improvements.md의 workstation 절대경로를 repo-relative path로 바꾸고, release/public-safety 문서에 GitHub Release 누락 복구와 ignored 파일 제외 검증 기준을 보강했습니다.

Deployment Notes

  • DB migration: 없음. Flyway 버전 변경 없음.
  • 배포 순서: 서버 먼저(auth contract에 새 404 응답 경로 추가). 프론트는 새 응답을 만들지 않으므로 영향 없음 — 단 안전을 위해 server → frontend 순서를 권장합니다. release tag push가 deploy-server.yml(GHCR image publish)과 deploy-front.yml(Cloudflare Pages production deploy) workflow를 함께 시작합니다. OCI compose stack 배포는 Deploy Server Image workflow가 GHCR에 같은 tag image를 게시한 후 ./deploy/oci/05-deploy-compose-stack.sh로 수동 실행합니다.
  • 새 환경 변수: 없음.
  • 운영 smoke check 기대값:
GET /api/bff/api/auth/me                      (anonymous)              -> 200 (authenticated:false)
GET /api/bff/api/auth/me                      (logged-in, valid slug)  -> 200 (currentMembership present)
GET /api/bff/api/auth/me                      (logged-in, unknown slug) -> 404 {"code":"CLUB_NOT_FOUND"}   # 신규
GET /api/bff/api/auth/me                      (logged-in, no headers)   -> 200 (unscoped)
GET /api/bff/api/public/club                  (anonymous)              -> 200
GET /api/bff/api/sessions/upcoming            (anonymous)              -> 401
  • Production manual repro: https://readmates.pages.dev/clubs/reading-sai/app/session/current 접근 → 멤버 로그인 → reading progress 조정 + 저장 → 빈 화면 미재발 확인.

Verification

  • ./server/gradlew -p server clean test — BUILD SUCCESSFUL (707+ tests passing, no regression).
  • pnpm --dir front lint — exit 0
  • pnpm --dir front test — 706 passing / 53 files
  • pnpm --dir front build — exit 0
  • ./scripts/public-release-check.sh — passed (gitleaks + 7 targeted content rules clean)
  • ./scripts/verify-public-release-fixtures.sh — passed
  • Skipped: pnpm --dir front test:e2e — 이번 릴리즈 준비 환경에 e2e용 MySQL(:3306) + Spring + Vite dev 서버 오케스트레이션이 활성화되어 있지 않아 실행하지 못했습니다. 잔여 리스크: 새 404 응답이 프론트엔드에서 어떻게 표시되는지 e2e로 검증하지 못함. 단 (1) 현재 client 코드는 의도적으로 잘못된 slug를 보내지 않으며 grep audit으로 0건 확인, (2) Zod schema fixture는 변경 없음, (3) 서버 단위 + 통합 테스트가 새 분기를 cover합니다. 배포 후 위 production manual repro로 보완 권장.

ReadMates v1.6.0

Choose a tag to compare

@beyondwin beyondwin released this 09 May 09:05

Highlights

보안 강화, BFF secret 무중단 rotation, 성능 개선, 아키텍처 정리를 포함한 대규모 업데이트입니다. DB migration 3개(V24 legacy password rename, V25 drop, V26 BFF rotation audit)가 포함되며, 신규 환경 변수 READMATES_IP_HASH_BASE_SECRET가 추가됩니다.

Security

  • BFF secret 무중단 rotation 지원: READMATES_BFF_SECRETS 환경 변수에 쉼표로 구분된 여러 시크릿을 설정할 수 있습니다. 기존 READMATES_BFF_SECRET은 fallback으로 계속 동작합니다. 매칭은 timing-safe 방식으로 모든 후보를 끝까지 비교합니다.
  • BFF rotation 감사 로그: 인증 성공 요청마다 사용된 secret alias("primary"/"secondary"/"index_N")를 bff_secret_rotation_audit 테이블에 비동기로 기록합니다. rotation 중 old-secret 트래픽이 0으로 떨어진 시점을 SQL로 확인할 수 있습니다. (V26 migration)
  • ClientIpHashing.kt를 추가해 RateLimitFilter의 IP 해시 salt를 ISO 주차 기준으로 자동 rotate합니다. base secret은 READMATES_IP_HASH_BASE_SECRET 환경 변수로 주입하며, 미설정 시 빈 문자열 fallback을 사용하고 startup 시 WARN을 출력합니다. (TASK-V2-028)
  • Spring Security role hierarchy를 ROLE_PLATFORM_ADMIN > ROLE_MEMBER, ROLE_HOST > ROLE_MEMBER로 정리했습니다. (TASK-V2-005)
  • Set-Cookie Domain 속성 stripping fix를 적용해 cross-origin cookie 노출을 방지합니다. (TASK-V2-004)
  • Support access grants: platform admin이 활성 HOST_SUPPORT_READ grant를 가지면 CheckSupportAccessGrantUseCase가 합성 HOST membership을 부여합니다. MemberAuthoritiesFilter, CurrentMemberArgumentResolver, ClubContextResolver.kt가 갱신됐습니다. (TASK-V2-024)
  • Sessions invariant enforcement: 세션 상태 전이 불변식을 서버에서 검증합니다. (TASK-V2-003)

Performance

  • CachedNotificationBacklogProvider.kt를 추가해 notification backlog gauge를 1분 주기 scheduled refresh로 캐싱합니다. ReadmatesOperationalMetrics가 캐시된 snapshot을 사용합니다. (TASK-V2-001)
  • 공개 endpoint에 Cache-Control 헤더를 추가하고 BFF cache를 연동했습니다. (TASK-V2-002)
  • 프런트엔드 route lazy loading을 적용해 초기 번들 크기를 줄였습니다. (TASK-V2-019)
  • archive detail batching으로 상세 페이지 API 호출 수를 줄였습니다. (TASK-V2-020)
  • HostSessionEditoruseReducer + memo를 적용해 불필요한 re-render를 제거했습니다. (TASK-V2-021)
  • Dynamic CORS origins 지원을 추가했습니다. (TASK-V2-016)

Removed

  • Legacy password column dropped from users table (Flyway V24+V25 deployed together).
  • POST /api/auth/password-reset/{token} and POST /api/host/members/{id}/password-reset endpoints removed (previously returned 410 GONE; now 404).

Deployment Notes

서버 배포 순서

  1. /etc/readmates/readmates.env에 신규 환경 변수 추가:
    READMATES_IP_HASH_BASE_SECRET=<openssl rand -base64 32으로 생성>
    
  2. GHCR image ghcr.io/<owner>/readmates-server:v1.6.0 pull 후 compose stack 재시작.
  3. Flyway V24, V25, V26 migration 자동 적용 확인.
  4. /internal/health 및 BFF smoke 확인.

Flyway migration

버전 내용
V24 users.password_hashlegacy_password_hash rename
V25 legacy_password_hash, legacy_password_set_at drop
V26 bff_secret_rotation_audit 테이블 생성 (감사 로그)

프론트엔드

v1.6.0 tag push로 GitHub Actions deploy-front.yml이 Cloudflare Pages 배포를 자동 실행합니다. zod가 devDependency로 추가됐으므로 빌드 시 pnpm install이 정상 실행돼야 합니다.

Verification

  • 서버 테스트: 707개 통과 (baseline 696 + 11)
  • 프론트 테스트: 705개 통과 (baseline 697 + 8)
  • TypeScript 오류: 36개 (pre-existing, 신규 없음)

v1.5.2

Choose a tag to compare

@beyondwin beyondwin released this 06 May 06:17

Fixed

  • Deploy Server Image workflow의 Docker action pin을 Node.js 24 기반 release로 갱신해 GitHub Actions Node.js 20 deprecation warning을 제거했습니다.

v1.5.1

Choose a tag to compare

@beyondwin beyondwin released this 06 May 03:57

Highlights

ReadMates v1.5.1은 v1.5.0 배포 중 확인된 server image release workflow 문제를 고친 patch release입니다. 애플리케이션 런타임 동작은 v1.5.0과 같고, GHCR server image를 OCI A1 VM에서 바로 실행할 수 있는 ARM64 image로 tag 기준 재현 가능하게 게시합니다.

Fixed

  • Deploy Server Image workflow의 docker/login-action pin을 실제 v3.6.0 commit으로 고쳐 GHCR server image 게시가 tag/manual dispatch에서 시작되도록 했습니다.
  • GHCR server image를 OCI A1 VM과 맞는 linux/arm64 platform으로 게시하도록 QEMU/Buildx 설정을 추가했습니다.
  • Release image workflow는 native runner에서 bootJar를 만든 뒤 별도 runtime Dockerfile로 ARM64 image를 조립해 Gradle build가 QEMU emulation에 묶이지 않게 했습니다.

v1.5.0

Choose a tag to compare

@beyondwin beyondwin released this 06 May 03:49

Highlights

ReadMates v1.5.0은 서버, Cloudflare Pages Functions, 프런트엔드가 같은 public-safe API 오류 계약을 사용하도록 맞춘 릴리즈입니다. 사용자는 403/404/409/410/5xx 상황에서 내부 예외나 인프라 세부사항 대신 route context에 맞는 안전한 오류 화면을 보게 됩니다.

운영 측면에서는 GHCR server image 게시 workflow와 OCI compose image tag 배포 기준을 정리했고, 기본 Playwright E2E 실행이 오래된 로컬 Flyway schema history에 막히지 않도록 현재 migration fingerprint 기반 schema를 사용합니다.

Added

  • 서버 이미지를 GitHub Container Registry에 게시하는 Deploy Server Image workflow를 추가했습니다. Release tag push와 수동 image_tag 입력 모두 같은 Docker tag 검증 경로를 사용합니다.
  • 공개 archive/public visibility regression coverage와 BFF header/club slug regression coverage를 추가했습니다.
  • 알림 이메일 delivery를 dispatch path와 pending worker path가 공유하는 NotificationDeliveryEngine으로 분리하고 retry/dead 전환, redacted error 저장, metrics/logging을 한 곳에서 처리합니다.
  • HostDashboard, MyPage, HostSessionEditor, HostMembers UI를 route/API 호출 없는 작은 presentation module로 분리했습니다.
  • Spring API 오류 응답을 public-safe { code, message, status } JSON body로 통일하는 shared ApiErrorResponse와 feature별 error handler coverage를 추가했습니다.
  • Cloudflare Pages Functions BFF 자체 400/403/404 거절도 같은 오류 body shape를 반환하도록 shared error helper를 추가했습니다.
  • React Router root/member/host/public/auth route error boundary와 unmatched route용 NotFoundRoute를 추가했습니다.
  • Playwright E2E 기본 database 이름을 현재 운영 migration과 dev seed SQL fingerprint 기반으로 정하는 regression coverage를 추가했습니다.

Changed

  • 운영 Flyway source of truth를 server/src/main/resources/db/mysql/migration으로 고정하고, 사용하지 않는 server/src/main/resources/db/migration tree를 제거했습니다.
  • 서버 application layer가 Spring Security/Web/JDBC 세부사항에 직접 의존하지 않도록 경계를 강화하고, persistence adapter는 필요한 JdbcTemplate을 직접 주입받아 wiring 오류를 빠르게 드러내도록 정리했습니다.
  • Cloudflare Pages Functions와 Vite proxy가 같은 club slug validation helper를 사용하게 했고, auth API helper는 raw 401을 유지해야 하는 preview/logout 흐름과 일반 BFF fetch 흐름을 분리했습니다.
  • OCI compose release deploy는 GHCR image tag를 VM에서 pull하고, 로컬 non-GHCR tag는 build/save/load 전환 검증 경로로 남깁니다.
  • Vite frontend source에서 불필요한 "use client" directive를 제거하고 agent guide에 재도입 금지 기준을 추가했습니다.
  • 공개 릴리즈 후보 builder가 server image workflow도 후보 tree에 포함하도록 manifest를 갱신했습니다.
  • 프런트엔드 shared/api parser가 non-OK 응답을 ReadmatesApiError로 변환하고, empty 또는 malformed response body는 HTTP status 기준 fallback code/message로 안전하게 처리합니다.
  • Route error UI는 HTTP status와 public/member/host/auth context를 기준으로 안내 문구와 복귀 버튼을 선택하되, 공개 세션 없음이나 피드백 문서 unavailable 같은 feature-specific 상태는 각 feature가 계속 소유합니다.

Fixed

  • Redis Testcontainers가 localhost를 반환할 때 Redis URL host를 127.0.0.1로 정규화해, 로컬 IPv6 localhost의 다른 서비스와 mapped port가 겹치는 테스트 flake를 줄였습니다.
  • 로그는 BFF secret rejection, notification relay/delivery, session lifecycle의 운영 이벤트를 남기되 raw secret, token, recipient 원문 같은 민감 값을 기록하지 않도록 보강했습니다.
  • 기본 pnpm --dir front test:e2e가 오래된 로컬 readmates_e2e schema의 Flyway checksum mismatch에 막히던 리스크를 제거했습니다. 명시적 READMATES_E2E_DB_NAME override는 그대로 유지됩니다.

Deployment Notes

이 변경 묶음은 새 운영 DB schema migration을 추가하지 않습니다. 사용하지 않는 legacy server/src/main/resources/db/migration tree를 제거했지만 운영 Flyway 경로는 server/src/main/resources/db/mysql/migration입니다.

서버 API, Cloudflare Pages Functions, 프런트엔드 route/API parser가 함께 바뀌므로 서버와 프런트엔드를 같은 v1.5.0 tag 기준으로 배포합니다. 먼저 Deploy Server Image workflow로 ghcr.io/<owner>/<repo>/readmates-server:v1.5.0 이미지를 게시하고, OCI compose backend는 그 image tag를 pull해 배포합니다. 이후 같은 tag의 Cloudflare Pages frontend/Functions 배포가 완료됐는지 확인합니다.

배포 후에는 Spring /internal/health, Pages /api/bff/api/auth/me, OAuth start redirect, public club API, 그리고 public 404 route error 화면을 smoke합니다. 비정상 응답 body는 stack trace, SQL detail, upstream host, secret, token 원문, 내부 exception class name을 포함하지 않는 { code, message, status } shape여야 합니다.

Verification

  • pnpm --dir front lint
  • ./server/gradlew -p server clean test
  • pnpm --dir front test - 50 files, 660 tests passed
  • pnpm --dir front build
  • pnpm --dir front test:e2e - 22 tests passed
  • git diff --check -- docs/development/architecture.md
  • git diff --check -- docs/development/test-guide.md docs/superpowers/plans/2026-05-06-readmates-error-boundary-contract-implementation-plan.md front/playwright.config.ts front/tests/e2e/readmates-e2e-config.ts front/tests/e2e/readmates-e2e-db.ts front/tests/unit/playwright-e2e-config.test.ts
  • ./scripts/build-public-release-candidate.sh
  • ./scripts/public-release-check.sh .tmp/public-release-candidate