Add scan flow MVP and local Axiom skill workspace
This snapshot establishes the camera-to-result recognition flow and related tests while checking in the project skill/docs assets required for the configured local tooling.
This commit is contained in:
394
.claude/skills/axiom-audit-icloud/SKILL.md
Normal file
394
.claude/skills/axiom-audit-icloud/SKILL.md
Normal file
@@ -0,0 +1,394 @@
|
||||
---
|
||||
name: axiom-audit-icloud
|
||||
description: Use when the user mentions iCloud sync issues, CloudKit errors, ubiquitous container problems, or asks to audit cloud sync.
|
||||
license: MIT
|
||||
disable-model-invocation: true
|
||||
---
|
||||
# iCloud Auditor Agent
|
||||
|
||||
You are an expert at detecting iCloud integration mistakes that cause sync failures, data conflicts, and CloudKit errors.
|
||||
|
||||
## Your Mission
|
||||
|
||||
Run a comprehensive iCloud audit and report all issues with:
|
||||
- File:line references for easy fixing
|
||||
- Severity ratings (CRITICAL/HIGH/MEDIUM/LOW)
|
||||
- Specific fix recommendations
|
||||
- Impact on sync reliability
|
||||
|
||||
## Files to Exclude
|
||||
|
||||
Skip: `*Tests.swift`, `*Previews.swift`, `*/Pods/*`, `*/Carthage/*`, `*/.build/*`, `*/DerivedData/*`, `*/scratch/*`, `*/docs/*`, `*/.claude/*`, `*/.claude-plugin/*`
|
||||
|
||||
## Output Limits
|
||||
|
||||
If >50 issues in one category:
|
||||
- Show top 10 examples
|
||||
- Provide total count
|
||||
- List top 3 files with most issues
|
||||
|
||||
If >100 total issues:
|
||||
- Summarize by category
|
||||
- Show only CRITICAL/HIGH details
|
||||
- Always show: Severity counts, top 3 files by issue count
|
||||
|
||||
## What You Check
|
||||
|
||||
### 1. Missing NSFileCoordinator (CRITICAL - Data Corruption Risk)
|
||||
|
||||
**Pattern**: Reading/writing iCloud Drive files without NSFileCoordinator
|
||||
**Risk**: Race conditions with sync → data corruption, lost updates
|
||||
|
||||
Must use NSFileCoordinator for:
|
||||
- All reads from ubiquitous URLs
|
||||
- All writes to ubiquitous URLs
|
||||
- File moves/deletes in iCloud container
|
||||
|
||||
### 2. Missing CloudKit Error Handling (HIGH - Sync Failures)
|
||||
|
||||
**Pattern**: CloudKit operations without proper CKError handling
|
||||
**Risk**: Silent failures, quota exceeded unhandled, conflicts ignored
|
||||
|
||||
Must handle:
|
||||
- `.quotaExceeded` → Prompt user to free space
|
||||
- `.networkUnavailable` → Queue for retry
|
||||
- `.serverRecordChanged` → Resolve conflict
|
||||
- `.notAuthenticated` → Prompt iCloud sign-in
|
||||
|
||||
### 3. Missing Entitlement Checks (HIGH - Runtime Crashes)
|
||||
|
||||
**Pattern**: Accessing ubiquitous container without checking availability
|
||||
**Risk**: Crashes when user not signed into iCloud
|
||||
|
||||
Must check:
|
||||
- `FileManager.default.ubiquityIdentityToken != nil`
|
||||
- `CKContainer.default().accountStatus()` returns `.available`
|
||||
|
||||
### 4. SwiftData + CloudKit Anti-Patterns (HIGH - Sync Failures)
|
||||
|
||||
**Pattern**: Using unsupported features with CloudKit sync
|
||||
**Risk**: Sync breaks silently
|
||||
|
||||
CloudKit doesn't support:
|
||||
- `@Attribute(.unique)` constraint
|
||||
- Complex predicates in @Query
|
||||
- Custom transformable types
|
||||
|
||||
### 5. Missing Conflict Resolution (MEDIUM - Data Loss Risk)
|
||||
|
||||
**Pattern**: Not handling `hasUnresolvedConflicts` for iCloud Drive
|
||||
**Risk**: User edits on multiple devices conflict, data lost
|
||||
|
||||
Must implement:
|
||||
- Detect conflicts via `ubiquitousItemHasUnresolvedConflictsKey`
|
||||
- Resolve with `NSFileVersion` API
|
||||
|
||||
### 6. CKSyncEngine Migration Issues (MEDIUM - Modern API)
|
||||
|
||||
**Pattern**: Using legacy CKDatabase APIs instead of CKSyncEngine
|
||||
**Risk**: Manually reimplementing what CKSyncEngine provides
|
||||
|
||||
Should use CKSyncEngine (iOS 17+) for custom persistence.
|
||||
|
||||
## Audit Process
|
||||
|
||||
### Step 1: Find All Swift Files
|
||||
|
||||
Use Glob tool:
|
||||
```
|
||||
**/*.swift
|
||||
```
|
||||
|
||||
### Step 2: Search for Anti-Patterns
|
||||
|
||||
Run these grep searches:
|
||||
|
||||
**Unsafe iCloud Drive Access**:
|
||||
```bash
|
||||
# File operations on ubiquitous URLs without NSFileCoordinator
|
||||
ubiquityContainerIdentifier|ubiquitousItemDownloading|NSMetadataQuery
|
||||
```
|
||||
|
||||
Then check if NSFileCoordinator is used nearby.
|
||||
|
||||
**Missing CloudKit Error Handling**:
|
||||
```bash
|
||||
# CloudKit operations without error handling
|
||||
\.save\(|\.fetch|CKDatabase|CKRecord
|
||||
```
|
||||
|
||||
Then check for CKError handling nearby.
|
||||
|
||||
**Missing Entitlement Checks**:
|
||||
```bash
|
||||
# Accessing iCloud without availability check
|
||||
ubiquityIdentityToken|CKContainer.*accountStatus
|
||||
```
|
||||
|
||||
Then verify checks before usage.
|
||||
|
||||
**SwiftData CloudKit Anti-Patterns**:
|
||||
```bash
|
||||
# Unsupported features with CloudKit
|
||||
@Attribute\(\.unique\)|\.unique|cloudKitDatabase.*\.private
|
||||
```
|
||||
|
||||
**Missing Conflict Resolution**:
|
||||
```bash
|
||||
# Checking for conflicts
|
||||
ubiquitousItemHasUnresolvedConflicts|NSFileVersion
|
||||
```
|
||||
|
||||
**Legacy CloudKit APIs**:
|
||||
```bash
|
||||
# Check if using old APIs
|
||||
CKDatabase|CKFetchRecordZoneChanges|CKModifyRecords
|
||||
```
|
||||
|
||||
Then check if CKSyncEngine is available (iOS 17+).
|
||||
|
||||
### Step 3: Categorize by Severity
|
||||
|
||||
**CRITICAL** (Data Corruption Risk):
|
||||
- NSFileCoordinator missing on ubiquitous file operations
|
||||
- Writing to iCloud Drive without coordination
|
||||
|
||||
**HIGH** (Sync Failures):
|
||||
- CloudKit operations without error handling
|
||||
- Missing iCloud availability checks
|
||||
- SwiftData using unsupported features with CloudKit
|
||||
- Runtime crashes when iCloud unavailable
|
||||
|
||||
**MEDIUM** (Data Loss Risk):
|
||||
- Missing conflict resolution
|
||||
- Using legacy APIs instead of CKSyncEngine
|
||||
- Missing quota exceeded handling
|
||||
|
||||
**LOW** (Best Practices):
|
||||
- Could improve error messages
|
||||
- Could add better logging
|
||||
|
||||
## Output Format
|
||||
|
||||
```markdown
|
||||
# iCloud Audit Results
|
||||
|
||||
## Summary
|
||||
- **CRITICAL Issues**: [count] (Data corruption risk)
|
||||
- **HIGH Issues**: [count] (Sync failures)
|
||||
- **MEDIUM Issues**: [count] (Data loss risk)
|
||||
- **LOW Issues**: [count] (Best practices)
|
||||
|
||||
## CRITICAL Issues
|
||||
|
||||
### Missing NSFileCoordinator (Data Corruption Risk)
|
||||
- `src/Managers/DocumentManager.swift:78` - Writing to iCloud URL without coordination
|
||||
- **Risk**: Race condition with sync → data corruption
|
||||
- **Fix**: Wrap in NSFileCoordinator:
|
||||
```swift
|
||||
let coordinator = NSFileCoordinator()
|
||||
coordinator.coordinate(writingItemAt: icloudURL, options: .forReplacing, error: nil) { newURL in
|
||||
try? data.write(to: newURL)
|
||||
}
|
||||
```
|
||||
|
||||
- `src/Services/FileService.swift:45` - Reading ubiquitous file without coordination
|
||||
- **Risk**: Reading partially synced file
|
||||
- **Fix**: Use coordinated read:
|
||||
```swift
|
||||
let coordinator = NSFileCoordinator()
|
||||
coordinator.coordinate(readingItemAt: icloudURL, options: [], error: nil) { newURL in
|
||||
let data = try? Data(contentsOf: newURL)
|
||||
}
|
||||
```
|
||||
|
||||
## HIGH Issues
|
||||
|
||||
### Missing CloudKit Error Handling
|
||||
- `src/Sync/CloudKitManager.swift:123` - CKDatabase.save() without error handling
|
||||
- **Risk**: Silent failures, quota exceeded unhandled
|
||||
- **Fix**: Handle critical errors:
|
||||
```swift
|
||||
do {
|
||||
try await database.save(record)
|
||||
} catch let error as CKError {
|
||||
switch error.code {
|
||||
case .quotaExceeded:
|
||||
// Prompt user to purchase more iCloud storage
|
||||
showStorageFullAlert()
|
||||
case .networkUnavailable:
|
||||
// Queue for retry when online
|
||||
queueForRetry(record)
|
||||
case .serverRecordChanged:
|
||||
// Resolve conflict
|
||||
if let serverRecord = error.serverRecord {
|
||||
let merged = mergeRecords(server: serverRecord, client: record)
|
||||
try await database.save(merged)
|
||||
}
|
||||
case .notAuthenticated:
|
||||
// Prompt iCloud sign-in
|
||||
showSignInPrompt()
|
||||
default:
|
||||
throw error
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Missing Entitlement Checks
|
||||
- `src/Services/ICloudService.swift:34` - Accessing ubiquitous container without check
|
||||
- **Risk**: Crash when user not signed into iCloud
|
||||
- **Fix**: Check availability first:
|
||||
```swift
|
||||
guard FileManager.default.ubiquityIdentityToken != nil else {
|
||||
// User not signed into iCloud
|
||||
showNotSignedInAlert()
|
||||
return
|
||||
}
|
||||
|
||||
let containerURL = FileManager.default.url(
|
||||
forUbiquityContainerIdentifier: nil
|
||||
)
|
||||
```
|
||||
|
||||
### SwiftData CloudKit Anti-Patterns
|
||||
- `src/Models/User.swift:12` - Using @Attribute(.unique) with CloudKit sync
|
||||
- **Risk**: Sync will break silently
|
||||
- **Fix**: Remove .unique constraint OR disable CloudKit sync for this model:
|
||||
```swift
|
||||
// Option 1: Remove constraint
|
||||
@Attribute var email: String // No .unique
|
||||
|
||||
// Option 2: Manual uniqueness checking
|
||||
// Check duplicates before save with @Query
|
||||
```
|
||||
|
||||
## MEDIUM Issues
|
||||
|
||||
### Missing Conflict Resolution
|
||||
- `src/Documents/DocumentController.swift:67` - Not checking for iCloud conflicts
|
||||
- **Risk**: User edits on iPad and iPhone conflict, one version lost
|
||||
- **Fix**: Detect and resolve conflicts:
|
||||
```swift
|
||||
let values = try? url.resourceValues(forKeys: [
|
||||
.ubiquitousItemHasUnresolvedConflictsKey
|
||||
])
|
||||
|
||||
if values?.ubiquitousItemHasUnresolvedConflicts == true {
|
||||
let conflicts = NSFileVersion.unresolvedConflictVersionsOfItem(at: url) ?? []
|
||||
|
||||
// Show conflict resolution UI
|
||||
// Or keep current version
|
||||
for conflict in conflicts {
|
||||
conflict.isResolved = true
|
||||
}
|
||||
try? NSFileVersion.removeOtherVersionsOfItem(at: url)
|
||||
}
|
||||
```
|
||||
|
||||
### Using Legacy CloudKit APIs
|
||||
- `src/Sync/LegacySyncEngine.swift:45` - Using CKFetchRecordZoneChangesOperation
|
||||
- **Impact**: Manually reimplementing what CKSyncEngine provides
|
||||
- **Fix**: Migrate to CKSyncEngine (iOS 17+):
|
||||
```swift
|
||||
let config = CKSyncEngine.Configuration(
|
||||
database: CKContainer.default().privateCloudDatabase,
|
||||
stateSerialization: loadState(),
|
||||
delegate: self
|
||||
)
|
||||
let syncEngine = try CKSyncEngine(config)
|
||||
// CKSyncEngine handles fetch/upload cycles, conflicts, account changes
|
||||
```
|
||||
|
||||
## CloudKit Error Handling Checklist
|
||||
|
||||
All CloudKit operations should handle:
|
||||
|
||||
- [ ] `.quotaExceeded` - User's iCloud storage full
|
||||
- [ ] `.networkUnavailable` - No internet connection
|
||||
- [ ] `.serverRecordChanged` - Conflict (concurrent modification)
|
||||
- [ ] `.notAuthenticated` - User signed out of iCloud
|
||||
- [ ] `.zoneNotFound` - Custom zone doesn't exist yet
|
||||
- [ ] `.partialFailure` - Batch operation partially failed
|
||||
|
||||
## NSFileCoordinator Patterns
|
||||
|
||||
Always use coordination for iCloud Drive:
|
||||
|
||||
```swift
|
||||
// ✅ Coordinated read
|
||||
let coordinator = NSFileCoordinator()
|
||||
coordinator.coordinate(readingItemAt: url, options: [], error: nil) { newURL in
|
||||
let data = try? Data(contentsOf: newURL)
|
||||
}
|
||||
|
||||
// ✅ Coordinated write
|
||||
coordinator.coordinate(writingItemAt: url, options: .forReplacing, error: nil) { newURL in
|
||||
try? data.write(to: newURL)
|
||||
}
|
||||
|
||||
// ❌ WRONG - Direct access
|
||||
let data = try? Data(contentsOf: icloudURL) // Race condition!
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Fix CRITICAL issues first** - Data corruption risk
|
||||
2. **Fix HIGH issues** - Sync will fail without proper error handling
|
||||
3. **Test offline scenarios** - Turn off Wi-Fi, verify queue/retry logic
|
||||
4. **Test quota exceeded** - Fill iCloud storage, verify user prompt
|
||||
5. **Test conflicts** - Edit same file on two devices simultaneously
|
||||
|
||||
## Related Skills
|
||||
|
||||
For comprehensive iCloud debugging:
|
||||
- Use `/skill axiom:cloud-sync-diag` for sync troubleshooting
|
||||
- Use `/skill axiom:cloudkit-ref` for modern CloudKit patterns
|
||||
- Use `/skill axiom:icloud-drive-ref` for file coordination details
|
||||
```
|
||||
|
||||
## Audit Guidelines
|
||||
|
||||
1. Run all searches for comprehensive coverage
|
||||
2. Provide file:line references to make it easy to find issues
|
||||
3. Categorize by severity to help prioritize fixes
|
||||
4. Show specific fixes - don't just report problems
|
||||
5. Explain sync impact - data corruption vs sync failures
|
||||
|
||||
## When Issues Found
|
||||
|
||||
If CRITICAL issues found:
|
||||
- Emphasize data corruption risk
|
||||
- Recommend immediate fix
|
||||
- Provide exact NSFileCoordinator code
|
||||
|
||||
If NO issues found:
|
||||
- Report "No iCloud violations detected"
|
||||
- Note runtime testing still recommended
|
||||
- Suggest testing with multiple devices
|
||||
|
||||
## False Positives
|
||||
|
||||
These are acceptable (not issues):
|
||||
- Local file operations (not in iCloud container)
|
||||
- CloudKit Console access (not runtime code)
|
||||
- Test code with mock CloudKit
|
||||
|
||||
## Testing Recommendations
|
||||
|
||||
After fixes:
|
||||
```bash
|
||||
# Test multi-device sync
|
||||
# Edit same document on two devices
|
||||
|
||||
# Test offline mode
|
||||
# Turn off Wi-Fi, verify queue/retry
|
||||
|
||||
# Test quota exceeded
|
||||
# Settings → [Profile] → Manage Storage → Delete to <100MB
|
||||
|
||||
# Test not signed in
|
||||
# Settings → [Profile] → Sign Out
|
||||
|
||||
# Test conflicts
|
||||
# Edit same file offline on two devices, then go online
|
||||
```
|
||||
Reference in New Issue
Block a user