Skip to content

[chore] prod 배포 v0.0.8 - #131

Merged
theminjunchoi merged 8 commits into
prodfrom
dev
Aug 12, 2026
Merged

[chore] prod 배포 v0.0.8#131
theminjunchoi merged 8 commits into
prodfrom
dev

Conversation

@theminjunchoi

@theminjunchoi theminjunchoi commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

📦 prod 승격 (dev → prod), v0.0.7 → v0.0.8

포함 변경 (8건)

기능 - 프롬프트 운영 (백오피스)

수정

인프라·CI

🗄️ DB 마이그레이션 (prod에 신규 적용)

prod는 현재 V21까지 적용돼 있고, 이번에 네 개가 실행됩니다.

  • V22 prompt_revisions 테이블 신설 - 프롬프트 편집 이력(append-only)
  • V23 기본 프롬프트 시드 - 코드에 있던 프롬프트 원문을 llm_settings로 이관(내용 동일, 골든 파일 바이트 일치 검증됨)
  • V24 엉뚱이 소재 목록 시드
  • V25 members.nickname varchar(20) → varchar(200) - 그래핌 20자(조합 이모지 포함) 수용

모두 신규 테이블·시드·컬럼 확장이라 즉시 끝나고, 롤백 위험이 있는 파괴적 변경은 없습니다.

배포 방식

docker-compose 변경 있음(#127) - 로그 로테이션 옵션이 추가되어 이번 배포에서 컨테이너들이 재생성됩니다. db 컨테이너도 재생성되면서 잠깐 재시작되니 트래픽 적은 시간대 배포를 권장합니다. nginx 설정 변경은 없습니다.

Summary by CodeRabbit

  • 새 기능

    • 관리자용 프롬프트 실험실을 추가해 댓글·답글 생성 결과를 미리 확인할 수 있습니다.
    • 토큰 사용량, 예상 비용, 지연 시간, 검증 및 생성 오류를 확인할 수 있습니다.
    • 프롬프트 유형별 변경 이력을 조회하고 이전 버전을 불러오거나 복원할 수 있습니다.
    • 엉뚱이 소재 목록을 관리하고 프롬프트 설정에 반영할 수 있습니다.
  • 개선 사항

    • 프롬프트 저장 시 버전 이력이 자동으로 기록됩니다.
    • 닉네임 길이 검증이 이모지와 조합 문자를 더 정확히 처리합니다.
    • 서비스 로그 파일의 크기와 보관 개수가 제한됩니다.

* feat: 프롬프트 리비전 테이블·엔티티 추가

append-only 편집 이력. 기존 llm_settings 값을 각 타입의 v1로 시딩해
이력의 시작점을 현재 상태로 맞춘다.

* feat: 프롬프트 저장·복원에 리비전 기록

저장은 현재값 갱신과 리비전 기록을 한 트랜잭션으로 묶고, 복원은 과거
내용을 새 리비전으로 저장해 이력이 끊기지 않는다. 내용이 같은 저장은
기록 없이 성공한다(멱등).

* feat: 프롬프트 리비전 조회·복원 API

리비전 목록(미리보기)·상세(전문)·복원 엔드포인트를 추가하고, 프롬프트
저장 시 로그인한 관리자를 기록자로 남긴다.

* feat: 백오피스 프롬프트 버전 이력 UI

타입별 이력 테이블(버전·저장자·시각·미리보기)과 전문 패널을 추가한다.
에디터로 불러와 저장 전 검토하거나, 확인 다이얼로그를 거쳐 특정
버전으로 즉시 복원할 수 있다.

* fix: 복원 배지가 좁은 버전 컬럼에서 세로로 줄바꿈되던 문제 해결

* fix: 복원 다이얼로그 설명이 한국어 단어 중간에서 줄바꿈되던 문제 해결

* fix: 현재 적용 중인 내용과 같은 리비전은 복원 버튼 비활성화

최신 리비전은 곧 현재값이라 복원이 무의미하다. 최신 버전 행에는
'현재 적용 중' 배지를 달고, 전문 패널에서는 내용이 현재값과 같으면
(우연히 같은 과거 버전 포함) 복원을 막고 이유를 안내한다.

* fix: 복원 출처 표기를 'v4에서 복원됨' 캡션으로 변경

'v4 복원' 배지는 복원할 대상인지 복원된 출처인지 방향이 모호했다.
버전 칩 아래 아이콘 딸린 캡션으로 옮겨 의미를 명확히 하고, 배지가
한 줄에 몰려 줄바꿈되는 여지도 없앤다.

* fix: 복원 출처를 아이콘 배지 'v4에서 복원'으로 표기

두 줄 캡션 대신 버전 칩 옆 한 줄 배지로 되돌리되, 복원 아이콘과
'~에서' 조사로 출처임을 명확히 한다. 버전 컬럼 고정 폭을 풀어
배지가 여러 개여도 줄바꿈되지 않는다.

* fix: 리비전 채번을 최신 리비전 행 잠금으로 직렬화

max 조회와 저장 사이에 동시 저장이 끼어들면 같은 버전을 쓰게 되는
경쟁을, 최신 리비전 행 비관 잠금으로 타입 단위 직렬화해 제거한다.
uk(prompt_type, version) 제약은 최후 방어로 남는다.

* fix: 리비전 미리보기의 공백을 정규화

개행만 치환하던 것을 연속 공백·탭·CR까지 한 칸으로 접고 앞뒤를
다듬어, 목록 미리보기의 정보량을 일정하게 유지한다.

* fix: 이력 UI의 오류 노출·탭 전환 초기화 보강

상세 조회 실패에 재시도 버튼을, 복원 실패에 오류 문구를 노출한다.
타입 탭 전환 시 컴포넌트를 재마운트해 페이지·펼침 상태가 남아 빈
목록에 갇히던 문제를 없애고, 복원 성공 시 중복 목록 요청을 제거한다.

* refactor: 동시 경합 재시도를 전역 정책으로 일반화

auth 전용이던 ConflictRetry를 global/retry로 옮기고, 재시도 대상을
RecoverableConflictException 공통 타입으로 추상화한다. 리비전 채번
경합 등 다른 도메인도 같은 정책을 재사용하기 위함(OCP).

* fix: 리비전 채번 잠금을 설정 행으로 교체하고 경합을 재시도로 해소

최신 리비전 행 잠금은 대기 후 낡은 행을 잡는 스테일 맥스 문제로
경합을 제거하지 못한다. 채번 도중에도 바뀌지 않는 llm_settings 행을
잠가 타입 단위로 직렬화하고, 잠글 행이 없는 최초 기록의 유니크 위반은
도메인 예외로 번역해 트랜잭션 바깥에서 재시도한다.

* test: 동시 저장이 잠금·재시도로 전원 성공하는지 실 스레드로 검증

8스레드가 설정 행이 없는 상태에서 동시에 저장해, 최초 기록 경합의
재시도와 이후 행 잠금 직렬화를 모두 통과하고 버전이 중복 없이
이어지는지 고정한다.

* fix: 프롬프트 저장·복원 내용을 trim으로 통일

프런트 dirty 판단(trim 기준)과 서버 비교·저장(원문 기준)이 어긋나
공백만 지우는 편집이 저장 불가능해지고, 공백만 다른 리비전이 쌓여도
미리보기로는 구분되지 않던 문제를 서버에서 기준을 통일해 막는다.

* test: 리비전 API 컨트롤러 통합 테스트 추가

관리자 principal이 기록자로 주입되는지, 복원 e2e 흐름과 출처 기록,
@validated의 page·size 검증(400), 미인증 401을 고정한다.

* test: 리비전 통합 테스트를 시딩 유무와 무관하게 보강

버전을 절대값으로 단정하던 컨트롤러 테스트는 현재 최신 버전에 상대적으로
검증하고, 시딩 데이터를 지우던 동시성 테스트는 스냅샷을 떠 원상 복원한다.
후속 PR의 V23 시딩 위에서도 같은 테스트가 그대로 성립한다.

* fix: 이력 목록 오류를 노출하고 복원 실패 표시를 리비전 단위로 정리

목록 조회 실패가 '아직 이력이 없습니다'로 위장되던 것에 재시도 UI를
추가하고, 전역이던 복원 실패 상태를 실패한 리비전에서만 표시한다.

* fix: 현재값도 trim 기준으로 비교하고 충돌 원인 예외를 보존

과거에 공백이 섞여 저장된 현재값과 비교가 어긋나 무의미한 리비전이
기록되던 누수를 막고, 재시도 소진 후 원인(유니크 위반·데드락)을
로그로 구분할 수 있게 cause를 전달한다.

* test: 동시성 테스트가 실패 경로에서도 스레드 풀을 정리하도록 보강
* feat: 프롬프트 리비전 테이블·엔티티 추가

append-only 편집 이력. 기존 llm_settings 값을 각 타입의 v1로 시딩해
이력의 시작점을 현재 상태로 맞춘다.

* feat: 프롬프트 저장·복원에 리비전 기록

저장은 현재값 갱신과 리비전 기록을 한 트랜잭션으로 묶고, 복원은 과거
내용을 새 리비전으로 저장해 이력이 끊기지 않는다. 내용이 같은 저장은
기록 없이 성공한다(멱등).

* feat: 프롬프트 리비전 조회·복원 API

리비전 목록(미리보기)·상세(전문)·복원 엔드포인트를 추가하고, 프롬프트
저장 시 로그인한 관리자를 기록자로 남긴다.

* feat: 백오피스 프롬프트 버전 이력 UI

타입별 이력 테이블(버전·저장자·시각·미리보기)과 전문 패널을 추가한다.
에디터로 불러와 저장 전 검토하거나, 확인 다이얼로그를 거쳐 특정
버전으로 즉시 복원할 수 있다.

* fix: 복원 배지가 좁은 버전 컬럼에서 세로로 줄바꿈되던 문제 해결

* fix: 복원 다이얼로그 설명이 한국어 단어 중간에서 줄바꿈되던 문제 해결

* fix: 현재 적용 중인 내용과 같은 리비전은 복원 버튼 비활성화

최신 리비전은 곧 현재값이라 복원이 무의미하다. 최신 버전 행에는
'현재 적용 중' 배지를 달고, 전문 패널에서는 내용이 현재값과 같으면
(우연히 같은 과거 버전 포함) 복원을 막고 이유를 안내한다.

* fix: 복원 출처 표기를 'v4에서 복원됨' 캡션으로 변경

'v4 복원' 배지는 복원할 대상인지 복원된 출처인지 방향이 모호했다.
버전 칩 아래 아이콘 딸린 캡션으로 옮겨 의미를 명확히 하고, 배지가
한 줄에 몰려 줄바꿈되는 여지도 없앤다.

* fix: 복원 출처를 아이콘 배지 'v4에서 복원'으로 표기

두 줄 캡션 대신 버전 칩 옆 한 줄 배지로 되돌리되, 복원 아이콘과
'~에서' 조사로 출처임을 명확히 한다. 버전 컬럼 고정 폭을 풀어
배지가 여러 개여도 줄바꿈되지 않는다.

* fix: 리비전 채번을 최신 리비전 행 잠금으로 직렬화

max 조회와 저장 사이에 동시 저장이 끼어들면 같은 버전을 쓰게 되는
경쟁을, 최신 리비전 행 비관 잠금으로 타입 단위 직렬화해 제거한다.
uk(prompt_type, version) 제약은 최후 방어로 남는다.

* fix: 리비전 미리보기의 공백을 정규화

개행만 치환하던 것을 연속 공백·탭·CR까지 한 칸으로 접고 앞뒤를
다듬어, 목록 미리보기의 정보량을 일정하게 유지한다.

* fix: 이력 UI의 오류 노출·탭 전환 초기화 보강

상세 조회 실패에 재시도 버튼을, 복원 실패에 오류 문구를 노출한다.
타입 탭 전환 시 컴포넌트를 재마운트해 페이지·펼침 상태가 남아 빈
목록에 갇히던 문제를 없애고, 복원 성공 시 중복 목록 요청을 제거한다.

* refactor: 동시 경합 재시도를 전역 정책으로 일반화

auth 전용이던 ConflictRetry를 global/retry로 옮기고, 재시도 대상을
RecoverableConflictException 공통 타입으로 추상화한다. 리비전 채번
경합 등 다른 도메인도 같은 정책을 재사용하기 위함(OCP).

* fix: 리비전 채번 잠금을 설정 행으로 교체하고 경합을 재시도로 해소

최신 리비전 행 잠금은 대기 후 낡은 행을 잡는 스테일 맥스 문제로
경합을 제거하지 못한다. 채번 도중에도 바뀌지 않는 llm_settings 행을
잠가 타입 단위로 직렬화하고, 잠글 행이 없는 최초 기록의 유니크 위반은
도메인 예외로 번역해 트랜잭션 바깥에서 재시도한다.

* test: 동시 저장이 잠금·재시도로 전원 성공하는지 실 스레드로 검증

8스레드가 설정 행이 없는 상태에서 동시에 저장해, 최초 기록 경합의
재시도와 이후 행 잠금 직렬화를 모두 통과하고 버전이 중복 없이
이어지는지 고정한다.

* fix: 프롬프트 저장·복원 내용을 trim으로 통일

프런트 dirty 판단(trim 기준)과 서버 비교·저장(원문 기준)이 어긋나
공백만 지우는 편집이 저장 불가능해지고, 공백만 다른 리비전이 쌓여도
미리보기로는 구분되지 않던 문제를 서버에서 기준을 통일해 막는다.

* test: 리비전 API 컨트롤러 통합 테스트 추가

관리자 principal이 기록자로 주입되는지, 복원 e2e 흐름과 출처 기록,
@validated의 page·size 검증(400), 미인증 401을 고정한다.

* test: 리비전 통합 테스트를 시딩 유무와 무관하게 보강

버전을 절대값으로 단정하던 컨트롤러 테스트는 현재 최신 버전에 상대적으로
검증하고, 시딩 데이터를 지우던 동시성 테스트는 스냅샷을 떠 원상 복원한다.
후속 PR의 V23 시딩 위에서도 같은 테스트가 그대로 성립한다.

* fix: 이력 목록 오류를 노출하고 복원 실패 표시를 리비전 단위로 정리

목록 조회 실패가 '아직 이력이 없습니다'로 위장되던 것에 재시도 UI를
추가하고, 전역이던 복원 실패 상태를 실패한 리비전에서만 표시한다.

* fix: 현재값도 trim 기준으로 비교하고 충돌 원인 예외를 보존

과거에 공백이 섞여 저장된 현재값과 비교가 어긋나 무의미한 리비전이
기록되던 누수를 막고, 재시도 소진 후 원인(유니크 위반·데드락)을
로그로 구분할 수 있게 cause를 전달한다.

* test: 동시성 테스트가 실패 경로에서도 스레드 풀을 정리하도록 보강

* feat: 코드 기본 프롬프트를 DB로 시딩(V23)

코드 기본값 4종을 값이 없는 타입에만 INSERT하고, 새로 들어간 타입의
v1 리비전도 함께 시딩한다. 이관 직전 defaultPrompt() 출력을 골든
파일로 고정해 시딩이 바이트 단위로 동일함을 테스트로 보장한다.

* refactor: 프롬프트 코드 기본값 제거, DB를 단일 원본으로

PromptProvider는 유저 콘텐츠 조립만 남긴다 — 섹션 구조·응답 조건
형식은 파서와 계약이라 코드가 소유하고, 시스템 프롬프트 본문은
V23 시딩 이후 DB가 단일 원본이다. 행이 없으면 시딩 누락이므로
즉시 실패시킨다(코드 폴백 삭제).

* refactor: 프롬프트 기본값 복원 버튼 제거

코드 기본값이 사라져 '기본값 변경'의 대상이 없다. 과거 상태로의
복원은 버전 이력의 리비전 복원이 대신한다.

* fix: V23 비-COMMON 행의 model이 시딩 시점 COMMON 값을 따르도록 변경

자리표시자에 상수를 넣으면 이후 모델 변경(COMMON만 갱신) 시 어긋난
값이 남는다. COMMON 참조 서브쿼리로 바꾸고 INSERT 순서 제약을 주석으로
남기며, 시딩 행의 model 일관성과 requireRow 실패 경로 전체(모델·프롬프트
읽기/쓰기·공통 뷰)를 테스트로 고정한다.
fix: 스택 squash 병합으로 중복된 currentPromptForUpdate 제거

118, 119를 순차 squash 머지할 때 GitHub 3-way 병합이 같은 메서드
추가를 두 번 적용해 dev가 컴파일되지 않았다(Conflicting overloads).
* feat: 엉뚱이 소재를 프롬프트 타입으로 추가하고 DB로 시딩(V24)

EONGTTUNG_TOPIC 타입(한 줄에 소재 하나)을 추가해 프롬프트와 같은
저장소·버전 이력·복원을 그대로 쓴다. 기존 소재 30개를 값이 없을
때만 시딩하고, 골든 파일로 이관 동일성을 고정한다.

* refactor: 엉뚱이 소재를 DB에서 읽도록 변경, 코드 목록 제거

셀렉터는 EONGTTUNG_TOPIC 프롬프트를 줄 단위로 파싱해 무작위로
고른다. 목록이 비어 있으면 시딩·편집 오류이므로 즉시 실패시킨다.

* feat: 백오피스에 엉뚱이 소재 탭 추가

한 줄에 소재 하나로 편집하며, 프롬프트와 같은 저장·버전 이력·복원
UI를 그대로 쓴다. 조립 안내 배너는 조립에 안 쓰이는 이 탭에서 숨긴다.

* fix: 소재 목록 타입의 시스템 프롬프트 조립을 계약으로 차단

resolve(EONGTTUNG_TOPIC)이 호출되면 공통+소재 나열이라는 성립
불가능한 프롬프트가 조용히 만들어진다. 호출자는 없지만 공개
컴포넌트의 계약으로 require 가드를 명시한다.

* fix: V24 소재 행의 model도 시딩 시점 COMMON 값을 따르도록 변경

V23과 같은 이유 — 자리표시자 상수가 모델 변경과 어긋나는 것을 막는다.
COMMON 행은 V23이 보장하므로 참조는 항상 성립한다.

* fix: 엉뚱이 소재 안내 문구를 실제 동작에 맞게 수정

목록이 비면 엉뚱이만 빠지는 게 아니라 생성이 실패하며, 그 상태는
저장 버튼 비활성화와 @notblank가 이미 막고 있다. 문구가 도달 불가한
동작을 설명하지 않도록 저장 불가 안내로 바꾼다.

* test: 엉뚱이 소재 선택이 한 소재로 고정되지 않는지 검증

멤버십만 확인하던 테스트는 항상 첫 줄을 돌려주는 구현도 통과했다.
100회 선택 결과를 집합으로 모아 2종 이상인지 확인한다.

* docs: LLM 설정 Swagger 태그 설명에 EONGTTUNG_TOPIC 용도 추가
* chore: dev·prod 전 컨테이너에 로그 로테이션 적용

기본 json-file 드라이버는 무제한 적재라 디스크가 차면 MySQL 쓰기
실패로 서비스 전체가 내려간다. YAML 앵커로 한 번 정의해 5개 서비스에
서비스당 최대 30MB(10MB×3) 상한을 건다.

* chore: 신규 서버 세팅에 도커 로그 로테이션 기본값 작성 추가

/etc/docker/daemon.json이 없을 때만 작성하고 도커를 재시작한다.
compose에 명시되지 않은 임시 컨테이너까지 기본값으로 덮는다.

* chore: 운영 중 재실행 시 도커 재시작을 미루고 live-restore 추가

스크립트는 멱등이라 컨테이너가 도는 서버에서 재실행될 수 있고, 그때
데몬 재시작은 전 컨테이너를 내렸다 올린다. 실행 중인 컨테이너가 있으면
재시작을 건너뛰고 안내만 한다. live-restore는 이후의 데몬 재시작에서
컨테이너를 유지시킨다.
* chore: CI에 admin-web 린트·타입체크·빌드 추가

프론트 타입 에러가 dev 머지 후 이미지 빌드에서야 드러나던 것을 PR
단계에서 잡는다. workflow_call로도 호출되므로 배포 전 게이트 역할도
겸한다. 1분 남짓이라 경로 필터 없이 항상 돌린다 - 조건부 스킵은
브랜치 보호 필수 체크와의 조합을 복잡하게 만든다.

* docs: admin 빌드 실패 시 실제 동작에 맞게 주석 정정

배포가 '이미 진행 중'인 게 아니라, deploy가 build-admin에 의존해
통째로 막힌다(백엔드 이미지만 GHCR에 올라감). 이 job을 넣는 더 강한
근거라서 사실대로 적는다.
* feat: 미저장 프롬프트로 생성하는 조립·생성기 경로 추가

플레이그라운드가 저장 전 프롬프트를 실제 생성과 같은 조립 규칙,
같은 호출 경로로 시험할 수 있게 SystemPromptResolver에 미리보기
조립을, CommentGenerator에 설정 주입 오버로드를 더한다. 기존
생성 경로의 동작은 그대로다.

* feat: 프롬프트 플레이그라운드 미리보기 API

미저장 프롬프트(공통·댓글)와 샘플 일기로 실제 생성 경로를 시험하는
관리자 전용 엔드포인트. 프롬프트도 생성 로그도 저장하지 않고, 생성·
검증 실패는 과금된 토큰과 함께 오류 필드로 담아 돌려준다(실패 관찰이
목적). 캐릭터·티키타카는 고정하거나 실제처럼 무작위 선택할 수 있다.

* feat: 백오피스 프롬프트 실험실 페이지

전용 페이지에서 샘플 일기·캐릭터 조건·프롬프트 오버라이드(비우면
저장값)를 지정해 미리보기를 실행한다. 결과는 메트릭 타일(모델·지연·
토큰·비용), 채팅 말풍선 피드, 계약 위반·생성 실패 배너, 실제 전달
프롬프트 뷰어로 보여준다.

* test: 미리보기 조립의 오버라이드·폴백 형식을 단위 테스트로 고정

* refactor: 캐릭터 고정 선택의 불변식을 CharacterSelection 팩토리로 이동

중복 제거·1명 이상·티키타카는 2명 이상이라는 규칙을 값 스스로
보장하게 해(일급 컬렉션), 무작위 선택과 플레이그라운드 고정 선택이
같은 불변식을 공유한다. 서비스는 잘못된 입력을 400으로 번역만 한다.

* refactor: 미리보기 결과 타입을 타입 모듈로 분리

* fix: 티키타카 말풍선의 아바타를 다른 댓글과 같게 왼쪽 정렬

들여쓰기로 아바타가 오른쪽으로 밀려 보이던 것을 없애고, 답장 관계는
이름 옆의 화살표 라벨로만 표시한다.

* feat: 미리보기 캐릭터·티키타카를 항상 직접 지정하도록 변경

무작위 옵션을 없애고 캐릭터를 필수 입력으로 만든다. 실험은 조건을
고정하고 프롬프트만 바꿔 비교하는 용도라 무작위가 낄 자리가 없고,
서버의 무작위 분기·셀렉터 의존도 함께 제거된다.

* feat: 답장 미리보기 API 추가

유저가 특정 캐릭터의 댓글에 답장했을 때 그 캐릭터의 재응답을 실제
답글 생성 경로(REPLY 조립·promptId·validateReply)로 시험한다.
미리보기 조립은 프롬프트 타입을 받는 형태로 일반화하고, 생성기에
답글 설정 주입 오버로드를 더한다.

* feat: 실험실에서 실제 유저처럼 이어서 대화·답장할 수 있게 확장

생성된 피드 아래 컴포저로 일반 메시지를 보내면 세션 대화를 요약으로
전달해 새 댓글 피드를 생성하고, 캐릭터 말풍선의 답장 버튼으로 특정
캐릭터에게 답장하면 그 캐릭터 1명이 재응답한다. 유저 말풍선은 오른쪽
정렬, 세션 누적 비용을 함께 보여준다.

* refactor: 미리보기 토큰·비용 조립을 PreviewUsage 값 객체로 통합

성공 출력과 실패 예외 양쪽에서 반복되던 토큰 4종·비용 계산(4블록)을
한 곳으로 모은다. 미리보기 타입이 늘어도 환산 규칙 복제가 반복되지 않는다.

* fix: 답장 미리보기 characterComment에 2000자 제한 추가

diaryContent·userReply와 같은 기준. 프롬프트 부풀림으로 인한 불필요한
비용을 요청 단계에서 막는다.

* test: 미리보기 조립에 COMMON을 넘기면 실패하는 가드 검증 추가

* fix: 세션 말풍선 id를 단조 카운터로 발급해 충돌 가능성 제거

세션 길이 기반 오프셋(+100/+200/+300)은 세션이 길어지면 이전 턴의
티키타카 id와 겹쳐 React key가 중복될 수 있었다.

* fix: 캐릭터를 모두 해제하면 일반 이어보내기를 막고 안내 문구 표시

세션 도중 캐릭터 칩을 전부 끄면 빈 characters로 400이 나던 것을
전송 비활성화 + 플레이스홀더 안내로 가드한다. 답장 전송은 영향 없다.

* fix: 미리보기 지연 측정을 단조 시계로 변경

벽시계(currentTimeMillis)는 NTP 보정에 흔들려 latencyMs가 왜곡되거나
음수가 될 수 있다. 프롬프트 안 비교 지표로 쓰이는 값이라 nanoTime으로 잰다.

* fix: 프롬프트 오버라이드와 요약에 길이 상한 추가

가장 긴 입력인 미저장 프롬프트에만 상한이 없어 입력 토큰·과금이
무제한으로 늘 수 있었다. 오버라이드는 20000자, 요약은 프로덕션
DTO와 같은 2000자. 저장 API(UpdatePromptRequest)에도 같은 상한을
둬서 저장과 미리보기의 규칙이 갈라지지 않게 한다.

* docs: 미리보기 응답 characters 설명을 실제 계약에 맞게 수정

무작위 선택 경로가 제거돼 '무작위 선택 결과 포함'은 사실과 다르다.

* test: 미리보기 검증·인가 계약 테스트 보강

- 빈 일기 400 테스트에 characters를 채워 NotBlank 위반만 남긴다
- 답장 character 누락이 Bean Validation 400으로 막히는 계약 고정
- 일반 회원 토큰 403(관리자 전용) 검증 추가
- 중복 제거 후 1명이면 티키타카 불가한 순서 의존성 케이스 추가

* feat: API 실패 응답의 서버 메시지를 담는 ApiError 도입

message는 기존 호출부 호환을 위해 분기용 코드를 유지하고, 사람이 읽을
서버 메시지는 detail에 담는다.

* fix: 실험실 coderabbitai 리뷰 반영(접근성·요약·오류 표면화·호출 통합)

- 답장 버튼에 focus-visible 가시성과 캐릭터를 담은 aria-label 추가
- 캐릭터 토글의 setTikitaka를 상태 updater 밖으로 이동(StrictMode 순수성)
- 세션 요약을 문자 대신 줄 단위로 잘라 화자 라벨 훼손을 막고, 관리자
  지정 요약은 항상 보존
- 400 입력 오류 시 서버가 내려준 사유(ApiError.detail)를 그대로 표시
- 일기·컴포저에 서버와 같은 2000자 제한과 글자 수 표시
- 세션 시작과 이어보내기의 피드 호출을 runFeedPreview 하나로 통합해
  두 경로의 생성 조건이 갈라지지 않게 함

* fix: 미리보기 조립에도 EONGTTUNG_TOPIC 거부 가드 적용

resolve()에만 있던 가드를 resolveForPreview()에도 확장한다. 없으면
소재 목록이 시스템 프롬프트처럼 조립되는 성립 불가능한 미리보기가
조용히 만들어진다.

* fix: 실험실 페이지 헤더에 섹션명이 비어 나오던 문제 수정

sectionOf에 /playground 분기가 없어 빈 문자열이 렌더됐다.

* feat: LLM 전달 내용 뷰어를 왼쪽 실험 조건 열로 이동

프롬프트를 편집하는 왼쪽 열에서 조립 결과를 바로 대조할 수 있도록
오른쪽 세션 아래에 있던 접이식 뷰어를 옮긴다.

* feat: 실험실 리뷰 반영 - 과거 요약 입력·티키타카 상한·PREVIEW 생성 로그

- pastSummaries 요청 필드 추가(최대 5개, 각 2000자): 프로덕션이 무작위
  주입하는 [과거 대화 요약] 섹션이 걸리는 프롬프트 지시까지 시험 가능
- tikitakaCount에 @min(0)/@max(5): 오타가 그대로 프롬프트에 박혀 출력
  토큰이 나가는 것을 방어
- 실험 호출을 GenerationType.PREVIEW로 기록해 실제 과금을 추적.
  memberId 없음(일일 상한 미집계), 품질 지표 findAllSince에서 제외

* fix: 실험실 리뷰 반영 - 답장 일기 맥락·티키타카 정렬·요약 가드

- 답장 요청이 첫 일기(firstDiary)에 고정되던 버그 수정: 말풍선에 소속
  일기를 담아 프로덕션(rootMessageId 조회)과 같은 맥락을 보낸다
- 티키타카를 답장 대상 댓글 바로 뒤에 끼워 프로덕션(MessageThreadOrder)과
  같은 읽기 순서로 표시
- 요약 입력에 maxLength·카운터 추가, 예산 음수로 세션 내용이 통째로
  빠지던 transcriptSummary 가드
- 과거 대화 요약 입력칸 추가(한 줄에 하나, 최대 5개)
@github-actions

Copy link
Copy Markdown

Test Results

522 tests  +54   522 ✅ +54   2m 8s ⏱️ +7s
 74 suites + 9     0 💤 ± 0 
 74 files   + 9     0 ❌ ± 0 

Results for commit 797dcdf. ± Comparison against base commit b62b823.

@github-actions

Copy link
Copy Markdown

Test Coverage

Overall Project 81.43% -0.98% 🍏
Files changed 92.52% 🍏

File Coverage
PromptPreviewResult.kt 100% 🍏
ReplyPreviewCommand.kt 100% 🍏
ReplyPreviewResult.kt 100% 🍏
PromptPreviewCommand.kt 100% 🍏
PreviewUsage.kt 100% 🍏
ConcurrentRegistrationException.kt 100% 🍏
AuthService.kt 100% 🍏
SystemPromptResolver.kt 100% 🍏
PromptRevisionConflictException.kt 100% 🍏
PromptType.kt 100% 🍏
CharacterSelector.kt 100% 🍏
EongttungTopicSelector.kt 100% 🍏
GenerationType.kt 100% 🍏
ErrorCode.kt 100% 🍏
PromptReplyPreviewResponse.kt 100% 🍏
PromptReplyPreviewRequest.kt 100% 🍏
PromptSettingResponse.kt 100% 🍏
PromptRevisionResponse.kt 100% 🍏
UpdatePromptRequest.kt 100% 🍏
PromptPreviewRequest.kt 100% 🍏
RecoverableConflictException.kt 100% 🍏
ConflictRetry.kt 100% 🍏
PromptProvider.kt 99.26% 🍏
PromptPreviewService.kt 98.89% -1.11% 🍏
PromptPreviewResponse.kt 98.56% -1.44% 🍏
PromptRevisionService.kt 94.77% -5.23% 🍏
PromptRevision.kt 93.85% -6.15% 🍏
Nickname.kt 93.41% 🍏
LlmSettingsService.kt 92.7% -1.46% 🍏
AdminPromptPreviewController.kt 87.67% -12.33% 🍏
LlmSettings.kt 81.82% 🍏
AdminLlmSettingsController.kt 60.87% -5.59% 🍏
GeminiCommentGenerator.kt 3.47% -8.9% 🍏
PromptRevisionDetailResponse.kt 0% 🍏

@theminjunchoi
theminjunchoi merged commit 7a4642f into prod Aug 12, 2026
10 of 11 checks passed
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4e5450ee-3d47-4e70-94ed-4737d923ee9c

📥 Commits

Reviewing files that changed from the base of the PR and between b62b823 and 797dcdf.

📒 Files selected for processing (75)
  • .github/workflows/ci.yml
  • admin-web/src/App.tsx
  • admin-web/src/components/layout/AdminLayout.tsx
  • admin-web/src/lib/api.ts
  • admin-web/src/pages/llm-settings.tsx
  • admin-web/src/pages/prompt-playground.tsx
  • admin-web/src/pages/prompt-revision-history.tsx
  • admin-web/src/types/promptPreview.ts
  • admin-web/src/types/promptRevision.ts
  • deploy/dev/docker-compose.yml
  • deploy/prod/docker-compose.yml
  • deploy/setup-server.sh
  • src/main/kotlin/com/nexters/gamss/admin/controller/AdminLlmSettingsController.kt
  • src/main/kotlin/com/nexters/gamss/admin/controller/AdminPromptPreviewController.kt
  • src/main/kotlin/com/nexters/gamss/admin/controller/dto/PromptPreviewRequest.kt
  • src/main/kotlin/com/nexters/gamss/admin/controller/dto/PromptPreviewResponse.kt
  • src/main/kotlin/com/nexters/gamss/admin/controller/dto/PromptReplyPreviewRequest.kt
  • src/main/kotlin/com/nexters/gamss/admin/controller/dto/PromptReplyPreviewResponse.kt
  • src/main/kotlin/com/nexters/gamss/admin/controller/dto/PromptRevisionDetailResponse.kt
  • src/main/kotlin/com/nexters/gamss/admin/controller/dto/PromptRevisionResponse.kt
  • src/main/kotlin/com/nexters/gamss/admin/controller/dto/PromptSettingResponse.kt
  • src/main/kotlin/com/nexters/gamss/admin/controller/dto/UpdatePromptRequest.kt
  • src/main/kotlin/com/nexters/gamss/auth/service/AuthService.kt
  • src/main/kotlin/com/nexters/gamss/auth/service/ConcurrentRegistrationException.kt
  • src/main/kotlin/com/nexters/gamss/global/exception/ErrorCode.kt
  • src/main/kotlin/com/nexters/gamss/global/retry/ConflictRetry.kt
  • src/main/kotlin/com/nexters/gamss/global/retry/RecoverableConflictException.kt
  • src/main/kotlin/com/nexters/gamss/llm/generation/CommentGenerator.kt
  • src/main/kotlin/com/nexters/gamss/llm/generation/GeminiCommentGenerator.kt
  • src/main/kotlin/com/nexters/gamss/llm/preview/PreviewUsage.kt
  • src/main/kotlin/com/nexters/gamss/llm/preview/PromptPreviewCommand.kt
  • src/main/kotlin/com/nexters/gamss/llm/preview/PromptPreviewResult.kt
  • src/main/kotlin/com/nexters/gamss/llm/preview/PromptPreviewService.kt
  • src/main/kotlin/com/nexters/gamss/llm/preview/ReplyPreviewCommand.kt
  • src/main/kotlin/com/nexters/gamss/llm/preview/ReplyPreviewResult.kt
  • src/main/kotlin/com/nexters/gamss/llm/prompt/PromptProvider.kt
  • src/main/kotlin/com/nexters/gamss/llm/prompt/PromptType.kt
  • src/main/kotlin/com/nexters/gamss/llm/selection/CharacterSelector.kt
  • src/main/kotlin/com/nexters/gamss/llm/selection/EongttungTopicSelector.kt
  • src/main/kotlin/com/nexters/gamss/llm/settings/LlmSettings.kt
  • src/main/kotlin/com/nexters/gamss/llm/settings/LlmSettingsRepository.kt
  • src/main/kotlin/com/nexters/gamss/llm/settings/LlmSettingsService.kt
  • src/main/kotlin/com/nexters/gamss/llm/settings/PromptRevision.kt
  • src/main/kotlin/com/nexters/gamss/llm/settings/PromptRevisionConflictException.kt
  • src/main/kotlin/com/nexters/gamss/llm/settings/PromptRevisionRepository.kt
  • src/main/kotlin/com/nexters/gamss/llm/settings/PromptRevisionService.kt
  • src/main/kotlin/com/nexters/gamss/llm/settings/SystemPromptResolver.kt
  • src/main/kotlin/com/nexters/gamss/member/domain/Nickname.kt
  • src/main/kotlin/com/nexters/gamss/monitoring/domain/GenerationType.kt
  • src/main/kotlin/com/nexters/gamss/monitoring/repository/GenerationLogRepository.kt
  • src/main/resources/db/migration/V22__prompt_revisions.sql
  • src/main/resources/db/migration/V23__seed_default_prompts.sql
  • src/main/resources/db/migration/V24__seed_eongttung_topics.sql
  • src/main/resources/db/migration/V25__expand_nickname_column.sql
  • src/test/kotlin/com/nexters/gamss/admin/controller/AdminLlmSettingsControllerIntegrationTest.kt
  • src/test/kotlin/com/nexters/gamss/admin/controller/AdminPromptPreviewControllerIntegrationTest.kt
  • src/test/kotlin/com/nexters/gamss/admin/service/DashboardStatsIntegrationTest.kt
  • src/test/kotlin/com/nexters/gamss/auth/service/AuthServiceTest.kt
  • src/test/kotlin/com/nexters/gamss/global/retry/ConflictRetryTest.kt
  • src/test/kotlin/com/nexters/gamss/llm/preview/PromptPreviewServiceTest.kt
  • src/test/kotlin/com/nexters/gamss/llm/selection/CharacterSelectionTest.kt
  • src/test/kotlin/com/nexters/gamss/llm/selection/EongttungTopicSelectorTest.kt
  • src/test/kotlin/com/nexters/gamss/llm/settings/LlmSettingsServiceTest.kt
  • src/test/kotlin/com/nexters/gamss/llm/settings/PromptRevisionConcurrencyIntegrationTest.kt
  • src/test/kotlin/com/nexters/gamss/llm/settings/PromptRevisionRepositoryTest.kt
  • src/test/kotlin/com/nexters/gamss/llm/settings/PromptRevisionServiceTest.kt
  • src/test/kotlin/com/nexters/gamss/llm/settings/PromptSeedIntegrationTest.kt
  • src/test/kotlin/com/nexters/gamss/llm/settings/SystemPromptResolverTest.kt
  • src/test/kotlin/com/nexters/gamss/member/domain/NicknameTest.kt
  • src/test/kotlin/com/nexters/gamss/support/FakeCommentGeneratorConfig.kt
  • src/test/resources/prompts/CARD.default.txt
  • src/test/resources/prompts/COMMENT.default.txt
  • src/test/resources/prompts/COMMON.default.txt
  • src/test/resources/prompts/EONGTTUNG_TOPIC.default.txt
  • src/test/resources/prompts/REPLY.default.txt

Walkthrough

관리자 프롬프트 리비전 저장·복원과 LLM 미리보기 기능을 추가했습니다. 프롬프트를 DB 단일 원본으로 관리하고, admin-web에서 실험 세션과 리비전 이력을 제공합니다. Docker 로그 로테이션, 닉네임 검증, CI도 변경했습니다.

Changes

프롬프트 리비전 관리

Layer / File(s) Summary
리비전 저장과 관리자 API
src/main/kotlin/com/nexters/gamss/llm/settings/*, src/main/kotlin/com/nexters/gamss/admin/controller/*, src/main/resources/db/migration/*
프롬프트 리비전 엔티티, 페이지 조회, 복원 API와 동시성 재시도를 추가했습니다. 초기 프롬프트와 v1 리비전을 마이그레이션으로 생성합니다.
LLM 미리보기 실행
src/main/kotlin/com/nexters/gamss/llm/preview/*, src/main/kotlin/com/nexters/gamss/llm/generation/*, src/main/kotlin/com/nexters/gamss/llm/selection/*
댓글·답글 미리보기를 실제 생성 경로로 실행합니다. 검증 오류, 생성 오류, 토큰, 비용, 지연 시간과 PREVIEW 로그를 반환합니다.
관리자 화면
admin-web/src/pages/prompt-playground.tsx, admin-web/src/pages/prompt-revision-history.tsx, admin-web/src/pages/llm-settings.tsx
플레이그라운드 입력, 대화 세션, 프롬프트 검사, 리비전 조회와 복원 UI를 추가했습니다.
계약과 통합 검증
admin-web/src/types/*, src/test/kotlin/com/nexters/gamss/admin/controller/*, src/test/kotlin/com/nexters/gamss/llm/*
미리보기·리비전 응답 타입과 관리자 권한, 입력 검증, 저장·복원·동시성 테스트를 추가했습니다.

플랫폼 운영 및 입력 검증

Layer / File(s) Summary
운영 설정과 입력 검증
deploy/*, src/main/kotlin/com/nexters/gamss/member/domain/Nickname.kt, src/main/resources/db/migration/V25__expand_nickname_column.sql
Docker 로그 파일을 서비스별로 순환 보관합니다. 닉네임은 그래핌과 코드 포인트 기준으로 검증합니다.
공통 충돌 재시도와 CI
src/main/kotlin/com/nexters/gamss/global/retry/*, .github/workflows/ci.yml
회복 가능한 충돌의 공통 예외와 재시도 대상을 확장했습니다. Node 22에서 admin-web lint, 타입체크와 빌드를 실행합니다.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant 관리자
  participant PromptPlaygroundPage
  participant AdminPromptPreviewController
  participant PromptPreviewService
  participant GeminiCommentGenerator
  관리자->>PromptPlaygroundPage: 샘플 일기와 프롬프트 입력
  PromptPlaygroundPage->>AdminPromptPreviewController: 댓글 또는 답글 미리보기 요청
  AdminPromptPreviewController->>PromptPreviewService: 검증된 미리보기 명령 전달
  PromptPreviewService->>GeminiCommentGenerator: 설정 지정 LLM 생성 요청
  GeminiCommentGenerator-->>PromptPreviewService: 생성 결과와 사용량 반환
  PromptPreviewService-->>AdminPromptPreviewController: 결과와 오류 메타데이터 반환
  AdminPromptPreviewController-->>PromptPlaygroundPage: 미리보기 응답 반환
  PromptPlaygroundPage-->>관리자: 피드, 비용, 토큰, 지연 시간 표시
Loading
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant