PRD.md §2.1-②·§3.1 P0 갭 우선순위 #2. close/formal 2종 관계 티어를 추가하고,
초안 생성 시 상대별 톤을 다르게 낸다.
- core-backend: TwinSettings.RelationshipTier(전역 기본값, 안전 우선 formal
기본), Contact.RelationshipTier(1:1 연락처별 오버라이드, nullable).
persona.go의 resolveRelationshipTier()가 연락처 오버라이드 → 전역 기본값 →
formal 순으로 해석하고, 그룹 대화는 상대가 여럿이라 항상 전역 기본값만 사용.
POST /conversations/:id/draft는 기존에 인증을 요구하지 않던 동작을 깨지
않도록 currentUser(..., false)로 선택적 인증 처리 후 티어를 주입.
- 안전 관련 부수 수정: 그룹 대화에서는 전역 자율성 레벨(L1/L2)과 무관하게
와카뷰 자동 발송을 무조건 차단(단톡 따라잡기는 L0 고정이 맞음).
- ai-service: RELATIONSHIP_TIER_INSTRUCTIONS + system_prompt_for_tier()로
Gemini system_instruction에 관계 톤 지침을 주입. 에스컬레이션/정체성 게이팅
로직은 티어와 무관하게 그대로 유지.
- mobile: 온보딩(말투 샘플 다음 단계)과 자율성 설정 화면에 전역 기본값
SegmentedButton, 연락처 추가/수정 다이얼로그에 _RelationshipTierPicker로
상대별 오버라이드 추가.
- 테스트: core-backend persona_test.go 6개(해석 순서·그룹 예외·비인증
기본값 포함), ai-service 6개(system_prompt_for_tier + draft_reply 경로)
전부 추가, 기존 스위트 모두 통과(go test, pytest 47/47, flutter analyze/test).
실제 Flutter 빌드 + Playwright로 온보딩→연락처 오버라이드→목록 표시까지
전체 라운드트립 시각 검증 완료.
- docs: roadmap.md §2.7-B, deploy-checklist.md N4-C2a~d 완료 처리. 다음
우선순위는 Track C3(스팸/도배 감지 최소 버전).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014YSB5PqF38raTxP5ABgr9m
Closes the biggest content gap found against PRD.md §3.1 P0: group chat
catch-up was one of only two v1 MVP scenarios and had zero implementation.
Backend (core-backend):
- ConversationParticipant.LastReadMessageID read marker.
- POST /conversations/:id/read advances the caller's marker (never backward).
- GET /conversations/:id/summary builds context from messages since that
marker and calls ai-service's new POST /summarize; returns unread_count
and needs_reply.
- GET /conversations now reports unread_count per room.
- Safety: group conversations now unconditionally block twin-authored
sends (POST /conversations/:id/messages), regardless of the sender's
global autonomy level. PRD.md §2.3-③ requires this scenario stay L0-fixed
with no auto-send; autonomy level is a per-user global setting today, so
this closes the only path a global L2 whitelist match could otherwise
auto-send into a group.
AI service (ai-service): new summarize.py module (same Gemini-call shape as
generation.py's draft_reply, no escalation/identity gating since nothing
generated here is ever sent) + POST /summarize.
Mobile: "새 대화" dialog now supports adding/removing multiple peer fields
(2+ peers -> is_group:true automatically); unread badge on conversation
rows; ChatScreen takes isGroup and renders its L1 panel as L0-locked for
groups; new "안 본 동안 요약" AppBar action opens a dialog with the summary
and, when a reply looks needed, a button that feeds straight into the
existing draft-request flow.
Verified with a real Flutter build against a live core-backend + ai-service
instance (Playwright driving 3 demo accounts through group creation, unread
badges, and the summary dialog) — this caught a real ordering bug: marking
the read marker on chat *open* meant the summary was always empty by the
time you could tap it, since opening the room already advanced the marker
past everything you'd come to catch up on. Fixed by marking read on screen
*exit* (dispose) instead, so the marker reflects what was unread that whole
visit and updates once you leave.
go test / pytest / flutter analyze+test all pass.
명칭 변경:
- decision-log.md Q6 확정: "분신"(가칭) → "와카뷰 (Ykavu)" — 焚身(분신자살)
동음이의 리스크도 있었고 메신저 브랜드로 부르기 무거웠음. Master가 최종
선택한 이름으로 변경(2026-07-31), 파생 문서(AGENTS.md/CLAUDE.md/PRD 등)·
Flutter 앱 텍스트·web manifest/index.html·AndroidManifest 라벨까지 전부 반영
- ai-service: 본인확인 고정 문구(identity.py)·시스템 프롬프트(generation.py)도
갱신 — 새 이름 기준으로 "본인이야 와카뷰야?" 류 질문을 감지하도록 정규식도
같이 손봄(단순 문자열 치환만으론 어미 형태가 안 맞아서 테스트 추가/조정)
- poc/tone-corpus/의 실험용 프롬프트는 의도적으로 그대로 둠(ai-service README에
이미 명시된 대로 프로덕션과 분리된 실험 도구)
UI/UX:
- mobile/lib/theme/app_theme.dart: 시드 컬러를 인디고/라벤더로 변경, 화면 전체에
깔리는 소프트 그라디언트(라이트: 라벤더→스카이→핑크 파스텔, 다크: 딥 인디고→
네이비→플럼) + 카드/인풋/칩을 반투명 "글래스" 서피스로 전환
- mobile/lib/widgets/gradient_backdrop.dart(신규): MaterialApp.builder에 연결해
모든 화면에 자동으로 그라디언트 배경 적용
- mobile/lib/screens/splash_screen.dart(신규) + main.dart 재구성: 기존엔
session.restore()를 기다리는 동안 아무 것도 안 그려서 흰 화면/텍스트만 뜨는
구간이 있었음 — runApp을 먼저 하고 restore 동안 브랜드 스플래시가 뜨도록 변경
- chat_screen·autonomy_settings_screen·data_flow_screen의 커스텀 패널들도
글래스 스타일로 맞춤
BunsinApp -> YkavuApp (mobile/lib/main.dart, test/widget_test.dart 동기화)
Flutter SDK가 없는 환경이라 flutter analyze/run으로 직접 컴파일 검증은 못했음 —
중괄호/괄호 균형과 기존에 검증된 API 패턴 위주로 신중하게 작성함. go test·
pytest는 전부 통과.
- Record draft/escalate latency and error rates on /admin/metrics
- Add minimal /admin/dashboard and full message JSON on send
- Fix identity answers in ai-service with stable copy + tests
- Flutter DataFlowScreen + SessionsScreen; device-token API stub
- Update roadmap B checkboxes after A3 API E2E
Co-authored-by: okuma <o0kuma@users.noreply.github.com>
ai-service:
- escalation_filter/retrieve_style/generation/main(FastAPI)의 ad-hoc
TestClient 검증을 ai-service/tests/ 정식 pytest 스위트로 승격(34개).
Gemini 호출은 mock, retrieve_style은 overlap이 recency를 항상
이긴다는 것(과거 버그 재발 방지)까지 포함
- on_event(deprecated) -> lifespan 컨텍스트 매니저로 교체
core-backend: 자율성 플로우(L0->L1->L2) 통합 테스트를 쓰려면 실제 분기
로직이 있어야 해서, roadmap.md §2.2에서 미결로 남아있던 자율성 엔진
오케스트레이션 최소 버전을 이번에 구현:
- PATCH /users/:id/twin-settings -- 자율성 레벨 변경 (기본값 L0)
- 트윈 발송 시 에스컬레이션 통과 후 레벨 확인: L0는 항상 차단, L1은
approved:true 필요, L2는 화이트리스트 매칭 시 즉시 자동발송·매칭
없으면 L1과 동일하게 승인 필요. 에스컬레이션은 레벨/화이트리스트/
승인 여부와 무관하게 항상 우선(테스트로 확인)
- 화이트리스트 매칭은 v1 최소 구현(전역 키워드 매칭, 상대별 예외는
아직) -- 대화방↔연락처 연결이 모델링되지 않아 보류, README에 명시
온보딩·채팅·설정 수동 QA는 Flutter 클라이언트가 없어 이 환경에서는
보류, roadmap.md에 근거 남김