---
name: axiom-cloudkit-ref
description: "Use when implementing 'CloudKit sync', 'CKSyncEngine', 'CKRecord', 'CKDatabase', 'SwiftData CloudKit', 'shared database', 'public database', 'CloudKit zones', 'conflict resolution' - comprehensive CloudKit database APIs and modern sync patterns reference"
---


# CloudKit Reference

**Purpose**: Comprehensive CloudKit reference for database-based iCloud storage and sync
**Context**: Modern CloudKit sync via CKSyncEngine (WWDC 2023) or SwiftData integration

## When to Use This Skill

Use this skill when:
- Implementing structured data sync to iCloud
- Choosing between SwiftData+CloudKit, CKSyncEngine, or raw CloudKit APIs
- Setting up public/private/shared databases
- Implementing conflict resolution
- Debugging CloudKit sync issues
- Monitoring CloudKit performance

**NOT for**: Simple file sync (use `skills/icloud-drive-ref.md` instead)

## Overview

**CloudKit is for STRUCTURED DATA sync** (records with relationships), not simple file sync.

Three modern approaches:
1. **SwiftData + CloudKit** (Easiest, iOS 17+)
2. **CKSyncEngine** (Custom persistence, iOS 17+, WWDC 2023)
3. **Raw CloudKit APIs** (Maximum control, more complexity)

---

## Approach 1: SwiftData + CloudKit (Recommended)

**When to use**: iOS 17+ apps with SwiftData models

**Limitations**:
- Private database only (no public/shared)
- Automatic sync (less control)
- `@Attribute(.unique)` not supported with CloudKit sync — remove if using CloudKit
- SwiftData constraints apply

```swift
// ✅ CORRECT: SwiftData with CloudKit sync
import SwiftData

@Model
class Task {
    var title: String
    var isCompleted: Bool
    var dueDate: Date

    init(title: String, isCompleted: Bool = false, dueDate: Date) {
        self.title = title
        self.isCompleted = isCompleted
        self.dueDate = dueDate
    }
}

// Configure CloudKit container
let container = try ModelContainer(
    for: Task.self,
    configurations: ModelConfiguration(
        cloudKitDatabase: .private("iCloud.com.example.app")
    )
)

// That's it! Sync happens automatically
```

**Entitlements required**:
- iCloud capability
- CloudKit container

**Use `skills/swiftdata.md` skill for SwiftData details**

---

## Approach 2: CKSyncEngine (Modern, WWDC 2023)

**When to use**: Custom persistence (SQLite, GRDB, JSON) with cloud sync

**Advantages over raw CloudKit**:
- Manages fetch/upload cycles automatically
- Retries transient failures (network, auth, rate-limit) automatically; conflicts are yours to merge
- Manages account changes
- Recommended over manual CKDatabase operations

```swift
// ✅ CORRECT: CKSyncEngine setup
import CloudKit

final class SyncManager {
    // The engine takes its delegate at construction, so it can only be built
    // once `self` exists — written once in init, read-only afterwards
    nonisolated(unsafe) private(set) var syncEngine: CKSyncEngine!

    init() {
        let config = CKSyncEngine.Configuration(
            database: CKContainer.default().privateCloudDatabase,
            stateSerialization: loadSyncState(),
            delegate: self
        )

        syncEngine = CKSyncEngine(config)
    }

    // Implement delegate methods
}

extension SyncManager: CKSyncEngineDelegate {
    // Handle events
    func handleEvent(_ event: CKSyncEngine.Event, syncEngine: CKSyncEngine) async {
        switch event {
        case .stateUpdate(let stateUpdate):
            saveSyncState(stateUpdate.stateSerialization)

        case .accountChange(let change):
            handleAccountChange(change)

        case .fetchedDatabaseChanges(let changes):
            applyDatabaseChanges(changes)

        case .fetchedRecordZoneChanges(let changes):
            applyRecordChanges(changes)

        case .sentDatabaseChanges(let changes):
            // Zone-level send failures — failedZoneSaves / failedZoneDeletes
            handleSentDatabaseChanges(changes)

        case .sentRecordZoneChanges(let changes):
            handleSentChanges(changes)

        case .willFetchRecordZoneChanges, .didFetchRecordZoneChanges,
             .willFetchChanges, .didFetchChanges,
             .willSendChanges, .didSendChanges:
            // Lifecycle notifications — nothing to apply
            break

        @unknown default:
            break
        }
    }

    // Next batch of changes to send
    func nextRecordZoneChangeBatch(
        _ context: CKSyncEngine.SendChangesContext,
        syncEngine: CKSyncEngine
    ) async -> CKSyncEngine.RecordZoneChangeBatch? {
        // Only the changes in this context's scope — the failable init walks them
        // in order and stops at the server's 250-record-per-batch limit
        let changes = syncEngine.state.pendingRecordZoneChanges
            .filter { context.options.scope.contains($0) }
        return await CKSyncEngine.RecordZoneChangeBatch(pendingChanges: changes) { recordID in
            await self.record(for: recordID)  // Local record to upload, or nil to skip
        }
    }
}
```

**Key concepts**:
- **State serialization**: Persist sync state between app launches
- **Events**: Delegate receives events for changes
- **Batches**: You provide pending changes, engine uploads them
- **Transient-error retries**: Engine retries network/auth/rate-limit failures; `serverRecordChanged` is yours to merge

---

## Approach 3: Raw CloudKit APIs (Legacy)

**When to use**: Only if CKSyncEngine doesn't fit (rare)

**Core types**:
- `CKContainer` — Entry point
- `CKDatabase` — Public/private/shared scope
- `CKRecord` — Individual data record
- `CKRecordZone` — Logical grouping
- `CKAsset` — Binary file storage

### Basic Operations

```swift
// ✅ Container and database
let container = CKContainer.default()
let privateDatabase = container.privateCloudDatabase
let publicDatabase = container.publicCloudDatabase

// ✅ Create record
let record = CKRecord(recordType: "Task")
record["title"] = "Buy groceries"
record["isCompleted"] = false
record["dueDate"] = Date()

// ✅ Save record
try await privateDatabase.save(record)

// ✅ Fetch record
let recordID = CKRecord.ID(recordName: "task-123")
let fetchedRecord = try await privateDatabase.record(for: recordID)

// ✅ Query records
let predicate = NSPredicate(format: "isCompleted == NO")
let query = CKQuery(recordType: "Task", predicate: predicate)
let (matchResults, _) = try await privateDatabase.records(matching: query)

for result in matchResults {
    if case .success(let record) = result.1 {
        print("Task: \(record["title"] as? String ?? "")")
    }
}

// ✅ Delete record
try await privateDatabase.deleteRecord(withID: recordID)
```

### Update Record

```swift
// ✅ Fetch-then-modify-then-save (prevents serverRecordChanged errors)
let record = try await privateDatabase.record(for: recordID)
record["title"] = "Updated title"
record["isCompleted"] = true
try await privateDatabase.save(record)

// ✅ Batch modify (save + delete in one operation)
let (saveResults, deleteResults) = try await privateDatabase.modifyRecords(
    saving: [updatedRecord1, updatedRecord2],
    deleting: [deletedID]
)
for (recordID, result) in saveResults {
    if case .failure(let error) = result {
        print("Failed to save \(recordID): \(error)")
    }
}
for (recordID, result) in deleteResults {
    if case .failure(let error) = result {
        print("Failed to delete \(recordID): \(error)")
    }
}
```

### Conflict Resolution

```swift
// ✅ Handle conflicts with savePolicy
let operation = CKModifyRecordsOperation(
    recordsToSave: [record],
    recordIDsToDelete: nil
)

// Save only if server version unchanged
operation.savePolicy = .ifServerRecordUnchanged

// Alternatives — pick one instead of the line above:
// operation.savePolicy = .allKeys      // Overwrite the server's values for every key
// operation.savePolicy = .changedKeys  // Save only the fields you changed; no change-tag comparison

operation.modifyRecordsResultBlock = { result in
    switch result {
    case .success:
        print("Saved")
    case .failure(let error as CKError):
        if error.code == .serverRecordChanged {
            // Conflict - merge manually
            let serverRecord = error.serverRecord
            let clientRecord = error.clientRecord
            let merged = mergeRecords(server: serverRecord, client: clientRecord)
            // Retry with merged record
        }
    case .failure(let error):
        print("Save failed: \(error)")
    }
}

privateDatabase.add(operation)
```

---

## Database Scopes

| Scope | Accessibility | SwiftData Support | Use Case |
|-------|---------------|-------------------|----------|
| **Private** | User only | ✅ Yes | Personal user data |
| **Public** | All users | ❌ No | Shared/public content |
| **Shared** | Invited users | ❌ No | Collaboration |

### Private Database

```swift
// ✅ Private database (most common)
let privateDB = CKContainer.default().privateCloudDatabase

// User must be signed into iCloud
// Data syncs across user's devices
// Not visible to other users
```

### Public Database

```swift
// ✅ Public database (for shared content)
let publicDB = CKContainer.default().publicCloudDatabase

// Accessible to all app users
// Even unauthenticated users can read
// Writes require authentication
// Use for: Leaderboards, public content, discovery
```

### Shared Database

```swift
// ✅ Shared database (collaboration)
let sharedDB = CKContainer.default().sharedCloudDatabase

// For CKShare-based collaboration
// Users invited to specific record zones
// Use for: Shared documents, team data
```

---

## CloudKit Assets (Files)

```swift
// ✅ Store files as CKAsset
let imageURL = saveImageToTempFile(image)  // Must be file URL
let asset = CKAsset(fileURL: imageURL)

let record = CKRecord(recordType: "Photo")
record["image"] = asset
record["caption"] = "Sunset"

try await privateDatabase.save(record)

// ✅ Retrieve asset
let fetchedRecord = try await privateDatabase.record(for: recordID)
if let asset = fetchedRecord["image"] as? CKAsset,
   let fileURL = asset.fileURL {
    let imageData = try Data(contentsOf: fileURL)
    let image = UIImage(data: imageData)
}
```

**Important**: A **locally-created** CKAsset requires a **file URL**, not Data — write data to a temp file first. The one exception is an asset obtained via the server-side import path below: its `fileURL` is `nil` (read it back through the record, never a local URL).

### Server-side asset copy (Photos → CloudKit) `OS27`

Copy a Photos asset straight into CloudKit — the **server** performs the copy on save, with no local download/re-upload round-trip (potentially into a different container).

```swift
// Producer — Photos; all Apple platforms at 27 except watchOS (no producer there):
@available(anyAppleOS 27, *)
@available(watchOS, unavailable)
func exportedID(for resource: PHAssetResource) async throws -> CKAsset.ExportedAssetID {
    try await PHAssetResourceManager.default().exportedAssetID(for: resource)
}

// Consumer — CKAsset(importing:) is available on all platforms, watchOS 27 included:
let id = try await exportedID(for: resource)
let asset = CKAsset(importing: id)           // fileURL is nil on this asset
record["image"] = asset
try await privateDatabase.save(record)       // server copies the asset
```

Data-safety edges (from the SDK doc comments — absent from the web docs):

| Edge | Consequence |
|------|-------------|
| `ExportedAssetID` is `Codable` but device-bound + expires in days | The conformance invites a serialize-and-ship mistake that silently breaks. Do NOT persist or transmit it. |
| Imported asset's `fileURL` is `nil` | Read it back via the fetched record, not a local URL. |
| Source asset gone from server | `CKError.unknownItem` (11) |
| ID invalid or expired | `CKError.assetNotAvailable` (35) |
| Reader OS floor: needs iOS 17 / macOS 14 / tvOS 17 / watchOS 10 / visionOS 1 to download | A writer on this path can mint an asset an older peer CANNOT fetch — a silent cross-version data-availability hazard in a shared or synced container. |
| Producer needs network + a cloud-enabled library | Local-only library → `PHPhotosError.requestNotSupportedForAsset` (3306); honors cancellation → `CancellationError`. |

**Availability trap**: `CKAsset(importing:)` is callable on watchOS 27, but its only input producer (`exportedAssetID(for:)`) is watchOS-**unavailable** — there is no supported way to obtain an `ExportedAssetID` on watchOS. The producer side (`exportedAssetID(for:)`, `PHAssetResource.dataSize`) is documented in axiom-media `photo-library-ref`.

---

## CloudKit Console (Monitoring - WWDC 2024)

### Developer Notifications

Set up alerts for:
- Schema changes
- Quota exceeded
- High error rates
- Custom thresholds

### Telemetry

Monitor:
- Request count
- Error rate
- Latency (p50, p95, p99)
- Bandwidth usage

### Logs

View:
- Individual requests
- Error details
- Performance bottlenecks

**Access**: https://icloud.developer.apple.com/dashboard

---

## Common Patterns

### Pattern 1: Initial Sync

```swift
// ✅ Fetch all records on first launch
func performInitialSync() async throws {
    let predicate = NSPredicate(value: true)  // All records
    let query = CKQuery(recordType: "Task", predicate: predicate)

    let (results, _) = try await privateDatabase.records(matching: query)

    for result in results {
        if case .success(let record) = result.1 {
            saveToLocalDatabase(record)
        }
    }
}
```

### Pattern 2: Incremental Sync

```swift
// ✅ Use CKServerChangeToken for incremental fetches
func fetchChanges(since token: CKServerChangeToken?) async throws {
    let zoneID = CKRecordZone.ID(zoneName: "Tasks")

    let config = CKFetchRecordZoneChangesOperation.ZoneConfiguration(
        previousServerChangeToken: token
    )

    let operation = CKFetchRecordZoneChangesOperation(
        recordZoneIDs: [zoneID],
        configurationsByRecordZoneID: [zoneID: config]
    )

    operation.recordWasChangedBlock = { recordID, result in
        if case .success(let record) = result {
            updateLocalDatabase(with: record)
        }
    }

    operation.recordWithIDWasDeletedBlock = { recordID, _ in
        deleteFromLocalDatabase(recordID)
    }

    operation.recordZoneFetchResultBlock = { zoneID, result in
        if case .success(let (token, _, _)) = result {
            saveChangeToken(token)  // For next fetch
        }
    }

    // add(_:) only enqueues the operation — await its result before returning
    try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
        operation.fetchRecordZoneChangesResultBlock = { result in
            continuation.resume(with: result)
        }
        privateDatabase.add(operation)
    }
}
```

---

## Entitlements

Required entitlements in Xcode:

```xml
<!-- iCloud capability -->
<key>com.apple.developer.icloud-services</key>
<array>
    <string>CloudKit</string>
</array>

<!-- CloudKit container -->
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
    <string>iCloud.com.example.app</string>
</array>
```

**Setup**:
1. Xcode → Target → Signing & Capabilities
2. "+ Capability" → iCloud
3. Check "CloudKit"
4. Select or create container

---

## Subscriptions (Push Notifications)

### Database Subscription

```swift
// ✅ Get notified of ANY change in private database
let subscription = CKDatabaseSubscription(subscriptionID: "all-changes")

let notificationInfo = CKSubscription.NotificationInfo()
notificationInfo.shouldSendContentAvailable = true  // Silent push
subscription.notificationInfo = notificationInfo

try await privateDatabase.save(subscription)
```

### Query Subscription

```swift
// ✅ Get notified when records matching a query change
let predicate = NSPredicate(format: "priority > 3")
let subscription = CKQuerySubscription(
    recordType: "Task",
    predicate: predicate,
    subscriptionID: "high-priority-tasks",
    options: [.firesOnRecordCreation, .firesOnRecordUpdate, .firesOnRecordDeletion]
)

let notificationInfo = CKSubscription.NotificationInfo()
notificationInfo.alertBody = "High priority task changed"
notificationInfo.shouldBadge = true
subscription.notificationInfo = notificationInfo

try await privateDatabase.save(subscription)
```

### Zone Subscription

```swift
// ✅ Get notified of any change in a specific zone
let zoneID = CKRecordZone.ID(zoneName: "Tasks")
let subscription = CKRecordZoneSubscription(
    zoneID: zoneID,
    subscriptionID: "tasks-zone"
)

let notificationInfo = CKSubscription.NotificationInfo()
notificationInfo.shouldSendContentAvailable = true
subscription.notificationInfo = notificationInfo

try await privateDatabase.save(subscription)
```

### Handling Push Notifications

```swift
// In AppDelegate
func application(_ application: UIApplication,
                 didReceiveRemoteNotification userInfo: [AnyHashable: Any]) async -> UIBackgroundFetchResult {
    guard let notification = CKNotification(fromRemoteNotificationDictionary: userInfo) else {
        return .noData
    }

    if notification.subscriptionID == "all-changes" {
        try? await fetchChanges(since: savedChangeToken)
        return .newData
    }
    return .noData
}
```

A public-database query subscription with `shouldSendContentAvailable` lets a server job send silent pushes without an APNs provider server — see `axiom-integration (skills/background-processing.md)`, Pattern 7, "Sending Silent Pushes Without an APNs Server".

---

## CloudKit Web Services (Server-to-Server)

A backend writes to the public database with a server-to-server key: generate a P-256 key (`openssl ecparam -name prime256v1 -genkey -noout -out eckey.pem`) and register its public key in CloudKit Console. For every request:

1. Sign `[ISO8601 date, no milliseconds]:[base64 SHA-256 of the body]:[subpath]` with ECDSA/SHA-256. The subpath is the URL without `https://api.apple-cloudkit.com` — `/database/1/iCloud.com.example.App/production/public/records/modify`
2. Send `X-Apple-CloudKit-Request-KeyID`, `X-Apple-CloudKit-Request-ISO8601Date` (the same date string), and `X-Apple-CloudKit-Request-SignatureV1` (base64 of the DER signature). A signed request is valid for 10 minutes

**The signature must be DER.** OpenSSL, Node's `crypto.sign`, and Swift Crypto's `derRepresentation` produce DER. Web Crypto's `crypto.subtle.sign` (Cloudflare Workers, Deno, browsers) returns the raw 64-byte `r‖s` form, and CloudKit rejects it with 401. Convert before base64-encoding:

```js
function p1363ToDer(sig) {  // sig: Uint8Array(64) from crypto.subtle.sign
  const int = (b) => {
    let i = 0;
    while (i < b.length - 1 && b[i] === 0) i++;
    b = b.slice(i);
    return b[0] & 0x80 ? [0x02, b.length + 1, 0, ...b] : [0x02, b.length, ...b];
  };
  const r = int(sig.slice(0, 32)), s = int(sig.slice(32));
  return new Uint8Array([0x30, r.length + s.length, ...r, ...s]);
}
```

---

## Sharing Records

CloudKit has two sharing models. Pick by how the app reads the shared data.

| Model | API | What's shared | Use when |
|-------|-----|---------------|----------|
| Hierarchical (root-record) | `CKShare(rootRecord:)` | Root record + its `parent`-linked descendants | You share one document/object and traverse its hierarchy from the root |
| Zone-wide | `CKShare(recordZoneID:)` | Every record in the zone | You read/write by enumerating the whole zone, so records aren't tied to a single root |

**Pitfall**: root-record sharing only shares records reachable through `parent` references from the root. If the app instead enumerates the entire zone (no root hierarchy), the participant sees an empty data set. Use zone-wide sharing for that access pattern.

### Create a Share (Root-Record)

```swift
// ✅ Share a record and its parent-linked descendants
let record = try await privateDatabase.record(for: recordID)

// Record must be in a custom zone (not the default zone)
let share = CKShare(rootRecord: record)
share[CKShare.SystemFieldKey.title] = "Shared Task List"
share.publicPermission = .none  // Invite-only

// Save the root record and share together, atomically
try await privateDatabase.modifyRecords(saving: [record, share], deleting: [])
```

### Share an Entire Zone (Zone-Wide)

```swift
// ✅ Every record in the zone is shared — no root record
// Custom zones in the private database have the .zoneWideSharing
// capability by default; the default zone cannot be shared.
let share = CKShare(recordZoneID: customZone.zoneID)
share[CKShare.SystemFieldKey.title] = "Household Inventory"
share.publicPermission = .none

// No root record to batch — save the share on its own
try await privateDatabase.save(share)
```

**Constraints**:
- A zone and its records can take part in **only one** share — zone-wide and per-record shares are mutually exclusive within the same zone.
- The zone-wide share's record name is the well-known constant `CKRecordNameZoneWideShare`.
- On accept, CloudKit copies the records into a new zone in the participant's shared database. Get the new zone ID with `CKFetchDatabaseChangesOperation`, then read records with `CKFetchRecordZoneChangesOperation` (there is no root record to fetch).

### Present Sharing UI

```swift
import CloudKit
import UIKit

// ✅ UIKit sharing controller
let sharingController = UICloudSharingController(share: share, container: container)
sharingController.delegate = self
present(sharingController, animated: true)

// Delegate methods
extension ViewController: UICloudSharingControllerDelegate {
    func cloudSharingController(_ csc: UICloudSharingController,
                                failedToSaveShareWithError error: Error) {
        print("Share failed: \(error)")
    }

    func itemTitle(for csc: UICloudSharingController) -> String? {
        return "My Shared List"
    }
}
```

### Manage Participants

```swift
// ✅ Check participants
for participant in share.participants {
    print("\(participant.userIdentity.nameComponents?.givenName ?? "Unknown")")
    print("  Acceptance: \(participant.acceptanceStatus)")
    print("  Permission: \(participant.permission)")
    // .readOnly, .readWrite, .none
}

// ✅ Remove participant
share.removeParticipant(participant)
try await privateDatabase.save(share)
```

**Footgun — participant equality is identity, not structural.** `CKShare.Participant`'s `==` / `isEqual:` answers *"is this the same person?"* — it returns `true` when `participantID`, `userIdentity.userRecordID`, **or** `userIdentity.lookupInfo` matches, and **ignores** `role`, `permission`, `acceptanceStatus`, and `dateAddedToShare`. So `share.participants.contains(updatedParticipant)` matches a participant whose **permission differs**, and two "equal" participants can carry different access. Compare the specific field (`permission`, `acceptanceStatus`) explicitly; never infer identical properties from equality. (Behavior on all versions — documented in 27.)

### Accept a Share

```swift
// In SceneDelegate or AppDelegate
func userDidAcceptCloudKitShareWith(_ cloudKitShareMetadata: CKShare.Metadata) {
    let operation = CKAcceptSharesOperation(shareMetadatas: [cloudKitShareMetadata])
    operation.acceptSharesResultBlock = { result in
        switch result {
        case .success: print("Share accepted")
        case .failure(let error): print("Accept failed: \(error)")
        }
    }
    CKContainer(identifier: cloudKitShareMetadata.containerIdentifier)
        .add(operation)
}
```

---

## Quick Reference

| Task | Modern API (iOS 17+) | Legacy API |
|------|----------------------|------------|
| Structured data sync | SwiftData + CloudKit | CKSyncEngine or CKDatabase |
| Custom persistence sync | CKSyncEngine | CKDatabase |
| Conflict resolution | Manual for CKSyncEngine (merge `error.serverRecord`); automatic for SwiftData | Manual (savePolicy) |
| Account changes | Handled automatically | Manual detection |
| Monitoring | CloudKit Console telemetry | Manual logging |

---

## Related Skills

- `skills/swiftdata.md` — SwiftData implementation details
- `skills/storage.md` — Choose CloudKit vs iCloud Drive
- `skills/icloud-drive-ref.md` — File-based iCloud sync
- `skills/cloud-sync-diag.md` — Debug CloudKit sync issues

---

**WWDC Sessions**: 2023-10188 (CKSyncEngine), 2024-10122 (CloudKit Console)


---
Source attribution: CharlesWiltgen
Original project: https://github.com/CharlesWiltgen/Axiom/blob/9cb83b3d53726aa94e5d3da0459433ffe0718f5a/axiom-codex/skills/axiom-data/skills/cloudkit-ref.md
License: MIT
License information: https://opensource.org/license/mit
Original upstream instructions; downloaded from Dave AI.

## Upstream license notice

MIT License

Copyright (c) 2025 Charles Wiltgen

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.



# Dave AI resource metadata

Creator / publisher: CharlesWiltgen
Original project: https://github.com/CharlesWiltgen/Axiom/blob/9cb83b3d53726aa94e5d3da0459433ffe0718f5a/axiom-codex/skills/axiom-data/skills/cloudkit-ref.md
Attribution basis: Recovered original package and original project license at a pinned revision

Fields of work: Development & Engineering, Product Management
Roles: Backend Developer, Mobile Developer, Product Manager
Specialties: Backend, Mobile
Tools: Cloudkit
Country applicability: Country not specified
Classification basis: Source catalog labels
Instruction SHA-256: 81c3a968661739be66fca2464cd46792760cc8ee031cf28c69d720f2733c5d46
Metadata reviewed: 2026-10-27
This metadata supersedes earlier Dave AI catalog attribution. Original instruction and supporting files are preserved.
