Repository navigation
Description
[P1][C01] 저장·백업 복원·초기화의 workflow와 단계별 결과를 UI에서 분리
문제와 영향
현재 내 데이터 화면이 BackupService, LocalDataResetService, ReconcileAlarmsUseCase를 직접 조합한다. 복원 DB transaction이 성공한 뒤 알림 재등록만 실패해도 화면은 전체 작업을 실패로 표시한다. 사용자가 같은 백업 복원을 다시 실행하면 이미 성공한 데이터 교체를 반복한다. 일정 저장에서도 일정 저장과 알림 조정이 먼저 실행되고 custom preparation 저장이 뒤따라, 사용자에게 표시하는 성공/실패와 실제 저장 상태가 달라질 수 있다. 복구 화면의 reset에는 예외 처리조차 없어 실패 시 busy 상태가 남을 수 있다.
C01은 이 실제 경계들을 application/domain workflow와 typed 결과로 연결하는 구조 개선이다. 모든 Bloc이나 use case를 일괄 재작성하는 작업이 아니다. 원자적 DB 변경과 OS·파일·runtime 후속 작업을 하나의 transaction으로 포장하지 않고, 각각 무엇이 완료됐는지 호출자가 정확히 알도록 한다.
우선순위 P1. 조사 기준은 **bca81bcca8fc94aaa1d2c205aae6ec4f63ca3b04**이다. A13의 별도 WIP가 동시에 있었으나 이번 조사에서는 이를 수정하지 않았다. 아래는 합의된 구현 계획이며 테스트 통과 또는 제품 적용 보고가 아니다.
현재 코드와 문서 근거
| 근거 | 관찰과 의미 |
|---|---|
| MyDataScreen:109–169 | export와 preview/apply/reconcile을 화면이 호출한다. apply 성공 후 reconcile 실패가 같은 catch로 전달되며 DB commit 사실을 별도로 표시할 수 없다. |
| MyDataScreen:172–228 | reset 성공이면 바로 resetComplete로 이동하고, 공통 오류는 raw $error 문자열을 사용자에게 보여 준다. 단계별 reset 미완료·후속 복구 결과가 없다. |
| LocalDataRecoveryScreen:77–100 | reset service 직접 호출에 try/finally가 없다. 실패하면 busy 해제와 안전한 retry 결과 처리가 없다. |
| BackupService:91–123 | A02는 실제 파일 저장과 freshness 갱신 실패를 이미 구분한다. C01에서 이 사실을 다시 단순 bool/성공·실패로 축소하면 안 된다. |
| BackupService:126–172 | preview는 decrypt/parse/모델 검증 후 메모리 _BackupData를 가진 candidate를 반환한다. 현재 candidate에는 확인 당시 generation/revision이 없으며 ADR 0028의 staging Local Data Store 검증 증거도 아니다. |
| BackupService:174–224 | DB 교체 자체는 transaction이다. 기존 runtime/메모리 세션/OS 후속 결과까지 이 transaction으로 보장하는 것은 아니다. |
| LocalDataOperationGate:15–35 | replacement action 호출 전에 generation을 증가시킨다. 단순히 action 안에서 이전 preview generation과 현재 값을 비교하면 자기 작업 때문에 stale 판정된다. exclusive claim 전·후 세대를 구분해야 한다. |
| CreateScheduleFormSubmissionUseCase:20–32, UpdateScheduleFormSubmissionUseCase:33–47 | 일반 일정과 준비 저장이 별도 호출이다. 반복 경로는 이미 별도의 repository transaction 경로이므로 전체 저장이 비원자적이라고 일반화하지 않는다. |
| CreateScheduleWithPlaceUseCase:16–22, ScheduleMutationAlarmEffectsCoordinator:23–59 | 일정 저장 직후 알림 effect를 요청하고 best-effort reconcile 실패는 로그로만 남긴다. 후속 preparation 저장과 완성된 aggregate의 알림 요청 순서를 A08 계약에 연결해야 한다. |
| ScheduleFormBloc:240–310 | FormSubmission Future 완료를 success/failure로 바꾼다. DB는 저장됐지만 delivery만 미완료인 결과를 별도로 받는 경로가 필요하다. |
| RecurringSchedulesUseCase:81–119 | 반복 저장도 후속 알림 coordinator를 사용한다. 반복 회차/공유 정의의 기존 transaction과 보호 정책을 유지하면서 결과 계약을 연결한다. |
CONTEXT의 Backup Restore, Restore Preview, Backup Cutoff, Local Data Reset, Durable OnTime Data, Reconstructible App State를 그대로 사용한다. application workflow와 operation receipt는 구현 용어이며 제품 glossary에 추가하지 않는다.
- ADR 0013: 검증된 백업으로 durable data를 교체하고 active session은 복원하지 않는다.
- ADR 0020: reset 중단 복구를 끝낸 뒤 새 profile을 만든다.
- ADR 0022: domain repository가 경계이며 durable data의 권위는 encrypted Drift 하나다.
- ADR 0023: Recovery Mode는 원본 DB/키를 보존하고 startup retry, backup restore, 명시적 reset을 제공한다.
- ADR 0027: snapshot 이후 암호화·파일 선택 중 일반 편집을 허용한다.
- ADR 0028: staging Local Data Store 검증을 마친 별도 미리보기와 최종 확인이 필요하다. 현재 메모리 candidate가 이 요구를 모두 충족한다고 표현하지 않는다.
확정한 구조와 책임
구체적인 세 가지 workflow 경계
- 일정 저장: 기존 create/update FormSubmission을 유지하면서 A08의 schedule/place/preparation/revision aggregate transaction에 연결한다. 최종 DB commit 이후에만 A15 delivery coordinator에 요청한다. 반복 series/occurrence, frozen 회차, preparation ownership, revision 규칙을 보존한다.
- 백업 내보내기·확정 복원: 암호 입력, 파일 선택, 검증된 preview, 최종 확인은 사용자 상호작용이다. snapshot/export 및 restore의 DB/runtime/delivery 순서는 workflow가 담당한다. A02 파일 저장 receipt와 A09 staging/replacement/runtime 정책을 재사용한다.
- Local Data Reset: 정상 화면과 Recovery Mode가 동일한 domain command/result 및 D03 cleanup engine/journal을 사용한다. 화면별 별도 삭제 조합을 만들지 않는다. DB/키를 열 수 없는 상태에서도 안전하게 reset 결과를 전달해야 하며 초기화 의도 없이 데이터를 삭제하지 않는다.
Domain은 operation별 command, 결과, 필요한 repository/backup/runtime/delivery port만 안다. Flutter BuildContext, dialog, router, Drift 구현, native picker/channel, key/files 구현을 domain으로 옮기지 않는다. data/core adapter가 구체 transaction·암호화·파일·OS 작업을 수행하고 workflow는 전후 순서 및 작업 소유권을 담당한다. 클래스/파일 이름은 구현 시 기존 선행 인터페이스를 보고 정하며 이슈가 불필요한 추상 계층 개수를 강제하지 않는다.
UI는 입력과 확인, 상태 표시, 안전한 결과 문구, 명시적 재시도와 navigation을 담당한다. MyDataScreen/RecoveryScreen은 concrete BackupService/ResetService/Reconcile를 직접 조합하지 않는다. 화면의 수명과 작업의 수명을 구별하며 widget이 dispose됐다고 전송된 native 요청이나 DB Future가 취소된 것으로 간주하지 않는다.
결과와 실패 사실
| 상태/사실 | 사용자 표시와 재시도 경계 |
|---|---|
| 사용자 취소 | 저장·복원·초기화 성공을 표시하지 않는다. 변경 전 취소면 기존 durable data를 보존한다. |
| 검증 실패 또는 stale preview | 파괴 작업 전에 멈추고 안전한 이유와 재선택/재확인 경로를 제공한다. |
| busy / unavailable | 경쟁 작업이 실제로 진행 중인지 DB가 더 이상 사용 가능하지 않은지 구별한다. 다른 작업을 중첩 실행하지 않는다. |
| DB commit 전 실패 | 데이터 변경 성공으로 표시하지 않는다. 입력을 보존하고 currentness 재검사 후 재시도를 허용한다. |
| DB commit 성공 + 후속 runtime/delivery 미완료 | 저장·복원 성공 사실을 유지한다. 알림 정리/재등록 등 실제 남은 단계만 재시도하며 durable mutation을 반복하지 않는다. |
| export 파일 저장 성공 + freshness 갱신 실패 | A02 의미를 그대로 유지한다. 파일 선택 UI handoff를 실제 저장 완료로 위조하지 않으며, metadata 실패 때문에 파일 쓰기를 자동 반복하지 않는다. |
| reset 데이터 삭제 완료 + 알림 정리 미확정 | D03 단계별 결과를 표시하고 journal을 보존한다. 완전 초기화 성공이나 새 profile 생성으로 진행하지 않는다. |
| 모든 요구 단계 완료 | 해당 작업의 실제 complete 결과만 표시하고 허용된 다음 화면으로 이동한다. |
결과 이름은 예시이며 sealed type/enum 구조는 구현 시 정한다. 적어도 commit 여부, 후속 단계와 재시도 가능성, operation identity/currentness를 잃지 않아야 한다. raw exception 문자열을 사용자 문구로 전달하지 않는다. 안전한 오류 코드와 한국어/영어 l10n으로 변환하고 OS/IO 메시지 속 경로·사용자 원문·암호를 로그나 UI에 노출하지 않는다. '복구 중'은 실제 작업 중일 때만 표시한다. 일반 freshness 조회 실패도 무한 '확인 중'에 남거나 unhandled error가 되지 않게 상태로 받는다.
검증된 preview와 적용 사이의 변경
- immutable validated backup candidate와 현재 data generation + durable revision을 묶은 메모리 전용 확인 token을 만든다. candidate는 A09/D02의 실제 staging 검증 계약을 충족한 것만 받는다.
- apply는 기존 gate/A15 replacement 경계의 exclusive claim 안에서 generation/revision을 다시 확인한다. 외부에서 확인한 뒤 나중에 들어가는 check-then-act 방식은 허용하지 않는다.
- 현재 gate는 replacement 진입 시 자체 generation을 먼저 올리므로, exclusive claim 당시의 이전 generation/revision과 교체 후 발급 세대를 구별해야 한다. 자기 작업의 증가 때문에 모든 preview를 stale로 판정하거나 이 문제를 피하려고 안전 검사를 제거하지 않는다.
- exclusive 획득 → 기존 generation/revision 검증 → 유효한 교체 작업의 세대 발급 순서 또는 동등한 claim receipt로 한 번의 교체 시도 경계를 만든다. A12의 pending tap 무효화 보장을 약화하지 않는다.
- 값이 바뀌었으면 DB/키/runtime의 파괴적 작업 전에 멈추고 최신 현재 상태에 맞는 대체 경고/preview를 다시 확인한다. snapshot 이후 export의 일반 편집 허용을 없애는 전체 편집 잠금으로 해결하지 않는다. revision은 기존 durable 변경 정의를 따르며 timer tick 같은 reconstructible 변경으로 부풀리지 않는다.
- 한 token의 동시 commit은 같은 pending receipt 공유를 권장한다. 구현상 busy 결과라면 typed로 명시하되 두 번 commit되면 안 된다. commit 후 token 재사용은 금지한다.
- 파일을 다시 읽어 preview와 다른 내용을 적용하지 않는다. commit 전 DB 실패의 retry는 동일 candidate를 재사용할 수 있지만 currentness를 재검사한다. commit 이후 delivery retry는 DB 교체를 다시 실행하지 않는다.
- password를 controller 밖의 장기 상태, 로그, journal, preference에 남기지 않는다. 후보/암호/확인 token의 불필요한 메모리 참조는 취소·route 종료·성공·세대 교체 시 해제한다. 이미 진행 중인 작업에 필요한 참조는 owner가 최종 결과/정리까지 소유한다. 메모리 참조 해제를 secure zeroization 보장이라고 과장하지 않는다.
- route exit, 새 알림 선택, restore/reset 세대 교체 후 늦은 완료는 새 화면에 이전 snackbar/navigation을 실행하지 않는다. 실제 부작용의 책임은 기존 coordinator/owner가 끝까지 유지한다.
transaction과 외부 작업
DB 원자성은 A08/A09/A04/A07의 실제 data transaction에서 보장한다. OS 알림 취소·예약, 파일 picker, 암호화, 사용자 확인을 DB transaction 안에서 await하지 않는다. 기존 gate와 A15 single writer를 재사용하고 nested gate run, 두 번째 lifecycle coordinator, generic retry/workflow framework를 만들지 않는다. 후속 실패를 'DB rollback됨'으로 바꾸거나 성공한 durable command를 재실행해서 해결하지 않는다.
의존성·구현 순서·제외 범위
| 항목 | 소유와 C01 연결 |
|---|---|
| A02/A03 | native export/import receipt, snapshot cutoff, picker 취소, 파일 저장과 metadata 구분을 재사용한다. 기존 code verification이 새로운 staging gap을 해결한 증거는 아니다. |
| A04 | 부분 profile 수정, 점수/revision transaction을 보존한다. 전체 profile 덮어쓰기로 회귀하지 않는다. |
| A11/A12 | privacy, active-run 재확인, notification pending/owner/generation invalidation을 유지한다. old operation이 새 선택/세션을 변경하지 않는다. |
| A13/A14/A15 | 실제 delivery status/관측/미확정 결과와 단일 OS writer를 재사용한다. C01이 또 다른 알림 coordinator를 만들지 않는다. |
| D03 | reset ownership journal, 단계별 cleanup/recovery, runtime/native pending 제거를 소유한다. C01은 같은 command/result를 양쪽 UI에 연결한다. |
| A06/A07 | 준비 action과 durable start 멱등성/후속 부분 오류 계약을 보존한다. 준비 시작이나 run 시각을 다시 설계하지 않는다. |
| A08 | 일반 일정 aggregate transaction, 중복 저장 방지, commit 후 알림 요청의 실제 구현을 소유한다. C01 FormSubmission 결과와 연결해 검증한다. |
| A09 | 정상 DB restore의 staging, 교체/실패 보존, runtime/session 정리와 재진입을 소유한다. C01은 검증된 candidate 및 단계별 결과를 사용한다. |
| D01/D02 | startup 제한 복구와 손상 DB backup replacement를 소유한다. Recovery Mode의 reset/restore도 같은 application boundary를 이용하며 정상 DB 필수 의존성을 숨기지 않는다. |
| U05 | 전체 앱 문구 현지화와 별개로 C01에서 새로 드러내는 오류/부분 결과는 한국어·영어 문구를 제공한다. |
작업 착수 순서는 A07 다음이다. C01의 포트/결과와 UI 분리를 먼저 진행할 수 있지만 A08/A09/D02와 관련된 통합 AC가 충족되기 전에는 C01을 닫지 않는다. 순서 때문에 준비된 interface만 남기고 실제 적용이 끝난 것처럼 보고하지 않는다. 후속 이슈는 선행 구현의 최신 계약을 다시 읽고 공통 engine/transaction을 중복 작성하지 않는다.
제외: 모든 Bloc/use case 개편, 파일 수를 줄이기 위한 합치기, 전역 generic framework, 서버·로그인·동기화, 새로운 백업 형식/암호 알고리즘, active session을 백업에 포함, reset journal 재설계, timer 의미 변경, 반복 일정 기능 재작성, 실제 OS 전달을 코드 반환만으로 보장하기. 생성 Dart는 필요 시 로컬 재생성하되 커밋하지 않는다.
수용 기준과 검증 계획
아래는 아직 실행하지 않은 검증 계획이다. spy의 호출 순서만 맞는 테스트로 실제 데이터 원자성·파일 저장·기기 전달을 대신하지 않는다.
| 시나리오 | 필요한 관찰 |
|---|---|
| 일반 일정 create/update의 preparation 또는 revision write 실패 | 실제 Drift fault injection으로 schedule/place/preparation/revision 전부 rollback. UI 입력 유지, 저장 성공/알림 요청 없음. 동일 입력 retry가 중복 일정을 만들지 않음. A08 연결. |
| 일정 commit 후 알림 일부 실패 | 저장 데이터가 실제 남고 UI가 '저장 실패'라고 표시하지 않음. retry는 delivery만 수행, durable revision/일정 개수 추가 변화 없음. |
| 반복 create/occurrence/following 변경 | 기존 범위, frozen 회차, 공유 preparation ownership과 DB transaction 보존. 완성된 commit 이후 한 delivery 요청 경계. |
| export 중 일반 편집 | 파일 내용은 한 cutoff이고 이후 수정은 active DB에 남음. 암호화/파일 picker 동안 일반 편집 가능. A02 기존 테스트 유지. |
| export 취소/실패/실제 저장/metadata 실패 | 각 상태를 UI에서 구별. 파일 handoff만으로 성공하지 않으며 이미 저장한 파일을 자동 재저장하지 않음. |
| preview 취소·잘못된 비밀번호·잘린 파일·검증 실패 | replacement/runtime 파괴 호출 없음, 원본 DB/키 유지, 안전한 오류/재시도와 후보 정리. |
| staging validation | 실제 staging DB 생성·migration·제약 검증 및 read-back 증거를 확인. 메모리 parse나 interface mock만으로 ADR 0028 충족 판정 금지. A09/D02 연결. |
| preview 후 일반 durable 수정 | exclusive claim 안의 revision 재검사로 파괴 작업 전에 중단. 변경 사실을 포함한 재확인 뒤에만 적용. |
| preview 후 reset/다른 restore | old token 무효. 새 generation의 동일 일정 ID/동일 revision 숫자만으로 stale candidate가 통과하지 않음. |
| 자신이 발급한 replacement generation | 정상 preview는 자기 generation 증가 때문에 거절되지 않음. 동시에 들어온 다른 replacement는 직렬 경계에서 분리됨. |
| 두 번 클릭·동시 apply | 같은 pending 결과 공유 또는 명시적 busy. transaction/키 교체/알림 effect를 두 번 실행하지 않음. |
| DB replacement 실패 | A09의 원본 보존/rollback 증거와 연결, commit 실패 결과가 UI에 전달됨. currentness 검사 후 retry 가능. |
| DB replacement 성공 후 알림 실패 | 복원 성공 사실과 실제 미완료 delivery를 함께 표시. 알림 retry에서 applyRestore 호출/DB dataRevision 재증가 없음. |
| 동일 ID·fingerprint 백업 복원 | 옛 active timer/early marker/Preparation Run이 재사용되지 않음. A09/A11/A12 회귀. |
| route exit 또는 새 알림 도중 늦은 완료 | 실제 작업 owner는 정리/receipt까지 유지. dispose된 context 사용, 새 route의 stale snackbar/강제 navigation 없음. |
| export·restore·reset 경쟁 | 기존 shared gate/A15만으로 배타적 교체. nested lock 교착 없음, busy/unavailable 구별, 원본 데이터 손실 없음. |
| 정상 화면과 Recovery Mode reset 실패 | 동일 D03 단계별 결과. busy 해제·안전한 재시도, 완전 삭제/새 profile을 거짓 표시하지 않음. |
| reset DB/키 삭제 완료·OS 취소 미확정 | journal/marker 유지, 단계별 truthful UI, 실제 남은 단계만 retry. |
| 손상 DB의 backup restore | 정상 AppDatabase를 열어야만 workflow를 resolve할 수 있는 구조가 아님. 보존된 DB/키는 D02 replacement 성공 전 유지. |
| freshness 조회 실패/재조회 | 무한 loading/unhandled exception 없음. 사용자에게 안전한 실패와 재시도 표시. |
| 오류·자원 검사 | 영어/한국어 문구 확인, raw exception/원문/암호가 state/log/journal에 없음. 후보 참조 정리와 전송된 Future 미취소의 차이를 확인. |
- 위 실제 callsite에서 UI의 concrete service 조합과 직접 reconcile 순서가 제거됐다.
- domain은 Flutter/BuildContext/native 구현을 import하지 않고, 실제 data adapter의 transaction 결과로 commit 사실을 반환한다.
- 이전 A02/A04/A07/A11/A12 및 반복 일정 회귀가 유지된다.
- A08/A09/D03/D02 통합 시 실제 DB, workflow, Bloc/widget가 같은 결과 의미를 관찰한다.
- unit/widget/Drift integration, analyzer, 적절한 전체 tests/CI 결과를 구현 SHA에 연결한다.
- native picker/실제 파일 저장·교차 플랫폼 복원 및 reset/provider 기기 검증은 기기·OS·artifact별로 별도 기록한다. mock/모델 테스트를 실제 OS 성공으로 보고하지 않는다.
- 미충족 staging·후속 이슈 통합·기기 검증을 숨기지 않고 실제 완료 범위를 상태에 반영한다.
grill-with-docs 실제 전체 문답
전담 서브에이전트 /root/grill_c01이 /Users/ejunpark/.agents/skills/grill-with-docs/SKILL.md를 직접 읽고 적용했다. 코드로 답할 수 있는 사항은 먼저 조사했고 root에게 한 번에 하나씩 질문하여 답변을 기다렸다. 사용자 지시에 따라 root가 대리 답했다. 다음은 실제 질문·답변 및 추가 조사 메시지 전체다. 붙은 단어 표기는 원문을 보존한다.
Q1 — 세 workflow와 이슈별 책임
전담 질문
C01 Q1 — 현재 MyDataScreen은 applyRestore → reconcile을 직접 이어 붙여 DB 교체 성공 뒤 알림 실패까지 ‘작업 실패’로 표시하고, 일반 일정 저장 use case는 일정 저장 → best-effort reconcile → custom preparation 저장으로 분리돼 있습니다. 추천 범위는 (1) 백업 내보내기/미리보기·확정 복원/초기화의 application-level domain workflow와 typed 단계별 결과를 만들고 UI는 확인·입력·결과 표시만 담당, (2) 일정 저장은 기존 FormSubmission 경로를 같은 ‘DB commit과 후속 delivery 결과 분리’ 계약에 연결하되 실제 aggregate transaction 정책은 A08, 복원 runtime 전체 정책은 A09, reset journal은 D03 소유로 유지하는 것입니다. C01에서 이들 구현이 아직 없으면 인터페이스/연결만 완료라고 닫지 않고 A08/A09 구현·통합 검증까지 의존 완료로 남깁니다. 전역 모든 use case/Bloc 재작성 없이 MyDataScreen, recovery reset, create/update form submission의 실제 경계만 대상으로 이 분담을 확정할까요?
Root 답변
C01 Q1 확정. 대상callsite명시한 application/domainworkflow로UI는입력/확인/결과표시, DBcommit사실과후속delivery/파일작업결과를typed단계별구별. A08 aggregate tx/A09restore runtime/D03reset journal 중복구현하지않고전담계약재사용, integration AC충족전close금지. DB교체성공후알림실패를전체복원실패/재복원하라고표현하지않음. backup export파일handoff/실제writesuccess/metadata revision도분리해A02계약유지. preview확인후실제apply사이에generation/input변화는다시검증하며UIrawDB/repo직접 orchestration제거. 전역genericworkflowframework/모든Bloc재작성제외, 구체3경계안에서완료.
Q2 — 오래된 preview와 재시도
전담 질문
C01 Q2 — 복원 미리보기가 열린 동안에는 다른 화면/알림 경로에서 원본 데이터를 편집할 수 있습니다. A02는 export 파일 선택 중 일반 편집을 허용하므로 전체 편집 잠금으로 해결하면 계약을 바꿉니다. 추천: preview는 검증한 immutable backup candidate와 현재 data generation + durable revision을 묶은 메모리 전용 확인 token을 반환합니다. apply는 대기 중 변경 여부를 배타적 replacement 경계 안에서 재검증하고, generation 또는 durable revision이 바뀌었으면 파괴 작업 없이 최신 현재 상태에 맞는 미리보기/대체 경고를 다시 확인하게 합니다. 한 token은 한 commit 시도 소유자이며 중복 클릭은 같은 pending 결과를 공유하거나 명시적 busy, commit 뒤 재사용은 금지합니다. 파일을 다시 읽어 다른 내용을 apply하지 않고 candidate/plaintext/password/token은 route 종료·취소·성공·세대 교체 시 해제하며 비밀번호를 상태/log/journal에 저장하지 않습니다. DB 실패로 commit되지 않은 retry는 동일 candidate 재사용 가능하되 다시 currentness 검사, commit 이후 delivery retry는 DB 교체를 재실행하지 않습니다. 이 stale preview/재시도 경계를 확정할까요?
전담 추가 조사 근거
C01 추가 조사 근거: ADR 0028은 검증된 staging Local Data Store를 거친 미리보기를 요구하지만 현재 BackupService.previewEncryptedBackup은 decrypt/parse/모델검증 후 메모리 _BackupData만 반환합니다. C01이 현재 candidate를 무조건 ADR 충족으로 선언하면 안 됩니다. A09/D02가 정상·Recovery Mode staging/replacement 프로토콜을 소유하고, C01의 preview/commit port는 최종 검증된 candidate만 받는 계약 및 통합 AC로 연결하겠습니다. 이번 조사에서는 소스나 ADR를 수정하지 않습니다.
Root 답변
Q2 확정. immutable검증candidate+generation+durablerevision메모리token, applyexclusive교체경계안에서재검증. 변경시파괴작업없이새경고/preview확인, generaleditlock확대금지. 단일token중복은같은pendingreceipt공유권장(구현상busy면명시typed, 두번commit금지). commit후token재사용/후속deliveryretry재복원금지. 파일재읽기TOCTOU없음, credential/state/log/journal저장금지,메모리참조해제(securezeroization보장과구분). routeexit시진행중native/DBfuture를취소했다과장하지말고exclusive작업owner는결과까지책임. 실패전candidate재사용시currentness재검사, generation교체시oldtoken무효. backuprevision과runtimechanges범위는기존domain정의준수.
Q3 — 계층, 결과와 진행 중 작업 소유권
전담 질문
C01 Q3 — UI를 단순히 service 이름만 바꾼 wrapper로 옮기면 오류 의미와 lifecycle 경쟁이 남습니다. 추천 구조: domain에는 operation별 command/result와 저장소·백업·runtime/delivery port를 두고, data/core adapter가 Drift transaction·암호화·파일 picker/native·key/files를 수행하며 workflow가 DB commit 전/후 순서와 기존 gate/A15 coordinator를 단일 소유합니다. UI는 BuildContext/Dialog/navigation을 유지하되 direct BackupService/ResetService/Reconcile 조합을 제거하고 input/confirm/state/action만 전달합니다. 결과는 cancelled, validation/currentness failure, busy/unavailable, DB-not-committed, committed-with-follow-up-pending, complete처럼 사실을 분리하고 export의 파일 저장 성공+metadata 실패 및 reset의 단계별 삭제/알림 미확정을 보존합니다. raw exception 문자열 대신 안전한 오류코드와 한국어/영어 문구, 해당 후속 단계 retry만 제공합니다. route exit 뒤 작업 자체는 owner가 끝까지 정리하되 새 화면에 stale snackbar/navigation은 하지 않습니다. 새 generic transaction/retry framework나 별도 gate를 만들지 않고 이 구체 구조·결과 계약으로 확정할까요?
전담 gate 조사 보충
C01 구현 의존성 함정도 기록합니다: 현재 gate.run(replacesData: true)는 action 실행 전에 generation을 증가시킵니다. 따라서 preview token 비교를 action 안에서 그대로 하면 자기 자신의 증가 때문에 전부 stale이 됩니다. exclusive claim 획득 당시의 이전 generation/revision을 검증한 뒤 교체 세대를 발급하거나 claim receipt에 두 세대를 구분해야 합니다. 외부에서 한 번 검사하고 들어가는 방식은 TOCTOU이므로 금지할 AC에 포함하겠습니다. A12/A15와 같은 gate를 사용하고 중첩 run lock은 만들지 않습니다.
Root 답변
Q3 구조/결과계약확정. domain은Flutter/BuildContext/native구현모름,ports/command/results로DBcommit전후사실명확화;기존gate/A15단일writer재사용,새genericframework금지. 안전errorcode→l10n, OS/IO원문민감로그금지. workflow작업수명UIroute와분리하되latereceipt는새route안건드림. staging추가근거도본문명시: 현재메모리parse후candidate를ADR0028충족이라고표현하지말고검증된staging포트/통합AC정의. 정상restore A09·Recovery D02와의담당정합성을명시하고, C01 AC충족에필요한실제staging존재/readback증거없으면완료로닫지않음. 기존A02codeverified는nativeexport/검증된범위일뿐이새gap해결증거아님. 충분하면전체문답/근거/정정포함본문완성해주세요.
Root gate 보충 확인
추가gate사실/AC맞습니다. exclusive획득→기존세대/revision검증→유효교체작업세대발급 순서또는동등한claimreceipt로self-invalidating방지. 외부사전검사만으로대체/중첩gate금지. 기존A12tapinvalidate보장을약화하지않고한번의교체시도경계를갖도록명시해주세요.
이번 작업의 실제 범위
읽기 조사, root와의 실제 3문답 및 추가 근거 전달, 이 문서 작성만 수행했다. CONTEXT/ADR를 포함한 다른 파일, 제품 소스, 테스트, shared tracking, GitHub, commit은 변경/실행하지 않았다. 새 glossary/ADR가 필요한 제품 결정은 없으며 기존 용어·ADR를 따른다. 구현과 실제 검증은 후속 순차 작업이다.
2026-09-24 구현 및 검증: 백업·초기화 경계
구현 커밋 4529d9f84e911b1ee2fbb42af09698a0c27cc9f2. MyDataScreen은 domain BackupWorkflow/LocalResetWorkflow를 사용하고, 복구 화면과 pre-DI reset도 DB를 열지 않는 동일 reset workflow 경계를 사용한다. 기존 D03 초기화 엔진과 journal 계약은 유지했다.
복원 preview를 generation/revision에 묶고 exclusive claim 이전에 검증하며, OS 취소 대기 중 발생한 편집은 실제 DB transaction의 마지막 revision 검사로 보존한다. 같은 token 동시 실행은 합류하고 commit된 token은 DB를 재적용하지 않는다. 실제 claim generation을 결과에 보존하며, 데이터 commit 후 알림 실패는 부분 성공으로 표시하고 알림만 재시도한다. 안전한 한국어/영어 오류, 백업 상태 재조회, 오래된 세대/이탈 화면의 늦은 결과 무시를 연결했다.
- 실제 baseline 음성 회귀: preview 뒤 여유시간을 17분으로 편집하면 기존 복원이 0분으로 덮어써 실패했다. 수정 후 실제 Drift로 보존·OS 취소0·세대변경0을 검증했다. 취소 대기 중 편집, transaction 실패 재시도, commit 후 callback 세대 변경도 별도 검증했다.
- 최종 소스 24파일을 동결해 작업 파일과 커밋 blob SHA256을 대조했다. 전체 Flutter 959개 통과, app-owned coverage 88.74% (13507/15221), analyzer no issues, generated/local-only/manifest 검사 및 Python11 통과. 집중52개 이후 추가한 gate 회귀1개도 최종 전체 실행에 포함됐다.
- KO/EN 글자200%, 430×932의 실제 widget 렌더링 8장을 담당자와 root가 확인했다. 백업 상태 실패·비밀번호·미리보기·복원 후 알림 미완료 상태이며 합성 입력을 사용했다. 실기기 파일 picker/암호화 왕복/알림 수신 증거는 아니다.
- 근거:
plans/audit-2026-09-23/c01-final-validation.json,artifacts/c01/의 원본 해시·정규화 로그·PNG·소스 동결·실제 검토 문답. 원격 CI는 이 기록 시점에 대기다.
#600 OPEN 유지. A08 FormSubmission aggregate 연결, A09 실제 staging DB·runtime/session 교체·모든 writer fencing, D02 손상 DB 복원 및 모바일 실제 검증이 남아 있다. 현재 candidate는 인증된 메모리 자료이며 검증된 staging DB가 아니다. claim 후 OS 취소 중 편집이 들어오면 DB overwrite는 차단하지만 이미 발생한 취소는 되돌리지 않는다. typed partial receipt로 남기고 현재 데이터의 알림만 재조정한다. 따라서 모든 stale 복원이 side effect 없이 거부된다는 전체 AC를 충족했다고 주장하지 않는다.
구현 중 실제 추가 문답 및 검토 원문
C01 구현 검토 실제 문답
기존 grill-with-docs 3문답은 issues/C01.md에 보존되어 있다. 아래는 구현 중 실제 추가 질문/검토와 답변이다.
구현 담당의 writer/claim 의존 조사
C01 적용 경계에서 한 가지 구현 의존을 확인했습니다. 현재 일반 durable writer는 LocalDataOperationGate에 참여하지 않아 gate 내부에서 revision을 읽은 뒤 await 알림 취소 동안 편집이 들어올 수 있습니다. C01에서 generation/revision token과 단일 claim을 추가하고, DB transaction 시작 시 revision을 다시 검사해 durable 교체를 막는 것은 가능합니다. 다만 stale 후에도 이미 실행한 알림 취소를 없던 일로 만들 수 없으므로 A09의 runtime/replacement 순서까지 완전 충족이라고 주장하면 안 됩니다. 권고: C01은 claim 전검사 + DB transaction 안 최종 revision 검사/부분상태를 구현하고, 검증된 staging 및 모든 일반 writer와 replacement fencing은 A09 통합 미완료로 명시합니다. 원본 데이터 stale overwrite는 막고 OS 정리가 이미 발생한 경우 후속 reconcile 필요 사실을 결과에 보존하겠습니다. 이 경계로 진행하겠습니다(새 제품정책이 아니라 기존 AC의 미충족 부분 명시).
Root 답변
이 경계로 진행하되 완전한 C01 stale-before-side-effects AC는 미완료로 명시하세요. 최소한 claim 이전 generation/revision 부적합이면 OS/runtime/data 변경0이어야 하고, claim 이후 일반 writer가 들어온 경우 DB transaction 안 최종 revision 재검사로 원본overwrite 방지. 이미 수행한 OS취소/세대변경은 typed receipt에 남기고 현재 데이터의 명시/기존 reconcile 복구로 이어야 하며 단순 stale=no-op이라고 보고하면 안 됩니다. DB tx 안 nativeawait 금지 유지. 결정적 barrier로 preview→claim 사이 변경과 OS취소대기중일반편집→최종tx rejection 두경로 실제Drift 검증. A09에 모든writer fencing/staging/runtime순서 통합을 명시해 C01닫지않음. 이 실제 구현 의존 질문/권고/답변 원문을 /tmp/c01-review-transcript.md에 보존해주세요.
Root retry/receipt 검토
root WIP 리뷰: BackupWorkflow._retry 단일 Future가 receipt/generation 구별 없이 공유되면 이전 restore의 느린 retry 중 새 generation의 다른 receipt 재시도가 이전 결과를 받을 수 있습니다. retry는 operation identity+generation 기준 singleflight로 묶고 과거 receipt가 새 작업의 후속으로 잘못 합류하지 않게 해주세요. 같은 token의 동시 apply 합류와 commit 후 재제출(재적용 금지)의 typed 결과도 별도 테스트. _completed Expando의 과거 pending receipt를 retry 성공 후 다시 읽을 때 pending이 계속 남는지도 확인 필요합니다. 아직 WIP임은 알고 있으며 final 전에 위 경계 확인 부탁합니다.
또한 _restore의 await _backup.apply(input) 뒤 generation getter를 읽어 committedGeneration으로 쓰면, commit 이후 반환 직전에 다른 replacement/reset이 generation을 바꾼 경우 이전 commit을 새 세대 receipt로 잘못 귀속할 수 있습니다. adapter apply가 실제 claim/commit generation을 반환하는 immutable receipt를 사용하고 후속 currentness는 그 receipt 기준으로 검사하는 편이 안전합니다. 에러 catch에서도 '현재 generation'을 이전 부분작업의 generation처럼 기록하지 않도록 시작/claim identity 구별. deterministic after-commit/before-workflow-continuation gate change 회귀 부탁합니다.
구현 담당 답변
리뷰 2개 반영 중입니다. retry singleflight는 receipt 객체별 Expando로 바꿨고, commit 직후 아직 delivery 대기 중인 동일 token은 completed보다 pending Future를 우선 공유합니다. BackupOperationsPort.apply는 claim이 발급한 정확한 generation을 반환하도록 바꾸며 catch 부분 결과에도 claim generation을 보존합니다. retry 완료 시 약한 참조로 연결된 token의 cached receipt도 갱신해 오래된 pending을 반복 반환하지 않겠습니다. 초기 회귀에서는 비밀번호 dialog 뒤 busy spinner 때문에 pumpAndSettle이 끝나지 않는 UI 상태를 발견해 입력대기와 실제 IO 표시를 분리했습니다.
Root UI currentness 검토
추가 UI currentness 리뷰: _loadFreshness 성공에는 generation 검사지만 catch에는 없고, _export/_restore catch도 _routeCurrent만 검사하므로 같은 route에서 세대 변경 후 늦은 old error가 새 화면에 표시될 수 있습니다. 실패 경로에도 operation 시작/claim receipt의 currentness를 적용해주세요. _resetData confirmation 대기에는 _busy가 false이고 확인 뒤 mounted만 확인하므로 확인화면 중복/대상세대변경 경계도 확인. reset은 성공적 invalidate 자체를 stale로 오인하면 안 되니 destructive acceptance 전 확인토큰과 결과표시소유권을 구별해주세요. 기존root검토와함께 transcript 보존 부탁합니다.
구현 담당 반영
freshness/export/preview 실패 경로에 시작 generation 검사를 적용했다. reset은 확인 화면을 열기 전 busy를 획득하고, 확인 직후 route 및 기존 generation을 검사한 다음 명시 reset을 접수한다. 실제 reset 결과는 자체 invalidate를 실패로 오인하지 않는다. old error/freshness/disposed route 회귀를 추가했다.
원격 CI 최종 확인
정확한 head aa2fda68fbf3ac431738de3792f5d587d35b6dda의 Flutter run35949254397에서 959 tests/analyze와 coverage **88.72% (13504/15221)**를 통과했다. Android run35949254443의 release APK·merged/packaged notification manifest 검사도 성공했다. artifact10787843483의 identity를 내려받아 source/test merge를 구분해 읽었다: 실제 synthetic merge e74e6c52d6b1c036f29ac828307a35c9ec04302b, APK SHA256 a3d2858af8f52adea00b361da6511a8432e62bcdbedfb3bd2e045a86213ee38d. 일회용 CI 서명이며 device_delivery_verified: false다. 근거 c01-ci-validation.json.
로컬 coverage88.74%와 원격88.72%는 각 실행의 실제 값이며 동일 값으로 합치지 않았다. 앞선 기록의 원격 CI 대기는 해소됐지만 A08/A09/D02 통합과 실제 모바일 검증은 남아 있어 #600은 OPEN이다.
2026-09-24 A08 통합 진행 갱신
A08 #601의 원자적 aggregate FormSubmission + ScheduleSaveWorkflow 연결을 126161dab042365c89983934b1673d860c27d8d3에 구현했다. 49파일 동결/commit blob 일치, 동일 최종 제품의 전체1,018 tests/88.96%, 최종 강화 회귀41/analyzer 및 KOEN200% 저장 결과 UI를 검증했다. a08-final-validation.json에 정확한 테스트 소스 차이를 기록했다. 위 과거 기록의 A08 구현 대기는 이 소스 기준 해소됐지만 A09 암호화 staging/runtime·writer fencing, D02 손상 복구, 실기기 및 최종 원격 CI는 여전히 별도 미완료다. C01 전체 완료·병합·출시로 표시하지 않고 OPEN 유지한다.
A08 최종 통합 원격검증도 HEAD ee6c1f8b에서 Flutter35954530542 1,018tests/analyze/88.96%(14112/15864), Android35954530563 APK/manifest 성공으로 확인했다. a08-ci-validation.json 참조. A09/D02 및 실제 기기 미완료 경계는 그대로 유지한다.
2026-09-24 U09 조사에서 확인한 기본 준비 저장 통합 미완료
U09 #629의 전담 grill-with-docs 실제 Q2·보충·Q3에서 기본 준비 편집의 순차 저장을 추가 확인했다. 고정 source 06a238b634a0206f257348db201034fc06eb05c7의 DefaultPreparationSpareTimeFormBloc L105는 기본 준비 write → 여유 시간 write → 사용자 reload를 순서대로 실행하고 모든 오류를 하나의 catch/e.toString으로 보낸다. 따라서 두 번째 write가 실패하면 첫 변경이 남을 수 있고, 두 write 성공 뒤 reload 실패도 사용자에게 저장 실패로 표시될 수 있다. 이는 A08 일정 폼 aggregate 구현으로 해소된 범위가 아니다.
C01의 P1 추가 통합 작업으로 A09 다음, D01 이전에 구현한다. 기존 domain workflow/data transaction을 통해 기본 준비와 여유 시간을 함께 원자 저장하고 A08 의존 version/revision 및 A09 writer generation을 보존한다. commit 전 실패와 commit 후 읽기·알림 후속 실패를 구분하는 typed 결과를 UI에 전달한다. 성공한 DB 변경은 reload-only/follow-up-only 재시도에서 다시 적용하지 않는다. 새 generic workflow 엔진을 만들지 않는다. U09는 해당 결과의 안전한 문구와 실제 재시도 행동을 연결하며 문구 수정만으로 원자성이 해결됐다고 판단하지 않는다.
추가 수용 기준(미실행):
- 실제 DB에서 두 번째 write fault로 부분 저장되는 변경 전 회귀를 재현한다.
- 변경 후 첫/두 번째 write fault 모두 기본 준비·여유 시간·의존 metadata/revision이 함께 rollback된다.
- commit 뒤 사용자 reload 실패는 저장 완료 사실을 유지하며 retry에서 durable write/revision 증가가 없다.
- 중복 제출, 이전 form owner 완료, restore/reset 세대 변경, 기본 준비에 의존하는 일정 읽기/알림 후속처리가 실제 기존 경계와 일치한다.
- 해당 typed 결과와 입력 보존·한국어/영어 메시지를 실제 Bloc/widget 경로로 검증한다.
실제 발견 질문·추천·root 답변·추가 책임 확인 원문 전체는 U09 #629의 문답에 보존했다. 이 추가 통합은 아직 구현하지 않았으며 #600은 OPEN이다.
Activity