Describe
API 명세서(34개 엔드포인트)는 #28 로 모두 구현되었다. 이 이슈는 명세서에 없지만 CLAUDE.md 와 암호화 설계 문서에 정의되어 있는, MVP 마감 전 남은 세 가지를 함께 처리한다.
1. 차단 / 신고 API
Block, Report 는 엔티티와 레포지토리만 있고 API가 없다.
문제는 차단 로직이 이미 서버 전반에서 동작 중이라는 점이다. CapsuleAccessPolicy, SendFriendRequestService, RespondFriendRequestService, CreateCapsuleService, SearchFriendService 가 모두 BlockRepository 를 호출한다. 즉 차단을 전제로 동작하는데 정작 차단할 수단이 없는 상태다.
POST /api/v1/blocks 차단, DELETE /api/v1/blocks/{memberId} 차단 해제, GET /api/v1/blocks 차단 목록
POST /api/v1/reports 신고 (uq_report_reporter_target 으로 중복 신고 차단)
- 차단 시 기존 친구 관계와 대기 중인 친구 요청을 어떻게 처리할지 결정 필요
Report.targetType 과 Report.reason 이 raw String 이다. CLAUDE.md 의 표현적 도메인 모델링 원칙에 따라 enum 으로 교체한다(마이그레이션 불필요, 컬럼은 이미 VARCHAR).
- 두 API 모두 명세서에 없으므로 Notion 반영이 필요하다.
2. 암호화 모델 결정 — Shamir's Secret Sharing
이건 구현 작업 전에 결정이 필요한 항목이다.
「🔐 암호화 설계」 문서와 현재 구현이 서로 다른 모델이다.
설계 문서 (클라이언트 사이드 E2E)
- CEK 를 클라이언트에서 생성하고, 서버에 평문으로 전달하지 않는다
- CEK 를 Shamir's Secret Sharing 으로 N개 조각 분할 →
tbl_key_share 에 저장 (2-of-3 기본, 3-of-5 강화)
- 열람 시 서버는 위치·비밀번호 검증 후 키 조각만 반환하고, 복호화는 클라이언트가 수행한다
- 서버는 캡슐 내용을 절대 볼 수 없다
현재 구현 (#23, 서버 사이드 봉투 암호화)
- 서버가 보유한
MUDDA_MASTER_KEY 를 KEK 로 사용한다
EncryptedStringConverter (JPA AttributeConverter) 가 TimeCapsule.content 를 투명하게 암·복호화한다
OpenCapsuleService 가 평문 내용을 응답에 담아 반환한다 → 서버가 평문을 볼 수 있다
tbl_key_share 는 존재하지 않는다
현재 구현은 저장소 유출에 대한 at-rest 암호화로는 유효하지만, CLAUDE.md 의 "서버는 캡슐 내용을 절대 평문으로 볼 수 없다" 는 만족하지 못한다.
결정할 것
- (A) 설계 문서대로 클라이언트 사이드 E2E 로 전환한다.
tbl_key_share 추가, Shamir GF(256) 직접 구현, 캡슐 생성/열람 API 의 요청·응답 계약 변경, 클라이언트 작업 동반. 범위가 크므로 별도 이슈로 분리하는 편이 낫다.
- (B) 현재 서버 사이드 모델을 유지하기로 확정하고, CLAUDE.md 와 암호화 설계 문서를 실제 구현에 맞게 수정한다. Shamir 는 스택에서 제외한다.
어느 쪽이든 문서와 코드 중 하나는 고쳐야 한다. 지금은 둘이 어긋난 상태다. 결정 전까지 이 항목은 구현하지 않는다.
3. 스케줄러 (배치)
@Scheduled 가 프로젝트에 하나도 없다.
- 미첨부 미디어 정리 —
tbl_media.time_capsule_id IS NULL 인 채로 일정 시간이 지난 pending 업로드는 S3 객체와 행이 모두 고아로 남는다. V4 에서 pending 업로드를 허용하면서 생긴 실제 누수다.
- 만료 캡슐 키 조각 삭제 — 암호화 설계 문서에 "만료된 캡슐의 키 조각은 스케줄러로 삭제 처리" 로 명시되어 있다. 위 2번 결정이 (A) 일 때만 해당한다.
- 참고: 캡슐 만료 상태 전이 배치는 불필요하다.
CapsuleAccessPolicy.requireAccessible 이 읽기 시점에 expiredAt 을 검사해 차단하고 있다. 배치가 필요한 이유는 정합성이 아니라 저장공간 회수다.
Additional
세 항목의 성격이 서로 달라 진행 중 분리가 필요하다고 판단되면 이슈를 쪼개도 된다.
2번은 결정 전까지 착수 불가다. 1번과 3번(미디어 정리)은 독립적으로 바로 진행할 수 있다.
Describe
API 명세서(34개 엔드포인트)는 #28 로 모두 구현되었다. 이 이슈는 명세서에 없지만 CLAUDE.md 와 암호화 설계 문서에 정의되어 있는, MVP 마감 전 남은 세 가지를 함께 처리한다.
1. 차단 / 신고 API
Block,Report는 엔티티와 레포지토리만 있고 API가 없다.문제는 차단 로직이 이미 서버 전반에서 동작 중이라는 점이다.
CapsuleAccessPolicy,SendFriendRequestService,RespondFriendRequestService,CreateCapsuleService,SearchFriendService가 모두BlockRepository를 호출한다. 즉 차단을 전제로 동작하는데 정작 차단할 수단이 없는 상태다.POST /api/v1/blocks차단,DELETE /api/v1/blocks/{memberId}차단 해제,GET /api/v1/blocks차단 목록POST /api/v1/reports신고 (uq_report_reporter_target으로 중복 신고 차단)Report.targetType과Report.reason이 rawString이다. CLAUDE.md 의 표현적 도메인 모델링 원칙에 따라 enum 으로 교체한다(마이그레이션 불필요, 컬럼은 이미 VARCHAR).2. 암호화 모델 결정 — Shamir's Secret Sharing
이건 구현 작업 전에 결정이 필요한 항목이다.
「🔐 암호화 설계」 문서와 현재 구현이 서로 다른 모델이다.
설계 문서 (클라이언트 사이드 E2E)
tbl_key_share에 저장 (2-of-3 기본, 3-of-5 강화)현재 구현 (#23, 서버 사이드 봉투 암호화)
MUDDA_MASTER_KEY를 KEK 로 사용한다EncryptedStringConverter(JPA AttributeConverter) 가TimeCapsule.content를 투명하게 암·복호화한다OpenCapsuleService가 평문 내용을 응답에 담아 반환한다 → 서버가 평문을 볼 수 있다tbl_key_share는 존재하지 않는다현재 구현은 저장소 유출에 대한 at-rest 암호화로는 유효하지만, CLAUDE.md 의 "서버는 캡슐 내용을 절대 평문으로 볼 수 없다" 는 만족하지 못한다.
결정할 것
tbl_key_share추가, Shamir GF(256) 직접 구현, 캡슐 생성/열람 API 의 요청·응답 계약 변경, 클라이언트 작업 동반. 범위가 크므로 별도 이슈로 분리하는 편이 낫다.어느 쪽이든 문서와 코드 중 하나는 고쳐야 한다. 지금은 둘이 어긋난 상태다. 결정 전까지 이 항목은 구현하지 않는다.
3. 스케줄러 (배치)
@Scheduled가 프로젝트에 하나도 없다.tbl_media.time_capsule_id IS NULL인 채로 일정 시간이 지난 pending 업로드는 S3 객체와 행이 모두 고아로 남는다. V4 에서 pending 업로드를 허용하면서 생긴 실제 누수다.CapsuleAccessPolicy.requireAccessible이 읽기 시점에expiredAt을 검사해 차단하고 있다. 배치가 필요한 이유는 정합성이 아니라 저장공간 회수다.Additional
세 항목의 성격이 서로 달라 진행 중 분리가 필요하다고 판단되면 이슈를 쪼개도 된다.
2번은 결정 전까지 착수 불가다. 1번과 3번(미디어 정리)은 독립적으로 바로 진행할 수 있다.