Skip to content

[codex] iCloud 동기화 데이터 초기화 방어 및 회귀 테스트 보강#179

Merged
leeari95 merged 7 commits into
release/3.2.4from
codex/fix-icloud-sync-reset
Jul 5, 2026
Merged

[codex] iCloud 동기화 데이터 초기화 방어 및 회귀 테스트 보강#179
leeari95 merged 7 commits into
release/3.2.4from
codex/fix-icloud-sync-reset

Conversation

@geeksbaek

@geeksbaek geeksbaek commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

요약

앱 재설치, iCloud 일시 오류, CloudKit sync reset/import 실패 상황에서 빈 UserCollectionEntity와 기본 DailyTaskEntity가 생성되어 기존 iCloud 데이터를 덮어쓸 수 있는 경로를 차단합니다.

실제 iCloud 로그인 계정 없이도 문제 경로를 결정적으로 재현할 수 있도록 CloudKit import/account 상태 seam을 테스트에 주입했고, 해당 회귀 테스트를 로컬 make test와 PR Check build에 연결했습니다.

변경 사항

  • CloudKit import 실패를 성공 import와 분리하여 timeout/reset 억제 상태를 유지합니다.
  • .temporarilyUnavailable, .couldNotDetermine iCloud 계정 상태를 local-only가 아닌 timeout/unknown 경로로 처리합니다.
  • 기존 사용자 플래그(hasEverHadUserCollection)를 lock으로 보호해 background context 간 race를 막습니다.
  • 단위 테스트 실행 시 Firebase, CloudKit, SafetySnapshot 앱 부트스트랩이 돌지 않도록 IS_UNIT_TESTING 환경을 분리했습니다.
  • SafetySnapshot 복원 실패 시 기존 데이터를 rollback으로 보존하고, 스냅샷 파일은 backup 제외 + 백그라운드 CloudKit flush와 양립 가능한 보호 등급으로 저장합니다.
  • CoreDataStorageICloudResetTests 13개를 make test, make ci, PR Check build에서 실행하도록 연결했습니다.
  • CI 시뮬레이터 첫 실행 지연으로 fetchTasks expectation이 흔들리지 않도록 회귀 테스트 대기 시간을 안정화했습니다.

재현 및 검증 범위

  • 첫 CloudKit import timeout 이후 앱이 빈 로컬 기본 데이터를 만들지 않는지 검증합니다.
  • CloudKit import 실패가 timeout/reset 억제 상태를 잘못 해제하지 않는지 검증합니다.
  • sync reset 중 import 실패가 빈 데이터 생성을 유발하지 않는지 검증합니다.
  • no-iCloud fresh user 경로에서는 기존처럼 로컬 기본 데이터 생성이 허용되는지 검증합니다.
  • transient iCloud account status가 no-iCloud로 오판되지 않는지 검증합니다.
  • SafetySnapshot 복원 실패 rollback, 파일 보호 등급, backup 제외 속성을 검증합니다.

적대적 검증 반영

  • 5개 리뷰 관점으로 실패 import, transient iCloud status, thread-safety, CI 누락, 테스트 격리, 스냅샷 복원/파일 보호 리스크를 점검했습니다.
  • 추가 correctness 재검증에서 P0~P3 추가 finding 없음 확인.
  • security 재검증의 P2/P3는 SafetySnapshot 보호 등급 조정과 rollback/파일 보호 테스트 2개 추가로 보완했습니다.

테스트 및 CI

  • 로컬: make testCoreDataStorageICloudResetTests 13개 통과
  • 로컬: swiftlint --config .swiftlint.yml --quiet — 통과, 기존 warning만 출력
  • 로컬: make validate — architecture/pattern/docs 검증 통과
  • 로컬: git diff --check — 통과
  • 원격: Check Build — head 6710d5b에서 통과
  • 원격: Architecture Check — head 6710d5b에서 통과
  • 원격: CodeRabbit — pass/skipped

남은 체크 상태

Claude Code Review 체크는 실패 상태입니다. 로그 원인은 앱 코드나 테스트 실패가 아니라 GitHub Actions/조직 설정 문제입니다.

Your organization has disabled Claude subscription access for Claude Code · Use an Anthropic API key instead, or ask your admin to enable access

따라서 이 PR의 앱 빌드 및 iCloud reset 회귀 테스트 CI는 통과했지만, 전체 PR 체크는 위 외부 Claude Code 설정 문제가 해결되어야 완전 green이 됩니다.

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 49996923-afb2-4a4e-870f-8ad4b19269e6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-icloud-sync-reset

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@geeksbaek geeksbaek changed the title [codex] Fix iCloud sync reset guard [codex] iCloud 동기화 초기화 방어 로직 수정 Jun 3, 2026
@geeksbaek geeksbaek changed the title [codex] iCloud 동기화 초기화 방어 로직 수정 [codex] iCloud 동기화 데이터 초기화 방어 및 회귀 테스트 보강 Jun 3, 2026
Comment on lines +286 to +295
func completeFirstImportWait(reason: FirstImportWaitCompletionReason) {
isWaitingForFirstImport = false
switch reason {
case .importArrived, .noICloud:
isFirstImportTimedOut = false
case .timeout:
isFirstImportTimedOut = lastSuccessfulImportDate == nil
}
Log.info("completeFirstImportWait reason=\(reason)")
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

recovery grace 자동 UC 생성 제거 — import 지속 실패 시 복구 유저가 빈 화면에 머무름

Pattern: getUserCollection()에서 isWithinRecoveryGracePeriod 예외 분기(기존 유일한 자동 escape hatch)가 제거됨.

Proof: performCloudKitRecovery()가 store를 비운 뒤 재시작하면 isFreshInstall()=truemarkWaitingForFirstImport() → 10초 timeout → completeFirstImportWait(.timeout)lastSuccessfulImportDate==nil이므로 isFirstImportTimedOut=true. 이후 import가 계속 실패하면 finishCloudImport(succeeded:false)가 플래그를 유지 → getUserCollection이 매번 .notFound, shouldSuppressDataCreation=true. 회귀 테스트 testRecoveryGraceDoesNotCreateEmptyUserCollectionForKnownUser가 이 차단 동작을 명시적으로 검증.

Impact: 느린/불안정 네트워크에서 iCloud 복원을 실행한 기존 유저는 import가 성공하기 전까지 모든 데이터 화면이 빈 상태/오류로 표시됨. 단, 이는 의도된 trade-off (빈 UC가 iCloud로 export되어 정상 데이터를 덮어쓰는 것을 막는 것이 PR 목적이며, 제거된 grace 분기 자체가 데이터 유실 경로였음). 크래시/하드 락아웃이 아닌 graceful degraded 상태이고, import 성공 또는 SafetySnapshot 수동 복원으로 해제됨. 팀에서 'degraded 상태 동안 UI 안내(스플래시 유지/복원 안내)' 추가 여부를 검토 권장.

Comment on lines +11 to +14
static var isUnitTesting: Bool {
ProcessInfo.processInfo.environment["IS_UNIT_TESTING"] == "1"
|| NSClassFromString("XCTestCase") != nil
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppEnvironment.isUnitTesting의 NSClassFromString("XCTestCase") fallback 위험

Pattern: isUnitTestingIS_UNIT_TESTING==1 OR NSClassFromString("XCTestCase") != nil로 판정. 이 단일 boolean이 (1) AppDelegate Firebase 부트스트랩, (2) Log의 모든 Crashlytics/Analytics, (3) SceneDelegate 앱 셋업을 게이팅.

Proof: AppEnvironment.swift:13의 런타임 클래스 probe가 production 경로에서 평가됨. AppDelegate.swift:21-23, Log.swift:73#if DEBUG 없이 소비.

Impact (2가지 관점):

  1. Correctness/UI-test: XCUITest의 앱 프로세스에는 XCTest.framework가 주입되므로 NSClassFromString("XCTestCase")가 non-nil → 앱이 빈 화면으로 부팅. 향후 UI 테스트 도입 시 깨짐.
  2. Security/Telemetry: App Store 빌드는 XCTest를 링크하지 않아 정상(=P2)이나, XCTest가 릴리스 바이너리에 새어 들어가면 크래시 리포팅/애널리틱스가 조용히 전면 비활성화되고 탐지 불가 — iCloud 데이터 유실을 진단하려는 릴리스에서 특히 치명적.

권장: Tuist 스킴이 이미 IS_UNIT_TESTING=1을 주입하므로 NSClassFromString 절을 제거하거나 #if DEBUG로 감쌀 것. (CO-3/SE-1/CQ-5 통합)

Comment on lines +57 to +71
init(
containerProvider: @escaping () -> NSPersistentContainer = { CoreDataStorage.shared.persistentContainer },
snapshotDirectoryProvider: @escaping () -> URL? = {
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
},
fileAttributeSetter: @escaping (URL, [FileAttributeKey: Any]) throws -> Void = { url, attributes in
try FileManager.default.setAttributes(attributes, ofItemAtPath: url.path)
},
beforeApplyingSnapshot: ((NSManagedObjectContext) throws -> Void)? = nil
) {
self.containerProvider = containerProvider
self.snapshotDirectoryProvider = snapshotDirectoryProvider
self.fileAttributeSetter = fileAttributeSetter
self.beforeApplyingSnapshot = beforeApplyingSnapshot
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SafetySnapshotService: 테스트 전용 beforeApplyingSnapshot가 production initializer에 노출

Pattern: 4번째 DI 파라미터 beforeApplyingSnapshot: ((NSManagedObjectContext) throws -> Void)? = nil이 fault injection 전용. 다른 3개 DI(containerProvider/snapshotDirectoryProvider/fileAttributeSetter)는 실제 인프라 seam이지만 이것은 production에서 항상 nil이며 restore()try self.beforeApplyingSnapshot?(context)(diff line 207)는 production에서 항상 no-op.

Proof: non-nil로 주입하는 유일한 호출자는 테스트 testSafetySnapshotRestoreFailureAfterWipeRollsBackExistingCollection(test line 213).

Impact: production restore 경로에 죽은 라인이 남고, 의도가 파라미터 이름으로 전달되지 않음. #if DEBUG seam 또는 테스트 서브클래스로 격리 권장. (참고: context.rollback() 추가 자체는 정당한 production 개선이며 문제 아님.)

Comment on lines +1059 to +1063
static func resetPersistentSyncFlagsForTesting() {
UserDefaults.standard.removeObject(forKey: hasEverHadUserCollectionKey)
UserDefaults.standard.removeObject(forKey: recoveryInitiatedAtKey)
}
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resetPersistentSyncFlagsForTesting가 UserDefaults.standard(공유 앱 suite)를 변경

Pattern: #if DEBUG resetPersistentSyncFlagsForTesting()UserDefaults.standard에서 hasEverHadUserCollectionKey, recoveryInitiatedAtKey를 제거. setUp/tearDown 양쪽에서 호출(test line 10, 14).

Proof: #if DEBUG 스코프는 올바르게 닫힘(diff line 1041-1064). 그러나 수동 QA에 쓰는 실기기에서 Debug 빌드로 make test를 돌리면 실제 앱의 known-user/recovery 영속 플래그를 변조. 다른 testing seam(markImportInProgressForTesting 등)은 in-memory lock만 변경하므로 안전.

Impact: 실기기 개발 반복 시 production 앱 동작 오염 가능(다음 실행 시 빈 UC 생성 여부에 영향). CI 클린 시뮬레이터에서는 낮음. 별도 UserDefaults suite 주입 또는 시뮬레이터 전용 실행 권장.

Comment on lines +110 to +116
_hasEverHadUserCollectionCached.withLock { cached in
guard cached != newValue else {
return
}
cached = newValue
UserDefaults.standard.set(newValue, forKey: Self.hasEverHadUserCollectionKey)
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT) hasEverHadUserCollection setter가 OSAllocatedUnfairLock 임계영역 내에서 UserDefaults I/O 수행

비재귀 OSAllocatedUnfairLock.withLock{} 안에서 UserDefaults.standard.set(...) 호출(diff line 110-116). 동일 스레드 재진입 옵저버가 현재 코드베이스에 없어 데드락은 가설적이고, UserDefaults.set은 경량이라 컨텐션도 미미. 다만 잠재적 재진입 트랩이므로 newValue를 스냅샷해 락 밖에서 쓰는 1줄 수정 권장.

static let waiting = "waiting"
static let importing = "importing"
static let reset = "reset"
static let timedOut = "timed_out"

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT) Log.Snapshot 구조체에 isFirstImportTimedOut 필드 누락(Crashlytics 진단 공백)

isFirstImportTimedOutLog.Param/ucCreationSuppressed 이벤트에는 추가됐으나 Log.Snapshot 구조체(Log.swift:169-182)에는 빠져 Crashlytics 세션 custom key 스냅샷에 timeout 차원이 누락. 이 PR이 강화하려는 진단 표면에 공백. snapshot에 필드 추가 권장.

Comment on lines 634 to 638
guard let self else {
return
}
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT) logSyncDiagnostics 클로저: guard owner 네이밍 미사용 + guard 뒤 빈 줄 누락

[weak self] 클로저의 guard let self else { return }(diff line 634-636)는 컨벤션 1.1.A(owner 사용)와 2.3(guard 뒤 빈 줄)을 위반하나, 이는 기존 single-line guard의 reformat으로 위반이 PR 이전부터 존재(공백만 PR이 도입). 재진입/누수 위험 없음. 라인을 이미 건드리는 김에 기회주의적으로 owner로 변경 + 빈 줄 추가 권장. (CV-1+CV-3 통합)

Comment on lines +1 to +6
//
// AppEnvironment.swift
// Animal-Crossing-Wiki
//
// Created by Codex on 2026/06/04.
//

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT) AppEnvironment.swift: 'Created by Codex' 작성자 헤더

신규 파일에 Xcode 자동생성 헤더 블록 포함(diff line 1-6). 다만 저장소 모든 기존 파일이 동일 패턴을 가지므로 헤더 자체는 기존 관례를 따름. 실질 nit은 Created by Codex on 2026/06/04 작성자 줄(AI 툴 이름)이며 프로젝트 표준 작성자로 정렬하거나 헤더 제거(저장소 전체 정리 시) 권장.

Comment on lines +1041 to +1042
#if DEBUG
extension CoreDataStorage {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT) #if DEBUG extension CoreDataStorage — MARK 주석 누락

신규 #if DEBUG extension CoreDataStorage(diff line 1041-1042)에 // MARK: - 없음. 컨벤션 8 적용(신규 코드라 actionable). 예: // MARK: - Testing. 조직화 nit.

Comment on lines +249 to +250
extension CoreDataStorageICloudResetTests {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT) CoreDataStorageICloudResetTests helper extension — MARK 주석 누락

테스트 helper extension CoreDataStorageICloudResetTests(test line 249-250)에 MARK 없음. 컨벤션 8 nominal 적용이나 테스트 파일이라 가치 낮음. 선택적.

리뷰에서 타당하다고 판단한 항목을 패치한다.

- AppEnvironment: isUnitTesting의 NSClassFromString("XCTestCase") fallback 제거.
  테스트 스킴이 IS_UNIT_TESTING=1을 주입하므로 환경 변수만으로 판정해 릴리스
  텔레메트리 무력화/향후 UI 테스트 부팅 실패 리스크를 차단.
- SafetySnapshotService: 테스트 전용 beforeApplyingSnapshot seam을 #if DEBUG로
  격리(production restore 죽은 코드 제거). backup 제외 실패를 Log.error로 보고.
- CoreDataStorage: 동기화 영속 플래그 저장소를 주입 가능한 userDefaults로 분리해
  테스트가 .standard(실제 앱 suite)를 오염시키지 않도록 함. hasEverHadUserCollection
  setter의 UserDefaults I/O를 lock 임계영역 밖으로 이동. completeFirstImportWait의
  timedOut/waiting 갱신 순서를 fail-safe하게 조정해 TOCTOU 윈도우 제거.
- Log.Snapshot에 isFirstImportTimedOut 추가(Crashlytics 진단 공백 보완),
  event/click의 isFirebaseConfigured 가드 스타일 통일.
- logSyncDiagnostics 클로저 owner 네이밍/guard 뒤 빈 줄, #if DEBUG 및 테스트 helper
  extension MARK 추가, 신규 파일 작성자 헤더 정렬.
- 테스트: 격리된 UserDefaults suite 주입으로 전환하고 정적 reset helper 제거.

https://claude.ai/code/session_01L4kUZhiUNUtnr18SabrHzR

Copy link
Copy Markdown
Collaborator Author

리뷰 코멘트 14개를 검토해, 타당한 12개를 반영했습니다. 변경은 이 브랜치(codex/fix-icloud-sync-reset)를 base로 한 stacked PR #180 에 올렸습니다 — 머지하면 이 PR에 그대로 흡수됩니다.

✅ 반영 (12)

  • AppEnvironment: isUnitTestingNSClassFromString("XCTestCase") fallback 제거 — 테스트 스킴이 IS_UNIT_TESTING=1을 주입하므로 환경 변수만으로 판정(릴리스 텔레메트리 무력화·향후 UI 테스트 부팅 실패 리스크 차단)
  • SafetySnapshotService: 테스트 전용 beforeApplyingSnapshot#if DEBUG seam으로 분리(production 죽은 코드 제거), backup 제외 실패를 Log.error로 보고
  • CoreDataStorage: sync 영속 플래그 저장소를 주입형 userDefaults로 분리(테스트가 .standard 오염 방지, 정적 reset helper 제거) · hasEverHadUserCollection setter의 UserDefaults I/O를 lock 밖으로 이동 · completeFirstImportWait 순서 조정으로 TOCTOU 윈도우 제거
  • 진단/컨벤션: Log.SnapshotisFirstImportTimedOut 추가 · Log.event/click 가드 통일 · logSyncDiagnostics owner 네이밍+빈 줄 · #if DEBUG/테스트 helper MARK · 작성자 헤더 정렬 · 실패 import 주석

⏸️ 미반영 (2) — 사유

  • recovery grace 자동 UC 생성 제거: 의도된 trade-off로 명시돼 코드 변경 요청이 아니며, degraded UI 안내는 제품 결정이라 범위 밖
  • Log 가드 5개 전면 통일: 제안된 단일 top-level guard isFirebaseConfigured는 항상 실행돼야 하는 os_log(로컬 로깅)까지 막으므로 그대로 적용 불가 → 안전한 event/click만 통일

ℹ️ CI 참고

  • make validate(architecture/pattern/docs) 통과. 다만 작업 환경이 Linux라 make test/빌드는 정적 검토만 수행했습니다.
  • #180은 base가 develop가 아니라 Check Build/Architecture Check가 돌지 않습니다 — #180을 이 브랜치에 머지하면 본 PR의 Check Build가 재실행되며 함께 검증됩니다.

Generated by Claude Code

geeksbaek and others added 3 commits June 10, 2026 22:46
[review] PR #179 리뷰 코멘트 반영 — 테스트 seam 격리·진단·컨벤션 보강
- cleanupOrphanedEntities가 첫 import 후 120초 grace period를 무시하고
  일시적 orphan을 삭제할 수 있던 경로 차단 (isWithinGracePeriod 가드 추가)
- 성공 import로 UC가 복구되면 recovery grace 타임스탬프를 정리하는
  clearRecoveryInitiatedIfRecovered 연결 (기존 clearRecoveryInitiated 데드 API 해소)
- 반복 sync 실패로 컬렉션 생성이 억제된 보호 모드 상태를 세션당 1회 Alert로 안내
  (isAwaitingCloudDataWithoutCollection + SceneDelegate.handleCloudSyncError)
- CoreDataStorage 파일 길이 한도 초과로 Data Recovery extension을
  CoreDataStorage+Recovery.swift로 분리
- 회귀 테스트 6개 추가 (총 19개 통과)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
빌드의 SwiftLint autocorrect 단계가 수정한 unneeded_override 등 기계적 변경.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@leeari95
leeari95 changed the base branch from develop to release/3.2.4 July 5, 2026 13:20
@leeari95
leeari95 marked this pull request as ready for review July 5, 2026 13:20
@leeari95
leeari95 merged commit 1019d3c into release/3.2.4 Jul 5, 2026
4 of 5 checks passed
@leeari95
leeari95 deleted the codex/fix-icloud-sync-reset branch July 5, 2026 13:21
leeari95 added a commit that referenced this pull request Jul 5, 2026
* Fix iCloud sync reset guard

* Harden iCloud reset regression coverage

* Stabilize iCloud reset regression wait

* ♻️ [refactor] PR #179 리뷰 코멘트 반영 — 테스트 seam 격리·진단·컨벤션 보강

리뷰에서 타당하다고 판단한 항목을 패치한다.

- AppEnvironment: isUnitTesting의 NSClassFromString("XCTestCase") fallback 제거.
  테스트 스킴이 IS_UNIT_TESTING=1을 주입하므로 환경 변수만으로 판정해 릴리스
  텔레메트리 무력화/향후 UI 테스트 부팅 실패 리스크를 차단.
- SafetySnapshotService: 테스트 전용 beforeApplyingSnapshot seam을 #if DEBUG로
  격리(production restore 죽은 코드 제거). backup 제외 실패를 Log.error로 보고.
- CoreDataStorage: 동기화 영속 플래그 저장소를 주입 가능한 userDefaults로 분리해
  테스트가 .standard(실제 앱 suite)를 오염시키지 않도록 함. hasEverHadUserCollection
  setter의 UserDefaults I/O를 lock 임계영역 밖으로 이동. completeFirstImportWait의
  timedOut/waiting 갱신 순서를 fail-safe하게 조정해 TOCTOU 윈도우 제거.
- Log.Snapshot에 isFirstImportTimedOut 추가(Crashlytics 진단 공백 보완),
  event/click의 isFirebaseConfigured 가드 스타일 통일.
- logSyncDiagnostics 클로저 owner 네이밍/guard 뒤 빈 줄, #if DEBUG 및 테스트 helper
  extension MARK 추가, 신규 파일 작성자 헤더 정렬.
- 테스트: 격리된 UserDefaults suite 주입으로 전환하고 정적 reset helper 제거.

https://claude.ai/code/session_01L4kUZhiUNUtnr18SabrHzR

* 🐛 [fix] 동기화 잔존 버그 수정 — orphan cleanup grace 가드·recovery 플래그 정리·보호 모드 안내

- cleanupOrphanedEntities가 첫 import 후 120초 grace period를 무시하고
  일시적 orphan을 삭제할 수 있던 경로 차단 (isWithinGracePeriod 가드 추가)
- 성공 import로 UC가 복구되면 recovery grace 타임스탬프를 정리하는
  clearRecoveryInitiatedIfRecovered 연결 (기존 clearRecoveryInitiated 데드 API 해소)
- 반복 sync 실패로 컬렉션 생성이 억제된 보호 모드 상태를 세션당 1회 Alert로 안내
  (isAwaitingCloudDataWithoutCollection + SceneDelegate.handleCloudSyncError)
- CoreDataStorage 파일 길이 한도 초과로 Data Recovery extension을
  CoreDataStorage+Recovery.swift로 분리
- 회귀 테스트 6개 추가 (총 19개 통과)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* 🎨 [style] SwiftLint autocorrect 자동 수정 반영 (Catalog)

빌드의 SwiftLint autocorrect 단계가 수정한 unneeded_override 등 기계적 변경.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* 🐛 [fix] 전 기능 영역 버그 일괄 수정 — 데이터 초기화 누락·매핑 오류·크래시·플레이어 옵저버

코드베이스 전수 조사에서 발견·검증된 버그 수정:

- Items.reset()이 npcLike relay를 초기화하지 않아 데이터 초기화 후에도
  NPC 좋아요 목록이 화면에 남던 문제 수정
- NPCLikeEntity.toDomain()이 genderAsia 대신 gender를 읽어 CoreData 왕복 시
  genderAsia가 손실되던 매핑 오류 수정
- CloudKit 부분 import로 DailyTask progressList가 amount보다 짧아지면
  toggle 시 index out of range로 크래시하던 문제 수정 — toDomain 정규화 +
  모델/storage toggle 길이 보정 (자가 치유)
- MusicPlayerManager: block 기반 곡 종료 옵저버가 removeObserver(self)로
  제거되지 않아 누적 등록되고(자동 다음 곡 다중 실행), 특정 AVPlayerItem에
  바인딩되어 곡 전환 후 알림을 못 받던 문제 수정 — 토큰 보관 + object nil +
  currentItem 검증, 등록 지점을 player 재생성 시점으로 일원화
- MusicPlayerManager: 잠금화면 아트워크 다운로드를 곡별 disposeBag으로 교체해
  이전 곡 이미지가 늦게 도착해 now playing 정보를 덮어쓰던 문제 수정
- VillagersCellReactor/NPCCellReactor: updateLike/updateHouse 시 state를
  갱신하지 않아 목록 셀의 하트/집 버튼이 시각적으로 토글되지 않던 문제 수정
- SafetySnapshotService.pendingWorkItem을 OSAllocatedUnfairLock으로 보호하고
  flushNow를 전용 queue 동기 실행으로 변경 (멀티스레드 data race 해소)
- AppCoordinator: AnimalsCoordinator만 setUpParent 누락되어 있던 비대칭 수정
- DashboardCoordinator가 사용하는 "Unknown error" 로컬라이제이션 키 추가 (ko/en)
- 회귀 테스트 2개 추가 (progressList 손상 시나리오, 총 21개 통과)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* 📝 [docs] 버그 수정 관련 문서 동기화 — music-player·dashboard·icloud-sync

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: 백종열 <jongyeol@baegjong-yeol-ui-Macmini.local>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Ari <lee_ari95@icloud.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants