Skip to content

Feature/#64 - 온보딩 Touch ID 인증 및 iCloud 동기화 연동 - #66

Merged
doyeonk429 merged 26 commits into
developfrom
feature/#64
Aug 8, 2026
Merged

Feature/#64 - 온보딩 Touch ID 인증 및 iCloud 동기화 연동#66
doyeonk429 merged 26 commits into
developfrom
feature/#64

Conversation

@dlguszoo

@dlguszoo dlguszoo commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

✨ What’s this PR?

📌 관련 이슈 (Related Issue)


🧶 주요 변경 내용 (Summary)

  • 온보딩 "Enable Touch ID"와 Lock 화면 "Unlock"에 실제 LocalAuthentication(Touch ID/시스템 암호) 인증 연결
  • 온보딩 "Enable Sync"에 실제 iCloud 계정 상태 확인(CloudKit) 연결, SwiftData ↔ CloudKit 미러링을 isICloudSyncEnabled 설정으로 제어
  • 마스터 암호화 키 iCloud Keychain 동기화 (Data Protection Keychain, AfterFirstUnlock 접근성 적용)
  • Bundle ID를 com.devault.app으로 변경, iCloud/CloudKit entitlements 및 keychain-access-groups 추가
  • SwiftData 모델을 CloudKit 호환 스키마로 수정 (관계 optional화, 기본값 추가, unique 제약 제거) 및 이 과정에서 발견된 to-many 관계 크래시 수정
  • 앱 아이콘 추가
  • refactoring: public extension 접근 제어 가이드 준수

📸 스크린샷 (Optional)

image

🧪 테스트 / 검증 내역

  • My Mac, macOS 환경에서 정상 빌드 확인
  • 온보딩에서 Touch ID 활성화 정상 동작 확인
  • Lock 화면에서 Touch ID/시스템 암호로 잠금 해제 확인
  • iCloud 동기화 활성화 시 CloudKit Console에서 Project 생성/동기화 이벤트(RecordSave, ZoneChanges 등) 확인

💬 기타 공유 사항

  • iCloud 동기화 진행 상태를 사용자에게 알려주는 UX(싱크 상태 표시, 새 기기 온보딩 시 안내 등)는 후속 이슈에서 다룰 예정입니다
  • 새 기기에서 동기화 완료 전 키 생성 경합 가능성은 팀 내 논의 후 현재는 감수하기로 했고, UX 개선으로 완화 예정입니다

🙇🏻‍♀️ 리뷰 가이드 (선택)

  • KeychainKeyStore.swift의 iCloud Keychain 동기화/접근성 설정
  • Project.swift, Secret.swift, SecretProjectLink.swift의 CloudKit 관계 optional 처리
  • LiveSettingsRepository.swift로 SettingsRepository를 Composition Root에서 단일 인스턴스로 관리하는 방식

Summary by CodeRabbit

  • 새로운 기능

    • iCloud 동기화 설정과 계정 상태 확인 기능을 추가했습니다.
    • 온보딩에서 Touch ID 및 iCloud 설정 결과와 오류 안내를 제공합니다.
    • 잠금 해제 실패 시 알림을 표시합니다.
    • 앱 아이콘과 사이드바 로고를 새 디자인으로 업데이트했습니다.
    • iCloud 동기화 데이터 저장과 로컬 전용 저장을 지원합니다.
  • 버그 수정

    • 프로젝트 연결이 없거나 일부 데이터 변환에 실패해도 다른 시크릿을 계속 표시합니다.
    • 시크릿과 프로젝트 관계가 누락된 경우에도 안정적으로 처리합니다.
  • 문서

    • 팀 서명 없이 로컬 환경에서 앱을 빌드하는 절차를 추가했습니다.

@dlguszoo dlguszoo self-assigned this Aug 3, 2026
@dlguszoo dlguszoo added the ✨ Feature 새로운 기능 개발 label Aug 3, 2026
@dlguszoo dlguszoo linked an issue Aug 3, 2026 that may be closed by this pull request
3 tasks
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@doyeonk429, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 52 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f0b94dff-654d-40de-bc90-acb70b234a0b

📥 Commits

Reviewing files that changed from the base of the PR and between 0d7c0fe and 02a0a21.

📒 Files selected for processing (4)
  • Projects/DVData/Sources/RepositoryImpl/Secret/SecretFetchDescriptorBuilder.swift
  • Projects/DVData/Sources/RepositoryImpl/Secret/SecretRepositoryImpl.swift
  • Projects/DVDomain/Sources/Repository/SecretQuery.swift
  • Projects/DVPresentation/Sources/Features/Sidebar/SidebarView.swift

Walkthrough

iCloud 계정 상태 조회와 동기화 설정 저장을 추가했습니다. CloudKit 기반 저장소와 SwiftData 관계를 조정했습니다. 온보딩 및 잠금 해제에 Touch ID, 오류 알림, 앱 아이콘을 연결했습니다. 로컬 서명용 개발 절차도 추가했습니다.

Changes

iCloud 계약과 저장소

Layer / File(s) Summary
iCloud 계약과 설정 저장소
Projects/DVCore/Sources/ICloudContainer.swift, Projects/DVDomain/Sources/Service/Interface/*, Projects/DVDomain/Sources/UseCase/Interface/Settings/*, Projects/DVData/Sources/RepositoryImpl/Settings/*
iCloud 컨테이너 식별자, 계정 상태 프로토콜, 동기화 설정 저장소와 사용 사례를 추가했습니다.
CloudKit 저장소와 관계 모델
Projects/DVData/Sources/Storage/Local/*, Projects/DVData/Sources/ServiceImpl/ICloudSync/*, Projects/DVData/Sources/ServiceImpl/Security/Keychain/*, Projects/DVData/Sources/RepositoryImpl/Secret/*
동기화 및 로컬 전용 SwiftData 구성을 추가했습니다. 관계를 optional로 조정하고 링크 식별자를 저장합니다. Secret 조회 실패는 해당 항목만 제외합니다.
iCloud 앱 구성
Projects/Devault/Resources/Devault.entitlements, Projects/Devault/Project.swift, Projects/Devault/Sources/Composition/*
CloudKit 계정 조회, entitlements, 실행 설정, 공유 설정 저장소를 앱 구성에 연결했습니다.

인증 및 프레젠테이션

Layer / File(s) Summary
온보딩 인증과 iCloud 흐름
Projects/DVPresentation/Sources/Dependencies/OnboardingClient.swift, Projects/Devault/Sources/Composition/Dependencies/OnboardingClient+Live.swift, Projects/DVPresentation/Sources/Features/Onboarding/*, Projects/DVPresentation/Tests/Onboarding/*
Touch ID와 iCloud 계정 상태를 온보딩 흐름에 연결했습니다. 인증 실패와 iCloud 사용 불가 상태를 alert로 표시합니다.
잠금 해제 인증과 오류 표시
Projects/DVPresentation/Sources/Dependencies/LockClient.swift, Projects/Devault/Sources/Composition/Dependencies/LockClient+Live.swift, Projects/DVPresentation/Sources/Features/Lock/*, Projects/DVPresentation/Tests/Lock/*
잠금 해제 인증의 성공 delegate와 실패 alert 처리를 추가했습니다.
앱 아이콘과 공개 API 정리
Projects/DVDesign/Resources/*, Projects/Devault/Resources/AppIcon/*, Projects/DVPresentation/Sources/Features/Sidebar/SidebarView.swift, Projects/DVDesign/Sources/*, Projects/DVCore/Sources/Logger/*
앱 아이콘과 Lottie 리소스를 추가했습니다. 이미지 및 여러 공개 선언의 접근 수준을 명시했습니다.
로컬 서명 개발 지원
.mise.toml, scripts/generate-local, README.md
팀 시트가 없는 환경에서 로컬 서명으로 프로젝트를 생성하는 절차와 스크립트를 추가했습니다.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested labels: ⚙ Setting

Suggested reviewers: yeseonglee

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 온보딩 Touch ID 인증과 iCloud 동기화라는 PR의 주요 변경 사항을 명확하게 요약합니다.
Linked Issues check ✅ Passed Touch ID 활성화, iCloud Sync 활성화, Lock 화면 인증 해제를 모두 구현하여 이슈 #64의 요구 사항을 충족합니다.
Out of Scope Changes check ✅ Passed 앱 아이콘, 서명 설정, CloudKit 호환성 수정은 PR 목표와 직접 관련된 지원 변경 사항이며 명백한 범위 외 변경은 없습니다.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch feature/#64
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/#64

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (6)
Projects/DVData/Sources/RepositoryImpl/Settings/SettingsRepositoryImpl.swift (1)

10-18: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

ubiquitousStore 의존성의 역할을 명확히 하세요.

Line 10-18은 NSUbiquitousKeyValueStore를 주입하고 저장하지만, Line 20-36의 설정 읽기와 쓰기는 모두 UserDefaults를 사용합니다. Projects/DVData/Sources/RepositoryImpl/Settings/UbiquitousStoreKey.swift의 접근자도 주석 처리되어 있습니다.

설정을 기기별로 관리할 의도라면 ubiquitousStore 프로퍼티와 초기화 파라미터를 제거하세요. 기기 간 설정 동기화가 요구사항이라면 KVS 키와 실제 읽기·쓰기 경로를 구현한 뒤 이 의존성을 사용하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVData/Sources/RepositoryImpl/Settings/SettingsRepositoryImpl.swift`
around lines 10 - 18, Clarify the role of the ubiquitousStore dependency in
SettingsRepositoryImpl: either remove the ubiquitousStore property and
initializer parameter if settings are device-local, or implement the KVS keys
and route the corresponding settings reads and writes through ubiquitousStore
when synchronization is required.
Projects/DVPresentation/Sources/Features/Lock/LockFeature.swift (1)

50-53: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

lockClient 의존성을 private로 좁히세요.

lockClientbody reducer 내부에서만 사용됩니다. private로 선언해 노출 범위를 최소화하세요.

♻️ 제안
-  `@Dependency`(\.lockClient) var lockClient
+  `@Dependency`(\.lockClient) private var lockClient

As per path instructions, "클래스 프로퍼티는 원칙적으로 private인지 확인하세요."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVPresentation/Sources/Features/Lock/LockFeature.swift` around lines
50 - 53, LockFeature의 의존성 선언에서 body reducer 내부에서만 사용하는 lockClient 프로퍼티를
private으로 제한하세요.

Source: Path instructions

Projects/DVPresentation/Sources/Features/Onboarding/OnboardingFeature.swift (1)

76-78: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

onboardingClient 의존성을 private로 좁히세요.

onboardingClientbody reducer 내부에서만 사용됩니다. 외부에서 접근할 필요가 없으므로 private로 선언해 캡슐화를 강화하세요.

♻️ 제안
-  `@Dependency`(\.onboardingClient) var onboardingClient
+  `@Dependency`(\.onboardingClient) private var onboardingClient

As per path instructions, "클래스 프로퍼티는 원칙적으로 private인지 확인하세요."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVPresentation/Sources/Features/Onboarding/OnboardingFeature.swift`
around lines 76 - 78, Update the onboardingClient dependency declaration in
OnboardingFeature to be private, since it is only used within the body reducer;
leave its dependency injection behavior unchanged.

Source: Path instructions

Projects/Devault/Sources/Composition/Dependencies/OnboardingClient+Live.swift (2)

3-7: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

import 순서를 알파벳순으로 정렬하세요.

현재 DVCore, DVPresentation, DVDomain, DVData 순서입니다. 알파벳순으로 정렬하면 DVCore, DVData, DVDomain, DVPresentation이 되어야 합니다.

♻️ 정렬 예시
 import ComposableArchitecture
 import DVCore
-import DVPresentation
-import DVDomain
 import DVData
+import DVDomain
+import DVPresentation

As per path instructions, "모듈 임포트가 알파벳 순으로 정렬되어 있는지 확인하세요."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Projects/Devault/Sources/Composition/Dependencies/OnboardingClient`+Live.swift
around lines 3 - 7, OnboardingClient+Live.swift의 모듈 import를 알파벳순으로 정렬하세요.
ComposableArchitecture와 DVCore, DVData, DVDomain, DVPresentation 순서를 유지하도록
import 구문만 변경하세요.

Source: Path instructions


15-16: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

SettingsRepositoryImpl 생성을 제거하고 공유 인스턴스를 사용하세요.

new SettingsRepositoryImpl() 호출이 .standard UserDefaults.default NSUbiquitousKeyValueStore를 그대로 재사용하므로 여기서는 LiveSettingsRepository.shared를 사용해 의존성 계층의 설정 저장소 인스턴스를 단순화하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Projects/Devault/Sources/Composition/Dependencies/OnboardingClient`+Live.swift
around lines 15 - 16, Replace the SettingsRepositoryImpl construction in the
dependency setup with the shared LiveSettingsRepository.shared instance, while
keeping the iCloudSyncSettings use-case initialization wired to the repository
variable.
Projects/Devault/Sources/Composition/Dependencies/LockClient+Live.swift (1)

3-6: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

import 순서를 알파벳순으로 정렬하세요.

현재 DVPresentation, DVDomain, DVData 순서입니다. 알파벳순은 DVData, DVDomain, DVPresentation입니다.

♻️ 정렬 예시
 import ComposableArchitecture
-import DVPresentation
-import DVDomain
 import DVData
+import DVDomain
+import DVPresentation

As per path instructions, "모듈 임포트가 알파벳 순으로 정렬되어 있는지 확인하세요."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/Devault/Sources/Composition/Dependencies/LockClient`+Live.swift
around lines 3 - 6, LockClient+Live.swift의 모듈 import 순서를 알파벳순으로 정렬하세요.
ComposableArchitecture는 기존 위치에 두고, DV 모듈은 DVData, DVDomain, DVPresentation 순서가
되도록 정리하세요.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Projects/Devault/Sources/Composition/LiveStorage.swift`:
- Around line 11-15: 온보딩의 iCloud 동기화 설정 저장 흐름에서 설정 저장 직후 syncingCompleted를 보내지
않도록 수정하세요. LiveStorage.shared가 생성되기 전에 설정을 저장하도록 초기화 순서를 보장하고, 현재 실행 중인
ModelContainer에 변경 사항이 반영되지 않는 경우 성공 상태 대신 앱 재시작이 필요함을 나타내는 상태를 유지하세요.

In
`@Projects/DVData/Sources/RepositoryImpl/Secret/SecretFetchDescriptorBuilder.swift`:
- Around line 61-63: Update the project-link filtering condition in the secret
fetch descriptor builder to compare `link.projectID` with `projectID` instead of
accessing `link.project?.id`, so links whose project relationship is nil are
still matched.

In `@Projects/DVData/Sources/RepositoryImpl/Secret/SecretRepositoryImpl.swift`:
- Line 79: Update fetch(_:) around the localSecrets-to-domain conversion to
avoid silently discarding toDomain() failures via try?. Align list-fetch
behavior with fetch(id:) by propagating corruptedStorage conversion errors, or
explicitly establish and preserve a contract that excludes failed items; ensure
the chosen behavior consistently handles missing payload and secretType/subType
mapping failures.

In
`@Projects/DVData/Sources/ServiceImpl/ICloudSync/CloudKitAccountServiceImpl.swift`:
- Around line 31-33: Update the container.accountStatus() error handling in
CloudKitAccountServiceImpl to map networkUnavailable/networkFailure to a
network-related ICloudAccountStatus and notAuthenticated/badContainer to an
authentication or configuration status, adding the required status categories
and user-facing messages to ICloudAccountStatus. Preserve .couldNotDetermine
only for unrecognized errors.

In
`@Projects/DVData/Sources/ServiceImpl/Security/Keychain/KeychainKeyStore.swift`:
- Line 41: Update getOrCreateSymmetricKey to detect an existing
non-synchronizable Keychain item returned by the kSecAttrSynchronizableAny query
and migrate it to a synchronizable item while preserving the key data, using
SecItemUpdate or re-saving a new item with the synchronizable attribute before
returning it.
- Around line 69-70: Update the keychain accessibility setting in the
KeychainKeyStore configuration to use kSecAttrAccessibleWhenUnlocked instead of
kSecAttrAccessibleAfterFirstUnlock, while preserving kSecAttrSynchronizable:
true separately.

In `@Projects/DVData/Sources/Storage/Local/LocalStorage.swift`:
- Around line 13-17:
Projects/DVData/Sources/Storage/Local/LocalStorage.swift:13-17에서 makeDefault의
동기화 모델 구성을 BackupRecord.filePath가 포함되지 않는 로컬 전용 저장소/모델과 분리하세요.
Projects/DVData/Sources/Storage/Local/Models/BackupRecord.swift:8-15의 filePath는
동기화 스키마에서 제외하고, 동기화 백업 식별이 필요한 경우 로컬 절대 경로 대신 기기 독립 식별자만 사용하도록 관련 모델 참조를 조정하세요.

In `@Projects/DVPresentation/Sources/Features/Sidebar/Model/ProjectItem.swift`:
- Around line 17-21: Replace the forced UUID unwraps in ProjectItem.previews
with safe initialization using guard let, if let, or an appropriate ?? fallback,
so invalid fixture strings cannot terminate previews initialization.

---

Nitpick comments:
In `@Projects/Devault/Sources/Composition/Dependencies/LockClient`+Live.swift:
- Around line 3-6: LockClient+Live.swift의 모듈 import 순서를 알파벳순으로 정렬하세요.
ComposableArchitecture는 기존 위치에 두고, DV 모듈은 DVData, DVDomain, DVPresentation 순서가
되도록 정리하세요.

In
`@Projects/Devault/Sources/Composition/Dependencies/OnboardingClient`+Live.swift:
- Around line 3-7: OnboardingClient+Live.swift의 모듈 import를 알파벳순으로 정렬하세요.
ComposableArchitecture와 DVCore, DVData, DVDomain, DVPresentation 순서를 유지하도록
import 구문만 변경하세요.
- Around line 15-16: Replace the SettingsRepositoryImpl construction in the
dependency setup with the shared LiveSettingsRepository.shared instance, while
keeping the iCloudSyncSettings use-case initialization wired to the repository
variable.

In
`@Projects/DVData/Sources/RepositoryImpl/Settings/SettingsRepositoryImpl.swift`:
- Around line 10-18: Clarify the role of the ubiquitousStore dependency in
SettingsRepositoryImpl: either remove the ubiquitousStore property and
initializer parameter if settings are device-local, or implement the KVS keys
and route the corresponding settings reads and writes through ubiquitousStore
when synchronization is required.

In `@Projects/DVPresentation/Sources/Features/Lock/LockFeature.swift`:
- Around line 50-53: LockFeature의 의존성 선언에서 body reducer 내부에서만 사용하는 lockClient
프로퍼티를 private으로 제한하세요.

In `@Projects/DVPresentation/Sources/Features/Onboarding/OnboardingFeature.swift`:
- Around line 76-78: Update the onboardingClient dependency declaration in
OnboardingFeature to be private, since it is only used within the body reducer;
leave its dependency injection behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0f9a1234-c402-41c0-a52e-2846e1d583e2

📥 Commits

Reviewing files that changed from the base of the PR and between ee3e731 and 92d7e0f.

⛔ Files ignored due to path filters (3)
  • Projects/DVDesign/Resources/Assets.xcassets/Image/appIcon.imageset/appIcon.pdf is excluded by !**/*.pdf
  • Projects/Devault/Resources/AppIcon/Devault_IC.icon/Assets/App Icon_BG.png is excluded by !**/*.png
  • Projects/Devault/Resources/AppIcon/Devault_IC.icon/Assets/App Icon_Lock.png is excluded by !**/*.png
📒 Files selected for processing (56)
  • Projects/DVCore/Sources/ICloudContainer.swift
  • Projects/DVCore/Sources/Logger/DVLogger.swift
  • Projects/DVData/Sources/RepositoryImpl/Secret/SecretFetchDescriptorBuilder.swift
  • Projects/DVData/Sources/RepositoryImpl/Secret/SecretRepositoryImpl.swift
  • Projects/DVData/Sources/RepositoryImpl/Settings/SettingsRepositoryImpl.swift
  • Projects/DVData/Sources/RepositoryImpl/Settings/UbiquitousStoreKey.swift
  • Projects/DVData/Sources/RepositoryImpl/Settings/UserDefaultsKey.swift
  • Projects/DVData/Sources/ServiceImpl/ICloudSync/CloudKitAccountServiceImpl.swift
  • Projects/DVData/Sources/ServiceImpl/Security/Keychain/KeychainKeyStore.swift
  • Projects/DVData/Sources/Storage/Local/LocalStorage.swift
  • Projects/DVData/Sources/Storage/Local/Models/AppAuditLog.swift
  • Projects/DVData/Sources/Storage/Local/Models/BackupRecord.swift
  • Projects/DVData/Sources/Storage/Local/Models/Project.swift
  • Projects/DVData/Sources/Storage/Local/Models/Secret.swift
  • Projects/DVData/Sources/Storage/Local/Models/SecretAuditLog.swift
  • Projects/DVData/Sources/Storage/Local/Models/SecretMetadata.swift
  • Projects/DVData/Sources/Storage/Local/Models/SecretPayload.swift
  • Projects/DVData/Sources/Storage/Local/Models/SecretProjectLink.swift
  • Projects/DVDesign/Resources/Assets.xcassets/Image/Contents.json
  • Projects/DVDesign/Resources/Assets.xcassets/Image/appIcon.imageset/Contents.json
  • Projects/DVDesign/Resources/Lottie/progress.lottie
  • Projects/DVDesign/Sources/Components/DVFloatingPanel.swift
  • Projects/DVDesign/Sources/Foundations/Color/Color+DVColor.swift
  • Projects/DVDesign/Sources/Foundations/Fonts/Font+DVFont.swift
  • Projects/DVDesign/Sources/Foundations/Fonts/View+DVFont.swift
  • Projects/DVDesign/Sources/Foundations/Image/DVImage.swift
  • Projects/DVDesign/Sources/Foundations/Image/Image+DVImage.swift
  • Projects/DVDomain/Sources/Repository/Interface/SettingsRepository.swift
  • Projects/DVDomain/Sources/Repository/SecretQuery.swift
  • Projects/DVDomain/Sources/Service/Interface/ICloudAccountService.swift
  • Projects/DVDomain/Sources/Service/Interface/ICloudAccountStatus.swift
  • Projects/DVDomain/Sources/UseCase/Impl/Settings/ICloudSyncSettingsUseCaseImpl.swift
  • Projects/DVDomain/Sources/UseCase/Interface/Secret/PatchSecretUseCase.swift
  • Projects/DVDomain/Sources/UseCase/Interface/Settings/ICloudSyncSettingsUseCase.swift
  • Projects/DVPresentation/Resources/Localizable.xcstrings
  • Projects/DVPresentation/Sources/Dependencies/LockClient.swift
  • Projects/DVPresentation/Sources/Dependencies/OnboardingClient.swift
  • Projects/DVPresentation/Sources/Dependencies/SecretClient.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/CreateSecretFeature.swift
  • Projects/DVPresentation/Sources/Features/Lock/LockFeature.swift
  • Projects/DVPresentation/Sources/Features/Lock/LockView.swift
  • Projects/DVPresentation/Sources/Features/Onboarding/OnboardingFeature.swift
  • Projects/DVPresentation/Sources/Features/Onboarding/OnboardingView.swift
  • Projects/DVPresentation/Sources/Features/Sidebar/Model/ProjectItem+Mapping.swift
  • Projects/DVPresentation/Sources/Features/Sidebar/Model/ProjectItem.swift
  • Projects/DVPresentation/Sources/Features/Sidebar/SidebarView.swift
  • Projects/DVPresentation/Sources/Support/UserAuthenticationAlert.swift
  • Projects/DVPresentation/Tests/Lock/LockFeatureTests.swift
  • Projects/DVPresentation/Tests/Onboarding/OnboardingFeatureTests.swift
  • Projects/Devault/Project.swift
  • Projects/Devault/Resources/AppIcon/Devault_IC.icon/icon.json
  • Projects/Devault/Resources/Devault.entitlements
  • Projects/Devault/Sources/Composition/Dependencies/LockClient+Live.swift
  • Projects/Devault/Sources/Composition/Dependencies/OnboardingClient+Live.swift
  • Projects/Devault/Sources/Composition/LiveSettingsRepository.swift
  • Projects/Devault/Sources/Composition/LiveStorage.swift
💤 Files with no reviewable changes (1)
  • Projects/DVPresentation/Resources/Localizable.xcstrings

Comment thread Projects/Devault/Sources/Composition/LiveStorage.swift
Comment thread Projects/DVData/Sources/RepositoryImpl/Secret/SecretRepositoryImpl.swift Outdated
/// Keychain에서 tag에 해당하는 raw key Data를 조회한다.
private func loadKeyData(tag: String) throws -> Data? {
var query = keyQuery(tag: tag)
query[kSecAttrSynchronizable] = kSecAttrSynchronizableAny

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 6 \
  'getOrCreateSymmetricKey|loadKeyData|saveOrLoadExistingKeyData|SecItem(Update|Add|CopyMatching|Delete)|kSecAttrSynchronizable' \
  Projects/DVData/Sources/ServiceImpl/Security/Keychain/KeychainKeyStore.swift

Repository: DevaultProject/Devault-macOS

Length of output: 2799


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant KeychainKeyStore implementation without executing repository code.
sed -n '1,120p' Projects/DVData/Sources/ServiceImpl/Security/Keychain/KeychainKeyStore.swift

# Locate all callers of getOrCreateSymmetricKey to assess write path impact.
rg -n -C 4 'getOrCreateSymmetricKey\(' Projects/DVData/Sources/ServiceImpl

Repository: DevaultProject/Devault-macOS

Length of output: 5925


기존 비동기화 Keychain 항목을 동기화 항목으로 마이그레이션하세요.

kSecAttrSynchronizableAny로 조회한 값이 있으면 getOrCreateSymmetricKey가 새 항목을 저장하지 않고 반환합니다. 기존 설치에서는 동기화용 암호 키가 iCloud Keychain에 저장되지 않아 새 기기 복호화는 실패합니다. 비동기화 항목을 감지하면 키 데이터를 보존한 상태로 SecItemUpdate 또는 새로운 동기화 항목으로 다시 저장해야 합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVData/Sources/ServiceImpl/Security/Keychain/KeychainKeyStore.swift`
at line 41, Update getOrCreateSymmetricKey to detect an existing
non-synchronizable Keychain item returned by the kSecAttrSynchronizableAny query
and migrate it to a synchronizable item while preserving the key data, using
SecItemUpdate or re-saving a new item with the synchronizable attribute before
returning it.

Comment thread Projects/DVData/Sources/ServiceImpl/Security/Keychain/KeychainKeyStore.swift Outdated
Comment on lines +13 to +17
public static func makeDefault(iCloudSyncEnabled: Bool) throws -> LocalStorage {
let configuration = ModelConfiguration(
isStoredInMemoryOnly: false,
cloudKitDatabase: iCloudSyncEnabled ? .private(ICloudContainer.identifier) : .none
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# appSchema에 BackupRecord가 포함되는지와 저장소 구성을 확인합니다.
rg -n -C 4 --glob '*.swift' 'appSchema|BackupRecord|ModelConfiguration|filePath' \
  Projects/DVData/Sources Projects/Devault/Sources

Repository: DevaultProject/Devault-macOS

Length of output: 5923


BackupRecord.filePath와 iCloud 동기화 경계를 분리하세요.

Schema.appSchemaBackupRecord를 포함하고 있고 그 스키마에 모두 cloudKit private database를 적용하므로, 로컬 절대 경로가 사용자의 동기화 데이터베이스에 포함될 수 있습니다. filePath는 로컬 전용 저장소/모델로 분리하고, 동기화 백업 식별이 필요하면 기기 독립 식별자만 사용하세요.

📍 Affects 2 files
  • Projects/DVData/Sources/Storage/Local/LocalStorage.swift#L13-L17 (this comment)
  • Projects/DVData/Sources/Storage/Local/Models/BackupRecord.swift#L8-L15
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVData/Sources/Storage/Local/LocalStorage.swift` around lines 13 -
17, Projects/DVData/Sources/Storage/Local/LocalStorage.swift:13-17에서
makeDefault의 동기화 모델 구성을 BackupRecord.filePath가 포함되지 않는 로컬 전용 저장소/모델과 분리하세요.
Projects/DVData/Sources/Storage/Local/Models/BackupRecord.swift:8-15의 filePath는
동기화 스키마에서 제외하고, 동기화 백업 식별이 필요한 경우 로컬 절대 경로 대신 기기 독립 식별자만 사용하도록 관련 모델 참조를 조정하세요.

Comment on lines +17 to 21
extension ProjectItem {
public static let previews: [ProjectItem] = [
.init(id: UUID(uuidString: "00000000-0000-0000-0000-000000000001")!, name: "Backend"),
.init(id: UUID(uuidString: "00000000-0000-0000-0000-000000000002")!, name: "Infrastructure"),
.init(id: UUID(uuidString: "00000000-0000-0000-0000-000000000003")!, name: "Mobile"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

프리뷰 UUID의 강제 언래핑을 제거하세요.

문자열이 현재 유효해도 fixture가 변경되면 ProjectItem.previews 초기화 중 프로세스가 종료될 수 있습니다. guard let으로 유효한 항목만 구성하거나, 요구사항에 맞는 ?? 기본값을 사용하세요.

안전한 초기화 예시
-public static let previews: [ProjectItem] = [
-  .init(id: UUID(uuidString: "00000000-0000-0000-0000-000000000001")!, name: "Backend"),
-  .init(id: UUID(uuidString: "00000000-0000-0000-0000-000000000002")!, name: "Infrastructure"),
-  .init(id: UUID(uuidString: "00000000-0000-0000-0000-000000000003")!, name: "Mobile"),
-]
+public static let previews: [ProjectItem] = {
+  guard
+    let backendID = UUID(uuidString: "00000000-0000-0000-0000-000000000001"),
+    let infrastructureID = UUID(uuidString: "00000000-0000-0000-0000-000000000002"),
+    let mobileID = UUID(uuidString: "00000000-0000-0000-0000-000000000003")
+  else {
+    return []
+  }
+  return [
+    .init(id: backendID, name: "Backend"),
+    .init(id: infrastructureID, name: "Infrastructure"),
+    .init(id: mobileID, name: "Mobile"),
+  ]
+}()

As per path instructions: Swift 파일의 강제 언래핑은 guard let / if let / ?? 대안으로 교체해야 합니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
extension ProjectItem {
public static let previews: [ProjectItem] = [
.init(id: UUID(uuidString: "00000000-0000-0000-0000-000000000001")!, name: "Backend"),
.init(id: UUID(uuidString: "00000000-0000-0000-0000-000000000002")!, name: "Infrastructure"),
.init(id: UUID(uuidString: "00000000-0000-0000-0000-000000000003")!, name: "Mobile"),
extension ProjectItem {
public static let previews: [ProjectItem] = {
guard
let backendID = UUID(uuidString: "00000000-0000-0000-0000-000000000001"),
let infrastructureID = UUID(uuidString: "00000000-0000-0000-0000-000000000002"),
let mobileID = UUID(uuidString: "00000000-0000-0000-0000-000000000003")
else {
return []
}
return [
.init(id: backendID, name: "Backend"),
.init(id: infrastructureID, name: "Infrastructure"),
.init(id: mobileID, name: "Mobile"),
]
}()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVPresentation/Sources/Features/Sidebar/Model/ProjectItem.swift`
around lines 17 - 21, Replace the forced UUID unwraps in ProjectItem.previews
with safe initialization using guard let, if let, or an appropriate ?? fallback,
so invalid fixture strings cannot terminate previews initialization.

Source: Path instructions

@doyeonk429 doyeonk429 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

코드래빗 리뷰 반영하면 크리티컬 이슈는 없네용

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@Projects/DVData/Sources/ServiceImpl/ICloudSync/CloudKitAccountServiceImpl.swift`:
- Around line 31-39: Update the CKError handling in ICloudAccountStatus mapping
so .badContainer and .missingEntitlement become a distinct configuration-error
status instead of .couldNotDetermine, while preserving existing network and
authentication mappings. Add the configuration status to ICloudAccountStatus and
handle it in OnboardingFeature’s switch. Log the relevant CKError code before
returning the configuration status, and update related tests to cover these
cases.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: cd2e8d0e-8afe-4b29-b476-8948e7bc42a0

📥 Commits

Reviewing files that changed from the base of the PR and between 92d7e0f and 732eba7.

📒 Files selected for processing (8)
  • Projects/DVData/Sources/RepositoryImpl/Secret/SecretFetchDescriptorBuilder.swift
  • Projects/DVData/Sources/RepositoryImpl/Secret/SecretRepositoryImpl.swift
  • Projects/DVData/Sources/ServiceImpl/ICloudSync/CloudKitAccountServiceImpl.swift
  • Projects/DVData/Sources/ServiceImpl/Security/Keychain/KeychainKeyStore.swift
  • Projects/DVData/Sources/Storage/Local/LocalStorage.swift
  • Projects/DVData/Sources/Storage/Local/Models/AppSchema.swift
  • Projects/DVDomain/Sources/Service/Interface/ICloudAccountStatus.swift
  • Projects/DVPresentation/Sources/Features/Onboarding/OnboardingFeature.swift
🚧 Files skipped from review as they are similar to previous changes (4)
  • Projects/DVData/Sources/RepositoryImpl/Secret/SecretFetchDescriptorBuilder.swift
  • Projects/DVData/Sources/RepositoryImpl/Secret/SecretRepositoryImpl.swift
  • Projects/DVData/Sources/Storage/Local/LocalStorage.swift
  • Projects/DVData/Sources/ServiceImpl/Security/Keychain/KeychainKeyStore.swift

@dlguszoo
dlguszoo requested a review from doyeonk429 August 8, 2026 05:08
@doyeonk429

Copy link
Copy Markdown
Contributor

@dlguszoo 이 PR merge 이후에 tuist generate → 앱 빌드가 정상 동작하는지 한 번만 확인 부탁드립니다 🙏

배경

앱 타겟에 iCloud entitlement가 들어가면서, Apple Developer 팀(UKY6HK6U6Y)에 초대되지 않은 계정으로는 서명에 실패해 앱 타겟 빌드가 불가능해졌습니다. 그래서 TUIST_LOCAL_SIGNING 스위치를 추가해, 팀 시트가 없는 사람은 entitlement 없이 ad-hoc 서명으로 빌드할 수 있게 했습니다.

generate-local   # tuist generate 대신

iCloud 동기화 기능만 사용할 수 없고(온보딩에서 켜면 안내 알럿), 나머지는 그대로 동작합니다.

확인 부탁드리는 이유

작업 중에 팀 모드(tuist generate)가 CLI에서 아예 빌드되지 않는 것을 발견했습니다.

error: "Devault" requires a provisioning profile. Enable development signing and
select a provisioning profile in the Signing & Capabilities editor.

-allowProvisioningUpdates를 붙여도 동일했습니다. 원인은 Tuist가 macOS 타겟에 CODE_SIGN_IDENTITY = "-"(ad-hoc)를 기본으로 주입하는데 CODE_SIGN_STYLE은 비어 있어서, iCloud entitlement가 요구하는 프로비저닝 프로파일을 잡지 못하는 것이었습니다.

그래서 팀 모드 설정에 아래 두 줄을 명시했습니다.

"CODE_SIGN_STYLE": "Automatic",
"CODE_SIGN_IDENTITY": "Apple Development",

수정 후 자동 서명 경로로 넘어가는 것까지는 확인했지만, 제 계정에는 팀 프로파일이 없어서 끝까지 검증하지 못했습니다.

  • 로컬 모드(generate-local): BUILD SUCCEEDED 확인 완료 — 서명된 앱의 entitlements가 get-task-allow 하나뿐인 것까지 확인
  • 팀 모드(tuist generate): 미검증 ← 이 부분 확인 부탁드립니다

혹시 지금까지 tuist generate 후 Xcode에서 "Automatically manage signing"을 수동으로 체크해서 빌드하고 계셨다면, 이 수정으로 그 수작업도 없어질 것으로 보입니다.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
Projects/Devault/Project.swift (1)

13-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

파일 전용 서명 설정을 private로 제한하세요.

isLocalSigning, teamSigningSettings, localSigningSettings은 이 파일 밖에서 사용되지 않습니다. 세 선언에 private를 추가해 manifest 내부 구현으로 제한하세요.

수정 예시
-let isLocalSigning = Environment.localSigning.getBoolean(default: false)
+private let isLocalSigning = Environment.localSigning.getBoolean(default: false)

-let teamSigningSettings: SettingsDictionary = [
+private let teamSigningSettings: SettingsDictionary = [
...
-let localSigningSettings: SettingsDictionary = [
+private let localSigningSettings: SettingsDictionary = [

As per path instructions, "접근 제어가 가능한 가장 엄격한 수준인지 확인하세요."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/Devault/Project.swift` around lines 13 - 29, Update the file-scoped
declarations isLocalSigning, teamSigningSettings, and localSigningSettings to
use private access control, restricting these manifest-only signing settings to
Projects/Devault/Project.swift.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@Projects/Devault/Project.swift`:
- Around line 13-29: Update the file-scoped declarations isLocalSigning,
teamSigningSettings, and localSigningSettings to use private access control,
restricting these manifest-only signing settings to
Projects/Devault/Project.swift.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 24341999-3c89-4094-9d4d-e08de004ff0f

📥 Commits

Reviewing files that changed from the base of the PR and between 732eba7 and 0d7c0fe.

📒 Files selected for processing (10)
  • .mise.toml
  • Projects/DVData/Sources/ServiceImpl/ICloudSync/CloudKitAccountServiceImpl.swift
  • Projects/DVDomain/Sources/Service/Interface/ICloudAccountStatus.swift
  • Projects/DVPresentation/Resources/Localizable.xcstrings
  • Projects/DVPresentation/Sources/Dependencies/SecretClient.swift
  • Projects/DVPresentation/Sources/Features/Onboarding/OnboardingFeature.swift
  • Projects/DVPresentation/Sources/Features/Sidebar/SidebarView.swift
  • Projects/Devault/Project.swift
  • README.md
  • scripts/generate-local
🚧 Files skipped from review as they are similar to previous changes (4)
  • Projects/DVDomain/Sources/Service/Interface/ICloudAccountStatus.swift
  • Projects/DVPresentation/Sources/Features/Sidebar/SidebarView.swift
  • Projects/DVPresentation/Sources/Features/Onboarding/OnboardingFeature.swift
  • Projects/DVPresentation/Sources/Dependencies/SecretClient.swift

dlguszoo added 17 commits August 8, 2026 19:11
CKContainer.accountStatus() 기반으로 iCloud 로그인 여부를 확인하고,
동기화 사용 여부를 UserDefaults에 저장하는 도메인/데이터 레이어를 추가한다.
Bundle ID를 com.devault.devault에서 com.devault.app으로 정리하고,
iCloud(CloudKit) 컨테이너 capability와 Touch ID Info.plist 설명 문구를 추가한다.
ModelConfiguration의 cloudKitDatabase 기본값(.automatic)은 앱에 iCloud
entitlement가 있으면 CloudKit 미러링을 시도한다. 현재 모델들은 CloudKit이
지원하지 않는 @Attribute(.unique)를 쓰고 있어 그대로 두면 ModelContainer
초기화가 loadIssueModelContainer 에러로 실패하므로, cloudKitDatabase를
.none으로 명시해 당장은 로컬 전용으로 유지한다.
didTapUnlock이 LockClient를 통해 실제 LocalAuthentication 인증을
수행하도록 연결하고, 인증 실패 시 alert로 안내한다.
Enable Touch ID는 OnboardingClient를 통해 실제 인증을 수행한 뒤 다음
단계로 넘어가고, Enable Sync는 iCloud 계정 상태를 확인해 사용 가능하면
동기화 설정을 저장한다. 두 경우 모두 실패 시 alert로 안내한다.
CloudKit은 유니크 제약을 지원하지 않아 CloudKit 미러링을 켜면 스키마
검증에서 실패한다. 유일성은 이미 각 RepositoryImpl이 insert 전 fetch로
직접 보장하고 있어(duplicateID/duplicateProjectLink 등), SwiftData
레벨의 제약은 제거해도 동작에 변화가 없다.
CloudKit은 to-one 관계가 optional이어야 한다. SecretProjectLink.project/
secret, SecretPayload.secret, SecretMetadata.secret을 optional로 바꾸고,
관련 소비처(Project.secrets/Secret.projects 계산 프로퍼티, 프로젝트별
조회 predicate)를 nil-safe하게 고친다. 현재 delete rule이 전부 cascade라
로컬 단일 기기 환경에서는 값이 항상 채워지며, 이 optional은 기기 간
CloudKit 동기화 지연으로 관계 대상이 아직 도착하지 않은 순간만 대비한다.
CloudKit은 non-optional 프로퍼티가 선언부에 기본값을 갖고 있어야 스키마로
인식한다. 커스텀 init이 항상 값을 채워주므로 런타임 동작에는 변화가 없고,
CloudKit 스키마 검증을 통과하기 위한 메타데이터 목적의 변경이다.
schemaVersion 기본값은 실제 SecretContent 타입들이 전부 1을 쓰고 있어 1로
맞춘다.
스키마가 CloudKit 호환 요건(unique 제거, 관계 optional화, 기본값 추가)을
모두 충족했으므로, cloudKitDatabase를 .none에서 등록해둔 iCloud 컨테이너로
명시해 실제 미러링을 켠다.
kSecAttrAccessibleWhenUnlockedThisDeviceOnly 대신 kSecAttrAccessibleWhenUnlocked +
kSecAttrSynchronizable를 사용해 마스터 키가 iCloud Keychain으로 기기 간 동기화되도록
한다. 조회 쿼리에는 kSecAttrSynchronizableAny를 추가해 이전 방식으로 저장된 키도
계속 찾을 수 있게 한다. 아직 실사용자 데이터가 없는 개발 단계라 별도 마이그레이션
로직 없이 단순 전환한다.
isICloudSyncEnabled는 기기가 아닌 사용자 단위 설정이라 iCloud Key-value
storage(NSUbiquitousKeyValueStore)로 옮겨 기기 간 동기화되게 한다.
hasCompletedOnboarding은 기기별로 Touch ID 확인을 다시 거쳐야 하므로 로컬
UserDefaults에 그대로 둔다. entitlements에 ubiquity-kvstore-identifier를
추가한다.
LocalStorage.makeDefault()가 항상 .private(...)로 CloudKit 미러링을
시도해, 온보딩에서 iCloud Sync를 거절해도 데이터가 그대로 올라가고
있었다. iCloudSyncEnabled 파라미터를 받아 false면 cloudKitDatabase를
.none으로 구성하도록 고치고, LiveStorage.shared가 SettingsRepository의
저장된 값을 읽어 넘기도록 한다.
Design모듈에 DVImage, Image extension으로 dv 메서드 추가
코드 가이드에 따라 public extension 대신 개별 선언마다 접근 제어자를 명시한다.
iCloud 컨테이너 식별자를 DVCore의 상수로 일원화한다.
dlguszoo and others added 9 commits August 8, 2026 19:11
Data Protection Keychain과 AfterFirstUnlock 접근성을 명시하고, 추후 CLI 공유를 위한 keychain access group을 등록한다.
isICloudSyncEnabled는 기기별 설정이므로 UserDefaults로 되돌리고, Composition Root에서 SettingsRepository를 공유 인스턴스로 관리한다.
SecretProjectLink에 project/secret id를 스냅샷으로 보관해 관계 동기화 지연 중에도 목록 조회와 프로젝트 연결 갱신이 흔들리지 않게 한다.
CloudKit은 to-many 관계도 optional이어야 하므로 Project/Secret의 관계 타입과 조회 predicate를 수정한다.
Touch ID/시스템 암호 인증 실패 알럿 생성 로직을 하나의 헬퍼로 통합하고, 도달 불가능한 케이스에 assertionFailure를 추가한다.
tuist generate로 스킴이 재생성되어도 유지되도록 CloudKit 디버그 launch argument를 Project.swift에 코드로 명시한다.
- 프로젝트 필터링 시 동기화 지연 중인 관계 대신 스냅샷 projectID로 판별한다.
- Secret 변환 실패 시 목록에서 제외하되 Log.warn으로 남긴다.
- iCloud 상태 조회 실패를 네트워크/인증 오류로 구분해 매핑한다.
- Keychain 마스터 키 접근성을 WhenUnlocked로 제한한다.
- BackupRecord를 별도 ModelConfiguration으로 분리해 로컬 경로가 CloudKit에 동기화되지 않게 한다.
- TUIST_LOCAL_SIGNING=1로 생성하면 iCloud entitlement 없이 ad-hoc 서명하는 로컬 빌드 모드 추가
- 앱 타겟이 프로비저닝 프로파일을 잡지 못해 서명에 실패하던 문제 수정 (CODE_SIGN_STYLE/IDENTITY 명시)
- 저장소 안에서만 scripts/를 PATH에 얹어 generate-local을 접두사 없이 실행하도록 구성
- README에 팀 시트가 없는 경우의 워크스페이스 생성 절차 추가
@doyeonk429
doyeonk429 merged commit 74352f5 into develop Aug 8, 2026
1 check passed
@doyeonk429
doyeonk429 deleted the feature/#64 branch August 8, 2026 10:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 새로운 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: 온보딩 기능 연결

3 participants