Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

All notable changes to CloudSaveKit are documented here.

## 0.3.1 — 2026-10-29

### Documentation

- Documented a tvOS secondary-profile failure mode in which a correctly entitled app receives the default profile's CloudKit account, with privacy-safe account/store diagnosis and bounded owner-coordinated profile re-addition.
- Clarified that CloudSaveKit binds CloudKit's verified account and cannot infer a mismatch from the visible system profile; consumer two-account acceptance remains necessary.

This release contains no public API or runtime behavior changes.

## 0.3.0 — 2026-10-28

### Added
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,16 @@ Add CloudSaveKit to your package dependencies:
```swift
.package(
url: "https://github.com/thatfactory/cloudsavekit.git",
from: "0.3.0"
from: "0.3.1"
)
```

Implement `CloudSaveClient` in the actor that owns your local database. For one personal collection per account, use `CloudSavePrivateCoordinator`: it constructs the container, observes account changes, verifies identity, and conditionally provisions a generic manifest. Its `CloudSavePrivateSession` permanently binds that account and zone to your client. See the [private collection integration guide](https://thatfactory.github.io/cloudsavekit/documentation/cloudsavekit/privatecollections/) for a complete neutral-schema example and the offline trust contract.

Personal sessions revalidate incoming manifest identities and drain admitted host callbacks during retirement. Await retirement before reading a checkpoint for a replacement session over the same store. Configured-zone network errors retain their transport classification so a verified, initialized cache can remain usable offline; a failed fetch never establishes initial-empty readiness.

For Apple TV consumers, a visible profile switch does not by itself prove that CloudKit supplied that profile's account. The [troubleshooting guide](Sources/CloudSaveKit/CloudSaveKit.docc/Troubleshooting.md#a-secondary-apple-tv-profile-opens-the-default-account) gives a privacy-safe account/store comparison and a bounded system-profile recovery path. CloudSaveKit binds the account actually verified by CloudKit; the host must test two real profiles before claiming isolation.

```swift
let coordinator = CloudSavePrivateCoordinator(
containerIdentifier: "iCloud.com.example.notebook"
Expand Down
2 changes: 2 additions & 0 deletions Sources/CloudSaveKit/CloudSaveKit.docc/PrivateCollections.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ For host-owned checkpoint recovery, verify account and canonical manifest first,

The package does not install a second database subscription or notification pipeline. `CKSyncEngine` retains that ownership. The application supplies platform entitlements and signing, including any tvOS current-user capability, and is responsible for real profile-switch acceptance. Credential-free package tests do not establish device account isolation or delivery of system notifications.

On Apple TV, a selected secondary profile can still receive the default account's CloudKit identity on an affected device even when the app is correctly entitled and launches a new process. The coordinator binds the account CloudKit actually verifies. Follow the <doc:Troubleshooting> account-versus-store evidence ladder before changing application bindings or package behavior; verify distinct accounts again after any system-profile repair.

## Diagnostics and tests

Personal lifecycle events use the existing `com.thatfactory.cloudsavekit` subsystem, `sync` category, and ☁️ prefix. Account, bootstrap, session, engine, and exact-record outcomes use fixed privacy-safe tokens. Account names, record/zone identifiers, manifest values, images, and error payloads never enter messages. Pure tokens and value types remain silent.
Expand Down
14 changes: 14 additions & 0 deletions Sources/CloudSaveKit/CloudSaveKit.docc/Troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,20 @@ If explicit fetch generations complete while the known server record remains abs

A successful fetch API call does not independently prove that a stale or mismatched checkpoint discovered the expected zone. Likewise, a configured-zone dirty flag of `false` is not proof that the server contains no newer records.

## A secondary Apple TV profile opens the default account

Apple's [current-user guidance](https://developer.apple.com/documentation/tvservices/personalizing-your-app-for-each-user-on-apple-tv) says a correctly entitled tvOS app receives the selected user's iCloud data. With the [modern User Management entitlement](https://developer.apple.com/documentation/tvservices/mapping-apple-tv-users-to-app-profiles), the system also separates `UserDefaults`; the user-independent Keychain API is a separate, optional choice. The host app must enable the capability in its signed tvOS build and embedded provisioning profile. CloudSaveKit verifies the account supplied by CloudKit, not the profile name displayed by tvOS Settings. An immutable personal session cannot infer that CloudKit supplied the wrong profile's otherwise valid account.

If a selected secondary profile displays the default person's private collection, establish which boundary crossed before changing code:

1. Confirm two distinct, fully signed-in Apple Accounts in native Apple TV Settings and record the selected profile using private A/B aliases.
2. Inspect the installed app's effective User Management and iCloud entitlements and provisioning. Launch that exact build from the native app grid under each profile; record fresh process/build identity.
3. Compare ``CloudSavePrivateCoordinator``'s verified account, the active ``CloudSavePrivateSession`` context, the host's account-qualified store/checkpoint, and a synthetic entry set. Retain only same/different and bounded-count results; do not log or publish account IDs, record IDs, credentials, or content. Repeat A → B → A and cold launches.

If CloudKit verifies B but the host opens A's store, repair the host's account-to-store handoff. If CloudKit verifies A under selected B, preserve the signed-build and process evidence and diagnose tvOS profile state before changing CloudSaveKit or adding manual account mapping. A new PID, `TVUserManager.shouldStorePreferencesForCurrentUser == true`, or a different Settings label does not establish that CloudKit supplied B. A debug-only `UserDefaults` marker written solely under A can test preference isolation separately; remove it after the experiment.

On one observed device, both supported current-user entitlement values and a normal restart still delivered the default account under the secondary profile. With the owner's confirmation that the secondary profile's local data was replaceable, removing and re-adding only that profile restored distinct verified accounts and stores through repeated native switches. This does not identify the internal tvOS cause or guarantee the same repair elsewhere. Try one normal restart first; if it fails, follow [Apple's profile removal and re-addition flow](https://support.apple.com/guide/tv/atvb0753b37d/tvos) only with the owner's action-time confirmation and owner-operated sign-in. Preserve the default and other profiles. Do not uninstall the app, clear trusted bindings/checkpoints, recreate zones, or factory-reset the device as a speculative fix. If the mismatch persists, retain a minimal privacy-safe reproduction for Apple support.

## Data appears but the manual result says zero

CKSyncEngine may perform a scheduled fetch immediately before the explicit generation. If logs show database discovery, zone delivery, and host apply before manual completion, the final explicit operation can correctly report zero transferred records because the scheduled generation already applied them. Treat operation counts as attribution snapshots and use the ordered timeline.
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.3.0
0.3.1