Commit Graph

14 Commits

Author SHA1 Message Date
Claude c35fad2737
자연스러움 피드백 계측(N4-12) 구현: 초안 무수정 발송률 + 원탭 나답아요 평가
암묵 신호: Message.DraftEdited(nullable)를 트윈 승인-발송 경로에서 클라이언트가
보낸 original_draft_text와 실제 발송 텍스트를 diff해 계산(사람 메시지·구버전
클라는 nil, 추측하지 않음). 명시 신호: Message.NaturalnessRating(nullable) +
POST /messages/:id/feedback(트윈 메시지만, 재제출은 덮어씀)로 "이 답장
나답아요?" 원탭 👍/👎을 채팅방에 비침투적으로(모달 아님, 한 번 탭하면
다시 안 보임) 노출. /admin/metrics에 draft_unedited_rate·
naturalness_positive_rate 추가(분모 0 zero-guard), 대시보드 카드 2개 추가.

PoC 실행이나 결론이 아니라 캡처 장치일 뿐 — 실제 자연스러움 %는 N5 이후
실 베타 데이터로 확정한다. docs/roadmap.md·deploy-checklist.md N4-12 동기화.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014YSB5PqF38raTxP5ABgr9m
2026-08-03 07:39:55 +00:00
Claude a1415f5ce0
운영 대시보드 낡은 메모 정정 + 트윈 발송 차단 사유별 분해 추가
roadmap.md §2.6의 "모니터링 대시보드 UI·지연시간·오류율 아직 없음" 메모가
낡은 기록이었음을 재확인 -- GET /admin/dashboard(HTML)와 /admin/metrics의
생성 지연시간·오류율 계측은 이미 이전 Phase 1 B 커밋에서 구현돼 있었고,
같은 문서 §B 항목과도 모순되고 있었음. [~] -> [x]로 정정하고 실제 배경을
남김.

실제로 오늘 추가한 것: RuntimeMetrics.TwinSendsBlocked를 사유별
(peer_veto/group_conversation/flood_blocked/flood_detected/
escalate_check_error/escalated/autonomy_l0/autonomy_l1_unapproved/
autonomy_l2_no_whitelist_match/autonomy_unknown_level)로 분해하는
TwinSendsBlockedByReason을 추가하고, /admin/metrics에
twin_sends_blocked_by_reason으로 노출(기존 twin_sends_blocked 총합은
유지). 이미 JSON에는 있었지만 화면엔 안 보이던 escalations_by_reason과
새 필드를 /admin/dashboard에 사유별 표로 렌더링.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014YSB5PqF38raTxP5ABgr9m
2026-08-03 07:12:34 +00:00
Claude f9fba46889
관계 메모 실제 반영(2.7-E) 구현: draftRequest에 RelationshipNote 전달 + 프롬프트 주입
Contact.RelationshipNote는 CRUD는 이미 있었지만 draftRequest에 필드 자체가 없어
ai-service 프롬프트에 전혀 전달되지 않는 저장 전용 스텁이었다. 이번 변경으로 실제
초안 생성에 반영되게 만든다.

- core-backend/aiservice.go: draftRequest에 RelationshipNote string 필드 추가
  (relationship_tier 옆, omitempty, 빈 문자열 = 무영향)
- core-backend/persona.go: resolveRelationshipNote() 추가. 티어/자율성과 달리
  메모는 순전히 개인별이라 전역 기본 메모 개념이 없음 -- 그룹 대화나 매칭되는
  Contact가 없으면 빈 문자열로 귀결. resolveRelationshipTier/resolveAutonomyLevel/
  resolveRelationshipNote 셋이 복붙하던 "1:1 상대 Contact 찾기" 루프를
  findCounterpartContact() 공용 헬퍼로 추출해 중복 제거
- core-backend/main.go: POST /conversations/:id/draft 핸들러가 resolveRelationshipNote
  결과를 draftRequest에 실어 보내도록 연결
- ai-service/app/generation.py: system_prompt_for_tier()가 relationship_note를
  받아 "[관계 메모] {note} -- ..." 문단을 관계 티어 지침과 별도로 추가(빈 값이면
  기존과 동일). draft_reply()도 파라미터 통과만 함 -- 에스컬레이션/정체성 게이팅은
  전혀 영향 없음
- ai-service/app/main.py: DraftRequest에 relationship_note 필드 추가, /draft가
  draft_reply로 그대로 전달
- 테스트: core-backend Go 64개(신규 3개: 메모 전달/빈 값/그룹 스킵) 전부 통과,
  ai-service pytest 52개(신규 5개) 전부 통과, mobile flutter analyze/test 변경 없이
  그대로 6개 통과(이 기능은 새 UI가 필요 없음 -- 필드는 이미 있었음)
- docs/roadmap.md §2.7-E, docs/deploy-checklist.md N4-C5a/b/c 완료 처리 + NOW/
  바로 다음 5개 요약 갱신

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014YSB5PqF38raTxP5ABgr9m
2026-08-03 05:37:33 +00:00
Claude 5733ec4399
관계별 페르소나(2.7-B) 구현: 관계 티어 필드 + 온보딩/연락처 UI + 톤 프롬프트 분기
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
2026-08-03 04:59:34 +00:00
Claude 64323b824e
feat: implement 단톡 따라잡기 (group catch-up summary) — roadmap.md §2.7-A
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.
2026-08-03 03:16:54 +00:00
Cursor Agent c7029ab2cd
feat: finish Track A polish and Track B demo pairing content
Add PATCH contact for missing peer IDs, contacts banner and edit UI,
enrich /demo with pairing steps, and update tester/signup guidance.

Co-authored-by: okuma <o0kuma@users.noreply.github.com>
2026-07-31 04:43:25 +00:00
Cursor Agent 9b02c59dd6
Complete Phase 1 B: encrypted drift DB, FCM notify, session revoke
- Flutter: drift + SQLCipher local store for tone samples/KV with
  secure-storage passphrase; migrate legacy SharedPreferences
- Core: FCM notifyUser on escalation, admin push-test, push metrics,
  DELETE session for multi-device logout
- Docs/roadmap B checkboxes updated; Linux SQLCipher apt notes

Co-authored-by: okuma <o0kuma@users.noreply.github.com>
2026-07-30 05:28:39 +00:00
Cursor Agent b288d5ed90
Implement Phase 1 A1/A2 APIs and record build plan in roadmap
Add conversation/contact/history/escalation-log endpoints, contact-scoped
whitelist matching, bearer sessions, ADMIN_API_TOKEN guards, and
`go run . migrate`. Update Flutter client to persist/send session tokens
and mark A1/A2 complete in the prioritized checklist.

Co-authored-by: okuma <o0kuma@users.noreply.github.com>
2026-07-30 04:47:06 +00:00
Claude a741001197
화이트리스트 규칙 CRUD API + 메시지 되돌리기(undo) API 추가
화이트리스트 CRUD:
- POST/GET /users/:id/whitelist-rules, DELETE /users/:id/whitelist-rules/:ruleId
- Flutter의 자율성 설정 화면(roadmap.md 2.3)이 바로 붙여 쓸 수 있게
  준비. contact_id는 저장되지만 매칭 로직(whitelistMatches)은 아직
  전역 키워드만 봄 -- 대화방-연락처 연결 모델링 필요(기존에 문서화된
  한계, 그대로 유지)

되돌리기(one-tap undo, AGENTS.md 안전 불변식):
- Message.Retracted 필드 추가
- POST /messages/:id/retract -- 트윈이 자동발송한 메시지만 대상(사람이
  쓴 메시지는 400), 이미 되돌린 건 409
- 성공 시 같은 대화방 WebSocket에 {"type":"retraction", "id":...}
  브로드캐스트. 일반 메시지 브로드캐스트도 {"type":"message"}를 붙여서
  클라이언트가 두 이벤트를 구분할 수 있게 함

되돌리기 버튼/사후알림 UI 자체는 여전히 Flutter 쪽 몫으로 남아있음
2026-07-30 03:01:06 +00:00
Claude f79108b9cd
2.6 베타 배포 준비: 실제 초대 코드 시스템 + 운영 지표 + 거부권 안전 불변식 수정
발견한 문제(2.6 진행 중): 거부권(peer veto) 안전 불변식이 코드에 전혀
구현되어 있지 않았음. Contact.TwinDisabledByPeer는 스키마에만 있고
어디서도 읽거나 쓰지 않았고, tech-design.md §4는 "대화방 단위 플래그"
라는데 실제로는 상대(Contact) 단위로 모델링돼 있어 설계 문서와도
불일치. Conversation.TwinDisabledByPeer로 옮기고 POST
/conversations/:id/veto 추가, 메시지 발송 시 거부권 -> 에스컬레이션
-> 자율성 레벨 순으로 체크(앞 단계가 뒤 단계를 항상 이김)하도록 수정.

2.6 본작업:
- 초대 기반 가입: 기존엔 아무 문자열이나 처음 쓰면 통과돼서 실제로는
  초대 기반이 아니었음. InviteCode 테이블 + POST /invites(발급)
  추가하고 /auth/signup이 미리 발급된 미사용 코드인지 검증하도록 변경
  (모르는 코드 400, 이미 쓴 코드 409). 계정 삭제 시 코드는 "사용됨"
  상태를 유지한 채 유저 참조만 지움
- GET /admin/metrics 추가 -- 메시지 수(휴먼/트윈), 에스컬레이션
  사유별 집계, 거부권 발동률(vision.md 거부율 지표의 1차 근사),
  초대 코드 발급/사용 수. 생성 지연시간·오류율은 별도 계측 계층이
  없어 넣지 않고 문서에 명시

실제 베타 오픈 시점 자체는 roadmap.md §3(PoC 결과 필요)이 끝나야
정할 수 있어서 여전히 보류 -- 이번엔 서버 인프라만 준비함
2026-07-30 02:51:15 +00:00
Claude de8b44d536
2.5 QA: ai-service pytest 스위트 승격 + 자율성 플로우(L0~L2) 통합 테스트
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에 근거 남김
2026-07-30 02:37:36 +00:00
Claude 9041dc9b64
에스컬레이션 하드게이트 우회 구멍 차단 + 서버 측 삭제 플로우 추가
발견한 문제: POST /conversations/:id/messages가 sender_mode=twin을
검증 없이 그대로 저장·브로드캐스트하고 있었음 -- 에스컬레이션 게이트는
초안 생성(/draft) 경로에만 있었고 실제 발송 경로엔 없어서, 클라이언트가
/draft를 거치지 않고 바로 twin 메시지를 보내면 안전선을 완전히
우회할 수 있었다.

- ai-service: /draft와 별개인 POST /escalate/check 하드게이트 엔드포인트 추가
- core-backend: AIServiceClient.checkEscalation 추가, 메시지 저장 직전에
  twin 발송이면 무조건 호출하도록 해서 발송이 실제로 일어나는 단
  하나의 지점에서 막음. AI 서비스 응답 불가 시 fail-safe로 발송 차단.
  에스컬레이션되면 저장/브로드캐스트 없이 escalation_logs에만 기록.
  사람이 직접 보내는 메시지는 게이트 대상 아님
- core-backend: DELETE /users/:id 추가 -- 유저가 걸린 모든 행(트윈 설정·
  화이트리스트·연락처·대화참여·메시지·에스컬레이션로그·유저 본인)을
  트랜잭션으로 삭제 (tech-design.md §5 "사용자가 언제든 초기화 가능")
- 온디바이스 암호화·데이터 흐름 대시보드는 Flutter 클라이언트 책임이라
  이 환경에서는 보류, roadmap.md에 근거 남김
2026-07-30 02:26:02 +00:00
Claude fb06306d04
Go 코어에서 AI 서비스를 실제로 호출하는 연동 코드 추가
AIServiceClient.requestDraft가 ai-service/의 POST /draft를 호출하고,
core-backend에 POST /conversations/:id/draft 라우트를 추가해 프록시한다.
mock AI 서비스로 정상 응답·404(대화 없음)·400(스타일 소스 없음) 케이스를
테스트로 확인. roadmap.md Phase 1 §2.2 체크리스트 반영.
2026-07-30 02:15:32 +00:00
Claude 3902597418
Implement core backend in Go (item 2 of the build order)
Ports the Python prototype (backend/) to the actual chosen stack --
Gin + gorilla/websocket + GORM, same DB schema (models.go mirrors
backend/app/models.py), same endpoints (signup, message send,
WebSocket relay). backend/ stays as a reference prototype, not
removed.

Verified with go test: signup, duplicate-invite-code rejection (409),
404 on an unknown conversation, and WebSocket broadcast delivery all
pass -- the same cases the Python version was checked against.

Push notifications, AI service integration, and multi-device sync
are not in this commit -- see core-backend/README.md.
2026-07-30 01:42:48 +00:00