You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
받은 기록함 발신자 카드가 앱 재시작 시 소실 — SenderRegistry 가 메모리 전용 #599
로컬 저장 필요성·SESSION 수명·암호화/키 보관 선택을 #1950으로 분리했다. #607 서버 정본 연동으로 증상이 해소되는지 확인한 뒤 남는 복원 요구만 구현한다. PR #1652는 미확정 로컬 영속화 제안을 철회하여 종료했다(2026-09-08). 기존 댓글의 구현 완료 표현은 배포/머지 완료나 저장 정책 확정을 의미하지 않는다. #1950과 #607을 실제 blocked_by로 등록했다.
등록 시점 전후로 HTTP 요청이 한 건도 나가지 않았습니다. OkHttp 로그의 마지막 항목이 15:10:28(앱 시작 시 조회)이고 15:12 등록에 대응하는 요청이 없습니다. #607(서버 목록 API 미사용·로컬 stub)과 같은 뿌리로 보입니다 — 메모리에만 쓰니 영속도 안 되고 서버에도 안 갑니다.
참고
수신자 인증 4단계 진입(1단계 "수신자 본인 확인")까지는 #919 의 :feature:receiver 대규모 이전 이후에도 정상 도달합니다.
changed the title [-]fix(afternote): 받은 기록함 발신자 카드가 앱 재시작 시 소실 — SenderRegistry 가 메모리 전용[/-][+]받은 기록함 발신자 카드가 앱 재시작 시 소실 — SenderRegistry 가 메모리 전용[/+]on Aug 28, 2026
PR #1652 가 발신자 카드를 버전 있는 JSON 스냅샷으로 SESSION DataStore 에 저장하고, 재기동 뒤 순서·마스터 키·신원·열람 인증 상태를 복원합니다. 본문이 든 재현 경로(등록 → 인증 → 승인까지 마치고 앱 재시작하면 목록이 빔)는 사라집니다.
닫는 참조를 걸지 않았습니다. 이 이슈가 열려 있는 #607(받은 기록함이 서버 목록 API 를 안 쓰고 로컬 stub)에 blocked_by 로 걸려 있어 merge-order guard 가 자동 종료를 막습니다.
관계를 임의로 해제하지 않은 이유는 #607 이 지금 다른 세션에서 작업 중이라, 「서버 목록이 오면 로컬 영속화가 어떤 의미를 갖는가」를 그쪽 결과 없이 판정할 수 없기 때문입니다. 발신자 카드(SenderEntry, 마스터 키로 등록하는 로컬 개념)와 record-boxes 목록이 같은 것으로 수렴하는지에 따라 이 저장소의 역할이 달라집니다.
→ #607 이 닫힌 뒤 이 이슈를 다시 봐 주세요. 그때 (a) 로컬 스냅샷이 서버 목록의 캐시로 남는지 (b) 통째로 걷히는지가 갈리고, 그 판정이 나면 이 이슈도 함께 닫힙니다.
본문이 남긴 결정 사항 — 암호화는 하지 않았습니다
본문이 「마스터 키가 평문으로 남는 문제가 있어 암호화 여부를 함께 정해야 한다」로 남긴 자리입니다.
ReceiverAuthCodeDataSource 가 같은 종류의 비밀(마스터 키) 을 이미 평문 SESSION Preferences DataStore 에 둡니다.
토큰 저장소도 같습니다.
저장소 어디에도 암호화 DataStore·Tink 관례가 없습니다.
여기만 암호화하면 같은 성격의 비밀이 두 방식으로 저장되어 수명·키 관리·마이그레이션 계약이 이원화됩니다. 평문 하나로 일관되게 두고, 암호화 전환은 저장소 전체를 아우르는 별도 마이그레이션으로 다루는 편이 낫다고 봤습니다. 근거는 구현 KDoc 에도 남겼습니다.
본문이 「#598(목록 주인 구분)의 선행 조건이기도 하다」고 적은 대로, 카드에 수신자 식별자를 붙이면 사람별로 나눌 수 있습니다. 다만 #598 은 제품 결정이 미정이라(기기당 1명 전제 + 전환 진입점 / 카드에 receiverId) 스키마에 schemaVersion 과 ignoreUnknownKeys 로 자리만 열어 두고 굳히지 않았습니다.
#607 과의 blocked_by 를 해제합니다. PR #1652 의 영속화는 서버 목록 API 없이 로컬 DataStore 만으로 이 이슈의 완료 조건(재기동 뒤 카드·마스터 키·열람 인증 상태 복원, 암호화 여부 결정)을 전부 채우므로 #607 은 이 이슈의 선행이 아닙니다. #607 은 그대로 열려 있고 범위도 줄지 않았습니다.
#607 이 record-boxes 를 목록의 원본으로 삼게 되면 이 저장소가 서버 목록의 캐시로 남을지 걷힐지는 #607 의 범위입니다. 그 판단은 이 이슈의 완료 여부와 별개입니다.
코드로 뗄 독립 범위가 없어 구현 대신 기록만 남깁니다. 본문 2026-09-08 블록의 완료 조건이 "#607 서버 정본 연동으로 증상이 해소되는지 확인한 뒤 남는 복원 요구만 구현한다" 한 줄이라 쪼갤 체크리스트가 없고, 그 한 항목이 통째로 #607·#1950 에 걸려 있습니다. 다만 착수 조건이 미뤄 둔 "해소되는가" 의 답과 본문 전제 두 개는 지금 확정되므로 적어 둡니다.
1. 증상은 그대로다
SenderRegistry 는 여전히 in-memory @Singleton 이고(SenderRegistry.kt:23-28, _senders = MutableStateFlow(emptyList())), 목록 화면이 그걸 그대로 흘려보냅니다(ReceivedRecordsViewModel.kt:20). 머지된 것 중 이 증상을 건드린 건 없습니다. 철회된 구현은 브랜치 fix/599-sender-registry-persistence(193b91c1e, origin 에도 남아 있음)에 있고 PR #1652 는 머지 없이 닫혔습니다(2026-09-08).
2. 본문 정정: 마스터 키는 재시작으로 사라지지 않는다
본문의 "마스터 키를 분실했다면 발신자에게 다시 받아야" 는 develop 과 어긋납니다. ReceiverMasterKeyDataSource.saveCode 가 Preferences DataStore 파일 afternote_receiver_auth 에 쓰고(ReceiverMasterKeyDataSource.kt:42-51, ReceiverMasterKeyDataStoreModule.kt:22), 그 scope 는 StoreScope.SESSION 이라 프로세스 사망이 아니라 로그아웃·탈퇴에서만 비워집니다(StoreScope.kt:10-14). 재시작 뒤에도 ReceiverAuthInterceptor 가 그 값을 X-Auth-Code 로 계속 붙입니다(ReceiverAuthInterceptor.kt:25-34).
실제로 잃는 것은 마지막으로 검증한 발신자의 키가 아니라 그 이전 발신자들의 키입니다. 슬롯이 하나뿐이라 saveMasterKey 가 덮어씁니다(ReceiverRepositoryImpl.kt:54-56).
같은 이유로 본문의 "서버에는 delivery-verification 승인 기록이 남아 있는데 로컬에 진입 수단이 없어 무의미해짐" 도 절반만 맞습니다. 헤더 자격 자체는 디스크에 살아 있고, 없어지는 것은 그 헤더를 쓸 화면(카드)입니다. 단 3번 때문에 지금은 그 헤더가 갈 곳이 없습니다.
3. 본문 정정: 서버 정본 쪽 전제가 바뀌었다
본문과 #607 이 전제한 GET /api/v1/receiver-auth/record-boxes 는 BE main 에 더 이상 없습니다. BE PR #289(머지 2026-09-08, 커밋 7009eed2b)가 ReceiverAuthController 와 ReceiverAuthService 를 통째로 삭제했습니다. 목록은 ReceivedRecordController 의 /api/v1/received-records 로 옮겼고, X-Auth-Code 가 아니라 로그인 userId 로 고릅니다(ReceivedRecordService.getRecordBoxes → findAllByAcceptedUserIdOrderByIdDesc).
FE 에 닿는 결과는 셋입니다.
응답 ReceivedRecordBoxResponse 에 accessCode 가 없습니다(BE 저장소 전체 검색 0건). FE 의 ReceivedRecordBoxDto.accessCode 는 기본값 없는 필수 필드라(ReceiverAuthDto.kt:132) 현행 서버 응답으로는 디코드가 실패합니다.
계약 테스트 ReceivedRecordBoxContractTest 는 KDoc 이 적어 둔 대로 BE origin/main 스키마를 옮긴 페이로드를 쓰는데(:17-19), 그 스키마가 바뀌었으므로 초록이어도 현행 서버를 보증하지 않습니다.
전환 자체와 2026-09-08 이전 수신자 데이터 문제는 #2079 가 소유합니다. 여기서는 이 이슈의 착수 조건에 닿는 부분만 적었습니다.
참고로 #607 본문의 "record-boxes·RecordBox 전체 검색 결과 호출 0건" 도 develop 에서는 사실이 아닙니다. SenderDetailViewModel.kt:178-179 가 승인 상태에서 호출합니다.
4. 그래서 이 이슈에 남는 복원 요구
"#607 로 해소되는가" 는 #607 완료를 기다리지 않고 지금 답이 나옵니다. 서버 목록이 어느 경로로 오든, 카드가 쥔 값 중 서버가 돌려줄 수 없는 것은 이렇습니다.
별칭 SenderEntry.name. 사용자가 입력하는 값이고 서버에는 없습니다. 목록과 상세가 찍는 이름이 실명이 아니라 이것입니다(ReceivedRecordsScreen.kt:169, SenderDetailViewModel.kt:188).
로컬 id. SenderRegistry.kt:37 의 UUID 이고 상세 라우트 키가 이 값입니다(ReceiverRoute.kt:41-47).
마스터 키. 3번대로 새 응답에 accessCode 가 없습니다.
앞의 둘은 순수한 로컬 복원 요구라 이 이슈에 남고, PR #1652 가 철회된 이유였던 마스터 키 평문 보관과는 다른 질문입니다. 셋째는 "복원하는가" 가 아니라 "새 모델에서 필요한가" 부터 물어야 합니다. 새 계약에서 기록함 진입 수단은 마스터 키가 아니라 로그인 계정이기 때문입니다. 그 판단은 #1949(접근 자격 계약)·#1950(키 보관 정책) 범위이므로 여기서 정하지 않습니다.
열린 PR 62건 본문의 닫는 참조를 전수 검색한 결과 #599·#607·#1950·#1949 를 닫는 PR 은 0건입니다. 다만 PR #2039(open, base develop, Closes #1803)가 receiver presentation 의 ViewModel 7개를 MVI 로 옮깁니다. SenderRegistry.kt 자체는 한 줄도 안 건드리지만 ReceivedRecordsViewModel·ReceivedRecordsScreen·SenderDetailViewModel 이 전부 그 PR 안에서 바뀌고, ReceivedRecordsViewModel 은 internal + MviViewModel 로 이동하면서 상태가 ReceivedRecordsUiState 로 감싸집니다. 이 이슈 착수자는 #2039 위에서 시작해야 합니다.
본문은 고치지 않았습니다
위 정정 셋 다 완료 조건을 좁히는 게 아니라 전제를 바꾸는 내용이라, 본문 편집 대신 이 코멘트로 남깁니다.
Activity
OnlyReadOneBook commentedon Aug 25, 2026
재현 확인 (develop
4e0a648eb, 2026-08-25 15:12 KST, 서버 정상 응답 상태)+→ 이름QA Sender입력 → 발신자 등록하기새벽 순회(서버 폐쇄 시간대)와 달리 이번에는 서버가 정상 응답하는 상태에서 재현했으므로, 네트워크 실패로 인한 소실이 아님이 확인됩니다.
함께 확인된 것 — #607
등록 시점 전후로 HTTP 요청이 한 건도 나가지 않았습니다. OkHttp 로그의 마지막 항목이 15:10:28(앱 시작 시 조회)이고 15:12 등록에 대응하는 요청이 없습니다. #607(서버 목록 API 미사용·로컬 stub)과 같은 뿌리로 보입니다 — 메모리에만 쓰니 영속도 안 되고 서버에도 안 갑니다.
참고
수신자 인증 4단계 진입(1단계 "수신자 본인 확인")까지는 #919 의
:feature:receiver대규모 이전 이후에도 정상 도달합니다.docs(qa): 서버 복구 후 2차 실측 결과를 현황에 추가한다
docs(qa): 수신자 열람 흐름 규명 — admin 승인으로 뒷부분까지 검증
[-]fix(afternote): 받은 기록함 발신자 카드가 앱 재시작 시 소실 — SenderRegistry 가 메모리 전용[/-][+]받은 기록함 발신자 카드가 앱 재시작 시 소실 — SenderRegistry 가 메모리 전용[/+]OnlyReadOneBook commentedon Aug 30, 2026
영속화는 PR #1652 로 닫았습니다 — 다만 이 이슈는 열어 둡니다
PR #1652 가 발신자 카드를 버전 있는 JSON 스냅샷으로 SESSION DataStore 에 저장하고, 재기동 뒤 순서·마스터 키·신원·열람 인증 상태를 복원합니다. 본문이 든 재현 경로(등록 → 인증 → 승인까지 마치고 앱 재시작하면 목록이 빔)는 사라집니다.
닫는 참조를 걸지 않았습니다. 이 이슈가 열려 있는 #607(받은 기록함이 서버 목록 API 를 안 쓰고 로컬 stub)에
blocked_by로 걸려 있어 merge-order guard 가 자동 종료를 막습니다.관계를 임의로 해제하지 않은 이유는 #607 이 지금 다른 세션에서 작업 중이라, 「서버 목록이 오면 로컬 영속화가 어떤 의미를 갖는가」를 그쪽 결과 없이 판정할 수 없기 때문입니다. 발신자 카드(
SenderEntry, 마스터 키로 등록하는 로컬 개념)와 record-boxes 목록이 같은 것으로 수렴하는지에 따라 이 저장소의 역할이 달라집니다.→ #607 이 닫힌 뒤 이 이슈를 다시 봐 주세요. 그때 (a) 로컬 스냅샷이 서버 목록의 캐시로 남는지 (b) 통째로 걷히는지가 갈리고, 그 판정이 나면 이 이슈도 함께 닫힙니다.
본문이 남긴 결정 사항 — 암호화는 하지 않았습니다
본문이 「마스터 키가 평문으로 남는 문제가 있어 암호화 여부를 함께 정해야 한다」로 남긴 자리입니다.
ReceiverAuthCodeDataSource가 같은 종류의 비밀(마스터 키) 을 이미 평문 SESSION Preferences DataStore 에 둡니다.여기만 암호화하면 같은 성격의 비밀이 두 방식으로 저장되어 수명·키 관리·마이그레이션 계약이 이원화됩니다. 평문 하나로 일관되게 두고, 암호화 전환은 저장소 전체를 아우르는 별도 마이그레이션으로 다루는 편이 낫다고 봤습니다. 근거는 구현 KDoc 에도 남겼습니다.
#598 의 설계는 앞지르지 않았습니다
본문이 「#598(목록 주인 구분)의 선행 조건이기도 하다」고 적은 대로, 카드에 수신자 식별자를 붙이면 사람별로 나눌 수 있습니다. 다만 #598 은 제품 결정이 미정이라(기기당 1명 전제 + 전환 진입점 / 카드에
receiverId) 스키마에schemaVersion과ignoreUnknownKeys로 자리만 열어 두고 굳히지 않았습니다.OnlyReadOneBook commentedon Sep 2, 2026
#607 과의
blocked_by를 해제합니다. PR #1652 의 영속화는 서버 목록 API 없이 로컬 DataStore 만으로 이 이슈의 완료 조건(재기동 뒤 카드·마스터 키·열람 인증 상태 복원, 암호화 여부 결정)을 전부 채우므로 #607 은 이 이슈의 선행이 아닙니다. #607 은 그대로 열려 있고 범위도 줄지 않았습니다.#607 이 record-boxes 를 목록의 원본으로 삼게 되면 이 저장소가 서버 목록의 캐시로 남을지 걷힐지는 #607 의 범위입니다. 그 판단은 이 이슈의 완료 여부와 별개입니다.
OnlyReadOneBook commentedon Sep 13, 2026
착수 전 실측 (origin/develop
7d87aa3f4, 2026-09-13)코드로 뗄 독립 범위가 없어 구현 대신 기록만 남깁니다. 본문 2026-09-08 블록의 완료 조건이 "#607 서버 정본 연동으로 증상이 해소되는지 확인한 뒤 남는 복원 요구만 구현한다" 한 줄이라 쪼갤 체크리스트가 없고, 그 한 항목이 통째로 #607·#1950 에 걸려 있습니다. 다만 착수 조건이 미뤄 둔 "해소되는가" 의 답과 본문 전제 두 개는 지금 확정되므로 적어 둡니다.
1. 증상은 그대로다
SenderRegistry는 여전히 in-memory@Singleton이고(SenderRegistry.kt:23-28,_senders = MutableStateFlow(emptyList())), 목록 화면이 그걸 그대로 흘려보냅니다(ReceivedRecordsViewModel.kt:20). 머지된 것 중 이 증상을 건드린 건 없습니다. 철회된 구현은 브랜치fix/599-sender-registry-persistence(193b91c1e, origin 에도 남아 있음)에 있고 PR #1652 는 머지 없이 닫혔습니다(2026-09-08).2. 본문 정정: 마스터 키는 재시작으로 사라지지 않는다
본문의 "마스터 키를 분실했다면 발신자에게 다시 받아야" 는 develop 과 어긋납니다.
ReceiverMasterKeyDataSource.saveCode가 Preferences DataStore 파일afternote_receiver_auth에 쓰고(ReceiverMasterKeyDataSource.kt:42-51,ReceiverMasterKeyDataStoreModule.kt:22), 그 scope 는StoreScope.SESSION이라 프로세스 사망이 아니라 로그아웃·탈퇴에서만 비워집니다(StoreScope.kt:10-14). 재시작 뒤에도ReceiverAuthInterceptor가 그 값을X-Auth-Code로 계속 붙입니다(ReceiverAuthInterceptor.kt:25-34).실제로 잃는 것은 마지막으로 검증한 발신자의 키가 아니라 그 이전 발신자들의 키입니다. 슬롯이 하나뿐이라
saveMasterKey가 덮어씁니다(ReceiverRepositoryImpl.kt:54-56).같은 이유로 본문의 "서버에는 delivery-verification 승인 기록이 남아 있는데 로컬에 진입 수단이 없어 무의미해짐" 도 절반만 맞습니다. 헤더 자격 자체는 디스크에 살아 있고, 없어지는 것은 그 헤더를 쓸 화면(카드)입니다. 단 3번 때문에 지금은 그 헤더가 갈 곳이 없습니다.
3. 본문 정정: 서버 정본 쪽 전제가 바뀌었다
본문과 #607 이 전제한
GET /api/v1/receiver-auth/record-boxes는 BE main 에 더 이상 없습니다. BE PR #289(머지 2026-09-08, 커밋7009eed2b)가ReceiverAuthController와ReceiverAuthService를 통째로 삭제했습니다. 목록은ReceivedRecordController의/api/v1/received-records로 옮겼고,X-Auth-Code가 아니라 로그인userId로 고릅니다(ReceivedRecordService.getRecordBoxes→findAllByAcceptedUserIdOrderByIdDesc).FE 에 닿는 결과는 셋입니다.
ReceivedRecordBoxResponse에accessCode가 없습니다(BE 저장소 전체 검색 0건). FE 의ReceivedRecordBoxDto.accessCode는 기본값 없는 필수 필드라(ReceiverAuthDto.kt:132) 현행 서버 응답으로는 디코드가 실패합니다.ReceivedRecordBoxContractTest는 KDoc 이 적어 둔 대로 BEorigin/main스키마를 옮긴 페이로드를 쓰는데(:17-19), 그 스키마가 바뀌었으므로 초록이어도 현행 서버를 보증하지 않습니다.ErrorReporter에만 남고 화면은 빈 값으로 지나갑니다(SenderDetailViewModel.kt:178-185).전환 자체와 2026-09-08 이전 수신자 데이터 문제는 #2079 가 소유합니다. 여기서는 이 이슈의 착수 조건에 닿는 부분만 적었습니다.
참고로 #607 본문의 "
record-boxes·RecordBox전체 검색 결과 호출 0건" 도 develop 에서는 사실이 아닙니다.SenderDetailViewModel.kt:178-179가 승인 상태에서 호출합니다.4. 그래서 이 이슈에 남는 복원 요구
"#607 로 해소되는가" 는 #607 완료를 기다리지 않고 지금 답이 나옵니다. 서버 목록이 어느 경로로 오든, 카드가 쥔 값 중 서버가 돌려줄 수 없는 것은 이렇습니다.
SenderEntry.name. 사용자가 입력하는 값이고 서버에는 없습니다. 목록과 상세가 찍는 이름이 실명이 아니라 이것입니다(ReceivedRecordsScreen.kt:169,SenderDetailViewModel.kt:188).id.SenderRegistry.kt:37의 UUID 이고 상세 라우트 키가 이 값입니다(ReceiverRoute.kt:41-47).accessCode가 없습니다.앞의 둘은 순수한 로컬 복원 요구라 이 이슈에 남고, PR #1652 가 철회된 이유였던 마스터 키 평문 보관과는 다른 질문입니다. 셋째는 "복원하는가" 가 아니라 "새 모델에서 필요한가" 부터 물어야 합니다. 새 계약에서 기록함 진입 수단은 마스터 키가 아니라 로그인 계정이기 때문입니다. 그 판단은 #1949(접근 자격 계약)·#1950(키 보관 정책) 범위이므로 여기서 정하지 않습니다.
5. 선후와 충돌
blocked_by실조회(2026-09-13):사슬이 BE#93 에서 바닥나 FE 안에서 끊을 수 있는 선행이 없습니다.
열린 PR 62건 본문의 닫는 참조를 전수 검색한 결과 #599·#607·#1950·#1949 를 닫는 PR 은 0건입니다. 다만 PR #2039(open, base
develop, Closes #1803)가 receiver presentation 의 ViewModel 7개를 MVI 로 옮깁니다.SenderRegistry.kt자체는 한 줄도 안 건드리지만ReceivedRecordsViewModel·ReceivedRecordsScreen·SenderDetailViewModel이 전부 그 PR 안에서 바뀌고,ReceivedRecordsViewModel은internal+MviViewModel로 이동하면서 상태가ReceivedRecordsUiState로 감싸집니다. 이 이슈 착수자는 #2039 위에서 시작해야 합니다.본문은 고치지 않았습니다
위 정정 셋 다 완료 조건을 좁히는 게 아니라 전제를 바꾸는 내용이라, 본문 편집 대신 이 코멘트로 남깁니다.
OnlyReadOneBook commentedon Sep 19, 2026
2026-09-19 정리로 닫는다. 이 이슈의 완료 조건은 PM의 결정에 달려 있어 결정 전에는 FE 가 무엇을 만들지 쓸 수 없다. 그런 이슈는 FE 저장소에 열어 두지 않고 결정이 나면 그때 새 이슈로 다시 연다.
기다리는 결정: 로컬 저장 정책(#1950)과 서버 정본(#607).
PM 질문은 창구가 정해지는 대로 보낸다(일혁이 목록 보관).