Skip to content

v1.0.0 beta.1#298

Open
leogdion wants to merge 21 commits into
mainfrom
v1.0.0-beta.1
Open

v1.0.0 beta.1#298
leogdion wants to merge 21 commits into
mainfrom
v1.0.0-beta.1

Conversation

@leogdion
Copy link
Copy Markdown
Member

@leogdion leogdion commented May 7, 2026

No description provided.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 7, 2026

Important

Review skipped

Too many files!

This PR contains 300 files, which is 150 over the limit of 150.

To get a review, narrow the scope:
• coderabbit review --type committed # exclude uncommitted changes
• coderabbit review --dir # limit to a subdirectory
• coderabbit review --base # compare against a closer base

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7370f082-0677-49d7-beae-7ef346cc8fc4

📥 Commits

Reviewing files that changed from the base of the PR and between 38f0d77 and d65d20b.

📒 Files selected for processing (300)
  • .claude/ci-failures-pr298.md
  • .claude/plan-pr298.md
  • .devcontainer/devcontainer.json
  • .devcontainer/swift-6.1/devcontainer-lock.json
  • .devcontainer/swift-6.3-nightly/devcontainer.json
  • .devcontainer/swift-6.3/devcontainer.json
  • .github/actions/setup-tools/action.yml
  • .github/workflows/MistDemo.yml
  • .github/workflows/MistKit.yml
  • .github/workflows/check-unsafe-flags.yml
  • .github/workflows/cleanup-caches.yml
  • .github/workflows/codeql.yml
  • .github/workflows/examples.yml
  • .github/workflows/swift-source-compat.yml
  • .gitignore
  • .swift-format
  • .swiftlint.yml
  • CLAUDE.md
  • Examples/BushelCloud/.github/actions/setup-mistkit/action.yml
  • Examples/BushelCloud/.github/workflows/BushelCloud.yml
  • Examples/BushelCloud/.github/workflows/bushel-cloud-build.yml
  • Examples/BushelCloud/.gitrepo
  • Examples/BushelCloud/.swift-format
  • Examples/BushelCloud/.swiftlint.yml
  • Examples/BushelCloud/Mintfile
  • Examples/BushelCloud/Package.resolved
  • Examples/BushelCloud/Scripts/bootstrap.sh
  • Examples/BushelCloud/Scripts/lint.sh
  • Examples/BushelCloud/Sources/BushelCloudCLI/Commands/SyncCommand.swift
  • Examples/BushelCloud/Sources/BushelCloudKit/CloudKit/BushelCloudKitError.swift
  • Examples/BushelCloud/Sources/BushelCloudKit/CloudKit/BushelCloudKitService.swift
  • Examples/BushelCloud/Sources/BushelCloudKit/CloudKit/KeyIDValidator.swift
  • Examples/BushelCloud/Sources/BushelCloudKit/Configuration/ConfigurationKeys.swift
  • Examples/BushelCloud/Sources/BushelCloudKit/Extensions/SwiftVersionRecord+CloudKit.swift
  • Examples/BushelCloud/Sources/BushelCloudKit/Extensions/XcodeVersionRecord+CloudKit.swift
  • Examples/BushelCloud/Sources/BushelCloudKit/Utilities/ConsoleOutput.swift
  • Examples/BushelCloud/mise.toml
  • Examples/CelestraCloud/.github/actions/setup-mistkit/action.yml
  • Examples/CelestraCloud/.github/workflows/CelestraCloud.yml
  • Examples/CelestraCloud/.github/workflows/update-feeds.yml
  • Examples/CelestraCloud/.gitrepo
  • Examples/CelestraCloud/.swift-format
  • Examples/CelestraCloud/.swiftlint.yml
  • Examples/CelestraCloud/Mintfile
  • Examples/CelestraCloud/Package.resolved
  • Examples/CelestraCloud/Package.swift
  • Examples/CelestraCloud/Scripts/header.sh
  • Examples/CelestraCloud/Scripts/lint.sh
  • Examples/CelestraCloud/Sources/CelestraCloud/Celestra.swift
  • Examples/CelestraCloud/Sources/CelestraCloud/Commands/AddFeedCommand.swift
  • Examples/CelestraCloud/Sources/CelestraCloud/Commands/ClearCommand.swift
  • Examples/CelestraCloud/Sources/CelestraCloud/Commands/ExitError.swift
  • Examples/CelestraCloud/Sources/CelestraCloud/Commands/UpdateCommand+Reporting.swift
  • Examples/CelestraCloud/Sources/CelestraCloud/Commands/UpdateCommand.swift
  • Examples/CelestraCloud/Sources/CelestraCloud/Commands/UpdateCommandError.swift
  • Examples/CelestraCloud/Sources/CelestraCloud/Commands/UpdateSummary.swift
  • Examples/CelestraCloud/Sources/CelestraCloud/Services/FeedUpdateProcessor+Fetch.swift
  • Examples/CelestraCloud/Sources/CelestraCloud/Services/FeedUpdateProcessor.swift
  • Examples/CelestraCloud/Sources/CelestraCloud/Services/FeedUpdateResult.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/CelestraConfig.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Configuration/CelestraConfiguration.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Configuration/CloudKitConfiguration.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Configuration/ConfigSource.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Configuration/ConfigurationKeys.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Configuration/ConfigurationLoader.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Configuration/EnhancedConfigurationError.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Configuration/UpdateCommandConfiguration.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Configuration/ValidatedCloudKitConfiguration.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Errors/CloudKitConversionError.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Errors/ConfigurationError.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Extensions/Article+MistKit.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Extensions/Feed+MistKit.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Extensions/RecordInfo+Parsing.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Models/ArticleSyncResult.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Models/BatchOperationResult.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Models/UpdateReport+JSONOutput.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Models/UpdateReport.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Protocols/CloudKitConvertible.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Protocols/CloudKitRecordOperating.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Services/ArticleCategorizer.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Services/ArticleCloudKitService.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Services/ArticleOperationBuilder.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Services/ArticleSyncService.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Services/CelestraError.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Services/CelestraLogger.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Services/CloudKitService+Celestra.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Services/FeedCloudKitService.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Services/FeedMetadataBuilder.swift
  • Examples/CelestraCloud/Sources/CelestraCloudKit/Services/FeedMetadataUpdate.swift
  • Examples/CelestraCloud/Tests/CelestraCloudTests/Configuration/UpdateCommandConfigurationTests.swift
  • Examples/CelestraCloud/Tests/CelestraCloudTests/Errors/CelestraErrorTests+Description.swift
  • Examples/CelestraCloud/Tests/CelestraCloudTests/Errors/CelestraErrorTests+RecoverySuggestion.swift
  • Examples/CelestraCloud/Tests/CelestraCloudTests/Errors/CelestraErrorTests.swift
  • Examples/CelestraCloud/Tests/CelestraCloudTests/Mocks/MockCloudKitRecordOperator.swift
  • Examples/CelestraCloud/mise.toml
  • Examples/MistDemo/.env.example
  • Examples/MistDemo/.periphery.yml
  • Examples/MistDemo/.swift-format
  • Examples/MistDemo/.swiftlint.yml
  • Examples/MistDemo/App/MistDemoApp.swift
  • Examples/MistDemo/Makefile
  • Examples/MistDemo/MistDemoApp.entitlements
  • Examples/MistDemo/Package.resolved
  • Examples/MistDemo/Package.swift
  • Examples/MistDemo/README.md
  • Examples/MistDemo/Scripts/header.sh
  • Examples/MistDemo/Scripts/lint.sh
  • Examples/MistDemo/Sources/ConfigKeyKit/Command.swift
  • Examples/MistDemo/Sources/ConfigKeyKit/CommandConfiguration.swift
  • Examples/MistDemo/Sources/ConfigKeyKit/CommandLineParser.swift
  • Examples/MistDemo/Sources/ConfigKeyKit/CommandRegistry.swift
  • Examples/MistDemo/Sources/ConfigKeyKit/CommandRegistryError.swift
  • Examples/MistDemo/Sources/ConfigKeyKit/ConfigKey+Bool.swift
  • Examples/MistDemo/Sources/ConfigKeyKit/ConfigKey+Debug.swift
  • Examples/MistDemo/Sources/ConfigKeyKit/ConfigKey.swift
  • Examples/MistDemo/Sources/ConfigKeyKit/ConfigKeySource.swift
  • Examples/MistDemo/Sources/ConfigKeyKit/ConfigurationParseable.swift
  • Examples/MistDemo/Sources/ConfigKeyKit/NamingStyle.swift
  • Examples/MistDemo/Sources/ConfigKeyKit/OptionalConfigKey+Debug.swift
  • Examples/MistDemo/Sources/ConfigKeyKit/OptionalConfigKey.swift
  • Examples/MistDemo/Sources/ConfigKeyKit/StandardNamingStyle.swift
  • Examples/MistDemo/Sources/MistDemo/CloudKit/MistKitClientFactory.swift
  • Examples/MistDemo/Sources/MistDemo/Commands/AuthTokenCommand.swift
  • Examples/MistDemo/Sources/MistDemo/Commands/CreateCommand.swift
  • Examples/MistDemo/Sources/MistDemo/Commands/CurrentUserCommand.swift
  • Examples/MistDemo/Sources/MistDemo/Commands/DemoInFilterCommand.swift
  • Examples/MistDemo/Sources/MistDemo/Commands/FetchChangesCommand.swift
  • Examples/MistDemo/Sources/MistDemo/Commands/LookupZonesCommand.swift
  • Examples/MistDemo/Sources/MistDemo/Commands/QueryCommand.swift
  • Examples/MistDemo/Sources/MistDemo/Commands/TestIntegrationCommand.swift
  • Examples/MistDemo/Sources/MistDemo/Commands/TestPrivateCommand.swift
  • Examples/MistDemo/Sources/MistDemo/Commands/UpdateCommand.swift
  • Examples/MistDemo/Sources/MistDemo/Commands/UploadAssetCommand.swift
  • Examples/MistDemo/Sources/MistDemo/Configuration/AuthTokenConfig.swift
  • Examples/MistDemo/Sources/MistDemo/Configuration/CreateConfig.swift
  • Examples/MistDemo/Sources/MistDemo/Configuration/CurrentUserConfig.swift
  • Examples/MistDemo/Sources/MistDemo/Configuration/FetchChangesConfig.swift
  • Examples/MistDemo/Sources/MistDemo/Configuration/Field.swift
  • Examples/MistDemo/Sources/MistDemo/Configuration/FieldType.swift
  • Examples/MistDemo/Sources/MistDemo/Configuration/LookupZonesConfig.swift
  • Examples/MistDemo/Sources/MistDemo/Configuration/MistDemoConfig.swift
  • Examples/MistDemo/Sources/MistDemo/Configuration/QueryConfig.swift
  • Examples/MistDemo/Sources/MistDemo/Configuration/TestIntegrationConfig.swift
  • Examples/MistDemo/Sources/MistDemo/Configuration/TestPrivateConfig.swift
  • Examples/MistDemo/Sources/MistDemo/Configuration/UpdateConfig.swift
  • Examples/MistDemo/Sources/MistDemo/Configuration/UploadAssetConfig.swift
  • Examples/MistDemo/Sources/MistDemo/Constants/MistDemoConstants.swift
  • Examples/MistDemo/Sources/MistDemo/Errors/QueryError.swift
  • Examples/MistDemo/Sources/MistDemo/Errors/UpdateError.swift
  • Examples/MistDemo/Sources/MistDemo/Extensions/FieldValue+FieldType.swift
  • Examples/MistDemo/Sources/MistDemo/Integration/IntegrationTestData.swift
  • Examples/MistDemo/Sources/MistDemo/Integration/IntegrationTestError.swift
  • Examples/MistDemo/Sources/MistDemo/Integration/IntegrationTestRunner.swift
  • Examples/MistDemo/Sources/MistDemo/MistDemo.swift
  • Examples/MistDemo/Sources/MistDemo/Models/AuthRequest.swift
  • Examples/MistDemo/Sources/MistDemo/Models/AuthResponse.swift
  • Examples/MistDemo/Sources/MistDemo/Models/CloudKitData.swift
  • Examples/MistDemo/Sources/MistDemo/Output/Escapers/YAMLEscaper.swift
  • Examples/MistDemo/Sources/MistDemo/Output/Formatters/CSVFormatter.swift
  • Examples/MistDemo/Sources/MistDemo/Output/Formatters/TableFormatter.swift
  • Examples/MistDemo/Sources/MistDemo/Output/Formatters/YAMLFormatter.swift
  • Examples/MistDemo/Sources/MistDemo/Output/OutputEscaping.swift
  • Examples/MistDemo/Sources/MistDemo/Protocols/OutputFormatting+Implementations.swift
  • Examples/MistDemo/Sources/MistDemo/Protocols/OutputFormatting+Records.swift
  • Examples/MistDemo/Sources/MistDemo/Protocols/OutputFormatting+Users.swift
  • Examples/MistDemo/Sources/MistDemo/Resources/index.html
  • Examples/MistDemo/Sources/MistDemo/Types/AnyCodable.swift
  • Examples/MistDemo/Sources/MistDemo/Types/FieldsInput.swift
  • Examples/MistDemo/Sources/MistDemo/Utilities/AsyncChannel.swift
  • Examples/MistDemo/Sources/MistDemo/Utilities/AsyncHelpers.swift
  • Examples/MistDemo/Sources/MistDemo/Utilities/AuthenticationHelper.swift
  • Examples/MistDemo/Sources/MistDemo/Utilities/BrowserOpener.swift
  • Examples/MistDemo/Sources/MistDemo/Utilities/FieldValueFormatter.swift
  • Examples/MistDemo/Sources/MistDemoApp/Models/Note.swift
  • Examples/MistDemo/Sources/MistDemoApp/Models/ZoneRow.swift
  • Examples/MistDemo/Sources/MistDemoApp/Services/CKDatabaseScope+Demo.swift
  • Examples/MistDemo/Sources/MistDemoApp/Services/CloudKitStore.swift
  • Examples/MistDemo/Sources/MistDemoApp/Services/CloudKitStoreError.swift
  • Examples/MistDemo/Sources/MistDemoApp/Views/AccountView+Actions.swift
  • Examples/MistDemo/Sources/MistDemoApp/Views/AccountView.swift
  • Examples/MistDemo/Sources/MistDemoApp/Views/DetailColumnRoot.swift
  • Examples/MistDemo/Sources/MistDemoApp/Views/NoteEditView.swift
  • Examples/MistDemo/Sources/MistDemoApp/Views/QueryView.swift
  • Examples/MistDemo/Sources/MistDemoApp/Views/RecordDetailView.swift
  • Examples/MistDemo/Sources/MistDemoApp/Views/RootView.swift
  • Examples/MistDemo/Sources/MistDemoApp/Views/SidebarItem.swift
  • Examples/MistDemo/Sources/MistDemoApp/Views/SidebarView.swift
  • Examples/MistDemo/Sources/MistDemoApp/Views/ZoneListView.swift
  • Examples/MistDemo/Sources/MistDemoKit/CloudKit/MistKitClientFactory.swift
  • Examples/MistDemo/Sources/MistDemoKit/CloudKitCommand.swift
  • Examples/MistDemo/Sources/MistDemoKit/Commands/AuthTokenCommand.swift
  • Examples/MistDemo/Sources/MistDemoKit/Commands/CreateCommand.swift
  • Examples/MistDemo/Sources/MistDemoKit/Commands/CurrentUserCommand.swift
  • Examples/MistDemo/Sources/MistDemoKit/Commands/DeleteCommand.swift
  • Examples/MistDemo/Sources/MistDemoKit/Commands/DeleteResult.swift
  • Examples/MistDemo/Sources/MistDemoKit/Commands/DemoErrorsCommand.swift
  • Examples/MistDemo/Sources/MistDemoKit/Commands/DemoErrorsRunner+Output.swift
  • Examples/MistDemo/Sources/MistDemoKit/Commands/DemoErrorsRunner.swift
  • Examples/MistDemo/Sources/MistDemoKit/Commands/DemoInFilterCommand.swift
  • Examples/MistDemo/Sources/MistDemoKit/Commands/FetchChangesCommand.swift
  • Examples/MistDemo/Sources/MistDemoKit/Commands/LookupCommand.swift
  • Examples/MistDemo/Sources/MistDemoKit/Commands/LookupZonesCommand.swift
  • Examples/MistDemo/Sources/MistDemoKit/Commands/MistDemoCommand.swift
  • Examples/MistDemo/Sources/MistDemoKit/Commands/ModifyCommand.swift
  • Examples/MistDemo/Sources/MistDemoKit/Commands/ModifyOutput.swift
  • Examples/MistDemo/Sources/MistDemoKit/Commands/ModifyResultRow.swift
  • Examples/MistDemo/Sources/MistDemoKit/Commands/QueryCommand+FilterParsing.swift
  • Examples/MistDemo/Sources/MistDemoKit/Commands/QueryCommand.swift
  • Examples/MistDemo/Sources/MistDemoKit/Commands/TestPrivateCommand.swift
  • Examples/MistDemo/Sources/MistDemoKit/Commands/TestPublicCommand.swift
  • Examples/MistDemo/Sources/MistDemoKit/Commands/UpdateCommand.swift
  • Examples/MistDemo/Sources/MistDemoKit/Commands/UploadAssetCommand.swift
  • Examples/MistDemo/Sources/MistDemoKit/Commands/WebCommand.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/AuthTokenConfig.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/BrowserFlagResolver.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/ConfigurationError.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/CreateConfig.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/CurrentUserConfig.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/DeleteConfig.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/DemoErrorsConfig.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/DemoErrorsError.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/ErrorScenario.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/FetchChangesConfig.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/Field.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/FieldParsingError.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/FieldType.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/LookupConfig.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/LookupZonesConfig.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/MistDemoConfig+DatabaseConfiguration.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/MistDemoConfig+Parsing.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/MistDemoConfig.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/MistDemoConfiguration.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/ModifyConfig.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/ModifyOperationInput.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/ModifyOperationKind.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/QueryConfig+Parsing.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/QueryConfig.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/SortOrder.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/TestPrivateConfig.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/TestPublicConfig.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/UpdateConfig.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/UploadAssetConfig.swift
  • Examples/MistDemo/Sources/MistDemoKit/Configuration/WebConfig.swift
  • Examples/MistDemo/Sources/MistDemoKit/Constants/MistDemoConstants+Defaults.swift
  • Examples/MistDemo/Sources/MistDemoKit/Constants/MistDemoConstants+Messages.swift
  • Examples/MistDemo/Sources/MistDemoKit/Constants/MistDemoConstants.swift
  • Examples/MistDemo/Sources/MistDemoKit/Errors/AuthTokenError.swift
  • Examples/MistDemo/Sources/MistDemoKit/Errors/ConfigError.swift
  • Examples/MistDemo/Sources/MistDemoKit/Errors/CreateError.swift
  • Examples/MistDemo/Sources/MistDemoKit/Errors/CurrentUserError.swift
  • Examples/MistDemo/Sources/MistDemoKit/Errors/DeleteError.swift
  • Examples/MistDemo/Sources/MistDemoKit/Errors/ErrorOutput+Convenience.swift
  • Examples/MistDemo/Sources/MistDemoKit/Errors/ErrorOutput.swift
  • Examples/MistDemo/Sources/MistDemoKit/Errors/FieldConversionError.swift
  • Examples/MistDemo/Sources/MistDemoKit/Errors/LookupError.swift
  • Examples/MistDemo/Sources/MistDemoKit/Errors/MistDemoError.swift
  • Examples/MistDemo/Sources/MistDemoKit/Errors/ModifyError.swift
  • Examples/MistDemo/Sources/MistDemoKit/Errors/OutputFormattingError.swift
  • Examples/MistDemo/Sources/MistDemoKit/Errors/QueryError.swift
  • Examples/MistDemo/Sources/MistDemoKit/Errors/UpdateError.swift
  • Examples/MistDemo/Sources/MistDemoKit/Errors/UploadAssetError.swift
  • Examples/MistDemo/Sources/MistDemoKit/Extensions/Array+Field.swift
  • Examples/MistDemo/Sources/MistDemoKit/Extensions/Command+AnyCommand.swift
  • Examples/MistDemo/Sources/MistDemoKit/Extensions/ConfigKey+MistDemo.swift
  • Examples/MistDemo/Sources/MistDemoKit/Extensions/FieldValue+FieldType.swift
  • Examples/MistDemo/Sources/MistDemoKit/Extensions/String+Padding.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/AssetUploadReceipt+PhaseState.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/CleanupPhaseMarker.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/CreatedRecordNames.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/IncrementalSyncInput.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/IntegrationPhase.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/IntegrationTest.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/IntegrationTestData.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/IntegrationTestError.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/IntegrationTestRunner.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/NoState.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/PhaseContext.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/PhaseState.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/PhaseStateDecodable.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/PhaseStateEncodable.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/PhasedIntegrationTest.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/Phases/CleanupPhase.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/Phases/CreateRecordsPhase.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/Phases/DiscoverUserIdentitiesPhase.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/Phases/FetchCallerPhase.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/Phases/FetchZoneChangesPhase.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/Phases/FinalVerificationPhase.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/Phases/IncrementalSyncPhase.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/Phases/InitialSyncPhase.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/Phases/ListZonesPhase.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/Phases/LookupRecordsPhase.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/Phases/LookupUsersByEmailPhase.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/Phases/LookupUsersByRecordNamePhase.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/Phases/LookupZonePhase.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/Phases/ModifyRecordsPhase.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/Phases/QueryRecordsPhase.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/Phases/UploadAssetPhase.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/SyncTokenSlot.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/Tests/PrivateDatabaseTest.swift
  • Examples/MistDemo/Sources/MistDemoKit/Integration/Tests/PublicDatabaseTest.swift

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 v1.0.0-beta.1

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 and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 7, 2026

Codecov Report

❌ Patch coverage is 21.16788% with 972 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.46%. Comparing base (38f0d77) to head (d65d20b).

Files with missing lines Patch % Lines
...ources/MistDemoKit/Commands/DemoErrorsRunner.swift 0.00% 116 Missing ⚠️
...rces/MistDemoKit/Commands/UploadAssetCommand.swift 0.00% 107 Missing ⚠️
...urces/MistDemoKit/Configuration/CreateConfig.swift 7.21% 90 Missing ⚠️
...ces/MistDemoKit/Commands/DemoInFilterCommand.swift 0.00% 78 Missing ⚠️
...Demo/Sources/MistDemoKit/Commands/WebCommand.swift 0.00% 70 Missing ⚠️
...ces/MistDemoKit/Commands/FetchChangesCommand.swift 0.00% 58 Missing ⚠️
...ources/MistDemoKit/Commands/AuthTokenCommand.swift 3.44% 56 Missing ⚠️
...urces/MistDemoKit/Configuration/DeleteConfig.swift 14.81% 46 Missing ⚠️
...o/Sources/MistDemoKit/Commands/ModifyCommand.swift 4.25% 45 Missing ⚠️
...o/Sources/MistDemoKit/Commands/UpdateCommand.swift 0.00% 44 Missing ⚠️
... and 17 more
Additional details and impacted files
@@             Coverage Diff             @@
##             main     #298       +/-   ##
===========================================
+ Coverage   25.58%   70.46%   +44.88%     
===========================================
  Files          95      551      +456     
  Lines        8232    15426     +7194     
===========================================
+ Hits         2106    10870     +8764     
+ Misses       6126     4556     -1570     
Flag Coverage Δ
mistdemo-spm-macos 60.02% <21.16%> (?)
mistdemo-swift-6.2-jammy 60.03% <21.16%> (?)
mistdemo-swift-6.2-noble 60.03% <21.16%> (?)
mistdemo-swift-6.3-jammy 60.03% <21.16%> (?)
mistdemo-swift-6.3-noble 60.03% <21.16%> (?)
spm 52.27% <ø> (+26.70%) ⬆️
swift-6.1-jammy 52.25% <ø> (+26.68%) ⬆️
swift-6.1-noble 52.22% <ø> (+26.65%) ⬆️
swift-6.2-jammy 52.30% <ø> (+26.73%) ⬆️
swift-6.2-noble 52.33% <ø> (+26.75%) ⬆️
swift-6.3-jammy 52.67% <ø> (?)
swift-6.3-jammynightly ?
swift-6.3-noble 52.67% <ø> (?)
swift-6.3-noblenightly ?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@claude
Copy link
Copy Markdown

claude Bot commented May 7, 2026

Code Review: v1.0.0 beta.1

This is a large, well-structured release covering CI modernization, example improvements, and library hardening. The overall quality is high — strong test coverage, strict linting, and solid concurrency safety. Below are the findings by area.


Overview

  • 43,756 additions / 24,212 deletions across 300+ files
  • Core changes: CI matrix refactoring, MistDemo improvements, CelestraCloud error tests, and example app updates
  • Library source (Sources/MistKit/) appears stable; main concerns are in the path helper pattern and availability annotations

Code Quality

CloudKitService.swift — Repetitive path helpers (lines 73–208)

Every createXPath(containerIdentifier:) method has an identical body — it constructs the same .init(version:container:environment:database:) triple with only the return type differing:

internal func createQueryRecordsPath(containerIdentifier: String) -> Operations.queryRecords.Input.Path { ... }
internal func createLookupRecordsPath(containerIdentifier: String) -> Operations.lookupRecords.Input.Path { ... }
// ... 8 more identical methods

These are compiler-safe but are a maintenance burden. If a field like version ever changes from "1", it must be updated in 10 places. A single generic helper or a local makePath() closure inside each operation call would eliminate the duplication.

@available annotations don't match declared minimums

Package.swift declares macOS 10.15 / iOS 13.0 as the minimum, but CloudKitService and all its extensions are annotated @available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *). This is presumably intentional (swift-openapi-runtime requires newer OS), but there's no comment explaining the gap. This will silently fail for any consumer targeting the declared minimum.

public import Foundation vs implicit internal imports

CloudKitError.swift uses public import Foundation and import OpenAPIRuntime (no qualifier). With InternalImportsByDefault enabled, an unqualified import becomes internal — but OpenAPIRuntime types appear in the public CloudKitError interface (via underlyingError). This compiles today but may generate warnings or become an error in a future Swift version. Audit all files that expose OpenAPIRuntime types publicly.


Potential Bugs

misdirectedRequestResponse type/name mismatch (CloudKitResponseType.swift:54)

var misdirectedRequestResponse: Components.Responses.UnprocessableEntity? { get }

The property is named for HTTP 421 (Misdirected Request) but the type is UnprocessableEntity (normally HTTP 422). The generated code comment confirms this is a CloudKit-specific quirk — the API uses 421 with the UnprocessableEntity response schema. However, the error constructor in CloudKitError+OpenAPI.swift hard-codes statusCode: 422 when handling this case, which will produce incorrect HTTP status codes in errors for what is actually a 421 response. The status code should be 421.

AdaptiveTokenManager.hasCredentials is misleading (AdaptiveTokenManager.swift:47-50)

public var hasCredentials: Bool {
    get async { !apiToken.isEmpty }
}

The protocol name implies "valid credentials available," but this only checks that the API token string is non-empty. An expired, malformed, or revoked token will return true. If downstream code branches on this to skip authentication or error handling, it will behave incorrectly. The docstring should explicitly call this out, or the implementation should call validateCredentials().

CloudKitError missing redirectURL and typed serverErrorCode

PR #279's article code shows:

} catch let error as CloudKitError where error.serverErrorCode == .authenticationRequired {
    if let redirectURL = error.redirectURL {

Neither serverErrorCode nor redirectURL exist as properties on CloudKitError. The redirectURL is available on the underlying Components.Schemas.ErrorResponse in generated code but is not surfaced. Before publishing the article, either add these properties to CloudKitError or update the article snippet to use pattern matching on .httpErrorWithDetails.


Performance Considerations

errorExtractors static array (CloudKitError+OpenAPI.swift:33–45)

The 11-element closure array is scanned linearly for every error response. At this scale it's not a practical concern, but because only one case can match per response type, a switch on the response's active case would be O(1) instead of O(n). Not urgent.

Platform.without() in Package.swift (line 134)

This helper allocates a new array every call. Since it's used at manifest evaluation time only, this is harmless. But the formatter left inconsistent style in the trailing closure:

result.removeAll{
$0 == platform      // missing leading space, not 2-space indented
}

swift-format should catch this — check that the manifest is not excluded from formatting.


Security

The SecureLogging pattern and the critical warning around AssetUploader transport separation are well-handled. No new security concerns found in the reviewed code.

The no_unchecked_sendable custom SwiftLint rule (.swiftlint.yml) is a strong guard — keep it.


CI/CD

lint waits on all platform builds (.github/workflows/MistKit.yml)

The lint job lists needs: [build-ubuntu, build-macos, build-macos-platforms, build-windows, build-android]. Android builds are slow and gated on full-matrix. On PRs that don't trigger full-matrix, build-android is skipped — does the lint job's if condition handle this correctly when some needs jobs are skipped? Worth verifying that skipped jobs don't block lint on non-full-matrix PRs.

ci skip check only works on push events

if: ${{ !contains(github.event.head_commit.message, 'ci skip') }}

github.event.head_commit is populated on push, but on pull_request events it's the PR's head commit message — this may behave unexpectedly. Consider using github.event.pull_request.title or the [skip ci] convention which GitHub Actions recognizes natively.

actions/checkout@v6 — this is a pre-release version. Pinning to a SHA for security would be preferable for an open-source library workflow. If @v6 is intentional (tracking latest), add a comment.

Experimental Swift features in Package.swift

20+ .enableExperimentalFeature(...) calls. Some of these (MoveOnlyClasses, NoncopyableGenerics, VariadicGenerics) can cause silent behavioral differences across Swift 6.1/6.2/6.3. The CI matrix covers all three toolchains which mitigates this, but if any feature is unused in the actual source, it should be removed — dead experimental feature flags add compile noise.


Test Coverage

139 test files for 104 source files is an excellent ratio. The concurrent token refresh suite and the mock uploader infrastructure for asset upload tests are particularly well-designed. No gaps identified in the reviewed areas.


Summary

Area Status
Architecture ✅ Clean, well-separated
Test coverage ✅ Comprehensive
Concurrency safety ✅ Strong (@unchecked Sendable banned, strict checking)
CI matrix coverage ✅ Ubuntu/macOS/Windows/Android/WASM
Repetitive path helpers ⚠️ Maintenance risk
@available gap vs declared minimum ⚠️ Needs comment or fix
misdirectedRequest → 421 but codes 422 ⚠️ Likely bug
hasCredentials semantics ⚠️ Misleading, document clearly
redirectURL/serverErrorCode missing from error ⚠️ Blocks PR #279 article from compiling
ci skip on PRs ℹ️ Minor

The 421→422 status code mismatch is the item I'd most want to verify before tagging the release. Everything else is either documentation/cleanup or low-risk.

@claude
Copy link
Copy Markdown

claude Bot commented May 7, 2026

Code Review: v1.0.0 beta.1

This PR has 300+ changed files (43,756 additions / 24,212 deletions), which exceeds the GitHub diff API limit. A full line-by-line review isn't possible without cloning locally.

For a release PR of this scope, I'd suggest:

  • Breaking the review into focused passes: generated code, manual Swift sources, tests, CI/CD config
  • Ensuring swift test passes clean on the branch before merge
  • Running the PR through /ultrareview for a multi-agent cloud review that can handle the full diff

If you'd like me to review specific files or subsystems from this PR, share the paths and I can dig in.


Review by Claude Sonnet 4.6

@claude
Copy link
Copy Markdown

claude Bot commented May 8, 2026

Code Review — v1.0.0-beta.1 (#298)

Overview

This is a significant beta release bringing together multiple improvements merged via sub-PRs: typed TokenManagerError, a refactored AuthenticationMiddleware, the new BatchSyncResult/OperationClassification pattern for tracking creates vs. updates, query pagination with continuation markers, substantial test reorganisation, and CI hardening. Overall the architecture is clean, the async/Sendable usage is consistent, and the documentation is excellent. A few concrete issues below.


Bugs

AdaptiveTokenManager.storage is declared but never used
storage is accepted in init(apiToken:storage:) and stored, but no TokenStorage calls appear anywhere in the actor. Credentials will never actually be persisted, silently defeating the persistence parameter.

// AdaptiveTokenManager.swift
internal let storage: (any TokenStorage)?   // stored, never written/read

AdaptiveTokenManager.webAuthToken has no mutation API
The property is var (implying upgradeability) but there is no func upgradeToWebAuth(token:) or equivalent public method. The actor cannot transition to web authentication after init, making the "adaptive" naming misleading.

ServerToServerAuthenticator — locale-sensitive PEM error detection

if error.localizedDescription.contains("PEM")
    || error.localizedDescription.contains("format")

localizedDescription content varies by locale and OS version. Prefer pattern-matching on the concrete error type (CryptoKitError) or always throw .invalidPEMFormat for any parse failure from P256.Signing.PrivateKey(pemRepresentation:).

lint.sh — unquoted pushd

pushd $PACKAGE_DIR   # breaks if PACKAGE_DIR contains spaces

Should be pushd "$PACKAGE_DIR" (and the popd/swift-format calls too).


Code Quality

CloudKitService+Classification.fetchExistingRecordNames silent truncation
The 200-record cap is documented in a - Important: block, which is good, but callers will silently misclassify updates-as-creates beyond the cap. Consider returning a tuple with a isTruncated: Bool flag, or throwing if limit exceeds 200, so callers can't accidentally get wrong results.

InMemoryTokenStorage broad decode catch
Failed authenticator decoding is swallowed into .corruptedStorage with no structured log. At minimum, log the underlying error at .debug level through MistKitLogger so storage corruption is diagnosable without attaching a debugger.

AuthenticationMiddleware — authenticator fetched per request
tokenManager.currentAuthenticator() is called on every HTTP request. For ServerToServerAuthenticator this reconstructs a P256 key wrapper each time. The result is immutable, so it could be cached across requests (invalidated only on credential changes). Not a correctness issue but worth a follow-up for request-heavy paths.

EnvironmentConfig token masking exposes too much
Showing the first 8 characters of an API token in logs (getMaskedEnvironment) leaks meaningful entropy. Consider showing only "<set>" / "<not set>" or at most the first 4 characters.

Repeated #available() guards in pagination tests
All 8 tests in CloudKitServiceQueryPaginationTests+SuccessCases.swift carry an identical #available guard. Hoist it to @Suite (or a withKnownIssue/XCTSkipUnless equivalent) to keep tests DRY.


Test Coverage

Performance suite is skeletal
ConcurrentTokenRefreshTests+Performance.swift contains a single test with no timing assertions. Either add clock.measure-style assertions or rename the suite to avoid implying performance coverage that isn't there.

Pagination tests are success-only
CloudKitServiceQueryPaginationTests+SuccessCases.swift covers the happy path well (single page, multi-page, stuck-marker detection), but there are no corresponding error tests. What happens when the continuation marker is malformed? When a mid-pagination request fails? A +ErrorHandling extension parallel to the other service test directories would complete the picture.

ConcurrentTokenRefreshTests+Performance undefined helpers
runConcurrent() and MockTokenManagerWithRateLimiting are referenced but not visible in the file. Verify these are in shared helpers and not accidentally omitted from the PR.


CI / Scripts

MistKit.ymlci skip check breaks on PRs

if: ${{ !contains(github.event.head_commit.message, 'ci skip') }}

github.event.head_commit is null for pull_request events, so this condition evaluates to false and skips all PR checks. Use github.event.commits[0].message for push events and guard by event type, or drop the ci skip check in favour of GitHub's native [skip ci] support.

lint.shheader.sh runs unconditionally in CI
$PACKAGE_DIR/Scripts/header.sh is called outside any $CI guard. If that script has side effects (file writes) it will modify files during the lint CI job. Wrap it in if [ -z "$CI" ].

lint.sh$PERIPHERY_OPTIONS undefined in normal mode
periphery scan $PERIPHERY_OPTIONS expands to an empty string outside STRICT mode; no issue today but add PERIPHERY_OPTIONS="" at the top of the else-branch to be explicit.


Security

ServerToServerAuthenticator.encoded() stores raw private key material
The WireFormat JSON includes the P-256 private key as a base64 string. The in-code comment acknowledges this, but it is worth a prominent ⚠️ in the public API documentation so callers don't inadvertently persist the output to disk or transmit it over a non-encrypted channel.


Positives

  • Typed TokenManagerError with associated reason enums is a big improvement over stringly-typed errors.
  • The BatchSyncResult/OperationClassification pattern elegantly solves a real CloudKit API limitation and is well-documented.
  • AuthenticationMiddleware refactor (each Authenticator applies itself) is clean and matches the protocol-oriented design goals.
  • Test file organisation (CloudKitServiceTests.Query+*, etc.) is now much easier to navigate.
  • Fixing lint.sh to count all failures (not just STRICT-mode ones) is a valuable correctness fix.
  • Comprehensive DocC-style inline documentation throughout.

Summary of Action Items

Priority Item
🔴 Bug AdaptiveTokenManager.storage stored but never used
🔴 Bug AdaptiveTokenManager has no public API to set webAuthToken
🔴 Bug PEM error detection via localizedDescription is locale-sensitive
🔴 CI ci skip check silently skips all PR jobs
🟡 Quality Unquoted pushd "$PACKAGE_DIR" in lint.sh
🟡 Quality fetchExistingRecordNames silent 200-record truncation
🟡 Test Pagination error-path tests missing
🟡 Test Performance suite has no measurements
🟢 Minor Token masking shows too many chars in EnvironmentConfig
🟢 Minor Repeated #available guards in pagination test suite
🟢 Minor header.sh runs unconditionally in CI

@claude
Copy link
Copy Markdown

claude Bot commented May 9, 2026

Code Review: v1.0.0 beta.1

Overview

This is a large release-candidate PR (~49k additions, ~25k deletions, 300+ files) touching three major areas:

  1. CI/CD overhaul — dedicated MistDemo workflow, smart full/minimal matrix, Android, WASM, and multi-platform macOS builds, CodeQL integration
  2. CelestraCloud refactoringUpdateCommand decomposed into focused files, JSON report output, new CelestraError test coverage, CloudKitRecordOperating protocol for testability
  3. BushelCloud improvementsKeyIDValidator for S2S key validation, a new PEM-string initializer for CI/CD environments

Overall quality is high and patterns align well with project conventions. The issues below are refinements.


Issues

Bug: Wrong field used for error logging in BushelCloudKitService

File: Examples/BushelCloud/Sources/BushelCloudKit/CloudKit/BushelCloudKitService.swift:247

// Current — logs recordType, not the error reason
Self.logger.debug("Error: recordName=\(result.recordName), reason=\(result.recordType)")

result.recordType is the record type identifier (e.g., "Feed"), not the error reason. This will produce misleading debug output when diagnosing batch operation failures.


Missing JSON serialization for computed properties in UpdateReport

File: Examples/CelestraCloud/Sources/CelestraCloudKit/Models/UpdateReport.swift:170,56

public var duration: TimeInterval { endTime.timeIntervalSince(startTime) }
public var successRate: Double { ... }

Swift's Codable synthesis does not encode computed properties. Consumers of the JSON report won't see duration or successRate in the output despite them being obvious report fields. Either compute and store them as let properties set at init time, or implement encode(to:) explicitly.


Type-unsafe status strings in UpdateReport.FeedResult

File: Examples/CelestraCloud/Sources/CelestraCloudKit/Models/UpdateReport.swift:130

public let status: String  // "success", "error", "skipped", "notModified"

A plain String status can silently produce invalid values through typos. A Codable enum would give compile-time exhaustiveness checks and safe decoding:

public enum Status: String, Codable, Sendable {
    case success, error, skipped, notModified
}

actions/checkout@v6 may not exist

Files: .github/workflows/MistKit.yml, .github/workflows/MistDemo.yml, example workflows

- uses: actions/checkout@v6

actions/checkout@v4 is the latest stable major version at time of writing. Using @v6 will either resolve to a pre-release or fail at runtime. Please verify this tag exists before merging.


MockCloudKitRecordOperator is not safe under parallel test execution

File: Examples/CelestraCloud/Tests/CelestraCloudTests/Mocks/MockCloudKitRecordOperator.swift:56-63

nonisolated(unsafe) internal private(set) var queryCalls: [QueryCall] = []
nonisolated(unsafe) internal var queryRecordsResult: Result<[RecordInfo], CloudKitError> = .success([])

The comment says "single-threaded test use only," but Swift Testing runs tests in parallel by default. Any two tests that mutate this mock concurrently will race. Either mark the test suite @Suite(.serialized) or wrap shared state in an Actor.


Overfetching in fetchExistingRecordNames

File: Examples/BushelCloud/Sources/BushelCloudKit/CloudKit/BushelCloudKitService.swift:169

let records = try await queryRecords(recordType: recordType)
let recordNames = Set(records.map(\.recordName))

All fields are fetched just to extract record names. Passing desiredKeys: [] would reduce response payload significantly for large record sets.


Minor / Nits

  • updateFeedMetadata success semantics (FeedUpdateProcessor+Fetch.swift:103): Returns .error when metadata.failureCount > 0, even if articles synced successfully. A partial-success variant would give consumers more accurate data than a blanket error.

  • ExitError is a one-liner struct with no payload. If used only to signal a CLI exit, consider ExitCode from swift-argument-parser directly, or add a message: String for call sites to provide context.

  • Copyright year inconsistency: CelestraErrorTests+Description.swift and CelestraErrorTests+RecoverySuggestion.swift carry © 2025 while all other new files use © 2026. Worth aligning if these files are new in this PR.


Strengths worth calling out

  • KeyIDValidator: Excellent defensive validation with actionable error messages. The "trim first, then check for whitespace" approach catches a very common copy-paste error cleanly.
  • CloudKitRecordOperating protocol + MockCloudKitRecordOperator: Good abstraction enabling unit testing without hitting CloudKit.
  • CelestraError.isRetriable: Thoughtful classification of transient vs. permanent errors. The HTTP status code logic (retry 5xx and 429, not other 4xx) is correct.
  • Smart CI matrix: Minimal matrix on feature branches, full matrix on main/semver/PRs-to-main is a sensible cost/coverage trade-off.
  • MistDemo-specific workflow: Path filtering means MistDemo builds only trigger on relevant changes, avoiding wasted CI minutes.
  • BushelCloudKitService PEM-string initializer: The CI/CD-friendly initializer accepting PEM content directly from an environment variable (instead of requiring a file path) is the right pattern for GitHub Actions secrets.

Comment thread Sources/MistKit/Service/ResponseProcessing/CloudKitError+OpenAPI.swift Dismissed
Comment thread Sources/MistKit/Service/ResponseProcessing/CloudKitError+OpenAPI.swift Dismissed
Comment thread Sources/MistKit/Service/ResponseProcessing/CloudKitError+OpenAPI.swift Dismissed
leogdion added a commit that referenced this pull request May 11, 2026
…rd returns

CodeFactor reported 19 violations on PR #298:

- `CredentialsTokenManagerTests.swift` (274 lines, 16× `conditional_returns_on_newline`):
  Convert the single suite struct into a namespace enum with four nested
  sub-suites (PublicDatabase, PrivateShared, UserContext, PrivateKeyLoad)
  following the existing `CloudKitServiceTests.FetchCaller` layout. Each
  `guard #available(...) else { return }` becomes multi-line.

- `CloudKitError+OpenAPI.swift` (237 lines):
  Move the 11 per-response private initializers into a sibling
  `CloudKitError+OpenAPI+Responses.swift`. Bump them from `private` to
  `internal` so the extractor array in the main file can still call them.

Also pick up filename-comment corrections in `OpenAPI/Components/*.swift`
that `Scripts/header.sh` (part of `Scripts/lint.sh`) normalized — stale
`+MistKit.swift` suffixes were trimmed to match the actual filenames.

Verified:
- `./Scripts/lint.sh` → 0 violations
- `swift test --filter CredentialsTokenManagerTests` → 18 tests pass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
leogdion added a commit that referenced this pull request May 11, 2026
Adds tests that don't require live CloudKit access — covers the cheapest
slice of the patch-coverage gap on PR #298 (codecov/patch: 13%).

New test files (all @testable, no network):
- Configuration/AuthTokenConfigTests.swift — memberwise init + parsed init via
  InMemoryProvider, including the missing/empty api.token error path
- Configuration/UploadAssetConfigTests.swift — memberwise init for every field
- Configuration/TestIntegrationConfigTests.swift — defaults + custom values
- Configuration/TestPrivateConfigTests.swift — defaults + private-DB pinning
- Commands/QueryCommand/QueryCommandTests+ParseFilter.swift — full coverage
  of parseFilter / inferFieldValue / shouldIncludeField / buildComparisonFilter
  (parameterized over every operator alias and error path)
- Commands/CreateCommand/CreateCommandTests+GenerateRecordName.swift —
  prefix, three-part format, suffix range, distinctness across 200 calls
- Commands/DemoErrorsRunnerOutputTests.swift — describe(_:) placeholder + echo

Source changes are minimal seams that mirror the existing
DeleteCommand.mapConflict pattern (internal-static for testability):
- QueryCommand: filter parsing helpers extracted into
  QueryCommand+FilterParsing.swift; private → internal static
- CreateCommand.generateRecordName: private → internal

901 MistDemo tests pass; lint + swift-format clean.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@claude
Copy link
Copy Markdown

claude Bot commented May 11, 2026

Code Review

Note: PR #298's diff exceeds GitHub's 300-file display limit, so this review combines direct file analysis of the repository state with the full diff of follow-up PR #316 (now closed), which contained fixes/additions from this branch.


Overview

PR #298 ("v1.0.0 beta.1") is a large release PR that includes:

  • New MistDemo commands and their configuration types
  • Availability bump for asset upload APIs (macOS 11 → 12 / iOS 14 → 15)
  • Logging redaction changes in CloudKitError+OpenAPI.swift
  • A large MistDemo test suite expansion (~1,300 lines of new tests)
  • CI/workflow updates and example app changes

Positive Highlights

  • Consistent availability bumps. The macOS 12.0 / iOS 15.0 bump is applied uniformly across URLSession+AssetUpload.swift, CloudKitService+AssetOperations.swift, CloudKitService+AssetUpload.swift, and all matching guard #available checks in test files. No stale guards remain.
  • Swift Testing adoption. All new tests use @Test, #expect, and @Suite as required by CLAUDE.md. No XCTest leftovers.
  • MistDemoConfiguration+Testing.swift is a clean test infrastructure. The InMemoryProvider-backed factory avoids boilerplate across all config test files, and the .-split key logic mirrors how production code reads config — good design.
  • Error-path coverage. Most config tests exercise the throwing path (await #expect(throws:)), including missing required fields and bad file paths. The UploadAssetCommand test that generates a random UUID path to guarantee file absence is particularly solid.
  • Parameterized test for ErrorScenario. DemoErrorsConfigTests.parsesScenarioRawValues(expected:) uses arguments: ErrorScenario.allCases — this is exactly the right use of parameterized tests.

Issues and Suggestions

1. Breaking availability change — needs changelog entry

// Before
@available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *)

// After
@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)

This is a breaking API change for any caller deployed on macOS 11 / iOS 14. The PR description and CHANGELOG.md (if one exists) should explicitly document this narrowing. The motivation (using URLSession.upload(for:from:delegate:) which requires iOS 15+) should also be stated — it's not clear from the diff alone why the floor moved.

2. Logging redaction regression risk

File: Sources/MistKit/Service/CloudKitError+OpenAPI.swift

The original code had:

MistKitLogger.logDebug(
    "Unhandled response (HTTP \(statusCode)): \(response)",
    logger: MistKitLogger.api,
    shouldRedact: false   // ← explicit
)

The new code drops shouldRedact: false, relying on the default (redaction on). The old comment said "Full body lives at debug level — may contain server-echoed request data (e.g. emails passed to lookupUsersByEmail). Warning stays sanitized" — meaning the warning was already sanitized, but the debug log was intentionally unredacted for local debugging.

Redacting debug-level logs of unhandled CloudKit responses makes debugging new/unexpected error shapes harder. Consider keeping shouldRedact: false for the debug-level call and the default (redact) only for the warning-level call, matching the original intent.

3. Smoke tests don't assert anything

Several tests in DemoErrorsRunnerOutputTests.swift pass by not throwing, but verify nothing:

@Test("printRunnerHeader does not throw")
internal func headerDoesNotThrow() async throws {
    let runner = try await DemoErrorsRunner(config: MistDemoConfig())
    runner.printRunnerHeader()
    // No assertion
}

If these methods print to stdout, capturing output (or even checking a return value where possible) would make these tests meaningful rather than just crash guards. For methods that truly have no observable output besides side effects, consider at minimum noting this limitation in the test name, e.g. "printRunnerHeader does not crash".

4. Force-unwrap in test file

File: CreateConfigTests+ParseFromConfiguration.swift

try json.data(using: .utf8)!.write(to: tmp)

While .utf8 encoding is infallible in practice, using Data(json.utf8) is cleaner and avoids the force-unwrap pattern, which the project avoids elsewhere:

try Data(json.utf8).write(to: tmp)

5. Multi-line doc comment in test helper

File: MistDemoConfiguration+Testing.swift

The doc comment block (lines explaining .-splitting of config keys) spans 6 lines. CLAUDE.md says to default to no comments and keep any comments to one short line. The behavior being documented (that "container.identifier" splits on .) is non-obvious and worth noting — but it can be condensed to a single line:

// Keys are split on "." to form AbsoluteConfigKey components.
internal static func testing(
    _ values: [String: ConfigValue]
) -> MistDemoConfiguration {

6. Inconsistent smoke-test commentary

UploadAssetCommandTests.swift has:

// Smoke test: type-check that the value is constructable.
_ = command

But equivalent smoke tests in DemoInFilterCommandTests.swift and FetchChangesCommandTests.swift have _ = command without any comment. Either add consistent comments or — per CLAUDE.md — remove them all (the _ = command idiom is self-explanatory).

7. PR title mismatch

The PR title "Update README with fixed serialization and integration test" does not match the actual changes (availability bumps, new test suite, logging changes). A more accurate title would help future git log readers.


Security

No security concerns found. The logging redaction change (item #2 above) is the closest thing — it actually increases default redaction, which is conservative. The risk is diagnostic, not security-related.

Test Coverage Assessment

The new config tests are thorough for the happy path and most error paths. One gap: there is no test verifying that FetchChangesConfig.parsesDefaults output format is .table (not .json) when unset — this asymmetry from other configs (which default to .json) is worth explicitly documenting as a test case (it is already covered implicitly in FetchChangesConfigTests+ParseFromConfiguration.parsesDefaults, so this is minor).


Summary

The PR is in good shape overall. The three items most worth addressing before merge are:

  1. Document the macOS 12 / iOS 15 availability bump as a breaking change.
  2. Restore shouldRedact: false on debug-level unhandled-response logging (or explicitly confirm the behavior change is intentional).
  3. Replace the force-unwrap in CreateConfigTests+ParseFromConfiguration.swift.

Everything else is low-priority style/polish.

leogdion and others added 7 commits May 11, 2026 15:15
subrepo:
  subdir:   "Examples/BushelCloud"
  merged:   "123a732"
upstream:
  origin:   "git@github.com:brightdigit/BushelCloud.git"
  branch:   "mistkit"
  commit:   "123a732"
git-subrepo:
  version:  "0.4.9"
  origin:   "https://github.com/Homebrew/brew"
  commit:   "b9763ee528"
subrepo:
  subdir:   "Examples/CelestraCloud"
  merged:   "4244497"
upstream:
  origin:   "git@github.com:brightdigit/CelestraCloud.git"
  branch:   "mistkit"
  commit:   "4244497"
git-subrepo:
  version:  "0.4.9"
  origin:   "https://github.com/Homebrew/brew"
  commit:   "b9763ee528"
#28, #34, #35) (#315)

* #312 library: add public+web-auth user-identity endpoints and users/caller migration

Implements the library side of #312 — adding/renaming user-identity endpoints
that require public-database routing with web-auth (user-context) credentials,
and unblocking the convenience initializers from their hardcoded database/
environment defaults.

#310: `CloudKitService` convenience initializers now accept `database:` and
`environment:` parameters with defaults that preserve current behavior.

#311: `users/current` → `users/caller`. Renamed in openapi.yaml and the
generated client; added a hand-written `fetchCaller()` plus an
`@available(*, deprecated, renamed: "fetchCaller")` `fetchCurrentUser()`
shim that forwards to the new method.

#28: GET `/users/discover` (`discoverAllUserIdentities`).

#34: POST `/users/lookup/email` (`lookupUsersByEmail`).

#35: POST `/users/lookup/id` (`lookupUsersByRecordName`).

The three new endpoints reuse `DiscoverResponse` for parsing — Apple returns
`{ users: [UserIdentity] }` for all of them. Each ships with a 5-file
test suite mirroring the existing `DiscoverUserIdentities` pattern.

#33 (`users/lookup/contacts`) intentionally not implemented: Apple has marked
the endpoint deprecated. To be closed as not-planned with a pointer to #34/#35.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* #312 MistDemo: separate database from authentication and add user-context phases

Refactors MistDemo's CloudKit configuration model and integration runner to
support the public+web-auth combination required by the user-identity
endpoints landed in the prior commit.

**Configuration refactor.** Replaces the `DatabaseCredentials` enum (which
coupled database choice to a single auth method per case, baking in a
public⇒S2S/private⇒webAuth assumption) with two orthogonal types:

  - `AuthenticationCredentials` — `serverToServer(keyID:privateKey:)` /
    `webAuth(apiToken:webAuthToken:)`
  - `DatabaseConfiguration` — pairs a `MistKit.Database` with an
    `AuthenticationCredentials`. The `make(database:authentication:)` factory
    rejects private+S2S and shared+S2S (which CloudKit rejects) so invalid
    combinations remain unrepresentable, while public+webAuth is now a valid
    construction.

`MistKitClientFactory.create(for:)` consumes `toPrimaryConfiguration()`;
the new `createUserContext(for:)` returns the optional public+web-auth
service from `toUserContextConfiguration()` when web-auth tokens are
configured.

**Phase plumbing.** `PhaseContext` and `IntegrationTestRunner` now thread an
optional `userContextService: CloudKitService?`. `PublicDatabaseTest` takes
`includeUserContextPhases:` and conditionally appends the new user-identity
phases:

  - `FetchCallerPhase` (renamed from `FetchCurrentUserPhase`)
  - `DiscoverUserIdentitiesPhase` (existed; updated to use userContextService)
  - `DiscoverAllUserIdentitiesPhase` (#28)
  - `LookupUsersByEmailPhase` (#34)
  - `LookupUsersByRecordNamePhase` (#35)

`PrivateDatabaseTest` no longer includes `FetchCurrentUserPhase`: CloudKit
rejects `users/caller` against the private database, matching the rest of
the user-identity family.

**Call-site updates.** `CurrentUserCommand` and `DemoErrorsRunner` swap
`fetchCurrentUser()` → `fetchCaller()`. `TestIntegrationCommand` and
`TestPrivateCommand` now build and pass `userContextService`.

Tests for `AuthenticationCredentials`, `DatabaseConfiguration.make`
validation, and `MistDemoConfig.toPrimaryConfiguration` /
`toUserContextConfiguration` ship alongside.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* #312: mark discoverAllUserIdentities() unavailable pending #28 investigation

Live verification on 2026-05-08 against iCloud.com.brightdigit.MistDemo
returned HTTP 500 from Apple's GET /users/discover. The first 12 phases
of mistdemo test-integration --verbose run green (the 8 base public+S2S
phases plus FetchCallerPhase, DiscoverUserIdentitiesPhase,
LookupUsersByEmailPhase, LookupUsersByRecordNamePhase) — only
discoverAllUserIdentities fails, blocking phases beyond it.

The endpoint is referenced in CloudKitJS but does not appear in Apple's
CloudKit Web Services REST documentation. The actual REST shape is still
under investigation under #28.

Changes:
- Marked `CloudKitService.discoverAllUserIdentities()`
  `@available(*, unavailable, message: ...)` with a pointer to #28.
- Removed `DiscoverAllUserIdentitiesPhase` from MistDemo and from
  `PublicDatabaseTest.phases`.
- Removed the `CloudKitServiceDiscoverAllUserIdentities` test directory
  (the unavailable method cannot be called from Swift code).

The OpenAPI definition, generated client, path builder, response
processor, Output extension, and Swift wrapper are all retained.
Unblocking is a one-line `@available` removal once the correct REST
shape is determined under #28.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* #315: resolve PR review — Credentials API, per-call database, cascade unavailable

Addresses all four review threads on PR #315:

- Comment #1 (error wording): removed `unsupportedDatabaseAuthCombination`
  along with `MistDemo.DatabaseConfiguration`; invalid combos now surface as
  `CloudKitError.missingCredentials` from the library.
- Comment #2 (per-call database): user-identity ops in
  `CloudKitService+UserOperations` hardcode `.public`; record/zone/asset/sync
  ops accept `database: Database? = nil` falling back to a service-level
  default.
- Comment #3 (unified credentials): new `Credentials` /
  `ServerToServerCredentials` / `APICredentials` value types replace the
  legacy `apiToken:`/`webAuthToken:` initializers. The token manager is
  selected based on the target database (S2S for `.public`, web-auth for
  `.private`/`.shared`). Lifted `PrivateKeyMaterial` into the library.
- Comment #4 (cascade unavailable): removed
  `Operations.discoverAllUserIdentities.Output: CloudKitResponseType`
  conformance entirely; `processDiscoverAllUserIdentitiesResponse` is now
  `@available(*, unavailable)` with a `fatalError` body.

Also migrates ~15 MistKit test helpers and the MistDemo factory to the new
Credentials API.

Breaking changes (pre-1.0): removed legacy `CloudKitService` initializers
taking `apiToken:`/`webAuthToken:`; `CloudKitService.apiToken` is removed,
`.database` is now `internal`.

Out of scope: per-call `TokenManager` dispatch (would let one service mix
S2S-for-public and web-auth-for-user-context). MistDemo still constructs a
separate `userContextService` for that scenario.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* #315: drop service-level database, per-call credential resolution [skip ci]

Resolves the architectural feedback in the PR-315 review:

* CloudKitService no longer carries `database` — operations take
  `database:` per call (defaulting to `.public`); user-identity routes
  drop the parameter since CloudKit pins them to `.public`. Subsumes
  Claude's "fetchCaller bypasses self.database" finding.
* Credentials.makeTokenManager(for:requiresUserContext:) resolves the
  appropriate token manager at dispatch time. A single service can now
  serve public-database S2S record ops and user-identity web-auth
  routes from one fully-populated `Credentials`. MistKitClient.swift is
  obsolete and removed; per-call dispatch lives in
  CloudKitService+ClientDispatch.
* Credentials.swift split per SwiftLint one_file_per_declaration into
  ServerToServerCredentials.swift + APICredentials.swift +
  Credentials.swift. New typed CredentialsValidationError; init asserts
  in debug, throws in release (no more precondition crash for dynamic
  config).
* MistDemo: userContextService workaround collapsed — single service
  handles all phases via per-call resolution.
* CI hotfix: 11 unused `public import` lines demoted to `internal`
  (the warnings-as-errors regression flagged in the review).
* Tests: 12-case routing-matrix unit suite for makeTokenManager and a
  fetchCaller suite parallel to LookupUsers* (success + validation).
  Obsolete MistKitClient tests removed.
* Polish: shorter @available message on discoverAllUserIdentities,
  structural comment for GET /users/discover in openapi.yaml,
  ConfigurationError.missingAPIToken (unused) removed.

475/475 tests pass. Library + MistDemo build clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Per review on PR #315: listZones, lookupZones, fetchZoneChanges now
default to .private since the public database only contains
_defaultZone, making .public a degenerate default. MistDemo callers
pass context.database / config.base.database explicitly so the
--database flag still drives the test runs.

Also repairs MistDemo test breakage from 7debe8d:
toUserContextCredentials() was removed but tests still referenced it;
rewritten against the replacement surface (toPrimaryCredentials embeds
apiAuth on .public, plus the new hasUserContextCredentials boolean).
The CredentialsValidationTests suite was deleted — it asserted
init-time validation that no longer exists under per-call credential
resolution; the equivalent .missingCredentials behavior is covered in
MistKitTests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* #312: gate @available(*,unavailable) on processDiscoverAllUserIdentitiesResponse to Swift 6.2+

Swift 6.1 rejects calls to an unavailable function from within another
unavailable function; 6.2 relaxed that rule. The internal helper
processDiscoverAllUserIdentitiesResponse is unavailable in lockstep with
its only caller — the also-unavailable CloudKitService.discoverAllUserIdentities() —
which built fine on 6.2+ but failed on Swift 6.1 with:

    error: 'processDiscoverAllUserIdentitiesResponse' is unavailable:
           Pending #28: discoverAllUserIdentities is not yet ready.

Wrap just the attribute in `#if swift(>=6.2)` so the body is shared and
6.1 compiles. Inline doc records the intent and the one-line cleanup
(delete the #if/#endif) once 6.1 is dropped from the matrix.

A `swiftlint:disable:next unavailable_function` is required because
swiftlint does not evaluate #if and otherwise sees a fatalError-only
function without the attribute.

Verified: swift build + swift test pass on Swift 6.1.3 (Linux container)
and on macOS Swift 6.2+ (475/475 tests).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* #315: split unhandled-response logging into debug (full body) + warning (type/status only)

CodeQL's swift/cleartext-logging flagged the existing warning logs
because lookupUsersByEmail(_:) propagates email-PII taint through the
response object. Move full \(response) interpolation to .debug so the
detail stays available for development without flowing into ops logs;
keep .warning at type(of:) + HTTP status code only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* #312: add --lookup-email / CLOUDKIT_LOOKUP_EMAIL to exercise users/lookup/email

LookupUsersByEmailPhase previously skipped whenever fetchCaller() didn't
return an email (which is the common case). Plumb a configurable lookup
email through TestIntegrationConfig / TestPrivateConfig → PhaseContext so
the phase can be driven against a known-discoverable iCloud account.
Falls back to caller email, then to a clearer skip message naming the
flag/env var.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: point CLAUDE.md lint section at mise (and Scripts/lint.sh)

swift-format / swiftlint / periphery are pinned in mise.toml; the
previous "requires swiftlint installation" wording led to PATH lookups
that fail in this repo. Replace with `mise exec --` invocations and
flag the full ./Scripts/lint.sh pipeline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* #315: address review punch list — invalidPrivateKey, recoverable unavailable response, supportsUserContextPhases derivation

- CloudKitError: add invalidPrivateKey(path:underlying:) so PEM-load failures
  carry the file path + original error instead of bare Foundation NSError.
  Wrap loadPEM() at the single call site in Credentials+TokenManager. Add
  PrivateKeyMaterial.filePath accessor for the diagnostic.

- processDiscoverAllUserIdentitiesResponse: replace fatalError with
  throw CloudKitError.unsupportedOperationType so a stray Swift 6.1 caller
  (where the @available cascade does not apply) gets a recoverable error
  instead of a crash.

- TestPrivateCommand: derive supportsUserContextPhases from
  config.base.hasUserContextCredentials, mirroring TestIntegrationCommand,
  so user-identity phases skip cleanly when web-auth env vars are absent.

- toPrimaryCredentials: replace try? with do/catch + stderr INFO line so
  operators see when web-auth is missing on a .public setup.

- CLAUDE.md: annotate discoverAllUserIdentities() as unavailable pending #28.

- CredentialsTokenManagerTests: fill the missing routing-matrix branches
  (user-context × .private/.shared, .shared + token-only) and cover the new
  .invalidPrivateKey path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@claude
Copy link
Copy Markdown

claude Bot commented May 11, 2026

Code Review — v1.0.0-beta.1

This is a substantial release PR (~50k lines changed across ~18 merged commits). The overall quality is high — the credentials redesign, per-call database routing, and pagination work are all solid. A few real bugs and a handful of nits worth addressing before the final release.


Overview of changes

  • Credentials API redesign: new Credentials / ServerToServerCredentials / APICredentials / PrivateKeyMaterial replacing legacy apiToken:/webAuthToken: initializers
  • Per-call database routing: CloudKitService no longer carries a database; every operation that supports multiple databases takes database: at the call site
  • User-identity endpoints: fetchCaller() (replaces deprecated fetchCurrentUser()), discoverUserIdentities(), lookupUsersByEmail(), lookupUsersByRecordName(); discoverAllUserIdentities() gated unavailable pending File Apple Feedback Assistant report: discoverAllUserIdentities returns HTTP 500 #28
  • Query pagination: queryRecords(...continuationMarker:) returning QueryResult, queryAllRecords(...) for auto-pagination
  • BatchSyncResult / OperationClassification: classify create-vs-update in modifyRecords batches
  • Error improvements: CloudKitError.invalidPrivateKey, missingCredentials, paginationLimitExceeded carrying accumulated records

Bugs

1. fetchExistingRecordNames and modifyRecords(_:classification:atomic:) are missing database: parameters

CloudKitService+Classification.swift lines 65–74 and 108–115:

public func fetchExistingRecordNames(
  recordType: String,
  limit: Int? = nil
) async throws(CloudKitError) -> Set<String> {
  let result: QueryResult = try await queryRecords(
    recordType: recordType,
    limit: limit ?? Self.maxRecordsPerRequest  // ← hardcodes .public via default
  )
  ...
}

public func modifyRecords(
  _ operations: [RecordOperation],
  classification: OperationClassification,
  atomic: Bool = false
) async throws(CloudKitError) -> BatchSyncResult {
  let records = try await modifyRecords(operations, atomic: atomic)  // ← hardcodes .public
  ...
}

Both the query and the modify in the classify-then-modify pattern silently default to .public. If a caller is working with the private or shared database:

  1. fetchExistingRecordNames scans the wrong database, so the classification is wrong.
  2. modifyRecords(_:classification:atomic:) then writes to the wrong database.

These should both take database: Database = .public and thread it through consistently.


2. queryRecords and lookupZones throw httpErrorWithRawResponse for client-side validation

CloudKitService+Operations.swift and CloudKitService+ZoneOperations.swift use:

throw CloudKitError.httpErrorWithRawResponse(
  statusCode: 400,
  rawResponse: "recordType cannot be empty"
)

...for preconditions like empty recordType or out-of-range limit. httpErrorWithRawResponse implies an HTTP response was received; using it for local validation errors makes httpStatusCode return 400 even when no network call was made. Recommend a dedicated CloudKitError.validationError(String) case, or at minimum using httpErrorWithDetails with a clear reason: and nil serverErrorCode.


Design / API concerns

3. fetchAllRecordChanges and fetchRecordChanges default to .public

Record changes are almost always relevant for .private or .shared databases; querying changes on the public database is the edge case. The CLAUDE.md notes this reasoning for zone operations defaulting to .private — the same logic applies to record changes. Current defaults are asymmetric with listZones / lookupZones / fetchZoneChanges.

4. public import OpenAPIRuntime in CloudKitService+Initialization.swift

// CloudKitService+Initialization.swift:31
public import OpenAPIRuntime

The public init(containerIdentifier:credentials:environment:transport: any ClientTransport) exposes ClientTransport from OpenAPIRuntime in MistKit's public API. This is an intentional design choice but it permanently ties MistKit's public surface to OpenAPIRuntime's versioning. Worth calling out explicitly in the release notes so consumers know to depend on both packages.

5. Cancellation maps to CloudKitError.underlyingError

In queryAllRecords and fetchAllRecordChanges, Task.checkCancellation() is caught and rethrown via mapToCloudKitError, which converts CancellationError to CloudKitError.underlyingError(CancellationError()). Callers cannot distinguish cancellation from other underlying errors without inspecting the boxed error. Consider a dedicated CloudKitError.cancelled case, or at least rethrowing CancellationError directly before the mapToCloudKitError catch.


Code quality / nits

6. Per-call Client construction

client(for:requiresUserContext:) builds a new Client (including a new middleware chain) on every dispatched operation. For high-throughput callers making many requests to the same database, this creates unnecessary allocations. A simple optimization would be to cache the resolved client keyed on (database, requiresUserContext) if/when performance becomes a concern. Not a blocker for beta, but worth a // TODO or issue.

7. Internal deprecated modifyRecords(operations:atomic:database:) in LookupOperations.swift

The file has a deprecated internal overload taking raw Components.Schemas.RecordOperation. Since it's internal and deprecated, it's fine now, but it will be a confusing stumbling block if someone reads the code. A @available(*, deprecated) notice is there — removing it entirely would be cleaner.

8. Credentials.init throws CredentialsValidationError (different type from CloudKitError)

Construction-time validation uses a separate CredentialsValidationError.empty. At call sites callers need to handle two distinct error domains: CredentialsValidationError on construction and CloudKitError on operations. The doc comment explains the debug-assert vs. release-throw split well, but this adds friction at the call site. Could wrap it as CloudKitError.invalidCredentials to unify the surface — though this is a subjective API preference and might break what's already been validated.


Positives worth calling out

  • The paginationLimitExceeded(maxPages:records:) error carrying accumulated records is excellent — callers can resume or surface partial results rather than losing all work.
  • The stuck-marker/stuck-token detection in both queryAllRecords and fetchAllRecordChanges is a nice guard against infinite loops.
  • PrivateKeyMaterial.loadPEM() normalizing \n sequences handles the env-var storage case cleanly.
  • The #if swift(>=6.2) guard on the @available(*, unavailable) cascade in processDiscoverAllUserIdentitiesResponse is a thoughtful cross-version compatibility fix.
  • The CodeQL cleartext-logging fix (downgrading full response interpolation to .debug) is the right call.
  • 178 test files with routing-matrix coverage for makeTokenManager is thorough.

Priority summary:

  • 🔴 Bug: fetchExistingRecordNames / modifyRecords(_:classification:atomic:) missing database: parameter
  • 🟡 Design: fetchAllRecordChanges / fetchRecordChanges default database
  • 🟡 Design: httpErrorWithRawResponse for client-side validation
  • 🟢 Nit: everything else

@claude
Copy link
Copy Markdown

claude Bot commented May 12, 2026

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

MistKit is a Swift Package for Server-Side and Command-Line Access to CloudKit Web Services. It targets cross-platform Swift (including Linux, WASI, and Windows) using modern Swift concurrency and code generated from Apple's CloudKit Web Services OpenAPI specification.

Key Project Context

  • Purpose: Provides a Swift interface to CloudKit Web Services (REST API) rather than the CloudKit framework
  • Target Platforms: Cross-platform including macOS, iOS, tvOS, watchOS, visionOS, Linux, WASI, and Windows
  • Default Branch: main
  • API Reference: The openapi.yaml file contains the OpenAPI 3.0.3 specification for Apple's CloudKit Web Services
  • Code Generation: Generated client code lives in Sources/MistKit/Generated/ and is not committed

Development Commands

Swift Package Commands

# Build the package
swift build

# Run tests
swift test

# Run tests with coverage
swift test --enable-code-coverage

# Build for release
swift build -c release

# Clean build artifacts
swift package clean

# Update dependencies
swift package update

# Resolve package dependencies
swift package resolve

# Generate Xcode project (if needed)
swift package generate-xcodeproj

OpenAPI Code Generation

# Generate OpenAPI client code (run this after modifying openapi.yaml)
./Scripts/generate-openapi.sh

# Or manually with swift-openapi-generator
swift run swift-openapi-generator generate \
    --output-directory Sources/MistKit/Generated \
    --config openapi-generator-config.yaml \
    openapi.yaml

Development Workflow

# Run specific test
swift test --filter TestClassName.testMethodName

# Run tests in parallel
swift test --parallel

# Show test output
swift test --verbose

# Format + lint
# swift-format, swiftlint, periphery, and swift-openapi-generator are pinned
# in mise.toml — do NOT invoke them from PATH directly. Run them THROUGH mise:
mise exec -- swift-format -i -r Sources/ Tests/
mise exec -- swiftlint              # lint
mise exec -- swiftlint --fix        # auto-fix

# Or run the full lint pipeline (build + swiftlint + header.sh + periphery):
./Scripts/lint.sh

MistDemo Commands

# MistDemo is located in Examples/MistDemo and must be run from there
cd Examples/MistDemo

# Build MistDemo
swift build

# Run MistDemo commands
swift run mistdemo --help
swift run mistdemo auth-token
swift run mistdemo current-user
swift run mistdemo query
swift run mistdemo lookup
swift run mistdemo create
swift run mistdemo update
swift run mistdemo modify
swift run mistdemo delete
swift run mistdemo upload-asset
swift run mistdemo lookup-zones
swift run mistdemo fetch-changes
swift run mistdemo demo-in-filter
swift run mistdemo demo-errors
swift run mistdemo test-integration
swift run mistdemo test-private

# Run with specific configuration
swift run mistdemo --config-file ~/.mistdemo/config.json query

Architecture Considerations

FieldValue Type Architecture

MistKit uses separate types for requests and responses at the OpenAPI schema level to accurately model CloudKit's asymmetric API behavior:

Type Layers:

  1. Domain Layer: FieldValue enum - Pure Swift types, no API metadata (Sources/MistKit/FieldValue.swift)
  2. API Request Layer: FieldValueRequest - No type field, CloudKit infers type from value structure
  3. API Response Layer: FieldValueResponse - Optional type field for explicit type information

Why Separate Request/Response Types?

  • CloudKit API has asymmetric behavior: requests omit type field, responses may include it
  • OpenAPI schema accurately models this asymmetry (openapi.yaml:867-920)
  • Swift code generation produces type-safe request/response types
  • Compiler prevents accidentally using response types in requests
  • Cleaner architecture without nil type values in conversion code

Generated Types:

  • Components.Schemas.FieldValueRequest - Used for modify, create, filter operations
  • Components.Schemas.FieldValueResponse - Used for query, lookup, changes responses
  • Components.Schemas.RecordRequest - Records in request bodies
  • Components.Schemas.RecordResponse - Records in response bodies

Conversion:

  • Request conversion: Extensions/OpenAPI/Components+FieldValue.swift converts domain FieldValueFieldValueRequest
  • Response conversion: Service/FieldValue+Components.swift converts FieldValueResponse → domain FieldValue

Modern Swift Features to Utilize

  • Swift Concurrency (async/await) for all network operations
  • Structured concurrency with TaskGroup for parallel operations
  • Actors for thread-safe state management
  • Result builders for query construction
  • Property wrappers for CloudKit field mapping

Package Structure

MistKit/
├── Sources/
│   └── MistKit/
│       ├── Generated/      # Auto-generated OpenAPI client code (not committed)
│       └── MistKitClient.swift  # Main client wrapper
├── Tests/
│   └── MistKitTests/
├── Scripts/
│   └── generate-openapi.sh # Script to generate OpenAPI code
├── openapi.yaml           # CloudKit Web Services OpenAPI specification
└── openapi-generator-config.yaml # Configuration for code generation

CloudKitService Operations

CloudKitService operations are split across focused extension files:

File Operations
CloudKitService+Initialization.swift initializer overloads (API token, web auth token, server-to-server)
CloudKitService+Operations.swift queryRecords, queryAllRecords, lookupRecords
CloudKitService+WriteOperations.swift modifyRecords, createRecord, updateRecord, deleteRecord
CloudKitService+ZoneOperations.swift listZones, lookupZones(zoneIDs:), fetchZoneChanges(syncToken:)
CloudKitService+SyncOperations.swift fetchRecordChanges(recordType:syncToken:), fetchAllRecordChanges(recordType:syncToken:)
CloudKitService+UserOperations.swift fetchCaller(), discoverUserIdentities(lookupInfos:), discoverAllUserIdentities() (unavailable — pending #28), lookupUsersByEmail(_:), lookupUsersByRecordName(_:), fetchCurrentUser() (deprecated, forwards to fetchCaller)
CloudKitService+AssetOperations.swift uploadAssets, requestAssetUploadURL
CloudKitService+AssetUpload.swift uploadAssetData
CloudKitService+RecordManaging.swift record-managing convenience surface
CloudKitService+Classification.swift operation classification (batch sync result tracking)
CloudKitService+ErrorHandling.swift error mapping helpers

Sync/Change Operations:

  • fetchRecordChanges(recordType:syncToken:)/records/changes — returns RecordChangesResult with records, syncToken, moreComing
  • fetchAllRecordChanges(recordType:syncToken:) — convenience wrapper that auto-paginates using moreComing
  • fetchZoneChanges(syncToken:)/zones/changes — returns ZoneChangesResult
  • lookupZones(zoneIDs:)/zones/lookup — returns [ZoneInfo]
  • discoverUserIdentities(lookupInfos:) → POST /users/discover — takes [UserIdentityLookupInfo], returns [UserIdentity]

User-Identity Operations (public DB + web-auth required):

  • fetchCaller()/users/caller — returns UserInfo. Replaces deprecated fetchCurrentUser() / users/current. Only valid against the public database with web-auth credentials.
  • discoverAllUserIdentities() → GET /users/discover — returns [UserIdentity] for every discoverable user in the caller's address book.
  • lookupUsersByEmail(_:) → POST /users/lookup/email — returns [UserIdentity].
  • lookupUsersByRecordName(_:) → POST /users/lookup/id — returns [UserIdentity].

In MistDemo, integration runs targeting these endpoints use PhaseContext.userContextService (a public+web-auth CloudKitService) which is built from CLOUDKIT_API_TOKEN + CLOUDKIT_WEB_AUTH_TOKEN regardless of the primary --database selection. The DatabaseConfiguration / AuthenticationCredentials types in Examples/MistDemo/Sources/MistDemoKit/Configuration/ enforce valid database+auth combinations at construction time.

Result Types (Sources/MistKit/Service/):

  • QueryResultrecords: [RecordInfo], continuationMarker: String?
  • RecordChangesResultrecords: [RecordInfo], syncToken: String?, moreComing: Bool
  • ZoneChangesResultzones: [ZoneInfo], syncToken: String?, moreComing: Bool
  • UserIdentityuserRecordName: String?, nameComponents: NameComponents?, lookupInfo: UserIdentityLookupInfo?
  • UserIdentityLookupInfoemailAddress: String?, phoneNumber: String?, userRecordName: String?
  • NameComponents — full personal name parts (givenName, familyName, nickname, etc.)

Protocols:

  • RecordTypeIterating (Sources/MistKit/Protocols/RecordTypeIterating.swift) — forEach(_ action:) to iterate over CloudKit record types; used by fetchAllRecordChanges

Key Design Principles

  1. Protocol-Oriented: Define protocols for all major components (TokenManager, NetworkClient, etc.)
  2. Dependency Injection: Use initializer injection for testability
  3. Error Handling: Use typed errors conforming to LocalizedError
  4. Sendable Compliance: Ensure all types are Sendable for concurrency safety

Logging

MistKit uses swift-log for cross-platform logging support, enabling usage on macOS, Linux, Windows, and other platforms.

Key Logging Components:

  • MistKitLogger - Centralized logging infrastructure with subsystems for api, auth, and network
  • Environment-based privacy control via MISTKIT_DISABLE_LOG_REDACTION environment variable
  • SecureLogging utilities for token masking and safe message formatting
  • Structured logging in LoggingMiddleware for HTTP request/response debugging (DEBUG builds only)

Logging Subsystems:

MistKitLogger.api      // CloudKit API operations
MistKitLogger.auth     // Authentication and token management
MistKitLogger.network  // Network operations

Helper Methods:

MistKitLogger.logError(_:logger:shouldRedact:)    // Error level
MistKitLogger.logWarning(_:logger:shouldRedact:)  // Warning level
MistKitLogger.logInfo(_:logger:shouldRedact:)     // Info level
MistKitLogger.logDebug(_:logger:shouldRedact:)    // Debug level

Privacy Controls:

  • By default, logs use SecureLogging.safeLogMessage() to redact sensitive information
  • Set MISTKIT_DISABLE_LOG_REDACTION=1 to disable redaction for debugging
  • Tokens, keys, and secrets are automatically masked in logged messages

Asset Upload Transport Design

⚠️ CRITICAL WARNING: Transport Separation

When providing a custom AssetUploader implementation:

  • NEVER use the CloudKit API transport (ClientTransport) for asset uploads
  • MUST use a separate URLSession instance, NOT shared with api.apple-cloudkit.com
  • MUST NOT share HTTP/2 connections between CloudKit API and CDN hosts
  • Custom uploaders should ONLY be used for testing or specialized CDN configurations
  • Production code should use the default implementation (URLSession.shared)

Why URLSession instead of ClientTransport?

Asset uploads use URLSession.shared directly rather than the injected ClientTransport to avoid HTTP/2 connection reuse issues:

  1. Problem: CloudKit API (api.apple-cloudkit.com) and CDN (cvws.icloud-content.com) are different hosts
  2. HTTP/2 Issue: Reusing the same HTTP/2 connection for both hosts causes 421 Misdirected Request errors
  3. Solution: Use separate URLSession for CDN uploads, maintaining distinct connection pools

Design:

  • AssetUploader closure type allows dependency injection for testing
  • Default implementation uses URLSession.shared.upload(_:to:) with separate connection pool
  • Tests provide mock uploader closures without network calls
  • Platform-specific: WASI compilation excludes URLSession code via #if !os(WASI)
  • CRITICAL: Custom uploaders must maintain connection pool separation from CloudKit API

Implementation Details:

  • AssetUploader type: (Data, URL) async throws -> (statusCode: Int?, data: Data)
  • Defined in: Sources/MistKit/Core/AssetUploader.swift
  • URLSession extension: Sources/MistKit/Extensions/URLSession+AssetUpload.swift
  • Upload orchestration:
    • uploadAssets() - Complete two-step upload workflow → Sources/MistKit/Service/CloudKitService+AssetOperations.swift
    • requestAssetUploadURL() - Step 1: Get CDN upload URL → Sources/MistKit/Service/CloudKitService+AssetOperations.swift
    • uploadAssetData() - Step 2: Upload binary data to CDN → Sources/MistKit/Service/CloudKitService+AssetUpload.swift

Future Consideration:
A ClientTransport extension could provide a generic upload method, but would need to:

  • Handle connection pooling separately for different hosts
  • Provide platform-specific implementations (URLSession, custom transports)
  • Maintain the same testability via dependency injection

FilterBuilder Extensions

FilterBuilder is split across extension files for maintainability:

  • Sources/MistKit/Helpers/FilterBuilder.swift — core comparators (EQUALS, NOT_EQUALS, LESS_THAN, etc.) and IN/NOT_IN
  • Sources/MistKit/Helpers/FilterBuilder+StringFilters.swift — string-specific: beginsWith, notBeginsWith, containsAllTokens
  • Sources/MistKit/Helpers/FilterBuilder+ListMemberFilters.swift — list-specific: listContains, etc.

IN/NOT_IN serialization: Uses ListValuePayload (Components.Schemas.ListValuePayload) to wrap array values. The fix in v1.0.0-alpha.5 ensures the correct value key structure is used when serializing list comparators.

CloudKit Web Services Integration

  • Base URL: https://api.apple-cloudkit.com
  • Authentication:
    • Public database: CLOUDKIT_KEY_ID + CLOUDKIT_PRIVATE_KEY or CLOUDKIT_PRIVATE_KEY_PATH → server-to-server signing
    • Private database: CLOUDKIT_API_TOKEN + CLOUDKIT_WEB_AUTH_TOKEN → web authentication
  • All operations should reference the OpenAPI spec in openapi.yaml
  • URL Pattern: /database/{version}/{container}/{environment}/{database}/{operation}
  • Supported databases: public, private, shared
  • Environments: development, production

Testing Strategy

  • Use Swift Testing framework (@Test macro) for all tests
  • Unit tests for all public APIs
  • Integration tests using mock URLSession
  • Use #expect() and #require() for assertions
  • Async test support with async let and await
  • Parameterized tests for testing multiple scenarios
  • See testing-enablinganddisabling.md for Swift Testing patterns

Asset Upload Testing

Integration Test Requirements:

  • Verify connection pool separation between CloudKit API and CDN
  • Test HTTP/2 connection reuse prevention
  • Validate 421 Misdirected Request error handling
  • Mock uploaders should simulate realistic HTTP responses

Test Files:

  • Tests/MistKitTests/Service/CloudKitServiceUpload/CloudKitServiceTests.Upload+*.swift
  • Tests/MistKitTests/Service/AssetUploadTokenTests.swift

MistDemo Integration Test Runner

Examples/MistDemo/Sources/MistDemoKit/Integration/ provides a live end-to-end test suite that runs against a real CloudKit container:

  • IntegrationTestRunner.swift — orchestrates all operations (query, create, update, lookup, upload, fetchChanges, lookupZones, discoverUserIdentities)
  • IntegrationTestData.swift — seed data for integration tests
  • IntegrationTestError.swift — typed errors for test failures
  • IntegrationTest.swift, PhasedIntegrationTest.swift, and Tests/ subdirectory — protocol-based phase pipeline introduced in Refactor IntegrationTestRunner into protocol-based phase pipeline (#254) #283

Run via swift run mistdemo test-integration or swift run mistdemo test-private (private database variant). Both commands require valid CloudKit credentials in the config file.

Important Implementation Notes

  1. Async/Await First: All network operations should use async/await, not completion handlers
  2. Codable Compliance: All models should be Codable with custom CodingKeys when needed
  3. CloudKit Types: Map CloudKit types (Asset, Reference, Location) to Swift types appropriately
  4. Error Context: Include request/response details in error types for debugging
  5. Pagination: Implement AsyncSequence for paginated results (queries, list operations)

OpenAPI-Driven Development

The Swift package uses Apple's swift-openapi-generator to create type-safe client code from the OpenAPI specification. Generated code is placed in Sources/MistKit/Generated/ and should not be committed to version control.

IMPORTANT: Never manually edit files in Sources/MistKit/Generated/. These files are auto-generated from openapi.yaml. Any manual edits will be lost when code is regenerated. Instead, modify openapi.yaml and regenerate using ./Scripts/generate-openapi.sh.

The openapi.yaml file serves as the source of truth for:

  • All available endpoints and their HTTP methods
  • Request/response schemas and models
  • Authentication requirements
  • Error response formats

Key endpoints documented in the OpenAPI spec:

  • Records: /records/query, /records/modify, /records/lookup, /records/changes
  • Zones: /zones/list, /zones/lookup, /zones/modify, /zones/changes
  • Subscriptions: /subscriptions/list, /subscriptions/lookup, /subscriptions/modify
  • Users: /users/caller, /users/discover (POST + GET), /users/lookup/email, /users/lookup/id
  • Assets: /assets/upload
  • Tokens: /tokens/create, /tokens/register

Reference Documentation

Apple's official CloudKit documentation is available in .claude/docs/ for offline reference during development:

When to Consult Each Document

webservices.md (289 KB) - CloudKit Web Services REST API

  • Primary use: Implementing REST API endpoints
  • Contains: Authentication, request formats, all endpoints, data types, error codes
  • Consult when: Writing API client code, handling authentication, debugging responses

cloudkitjs.md (188 KB) - CloudKit JS Framework

  • Primary use: Understanding CloudKit concepts and operation flows
  • Contains: Container/database patterns, operations, response objects, error handling
  • Consult when: Designing Swift types, implementing queries, working with subscriptions

testing-enablinganddisabling.md (126 KB) - Swift Testing Framework

  • Primary use: Writing modern Swift tests
  • Contains: @Test macros, async testing, parameterization, migration from XCTest
  • Consult when: Writing or organizing tests, testing async code

swift-openapi-generator.md (235 KB) - Swift OpenAPI Generator Documentation

  • Primary use: Understanding code generation configuration and features
  • Contains: Generator configuration, type overrides, middleware system, transport protocols, API stability
  • Consult when: Configuring openapi-generator-config.yaml, implementing middleware, troubleshooting generated code

See .claude/docs/README.md for detailed topic breakdowns and integration guidance.

MistDemo Documentation

  • Swift Configuration Reference (.claude/docs/mistdemo/swift-configuration-reference.md) - Guide for using Swift Configuration in MistDemo
  • Official Swift Configuration Docs (.claude/docs/https_-swiftpackageindex.com-apple-swift-configuration-1.0.0-documentation-configuration.md) - Full API reference

CloudKit Schema Language

cloudkit-schema-reference.md - CloudKit Schema Language Quick Reference

  • Primary use: Working with text-based .ckdb schema files
  • Contains: Complete grammar, field options, data types, permissions, common patterns, MistKit-specific notes
  • Consult when: Reading/modifying schemas, understanding indexing, designing record types

sosumi-cloudkit-schema-source.md - Apple's Official Schema Language Documentation

  • Primary use: Authoritative reference for CloudKit Schema Language
  • Contains: Full grammar specification, identifier rules, system fields, permission model
  • Consult when: Understanding schema fundamentals, resolving syntax questions

Comprehensive Schema Guides

For detailed schema workflows and integration:

  • AI Schema Workflow (Examples/CelestraCloud/.claude/AI_SCHEMA_WORKFLOW.md) - Comprehensive guide for understanding, designing, modifying, and validating CloudKit schemas with text-based tools
  • Quick Reference (Examples/SCHEMA_QUICK_REFERENCE.md) - One-page cheat sheet with syntax, patterns, cktool commands, and troubleshooting

Additional Notes

@claude
Copy link
Copy Markdown

claude Bot commented May 12, 2026

MistKit Logo

MistKit

SwiftPM
Swift Versions
Platforms
License
GitHub Workflow Status
Codecov
CodeFactor Grade
Maintainability
Documentation

A Swift Package for Server-Side and Command-Line Access to CloudKit Web Services

Table of Contents

Overview

MistKit provides a modern Swift interface to CloudKit Web Services REST API, enabling cross-platform CloudKit access for server-side Swift applications, command-line tools, and platforms where the CloudKit framework isn't available.

Built with Swift concurrency (async/await) and designed for modern Swift applications, MistKit supports all three CloudKit authentication methods and provides type-safe access to CloudKit operations.

Key Features

  • 🌍 Cross-Platform Support: Works on macOS, iOS, tvOS, watchOS, visionOS, and Linux
  • ⚡ Modern Swift: Built with Swift 6 concurrency features and structured error handling
  • 🔐 Multiple Authentication Methods: API token, web authentication, and server-to-server authentication
  • 🛡️ Type-Safe: Comprehensive type safety with Swift's type system
  • 📋 OpenAPI-Based: Generated from CloudKit Web Services OpenAPI specification
  • 🔒 Secure: Built-in security best practices and credential management

Getting Started

Installation

Add MistKit to your Package.swift:

dependencies: [
    .package(url: "https://github.com/brightdigit/MistKit.git", from: "1.0.0-beta.1")
]

Or add it through Xcode:

  1. File → Add Package Dependencies
  2. Enter: https://github.com/brightdigit/MistKit.git
  3. Select version and add to your target

Requirements

  • Swift 6.1+
  • Xcode 16.0+ (for iOS/macOS development)
  • Linux: Ubuntu 18.04+ with Swift 6.1+

Platform Support

Minimum Platform Versions

Platform Minimum Version Server-to-Server Auth
macOS 10.15+ 11.0+
iOS 13.0+ 14.0+
tvOS 13.0+ 14.0+
watchOS 6.0+ 7.0+
visionOS 1.0+ 1.0+
Linux Ubuntu 18.04+
Windows 10+

Quick Start

1. Choose Your Authentication Method

MistKit supports three authentication methods depending on your use case:

API Token (Container-level access)
import MistKit

let service = try CloudKitService(
    containerIdentifier: "iCloud.com.example.MyApp",
    apiToken: ProcessInfo.processInfo.environment["CLOUDKIT_API_TOKEN"]!
)
Web Authentication (User-specific access)
let service = try CloudKitService(
    containerIdentifier: "iCloud.com.example.MyApp",
    apiToken: ProcessInfo.processInfo.environment["CLOUDKIT_API_TOKEN"]!,
    webAuthToken: userWebAuthToken
)
Server-to-Server (Enterprise access, public database only)
let serverManager = try ServerToServerAuthManager(
    keyIdentifier: ProcessInfo.processInfo.environment["CLOUDKIT_KEY_ID"]!,
    privateKeyData: privateKeyData
)

let service = try CloudKitService(
    containerIdentifier: "iCloud.com.example.MyApp",
    tokenManager: serverManager,
    environment: .production,
    database: .public
)

2. Create CloudKit Service

do {
    let service = try CloudKitService(
        containerIdentifier: "iCloud.com.example.MyApp",
        apiToken: ProcessInfo.processInfo.environment["CLOUDKIT_API_TOKEN"]!
    )
    // Use service for CloudKit operations
} catch {
    print("Failed to create service: \\(error)")
}

Usage

Authentication

API Token Authentication

  1. Get API Token:

  2. Set Environment Variable:

    export CLOUDKIT_API_TOKEN="your_api_token_here"
  3. Use in Code:

    let service = try CloudKitService(
        containerIdentifier: "iCloud.com.example.MyApp",
        apiToken: ProcessInfo.processInfo.environment["CLOUDKIT_API_TOKEN"]!
    )

Web Authentication

Web authentication enables user-specific operations and requires both an API token and a web authentication token obtained through CloudKit JS authentication.

let service = try CloudKitService(
    containerIdentifier: "iCloud.com.example.MyApp",
    apiToken: apiToken,
    webAuthToken: webAuthToken
)

Server-to-Server Authentication

Server-to-server authentication provides enterprise-level access using ECDSA P-256 key signing. Note that this method only supports the public database.

  1. Generate Key Pair:

    # Generate private key
    openssl ecparam -genkey -name prime256v1 -noout -out private_key.pem
    
    # Extract public key
    openssl ec -in private_key.pem -pubout -out public_key.pem
  2. Upload Public Key: Upload the public key to Apple Developer Console

  3. Use in Code:

    let privateKeyData = try Data(contentsOf: URL(fileURLWithPath: "private_key.pem"))
    
    let serverManager = try ServerToServerAuthManager(
        keyIdentifier: "your_key_id",
        privateKeyData: privateKeyData
    )
    let service = try CloudKitService(
        containerIdentifier: "iCloud.com.example.MyApp",
        tokenManager: serverManager,
        environment: .production,
        database: .public
    )

Error Handling

MistKit provides comprehensive error handling with typed errors:

do {
    let service = try CloudKitService(
        containerIdentifier: "iCloud.com.example.MyApp",
        apiToken: apiToken
    )
    // Perform operations
} catch let error as CloudKitError {
    print("CloudKit error: \\(error.localizedDescription)")
} catch let error as TokenManagerError {
    print("Authentication error: \\(error.localizedDescription)")
} catch {
    print("Unexpected error: \\(error)")
}

Error Types

  • CloudKitError: CloudKit Web Services API errors
  • TokenManagerError: Authentication and credential errors
  • TokenStorageError: Token storage and persistence errors

Advanced Usage

Using AsyncHTTPClient Transport

For server-side applications, MistKit can use swift-openapi-async-http-client as the underlying HTTP transport. This is particularly useful for server-side Swift applications that need robust HTTP client capabilities.

import MistKit
import OpenAPIAsyncHTTPClient

// AsyncHTTPClient instance usually supplied by the Server API
let httpClient : HTTPClient

// Create the transport
let transport = AsyncHTTPClientTransport(client: httpClient)

// Use with CloudKit service
let service = try CloudKitService(
    containerIdentifier: "iCloud.com.example.MyApp",
    apiToken: apiToken,
    transport: transport
)

Adaptive Token Manager

For applications that might upgrade from API-only to web authentication:

let adaptiveManager = AdaptiveTokenManager(
    apiToken: apiToken,
    storage: storage
)

// Later, upgrade to web authentication
try await adaptiveManager.upgradeToWebAuthentication(webAuthToken: webToken)

Examples

Check out the Examples/ directory for complete working examples:

  • MistDemo: Web-based CloudKit authentication demo with automatic token capture
  • BushelCloud: Server-to-Server auth demo syncing macOS restore images, Xcode, and Swift versions
  • CelestraCloud: RSS reader demonstrating CloudKit query filtering, sorting, and web etiquette patterns

Documentation

License

MistKit is released under the MIT License. See LICENSE for details.

Acknowledgments

Roadmap

v1.0.0-alpha.1

v1.0.0-alpha.2

v1.0.0-alpha.3

v1.0.0-alpha.4

v1.0.0-alpha.5

v1.0.0-beta.1

Querying & Sync

  • Query pagination with continuation markers (#306) ✅
  • Operation classification & batch sync result tracking (#296) ✅

Authentication

  • AuthenticationMiddleware refactor — each Authenticator applies itself (#294) ✅
  • Strengthened environment & database configuration validation (#293) ✅

Error Handling

  • Typed TokenManagerError and safe RecordOperation conversion (#305) ✅
  • Move CloudKitResponseType defaults to protocol extension (#292) ✅

Concurrency

  • Replace custom AsyncChannel with swift-async-algorithms (#280) ✅

MistDemo

  • --database flag and demo-errors command (#282) ✅
  • Test split, CRUD commands, auth fix, native app (#271 / #273) ✅
  • IntegrationTestRunner refactored into protocol-based phase pipeline (#283) ✅

Tooling & CI

  • Test suite improvements (#286 / #287) ✅
  • CI updates for May 2026 (#277) ✅
  • Fail lint job when any command fails (#303) ✅

v1.0.0-alpha.X

v1.0.0

v1.1.0

Support


MistKit: Bringing CloudKit to every Swift platform 🌟

@claude
Copy link
Copy Markdown

claude Bot commented May 12, 2026

Code Review — v1.0.0 beta.1

This is a large, multi-feature release PR (52k+ additions). Review focuses on the new library surface introduced here rather than example app or CI changes.


Overview

Introduces five major features for the beta milestone:

  1. Credentials API — replaces the previous apiToken:/webAuthToken: initializers with a structured, routing-capable credential system.
  2. Per-call credential resolution — a single CloudKitService can now handle public S2S, web-auth, and user-context routes without a separate userContextService.
  3. Query paginationqueryRecords → QueryResult + queryAllRecords auto-pagination with stuck-marker detection and partial-result recovery.
  4. User-identity endpointsfetchCaller, lookupUsersByEmail, lookupUsersByRecordName (discoverAllUserIdentities correctly gated unavailable pending File Apple Feedback Assistant report: discoverAllUserIdentities returns HTTP 500 #28).
  5. Typed errors — new cases: paginationLimitExceeded, missingCredentials, invalidPrivateKey.

Code Quality

Credentials API (Authentication/Credentials/)

The Credentials / ServerToServerCredentials / APICredentials split is clean. The routing logic in Credentials+TokenManager.swift is well-documented and covers all meaningful combinations. The dual-mode validation (debug assert + release throws(CredentialsValidationError)) is the right pattern for a configuration object loaded from dynamic config.

Per-call dispatch (CloudKitService+ClientDispatch.swift)

Creating a fresh Client per operation is correct and ensures no auth-state leakage between calls. The fallback path (fixedTokenManagercredentials → throw) is clear and testable.

Pagination (CloudKitService+Operations.swift, +QueryPagination.swift)

The two-level API (single-page QueryResult + auto-paginating queryAllRecords) is well-designed. The maxPages guard throws paginationLimitExceeded with the accumulated records, allowing callers to resume — a thoughtful recovery pattern.

One edge case to be aware of: the stuck-marker guard (result.records.isEmpty && result.continuationMarker != nil && result.continuationMarker == currentMarker) only fires when the server returns the same marker with no records. If a server pathologically returns a new marker each page with empty records, the loop runs to maxPages before stopping. This is acceptable since maxPages already handles it, but worth a comment.

User operations (CloudKitService+UserOperations.swift)

All user-identity routes correctly hardcode .public and set requiresUserContext: true. The @available(*, unavailable) on discoverAllUserIdentities() with the #if swift(>=6.2) workaround for the 6.1 compiler limitation is correctly handled with a swiftlint:disable:next unavailable_function annotation.

Errors (CloudKitError.swift)

Comprehensive LocalizedError descriptions with coding-path detail in decodingError. The httpStatusCode computed property is a useful filtering hook.

Minor: public import Foundation on line 30 re-exports all Foundation symbols to MistKit consumers. This is likely intentional (since URLError appears in the public API), but it's a broad re-export — worth documenting as deliberate if so.

Test coverage

The extension-per-feature test organization mirrors the source structure well. The routing-matrix tests in CredentialsTokenManagerTests+*.swift cover the most important security-sensitive paths.


Issues

Validation errors use HTTP error codes (CloudKitService+Operations.swift:157–168)

guard !recordType.isEmpty else {
  throw CloudKitError.httpErrorWithRawResponse(statusCode: 400, rawResponse: "recordType cannot be empty")
}
guard effectiveLimit > 0 && effectiveLimit <= 200 else {
  throw CloudKitError.httpErrorWithRawResponse(statusCode: 400, rawResponse: "limit must be between 1 and 200...")
}

Using .httpErrorWithRawResponse for client-side precondition failures is misleading — no HTTP request was made. A dedicated .validationError(String) case (or at minimum .unsupportedOperationType) would make it easier to distinguish network errors from programmer errors.

Import style inconsistency

CloudKitService+Operations.swift uses bare import Foundation / import OpenAPIRuntime, while CloudKitService+ClientDispatch.swift uses internal import throughout. In Swift 6 these are semantically equivalent, but the inconsistency is worth tidying for consistency with the project's access-level-explicit style.


Suggestions

  1. Consider adding a validationError(String) case to CloudKitError to distinguish programmer mistakes from server responses.
  2. Add a comment on the stuck-marker guard noting the known gap (rotating marker / empty records) so future contributors don't remove it as dead code.
  3. The public import Foundation on CloudKitError.swift:30 is worth a short inline comment if re-exporting Foundation is intentional API policy.
  4. Deprecation message on the queryRecords → [RecordInfo] overload is clear; consider linking to a migration guide in the 1.0.0 release notes once the API stabilises.

Summary

Solid beta release. The Credentials API refactor and per-call dispatch are well-architected; pagination and user-identity surface are complete and tested. The issues above are minor — none are blockers. #28 (discoverAllUserIdentities) remains the main outstanding gap before the feature set is complete.


Reviewed by Claude (claude-sonnet-4-6)

@claude
Copy link
Copy Markdown

claude Bot commented May 12, 2026

v1.0.0 beta.1 Code Review

This is a large accumulation PR (1,006 files, ~52 k additions). I reviewed it by reading the key new source files directly. Overall the architecture is solid and the code quality is high. Notes below are roughly ordered by impact.


Overview

The PR lands several substantial features for the v1.0 milestone:

  • Per-call credential resolutionCredentials / ServerToServerCredentials / APICredentials replace the old per-service token model; a single service can now mix S2S public-DB reads with web-auth user-context calls.
  • User-identity endpointsfetchCaller, discoverUserIdentities, lookupUsersByEmail, lookupUsersByRecordName. discoverAllUserIdentities correctly marked @available(*, unavailable) pending File Apple Feedback Assistant report: discoverAllUserIdentities returns HTTP 500 #28.
  • PaginationqueryAllRecords with stuck-marker detection, plus QueryResult with continuation markers.
  • OperationClassification / BatchSyncResult — pre-fetch + classify pattern for create/update tracking in batch modify.
  • Typed errorsCloudKitError.invalidPrivateKey, paginationLimitExceeded(maxPages:records:), missingCredentials.

Test suite is thorough (178 files, well-organized by feature).


Issues

1. RecordInfo.isError sentinel is fragile (Service/Models/RecordInfo.swift:68)

A CloudKit record type literally named "Unknown" would produce a false positive. The doc comment acknowledges this, but it remains a real footgun for anyone who names their own record types. A stored Bool set during init(from:) that checks record.recordType == nil || record.recordName == nil would be both cheaper and unambiguous.


2. modifyRecords(_:classification:atomic:) is missing a database: parameter (Service/Extensions/CloudKitService+Classification.swift:108)

The overload delegates to modifyRecords(_:atomic:database:) without forwarding a database parameter, so it silently targets .public. Callers who want to classify creates/updates against a private or shared database are forced to call the two methods separately, defeating the helper's purpose. Suggested fix: add database: Database = .public and thread it through to the underlying call.


3. fetchExistingRecordNames is missing a database: parameter (Service/Extensions/CloudKitService+Classification.swift:65)

Same issue — it calls queryRecords(recordType:limit:) which defaults to .public. Every other write-path helper accepts database:; this being the exception is likely an oversight that will bite anyone doing private-DB sync classification.


4. Validation errors masquerade as HTTP errors (Service/Extensions/CloudKitService+Operations.swift:157)

Client-side guard failures (empty recordType, out-of-range limit) are thrown as .httpErrorWithRawResponse(statusCode: 400, ...). A caller testing error.httpStatusCode == 400 cannot distinguish a real HTTP 400 from a local validation rejection. A .validationError(reason: String) case would make the error surface cleaner.


5. CloudKitResponseProcessor methods are marked async without any await (Service/ResponseProcessing/CloudKitResponseProcessor.swift)

Every process*Response method is declared async throws but none contain an await expression. The async annotation propagates to callers for no benefit. Dropping async from the processor methods removes unnecessary overhead from Swift's concurrency machinery.


6. Empty-array edge case for lookup operations (Service/Extensions/CloudKitService+UserOperations.swift:121, 151)

lookupUsersByEmail([]) and lookupUsersByRecordName([]) will send an HTTP request with an empty users array. CloudKit's behavior for an empty lookup body is unspecified in the REST docs. A cheap early-exit avoids an unnecessary round trip and any undefined server behavior.


Minor Nits

  • Credentials.init double-check pattern (Authentication/Credentials/Credentials.swift:54) — The simultaneous assert + guard is intentional and documented, but a debug build crashes before the typed error is thrown, which may surprise library users who expect a recoverable error. Worth noting in the public doc comment.

  • discoverAllUserIdentities #if swift(>=6.2) unavailability cascade — The workaround is well-commented and correct. Delete the #if/#endif once Swift 6.1 is dropped from the matrix.

  • queryAllRecords default maxPages: 1000 — At 200 records/page this is up to 200,000 records in memory. A doc-comment note on memory implications would help callers choose an appropriate maxPages value.

  • modifyRecords(_:atomic:database:) vestigial error re-throw (WriteOperations.swift:82) — The catch let cloudKitError as CloudKitError branch before the catch-all is unnecessary now that typed-throws propagate the CloudKitError automatically. Harmless but dead code.


Security

No concerns. The CodeQL cleartext-logging flag (user email PII in response objects) was addressed by splitting full-body logging to .debug and keeping only type+status at .warning. PrivateKeyMaterial handles \n-escaped env-var PEMs correctly, and invalidPrivateKey attaches path diagnostics without logging them at high severity by default.


Summary

Issues 2 and 3 (missing database: on the classification helpers) are the most impactful since they silently route private/shared operations to the public database. Issue 1 (isError sentinel) is a correctness risk worth fixing before 1.0. The rest are improvements rather than blockers.

@claude
Copy link
Copy Markdown

claude Bot commented May 12, 2026

Code Review: PR #298 — v1.0.0 beta.1

Note on review scope: This is a very large PR (52,607 additions / 25,812 deletions across 300+ files). GitHub's diff API returned a 406 too-large response, and the CI environment uses a shallow clone, so this review is based on the PR metadata, the branch state visible locally, and the 44-file diff between the current branch tip and the draft PR #332.


Missing PR Description

The PR body is empty. For a major versioned release (v1.0.0 beta.1), a description should include:

  • Summary of what changed since the last release tag (e.g., alpha.5 or alpha.6)
  • Breaking changes and migration notes
  • New features added in this beta
  • Known issues or limitations

This is especially important for a 52K-line PR where reviewers cannot realistically read every diff line.


Branch State

The v1.0.0-beta.1 branch tip (a28ab3c — Resolve #313: paginationLimitExceeded carries accumulated records) is currently at the same commit as main. This means the branch has been kept in sync with main as sub-PRs were merged, which is good practice for a long-running release branch.


Code Quality Observations (from visible diff context)

The changes visible through the local diff between v1.0.0-beta.1 and the PR #332 merge ref reveal some patterns in the surrounding codebase that are relevant to review:

  1. AsyncHelpers.swift — withTimeoutAndSignals signature: The function signature was simplified from 'seconds: Double?' to 'seconds: Double', removing dead code paths where the optional was never nil. This is correct simplification per the project's no-unnecessary-fallback guideline.

  2. AuthTokenCommand refactoring context: The auth-token server scaffolding uses WebServer as a monolithic class. The upcoming PR Resolve #330: interactive MistDemo (web toggle + native app refresh) #332 extracts this into focused extension files (AuthTokenCommand+Routes.swift, AuthTokenIndexHTML+Script*.swift), which is consistent with the CloudKitService extension pattern documented in CLAUDE.md.

  3. Import access control: AuthTokenCommand+Routes.swift uses bare 'import' (public access) for all dependencies (AsyncAlgorithms, Hummingbird, etc.) rather than 'internal import' as used in the old WebServer.swift. For module-private code, 'internal import' is preferred to avoid leaking symbols — this is worth checking across the new files in this PR.

  4. Loopback authority validation: The 'isLoopbackAuthority(_:)' function in AuthTokenCommand.swift manually parses the HTTP authority header to enforce that the config endpoint is only accessible from loopback addresses. This is security-sensitive parsing — confirm it handles edge cases: missing port, IPv6 with zone IDs (e.g., [::1%25eth0]), and authority values with unexpected whitespace.

  5. Unstructured Task in addAuthEndpoint: The responseCompleteChannel.send(()) call is wrapped in an unstructured 'Task { ... }' with a 200ms sleep. This fire-and-forget Task is not cancelled if the outer operation throws. Consider using structured concurrency or documenting why the fire-and-forget is intentional here.


Test Coverage

Based on the file list visible in the diff context:

  • WebServerTests+CRUD.swift, WebServerTests+Index.swift, WebServerTests.swift are being removed along with WebServer.swift — confirm equivalent coverage exists in the new AuthTokenCommandTests+* files.
  • AuthTokenCommandTests+LoopbackAuthorityValidation.swift covers the loopback parsing logic, which is the right place to test security-sensitive parsing.

Suggestions

  • Add a PR description with a changelog summary before merging; this is the first public beta and will be referenced by users and in release notes.
  • Audit 'internal import' vs bare 'import' in the new files introduced by this PR to avoid unintended symbol exposure.
  • Add a test case for malformed or empty authority strings in the loopback validation (e.g., empty string, whitespace-only, IPv6 with zone ID).

@claude
Copy link
Copy Markdown

claude Bot commented May 12, 2026

Code Review — PR #298 (v1.0.0 beta.1)

Overview

This is a large beta release PR combining many changes: the core bug fix is #313paginationLimitExceeded now carries the accumulated records: [RecordInfo] so callers can recover partial results. Alongside this are a complete authentication subsystem rewrite, new user-identity operations, CI matrix updates, and example app refactors.


✅ What Looks Good

  • paginationLimitExceeded fix — the error now carries records: [RecordInfo] and the test in CloudKitServiceTests.QueryPagination+ErrorCases.swift correctly verifies the 5 accumulated records across 2 pages before the maxPages: 2 cap fires.

  • Stuck-marker / stuck-token detection — both queryAllRecords and fetchAllRecordChanges guard against infinite loops with the same continuation marker. The logic is readable and correct.

  • Cooperative cancellation — both pagination loops call Task.checkCancellation() before each page request. Important for long-running auto-paginate calls.

  • CloudKitError.errorDescription — thorough, structured messages for all cases including deep DecodingError unwrapping.

  • ServerToServerAuthenticator.authenticate — correctly buffers the request body once, signs it, and replays the buffered bytes so downstream middleware sees the same content regardless of HTTPBody iteration behaviour. The comment explaining why is valuable.


🐛 Bugs / Issues

1. SecureLogging redaction is now opt-in, but CLAUDE.md documents it as opt-out (security regression)

SecureLogging.swift reads:

guard ProcessInfo.processInfo.environment["MISTKIT_ENABLE_LOG_REDACTION"] != nil else {
    return message  // no redaction
}

The comment says "Redaction disabled by default." But CLAUDE.md states the opposite: "Set MISTKIT_DISABLE_LOG_REDACTION=1 to disable redaction for debugging." This means in production the env var MISTKIT_DISABLE_LOG_REDACTION is set to nothing, which means no redaction of tokens/keys in logs. One of the two is wrong; the CLAUDE.md description implies redaction should be on by default.

2. modifyRecords uses inconsistent error-mapping pattern

Every other operation calls mapToCloudKitError(_:context:), which properly unwraps ClientError.underlyingError before classifying URLErrors and DecodingErrors. modifyRecords does not:

} catch let cloudKitError as CloudKitError {
    throw cloudKitError
} catch {
    throw CloudKitError.underlyingError(error)   // ← skips ClientError unwrapping
}

A transport-level URLError wrapped in ClientError will become .underlyingError instead of .networkError. Use throw mapToCloudKitError(error, context: "modifyRecords") to match the rest of the service.

3. ServerToServerAuthenticator.init(keyID:pemString:) — locale-sensitive error classification

if error.localizedDescription.contains("PEM") || error.localizedDescription.contains("format") {
    throw TokenManagerError.invalidCredentials(.invalidPEMFormat(error))
}
throw TokenManagerError.invalidCredentials(.privateKeyParseFailed(error))

localizedDescription is locale-dependent; non-English systems may produce messages that don't contain "PEM". The fix is to always use .invalidPEMFormat for PEM parse errors (the call site is PEM-only) or to use String(reflecting: error) for the substring check.

4. KeyIDValidator vs ServerToServerAuthenticator length validation mismatch

KeyIDValidator.validate requires exactly 64 hex characters (SHA-256 fingerprint). ServerToServerAuthenticator.init requires ≥ 8 characters. A key with 40 hex characters will be accepted by the authenticator but rejected by the validator — an inconsistency that will confuse consumers.


⚠️ Significant Concerns

5. .claude/ planning artifacts committed to source control

.claude/ci-failures-pr298.md (428 lines) and .claude/plan-pr298.md (287 lines) are AI-assistant planning documents specific to this PR. They contain CI failure snapshots, internal review context, and a plan that will be stale the moment this PR lands. These should not be checked in — they bloat the repo and expose internal development workflow. Recommend updating .gitignore to exclude '.claude/**/*.md' (excluding docs/) or at minimum removing these two files before merging.

6. Bulk .claude/docs/ dump inflates repo history

The commit adds ~50K+ lines of fetched documentation (swift-log, swift-openapi, swift-configuration, cloudkitjs, cktool, etc.) under .claude/docs/. While useful for AI-assisted development, committing auto-fetched reference docs to the main source tree is not standard practice — these files can go stale and make git clone heavier. Consider a fetch-docs.sh script with a .gitignore covering '.claude/docs/*.md' instead.


💡 Minor Suggestions

7. ISO8601DateFormatter allocated per signing call

In ServerToServerAuthenticator+Signing.swift, a new ISO8601DateFormatter is created on every signRequest call. For a library that may sign hundreds of requests, this is unnecessary heap pressure. A static let or nonisolated(unsafe) static let works fine:

private static let iso8601Formatter: ISO8601DateFormatter = {
    let f = ISO8601DateFormatter()
    f.formatOptions = [.withInternetDateTime, .withTimeZone]
    return f
}()

8. Test coverage gap for fetchAllRecordChanges pagination overflow

CloudKitServiceTests.QueryPagination+ErrorCases.swift tests queryAllRecords overflow. There is no parallel test verifying that fetchAllRecordChanges also populates the records payload in the thrown paginationLimitExceeded error. Given that was the headline fix (#313), both code paths should have explicit coverage.

9. assertionFailure in release builds

In CloudKitResponseProcessor+Changes.swift and related files:

default:
    assertionFailure("Unexpected response case after error handling")
    throw CloudKitError.invalidResponse

assertionFailure crashes in debug/test but silently falls through to throw CloudKitError.invalidResponse in release builds. That's probably the intended behavior, but it's worth a comment noting that the assertionFailure is intentionally debug-only.


📊 CI Status (from .claude/ci-failures-pr298.md)

Per the committed CI snapshot, there are still a few failing checks:

  • CodeQL: 2 high-severity "Cleartext logging of sensitive information" alerts — likely related to finding Migrating code #1 above
  • codecov/patch: 15.61% of diff hit vs 25.58% target
  • watchOS: withTimeout cancels other tasks in group test is flaky

These should be resolved before merging to main.


Summary

The core change — paginationLimitExceeded carrying accumulated records — is solid. The blocking items before merge are: (1) the SecureLogging redaction regression, (2) the .claude/ artifact files, and (3) the remaining CI failures. The modifyRecords error-mapping inconsistency and PEM error classification are lower priority but worth fixing before beta.1 ships as a public API.

@claude
Copy link
Copy Markdown

claude Bot commented May 13, 2026

Code Review: v1.0.0 beta.1 (#298)

This is a substantial release with 52K additions spanning the core library, CI infrastructure, and two example apps. The overall architecture is sound and the code quality is high. Below are the key findings.


✅ Strengths

  • Asset upload transport isolation is correctly implemented. URLSession.shared is used for CDN uploads instead of the injected ClientTransport, preventing the HTTP/2 421 Misdirected Request issue documented in CLAUDE.md. The closure-based AssetUploader injection is clean and testable.
  • APITokenAuthenticator validates against the real 64-char hex format via regex — solid input validation at the boundary.
  • CI matrix is well-structured with intelligent scope reduction on feature branches and full coverage on main/semver tags.
  • Test suite is comprehensive with dedicated files for asset upload validation, error handling, and platform-specific guards (Platform.isWasm).
  • Typed throws (throws(CloudKitError)) throughout the service layer give callers precise error information.

🔴 High Priority

1. Log Redaction Is Opt-In — Sensitive Data Logged by Default

File: Sources/MistKit/Authentication/Internal/SecureLogging.swift:77

safeLogMessage() returns the message unredacted unless MISTKIT_ENABLE_LOG_REDACTION is set. This is inverted from the safe default.

// Current: redaction only happens when env var is set
guard ProcessInfo.processInfo.environment["MISTKIT_ENABLE_LOG_REDACTION"] != nil else {
    return message  // returns raw message with tokens, keys, etc.
}

For a library used in server-side contexts, logs are typically forwarded to aggregators. An operator who doesn't know to set MISTKIT_ENABLE_LOG_REDACTION will silently leak tokens.

Recommendation: Invert the default — redact by default, opt-out with MISTKIT_DISABLE_LOG_REDACTION=1 (which is what the CLAUDE.md already documents, but the code doesn't match).

2. Error Logs Bypass Redaction Entirely

File: Sources/MistKit/Service/Extensions/CloudKitService+ErrorHandling.swift (9 call sites)

All error-path log calls use shouldRedact: false:

MistKitLogger.logError(errorMessage, logger: logger, shouldRedact: false)

Error messages can contain raw API responses, which may include record data, field values, or request parameters. These will be logged unredacted regardless of environment variable settings.


🟡 Medium Priority

3. Silent Record Conversion Failures in modifyRecords

File: Sources/MistKit/Service/Extensions/CloudKitService+WriteOperations.swift:80

return modifyResponse.records?.compactMap { RecordInfo(from: $0) } ?? []

If any record fails to convert from the response, it is silently dropped. The caller receives a shorter array than expected with no indication records were lost. Consider logging a warning or throwing when the converted count differs from the response count.

4. Stuck-Token Detection Breaks Silently

File: Sources/MistKit/Service/Extensions/CloudKitService+SyncOperations.swift:198-200

if result.records.isEmpty && result.moreComing && result.syncToken == currentToken {
    break  // exits loop silently
}

When this triggers, fetchAllRecordChanges returns the records gathered so far with no indication the sync was incomplete. The caller has no way to distinguish a clean sync from a truncated one (the return type is (records: [RecordInfo], syncToken: String?)). Consider throwing CloudKitError.stuckPaginationToken or returning a boolean isComplete flag.

5. ci skip Has No Effect on Pull Requests

File: .github/workflows/MistKit.yml:77,130,164,199,243,318

if: ${{ !contains(github.event.head_commit.message, 'ci skip') }}

github.event.head_commit is null in pull_request events. !contains(null, 'ci skip') evaluates to true, so jobs always run on PRs regardless of [ci skip] in the commit message. The intent to skip CI is silently ignored. Use github.event.commits[0].message or check for the PR title instead.


🟢 Low Priority / Observations

6. PrivateKeyMaterial — Escaped Newline Handling

File: Sources/MistKit/Authentication/Credentials/PrivateKeyMaterial.swift

The replacingOccurrences(of: "\\n", with: "\n") call handles the common case of environment variables with literal \n characters, but it won't handle \r\n or other line endings. Worth a brief comment explaining the environment variable scenario this addresses.

7. AdaptiveTokenManager.hasCredentials Doesn't Validate Format

File: Sources/MistKit/Authentication/TokenManagers/AdaptiveTokenManager.swift:47-51

public var hasCredentials: Bool {
    get async { !apiToken.isEmpty }
}

hasCredentials returns true for any non-empty string, but validateCredentials() performs proper format validation. Callers checking hasCredentials before proceeding may get a false positive. Consider delegating to validateCredentials() or noting the distinction in documentation.

8. Deprecated queryRecords Silently Drops Pagination State

File: Sources/MistKit/Service/Extensions/CloudKitService+Operations.swift

The deprecated queryRecords() overload that returns [RecordInfo] discards the continuationMarker. Callers migrating from this API to the new one may not realize they were receiving paginated results without moreComing awareness. A deprecation message that explicitly mentions this would help.


Summary

Area Status
Asset upload transport isolation ✅ Correctly implemented
Authentication / token validation ✅ Solid
Test coverage ✅ Comprehensive
CI/CD structure ✅ Good, minor head_commit bug
Log redaction default 🔴 Needs inversion
Error log redaction bypass 🔴 Review shouldRedact: false usage
Silent pagination failures 🟡 Consider explicit error
Silent record conversion drops 🟡 Add logging/assertion

The core design and architecture are solid. The redaction default is the most important thing to address before GA since it affects all server-side deployments.

@claude
Copy link
Copy Markdown

claude Bot commented May 13, 2026

Code Review: PR #298 — v1.0.0 beta.1

This is a comprehensive review of the v1.0.0 beta.1 release PR (52K+ additions, 25K+ deletions, 300+ files). The architectural direction is excellent. I focused on issues that could affect API consumers or correctness.


Overview

This PR delivers the v1.0.0 beta.1 milestone with several significant improvements:

  • Credentials architecture refactor — orthogonal Credentials / ServerToServerCredentials / APICredentials with per-call token manager resolution
  • Per-call database dispatchdatabase: is now a call-site argument, not a service property
  • User identity endpointsfetchCaller(), lookupUsersByEmail(), lookupUsersByRecordName(), discoverUserIdentities()
  • Query paginationqueryRecords() -> QueryResult + queryAllRecords() with continuation markers
  • Operation classificationOperationClassification + BatchSyncResult for tracking creates vs. updates
  • Improved error handling — typed TokenManagerError, CloudKitError.invalidPrivateKey, CredentialsValidationError
  • CodeQL security fix — email PII in response bodies demoted from .warning to .debug

Issues

1. RecordInfo.isError sentinel is fragile [medium]

isError returns true when recordType == "Unknown", but "Unknown" is also used as the fallback for recordName. Any legitimate record type actually named "Unknown" would be falsely flagged as an error. The comment in the file acknowledges this, but it is a real hazard in production.

Suggestion: Use the conjunction as the sentinel (recordType == "Unknown" && recordName == "Unknown"), or track error state via a separate stored Bool set during init(from:) rather than through inference.


2. RecordManaging.queryRecords hard-codes limit: 200 [minor]

The RecordManaging protocol conformance at CloudKitService+RecordManaging.swift:48 uses a hard-coded limit of 200 while the service defaultQueryLimit is 100. This inconsistency may surprise protocol consumers who expect uniform defaults.


3. Local validation errors use httpErrorWithRawResponse [minor]

In CloudKitService+Operations.swift:157-163, pre-flight validation failures throw CloudKitError.httpErrorWithRawResponse(statusCode: 400, ...). No HTTP request was made when these fire, so calling the error httpErrorWithRawResponse is semantically misleading — a caller checking error.httpStatusCode will get 400 back even though the request never left the process.

Suggestion: Add a CloudKitError.validationError(String) case, or at minimum use .invalidResponse with a descriptive message.


4. discoverAllUserIdentities unavailability workaround adds compiler complexity [minor]

The #if swift(>=6.2) gate on processDiscoverAllUserIdentitiesResponse plus the required swiftlint:disable:next unavailable_function suppression is a code smell. For a v1.0.0 beta, simply omitting the method body and internal helper until issue #28 resolves would be cleaner and eliminate both the version gate and the lint suppression.


5. PrivateKeyMaterial.loadPEM() is synchronous [minor]

String(contentsOfFile:encoding:) at PrivateKeyMaterial.swift:65 blocks a cooperative thread. For a tiny PEM file this is practically harmless, but async-first callers may not expect synchronous I/O here. Worth documenting the behaviour or wrapping in a detached task.


Security

  • CodeQL fix — moving full response interpolation that could carry email PII from .warning to .debug is the right call.
  • MISTKIT_DISABLE_LOG_REDACTION — this env var globally disables redaction. It should be clearly documented as a development-only flag, unsuitable for production deployments.
  • Inline PEM storagePrivateKeyMaterial.raw(String) keeps the PEM as a plain string. Callers who inadvertently serialize ServerToServerCredentials (e.g., to a log or JSON encoder) would expose the private key. A doc note on raw reminding callers not to serialize it would be a useful guard.

Test Coverage

Strong overall — 178 test files with per-feature suites for all major new surfaces.

  • CredentialsTokenManagerTests covers the full routing matrix (database x credential x user-context). Excellent.
  • CloudKitServiceTests.QueryPagination has good breadth including stuck-marker detection and the paginationLimitExceeded error path.
  • Minor: the guard #available(macOS 11.0, ...) ... return boilerplate repeats in every single test function. Using .enabled(if:) at the @Suite level would reduce noise.

Performance

  • Per-call Client construction — a new Client(...) is built for every dispatched operation (required for per-call credential resolution). The underlying URLSessionTransport handles connection pooling so this is functionally correct. Not a blocking concern for beta.
  • maxPages: 1_000 default — well-documented, but a single queryAllRecords call can make up to 1,000 sequential HTTP requests. Consider whether a lower default (e.g., 100) with callers explicitly opting into larger values is safer ergonomics.

Misc

  • .claude/plan-pr298.md and .claude/ci-failures-pr298.md are in the diff — internal planning artifacts should not land on main. Adding .claude/ to .gitignore would prevent future leakage.
  • Empty PR body — for a v1.0.0 release PR a changelog or feature summary would make the history far more useful.

Overall

The architecture is sound and this is a strong beta release. The Credentials + per-call dispatch design is notably clean. The issues above are all minor-to-medium; none are blockers for beta, but issues 1 (fragile isError), 3 (misleading validation error type), and the .claude/ artifacts on main are worth addressing before the final v1.0.0 cut.

Generated by Claude Sonnet 4.6

@claude
Copy link
Copy Markdown

claude Bot commented May 14, 2026

Code Review: PR #298 (v1.0.0 beta.1)

This is a large accumulation of changes (100 files, +52k/-26k lines) across the Examples layer, CI infrastructure, and tooling. The core MistKit library (Sources/) is unchanged. Review focuses on the substantive new code in the examples.


OVERVIEW

The PR bundles several distinct improvements:

  • CelestraCloud: reporting pipeline refactor (UpdateCommand+Reporting, UpdateSummary, UpdateReport JSON output, FeedUpdateProcessor+Fetch extraction)
  • CelestraCloud: CloudKitRecordOperating protocol formalisation with Mutex-backed mock for testability
  • BushelCloud: KeyIDValidator + PEMValidator input validation before cryptographic operations
  • MistDemo: Initial SwiftUI app scaffold (MistDemoApp.swift)
  • CI: New MistDemo.yml workflow, mise.toml adoption replacing Mintfile across examples
  • Config: Deprecation of environment-variable-only CelestraConfig.createCloudKitService()

WHAT IS GOOD

  1. KeyIDValidator (BushelCloud) is excellent defensive input validation. Checking for empty, surrounding whitespace, wrong length, and non-hex characters gives clear, actionable error messages before any cryptographic work or file I/O. The whitespace check (trimmed != keyID) is especially user-friendly.

  2. MockCloudKitRecordOperator uses Mutex from the Synchronization module for thread safety under Swift Testings default parallel execution. This is the right approach and the implementation is clean.

  3. Extracting UpdateCommand+Reporting.swift and FeedUpdateProcessor+Fetch.swift is a good separation of concerns. Both files are focused and stay within the project style.

  4. UpdateReport is well-modelled: Codable, Sendable, nested structs with clear ownership. The successRate computed on init (rather than as a computed property) avoids repeated floating-point division.

  5. The CloudKitRecordOperating conformance on CloudKitService correctly forwards to the public-database overload and uses typed throws(CloudKitError). The Sendable constraint on the protocol is appropriate for async usage.

  6. ConfigurationError as a proper LocalizedError struct (rather than a raw string or fatalError) keeps error handling at the call site clean.


ISSUES AND SUGGESTIONS

  1. COPYRIGHT HEADERS (medium): Three new test/mock files in this PR are dated 2025 instead of 2026:

    • Tests/CelestraCloudTests/Errors/CelestraErrorTests+Description.swift
    • Tests/CelestraCloudTests/Errors/CelestraErrorTests+RecoverySuggestion.swift
    • Tests/CelestraCloudTests/Mocks/MockCloudKitRecordOperator.swift
      PR chore: update CelestraCloud test copyright headers 2025 → 2026 #331 only fixes two of the pre-existing files and will not catch these.
  2. MIST DEMO APP USES @StateObject (low): MistDemoApp.swift line 35 uses @StateObject, which is the pre-Observation API. The PR title for Resolve #328: MistDemoApp CloudKit refresh (CKRecord-first, @Observable) #339 explicitly calls out moving to @observable. If NativeCloudKitService is intended to be an ObservableObject, this is consistent but inconsistent with the direction stated in Resolve #328: MistDemoApp CloudKit refresh (CKRecord-first, @Observable) #339. If it already conforms to Observable, the entry point should use @State instead.

  3. HARDCODED PUBLIC DATABASE IN PROTOCOL CONFORMANCE (low): CloudKitRecordOperating.swift lines 96-126 hardcode database: .public in both queryRecords and queryAllRecords forwarding methods. This is fine for CelestraCloud which only ever targets the public database, but the conformance is on CloudKitService itself (not a wrapper), so any caller using the protocol against a service configured for private/shared will silently query the wrong database. A short doc comment on the protocol methods noting the public-database constraint would prevent future confusion.

  4. COMMENTS THAT EXPLAIN WHAT (style): FeedUpdateProcessor+Fetch.swift line 47 has // Sync articles via ArticleSyncService. CLAUDE.md is explicit: Do not explain WHAT the code does. Same file line 61 repeats the pattern. The CelestraConfig.swift deprecated method (lines 79-98) has step-by-step // Read private key from file / // Determine environment / etc. comments that duplicate what the code already says. These do not convey a non-obvious constraint and should be removed.

  5. NO PR DESCRIPTION (medium): This PR has an empty body. For a beta release that accumulates multiple feature areas it would help reviewers and release-note readers to have at least a brief summary of what changed and what issues it resolves. The plan is in .claude/plan-pr298.md but that is not surfaced in the PR body.

  6. DEPRECATED METHOD BODY (low): CelestraConfig.createCloudKitService() is correctly marked @available(*, deprecated) but its body reads CLOUDKIT_CONTAINER_ID from ProcessInfo rather than calling the new overload that accepts ValidatedCloudKitConfiguration. If the deprecated path is ever invoked it diverges from the canonical path (e.g. skips PEMValidator.validate). Consider delegating to the non-deprecated path from within the deprecated method or removing the body entirely if callers are already migrated.


SECURITY

No concerns. Key ID validation before file I/O and cryptographic work is correct. JSON report writes are user-specified paths from the CLI, appropriate for a local CLI tool. Private key files are read with existence checks and typed errors (BushelCloudKitError.privateKeyFileNotFound, .privateKeyFileReadFailed).


TEST COVERAGE

The new error-description and recovery-suggestion tests (CelestraErrorTests+Description.swift, +RecoverySuggestion.swift) provide good coverage of LocalizedError conformance. The MockCloudKitRecordOperator properly captures both query and modify calls for assertion. The split into extension files per-test-category is clean and matches project convention.


SUMMARY

The code is overall high quality and follows project conventions well. The most actionable items before merge are (1) fix copyright headers on the three new 2025-dated files, (2) add a PR description, and (3) remove the what-not-why comments in FeedUpdateProcessor+Fetch.swift and the deprecated CelestraConfig method body.

@claude
Copy link
Copy Markdown

claude Bot commented May 14, 2026

Code Review: v1.0.0 beta.1

1006 files changed (+52607/-25812). Review covers substantive source changes; generated code and docs excluded.


Overview

Two major changes:

  1. Database type simplified — removes PublicAuthPreference and CredentialAvailability, replacing per-call auth-mode control with a requiresUserContext: Bool parameter threaded through the credential dispatcher.
  2. AuthTokenCommand rewrite — replaces the WebServer/WebBackend/WebResponse scaffolding with a lean Hummingbird router using AsyncChannel for the token hand-off.

Both are the right call for beta.1. The old PublicAuthPreference encoded signing choices in the wrong layer. The server rewrite removes ~900 lines of indirect scaffolding and replaces it with ~400 lines that are easy to follow end-to-end.


Core Library (Sources/MistKit/)

Database.swift — simplified to String RawRepresentable

Good. rawValue replaces the custom pathSegment, and Hashable is synthesized automatically. One nit: import Foundation was added to this file but Database uses no Foundation types — remove it.

Credentials+TokenManager.swift — auth resolution rewritten

Clearer logic overall. The old makeServerToServerManager / makeAPITokenManager helpers are gone; S2S key-loading is now inlined in makePublicTokenManager(). Fine for now, but if a third credential path ever needs S2S the code will need deduplication. A private loadServerToServerManager() helper would keep it DRY.

Loss of per-call auth-mode control

The old PublicAuthPreference let callers force user-attributed writes with database: .public(.requires(.webAuth)) even when S2S credentials were configured. That escape hatch is gone — the public DB now always prefers S2S → web-auth → API token with no per-call override. The only "force web-auth" path is the internal requiresUserContext: true flag used by user-identity operations.

A server with both S2S and web-auth configured that wants user-attributed records to the public DB can no longer express that intent. Consider documenting whether this is intentional before v1.0.0 final.

CloudKitError.missingCredentialsavailability label removed

Simpler error message is acceptable. However, CloudKitErrorTests.swift was deleted with no replacement — see Test Coverage below.

Default database: Database = .public

Appropriate default. Callers targeting .private or .shared must still pass the argument explicitly, which is correct.


MistDemo (Examples/MistDemo/)

AuthTokenCommand rewrite

The AsyncChannel-based design is cleaner than the old WebAuthTokenStore / TaskGroup approach. The buildRouter / addConfigEndpoint / addAuthEndpoint / openBrowserIfNeeded / waitForToken split makes the flow easy to follow.

Two observations:

  1. responseCompleteChannel timing is fragile. addAuthEndpoint signals the channel inside a detached Task with a 200 ms sleep, then execute() cancels the server after receiving it. If 200 ms elapses before the response body reaches the client socket, the server cancels mid-flight. A more robust signal would fire after writer.finish(nil) rather than on a timer.

  2. isLoopbackAuthority untested edge case. One untested input: "localhost:" (trailing colon, no port). split(separator: ":").first returns "localhost", which the allowlist accepts. Functionally fine, but worth adding to the acceptance test list to make intent explicit.


Test Coverage

Gaps introduced by this PR:

  • CloudKitErrorTests.swift was deleted with no replacement. It tested that CloudKitError.missingCredentials.errorDescription contained expected substrings. Add a minimal test verifying errorDescription is non-empty and contains the database rawValue and reason string.

  • The old requiresS2S/WebAuth...ThrowsPreferenceRequired tests are gone. The new user-context tests cover that rejection path, but no test covers the fallthrough throw at the bottom of makePublicTokenManager() (i.e., Credentials with neither serverToServer nor apiAuth). The initializer currently enforces at least one set, so if the path is truly unreachable, document it; otherwise test it.

Tests removed appropriately: Query+ExistingRecordNames.swift and the WebServer* suite were testing deleted abstractions.


Minor Issues

  • AuthTokenCommandTests+Timeout.swift: Removing withKnownIssue(isIntermittent: true) is fine if visionOS/watchOS simulators are not in MistDemo CI scope. If they are, the original flakiness may resurface.

  • authResponseEncodesCorrectly test: The only assertion is !data.isEmpty. Assert at least one expected JSON key (e.g., "userRecordName") to make the test meaningful.

  • Source-breaking change: database: Database = .public is a breaking change for callers who previously passed an explicit argument. Ensure the changelog notes it.


Security

The isLoopbackAuthority exact-match allowlist on /api/config (which vends the API token) is the correct mitigation. No issues found.


Summary

Area Status
Database simplification Good
Auth credential resolution Good — minor DRY opportunity in S2S key loading
Per-call auth-mode control Feature removed — document intent before v1.0.0 final
AuthTokenCommand rewrite Good — responseCompleteChannel timing is fragile
Test coverage Gaps: CloudKitError description tests, unconfigured-public fallthrough
Security No concerns

@claude
Copy link
Copy Markdown

claude Bot commented May 14, 2026

Code Review: v1.0.0 beta.1 (#298)

Note: This PR has 1,006 changed files (+52,607/−25,812). GitHub's diff API returns a 406 too-large response, and the CI environment is a shallow clone, so this review is based on reading current source files in the working tree plus PR metadata. Seventeen prior Claude review passes are already in this thread — this pass focuses on gaps not yet covered and a high-level release readiness assessment.


Release Readiness Concerns

🔴 No PR description. The body field is empty. For a v1.0.0 beta.1 accumulation PR of this scope, the description is the primary artifact reviewers, changelog generators, and future blame readers rely on. At minimum it should list:

  • The sub-PRs / issues this resolves
  • The breaking API changes (if any)
  • Upgrade notes for consumers currently on alpha

🟡 Patch coverage at 23.6% (per Codecov). 791 lines of new code have no test coverage. Before promoting to a stable 1.0.0, this gap should be addressed. Acceptable for a beta tag, but worth tracking in the milestone.

🟡 CodeRabbit skipped (300-file limit exceeded). No automated review tool has been able to fully scan the diff. This means any subtler issues in the later alphabetical files were missed.


Structural Observations

Size: 1,006 changed files is unusually large for a single review unit. Even for a "squash of many sub-PRs" workflow, the standard practice is to merge sub-PRs to an integration branch and keep each sub-PR reviewable. Consider whether the next beta cycle can be structured with more atomic PRs (e.g., one per major feature area: credentials, pagination, CI, examples) so reviewers can give meaningful approval.

CI confidence: Multiple automated checks (GitHub Advanced Security, Codecov) are reporting, which is a good signal. Confirm all required status checks are green before merging.


What the Prior Reviews Have Flagged

Across the 17 existing review comments, the recurring findings are:

  1. paginationLimitExceeded carrying accumulated records: [RecordInfo] — surfaced as a correctness improvement
  2. Credentials redesign (PublicAuthPreference, per-call auth routing) — generally praised, no blocking issues found
  3. Asset upload transport isolation — confirmed correct per CLAUDE.md guidance
  4. BatchSyncResult / OperationClassification pattern — no issues raised

No review has flagged a blocking bug in the core library surface.


Recommendation

This PR appears structurally sound for a beta tag. The two items to address before 1.0.0 stable:

  1. Add a PR description — even a bullet list of included sub-PRs is sufficient.
  2. Track the coverage gap — 791 uncovered lines should be filed as issues against the milestone.

Reviewed by Claude (claude-sonnet-4-6)

@claude
Copy link
Copy Markdown

claude Bot commented May 14, 2026

Code Review — PR #298: v1.0.0 beta.1

Overview: This is a large release-candidate PR (~52k additions / ~26k deletions across 300+ files) promoting BushelCloud, CelestraCloud, and MistDemo examples to a beta-1-ready state, alongside CI matrix, devcontainer, and tooling updates. The core Sources/MistKit/ library is unchanged.


CI Status

Six checks were failing at time of the original review snapshot; all but one are now green:

Check Was Now
Test BushelCloud on Ubuntu ❌ compile error
Test CelestraCloud on Ubuntu ❌ deprecation-as-error
Build on macOS / watchOS ❌ test flake
CodeQL ❌ 2 high-severity alerts
CodeFactor ❌ transient tool error
codecov/patch ❌ 15.61% < 25.58% target ❌ still failing

What Was Fixed Well

Compile blockers (BushelCloud)

  • database: .public argument correctly removed from both CloudKitService.init call sites (BushelCloudKitService.swift:113, 151). The init signature change now forces per-call database selection, and the fix matches that contract.
  • fetchExistingRecordNames now passes desiredKeys: [] — eliminates the overfetch of all fields just to read record names. Good change.
  • Log line at :247 trimmed to recordName=\(result.recordName) only — previously the misleading reason=\(result.recordType) printed the record type as if it were the failure reason.

Deprecation migration (CelestraCloud)

  • queryFeeds correctly migrated to queryAllRecords(pageSize:) — removes the deprecated call that was treated as an error under -warnings-as-errors.
  • deleteAllFeeds correctly migrated to queryRecords -> QueryResult with a repeat/until loop on continuationMarker. Pagination logic reads cleanly.

watchOS flake

  • cancelsOtherTasks test wrapped with withKnownIssue(isIntermittent: true), matching the existing pattern in AsyncHelpersTests+Timeout.swift. Correct and minimal fix.

Thread safety (MockCloudKitRecordOperator)

  • Converted from nonisolated(unsafe) mutable shared state to a Mutex<State> backed struct. Thread-safe under Swift Testing's default parallel execution. The Synchronization import is the right call over an actor here since the call sites stay synchronous.

UpdateReport model quality

  • FeedResult.status promoted from String to a nested Status: String, Codable, Sendable enum — eliminates stringly-typed status values and catches invalid states at compile time.
  • Summary.successRate and UpdateReport.duration converted from computed properties to stored let fields initialized at init time — ensures Codable synthesis encodes them in JSON output.

Remaining Concern: codecov/patch (15.61% vs 25.58% target)

This is the only failing check. The uncovered lines are concentrated in Examples/MistDemo/Sources/MistDemoKit/ — particularly command files (0% coverage on DemoErrorsRunner, AuthTokenCommand+Routes, FetchChangesCommand, etc.) and configuration decoders (FetchChangesConfig, LookupZonesConfig, DemoErrorsConfig).

The fastest path to close the gap is the Configuration test suite — these are pure decoders with no I/O and no mocks required, following the existing LookupConfigTests / DeleteConfigTests patterns. Adding tests for the three zero-coverage Config files alone should contribute ~90 lines, and expanding the existing Create/Lookup/AuthToken/CurrentUser tests another ~150.

This needs to be resolved before merging if the codecov gate is mandatory.


Minor Issues / Nits

queryAllRecords(pageSize: limit) semantic change in queryFeeds — worth documenting:

  • Previously queryRecords(limit: limit) capped total results at limit.
  • Now queryAllRecords(pageSize: limit) fetches records limit-per-page but returns all pages.
  • If callers pass limit: 10 expecting at most 10 feeds, they now get all feeds in pages of 10. Verify callers in UpdateCommand.swift handle an unbounded result set correctly, or if a total cap is still needed consider adding a maxPages constraint.

actions/checkout@v6 — used throughout .github/workflows/v6 does not appear to be a published stable major of actions/checkout (latest stable is v4). The plan notes this is intentional, and CI is currently passing (GitHub likely resolves it to a pre-release or nightly tag), but it's worth pinning to a known-stable tag before the 1.0.0 general release to avoid a broken workflow if a pre-release tag is deleted.

Copyright in MockCloudKitRecordOperator.swift — header still reads © 2025 BrightDigit. Minor cosmetic gap given the file was substantively rewritten for this PR.

Periphery dead code — several unused symbols flagged by Periphery across Sources/MistKit/ and Examples/MistDemo/ (tracked in .claude/ci-failures-pr298.md §5a/5b). These are scoped to a follow-up issue per the plan and are not blocking.


Strengths Worth Calling Out

  • KeyIDValidator — trim-then-check-for-whitespace is exactly the right order; catches the common copy-paste error where keys contain trailing newlines.
  • CelestraError.isRetriable — HTTP retry classification (5xx + 429, not other 4xx) is correct.
  • Smart CI matrix — minimal matrix on feature branches, full matrix on main/semver/PRs-to-main is a sensible cost/coverage tradeoff.
  • MockCloudKitRecordOperator with Mutex — using Synchronization.Mutex rather than an actor is the right call for a sync-friendly mock. The Sendable conformance and final class combination is clean.
  • PEM-string initializer on BushelCloudKitService — accepting PEM content directly (instead of a file path) is the right pattern for CI/CD secrets.

Verdict

✅ Code quality improvements are solid and well-targeted. All previously failing checks except codecov/patch are resolved.

Blocking before merge: Patch coverage must reach ≥ 25.58%. Adding Configuration-layer tests for FetchChangesConfig, LookupZonesConfig, and DemoErrorsConfig should close most of the gap without requiring any mocking infrastructure.

Non-blocking: actions/checkout@v6 pin, queryAllRecords limit semantics documentation, Periphery cleanups (tracked in follow-up).

@claude
Copy link
Copy Markdown

claude Bot commented May 14, 2026

Code Review: v1.0.0 beta.1

Overview

This is a large release-preparation PR (~52k additions, ~26k deletions) targeting three example projects (BushelCloud, CelestraCloud, MistDemo) plus CI infrastructure. The core MistKit library itself is unchanged. The changes address CI failures, code-review comments from a prior audit, lint violations, and thread-safety gaps identified in the pre-release review.


Strengths

MockCloudKitRecordOperatorMutex<State> (excellent fix)
Converting from nonisolated(unsafe) mutable state to Mutex<State> from Synchronization is the right call. Swift Testing parallelises suites by default and the old comment "single-threaded test use only" was a landmine. The new design is intrinsically safe regardless of suite configuration, and the withLock call-sites are clean.

UpdateReport: computed → stored properties
successRate (Summary) and duration (UpdateReport) are now computed at init time and stored, so Codable synthesisation serialises them correctly. The FeedResult.Status: String, Codable, Sendable enum is a meaningful improvement over a raw String — exhaustive switching prevents invalid values from ever reaching the JSON output.

KeyIDValidator — clear, actionable errors
The sequential validation (empty → whitespace → length → hex charset) gives callers a specific, actionable error at each stage. The reason + suggestion pair on BushelCloudKitError.invalidKeyID maps cleanly to LocalizedError's errorDescription and recoverySuggestion. Checking trimmed != keyID before the length check is a nice touch — a key with a trailing newline would otherwise silently fail the 64-char guard with a misleading message.

fetchExistingRecordNames over-fetch fix
Passing desiredKeys: [] to queryAllRecords is correct — CloudKit returns only ___recordID when desiredKeys is empty, dramatically cutting response payload for large datasets.

CI matrix reduction
Running the single-platform fast matrix on feature branches and expanding only on main/semver branches is good practice. The regex ^v?[0-9]+\.[0-9]+\.[0-9]+ correctly catches both v1.0.0 and 1.0.0 release branches.

Typed throws throughout
throws(CloudKitError) in CloudKitRecordOperating and the mock keeps error types explicit and compiler-verified — good alignment with the project's Swift 6 direction.


Issues

1. Copyright year still stale in new/expanded files

Three files added or heavily modified in this PR carry © 2025:

  • Examples/CelestraCloud/Tests/CelestraCloudTests/Mocks/MockCloudKitRecordOperator.swift (77 additions)
  • Examples/CelestraCloud/Tests/CelestraCloudTests/Errors/CelestraErrorTests+Description.swift (105 additions — new file)
  • Examples/CelestraCloud/Tests/CelestraCloudTests/Errors/CelestraErrorTests+RecoverySuggestion.swift (96 additions — new file)

PR #331 addresses CelestraErrorTests.swift and CloudKitConversionErrorTests.swift but misses these three. The follow-up issue mentions +Description and +RecoverySuggestion; MockCloudKitRecordOperator.swift is not tracked anywhere. Consider fixing MockCloudKitRecordOperator.swift here or adding it to the follow-up.

2. ExitError carries no diagnostic context

internal struct ExitError: Error {}

An empty error type makes it impossible to distinguish why the command exited without examining the surrounding print output. The plan document marks a refactor (use ArgumentParser.ExitCode or add a message: String) as a follow-up, but since this type is new in this PR, it would be cheap to at least add a message now:

internal struct ExitError: Error {
  internal let message: String
}

3. .claude/ plan documents committed to repo

.claude/plan-pr298.md (287 lines) and .claude/ci-failures-pr298.md (428 lines) are tracked files in this PR. These are ephemeral planning artifacts — their content belongs in the PR description or a linked document, not in the repository history. They will accumulate as dead weight and future contributors will have no clear signal about their relevance or currency. Recommend deleting them before merge (or via a follow-up cleanup commit).

4. Comments describe what, not why, in BushelCloudKitService

// Read PEM file from disk
let pemString: String
// Validate PEM format BEFORE passing to MistKit
try PEMValidator.validate(pemString)
// Create Server-to-Server authentication manager
let tokenManager = try ServerToServerAuthManager()

Per CLAUDE.md, comments should only explain non-obvious why, not narrate what the code does — well-named identifiers already do that. The tutorial intent of the example is understandable, but these inline narration comments don't add signal for anyone reading the code. If the tutorial angle is important, consider a README.md or doc comment on the type rather than scattered inline comments.

5. Dual UpdateSummary / UpdateReport.Summary sync point

UpdateCommand.processFeeds accumulates state in UpdateSummary (mutable, internal), and UpdateCommand.writeJSONReport reconstructs totals from it to build UpdateReport.Summary. The invalid-URL path (lines 155–169 of UpdateCommand.swift) increments summary.errorCount directly rather than via summary.record(_:), which is fine, but any future path that writes to feedResults without updating summary would silently produce a mismatched JSON report. A simple guard is that totalFeeds in writeJSONReport is derived from summary counts rather than feedResults.count — worth keeping that invariant explicit (perhaps a #assert or a comment on the totalFeeds line).

6. Emoji usage in production code

BushelCloudKitService uses emojis directly in print() / ConsoleOutput.print() calls (e.g. 📊, , 🔄, ). CLAUDE.md says "Only use emojis if the user explicitly requests it." This is a demo/example app so the impact is low, but it's worth flagging as an inconsistency with the stated project conventions.


Minor Notes

  • MockCloudKitRecordOperator.queryAllRecords maps pageSize to QueryCall.limit (line 135). These are semantically different concepts and a test asserting call.limit == 200 could inadvertently pass against the wrong overload. Consider a separate pageSize: Int? field on QueryCall, or at minimum a comment explaining the mapping.
  • database: .public (unparenthesised) in CloudKitRecordOperating.swift lines 103 and 124: CLAUDE.md states Database.public is case public(PublicAuthPreference). Verify a convenience static property or default exists for this syntax, or add explicit preference (e.g. .public(.prefers(.serverToServer))).
  • The hardcoded URL https://icloud.developer.apple.com/dashboard/ in BushelCloudKitError.recoverySuggestion is cosmetic but Apple URLs tend to change; consider omitting the exact URL and instructing users to search for "CloudKit Dashboard" instead.

Test Coverage

The new CelestraErrorTests+Description.swift and +RecoverySuggestion.swift files add solid coverage for CelestraError's LocalizedError conformance, including HTTP status code boundary conditions (429, 4xx, 5xx). This is a good pattern. The retriability matrix in CelestraErrorTests.swift covers all cases cleanly.


Summary

The thread-safety fix, Codable correctness fix, fetchExistingRecordNames over-fetch fix, and Status enum promotion are all good, targeted improvements. The main things to address before merge are: (1) the stale copyright in MockCloudKitRecordOperator.swift, (2) removing the .claude/ plan documents from tracked files, and (3) considering whether ExitError should carry a message before it lands in a release.

@claude
Copy link
Copy Markdown

claude Bot commented May 14, 2026

Code Review: v1.0.0 beta.1 (#298)

This is a large, well-structured beta release. The overall architecture is solid — typed errors, per-call credential dispatch, OpenAPI-driven generation, and proper Sendable compliance throughout. Comments below are focused on the gaps worth closing before a stable 1.0.


Security

processQueryRecordsResponse logs full response at .error level when redaction is disabled

CloudKitResponseProcessor.swift:130-134:

MistKitLogger.logError(
    "CloudKit queryRecords failed with response: \(response)",
    logger: MistKitLogger.api,
    shouldRedact: false  // ← unconditionally off
)

The earlier CodeQL fix on lookupUsersByEmail (debug vs warning) wasn't applied consistently here. A failed query response can still carry record field data (e.g. CloudKit echoes the query body on some 4xx). Consider shouldRedact: true or at minimum shouldRedact: MistKitLogger.shouldRedact.


Correctness

boolValue silently returns non-zero as true after failed assertion in release

FieldValue+Convenience.swift:148-154:

assertionHandler(value == 0 || value == 1, "Boolean int64 value must be 0 or 1, got \(value)")
return value != 0   // ← always executed

In release builds (where assert is stripped), an int64 field holding 2 or -1 silently returns true. For a CloudKit field that is truly boolean this is unlikely but can happen when schema validation is loose. The fix is simple — return nil when the value is out of range:

guard value == 0 || value == 1 else { return nil }
return value != 0

queryAllRecords swallows stuck-marker silently

CloudKitService+QueryPagination.swift:96-101:

if result.records.isEmpty
    && result.continuationMarker != nil
    && result.continuationMarker == currentMarker {
    break  // silent partial result
}

The doc comment does say "stops early", but callers receive a normal [RecordInfo] with no indication the result is incomplete. Given paginationLimitExceeded already carries accumulated records for the cap case, applying the same pattern here (a dedicated CloudKitError case, or at minimum a warning log) would make the behavior observable. Otherwise a silent short-read is indistinguishable from a complete fetch.

makeTokenManager re-reads PEM from disk on every call

Credentials+TokenManager.swift:149-162makeTokenManager(for:) is called once per dispatched request (CloudKitService+ClientDispatch.swift:56), and makeServerToServerManager calls s2s.privateKey.loadPEM() which does String(contentsOfFile:) on each invocation. Under any request concurrency this is: (a) unnecessary disk I/O per request; (b) a TOCTOU window (key file deleted after first successful read silently succeeds on cached pages, or fails abruptly on a cold read). Consider caching the resolved PEM inside ServerToServerCredentials or building the ServerToServerAuthManager once and caching it in Credentials.


API Design

uploadAssets throws httpErrorWithRawResponse(statusCode: 413, ...) for oversized data

CloudKitService+AssetOperations.swift:82-87: The 15 MB guard is client-side but surfaces as an HTTP 413 error — which callers would reasonably interpret as a server rejection. A dedicated CloudKitError case (e.g. .assetTooLarge(size: Int, limit: Int)) is more descriptive and lets callers distinguish "I should split the upload" from "the server rejected it". Similarly the empty-data guard throws HTTP 400, conflating a precondition failure with a server response.

discoverAllUserIdentities is @available(*, unavailable) but still has a body

CloudKitService+UserOperations.swift:88-113: In Swift 6.1 the #if swift(>=6.2) gate on processDiscoverAllUserIdentitiesResponse is necessary, but the method body on the @available(*, unavailable) declaration still compiles and sits in the binary. This isn't harmful, just dead weight until #28 is resolved. Worth a comment noting when to remove the #if guard.

Credentials.init uses assert + throws for the same condition

Credentials.swift:55-61: The assert fires in debug before the guard/throw can run, meaning in debug the error is an assertion crash (not catchable), while in release it's a typed throw (catchable). For dynamic config loading this asymmetry could mask a real issue: integration tests run in release mode would silently attempt to continue until the first request throws, while debug runs would crash early. Either remove the assert (let the typed throw be the only signal) or document the intentional split.


Code Quality / Minor

CloudKitResponseProcessor default: branches after exhaustive error handling

Every processX method in CloudKitResponseProcessor.swift has the pattern:

if let error = CloudKitError(response) { throw error }
switch response {
    case .ok(...): ...
    default:
        assertionFailure("Unexpected response case after error handling")
        throw CloudKitError.invalidResponse
}

The default: + assertionFailure is appropriate defensively, but the assertionFailure is stripped in release, meaning execution continues to throw .invalidResponse silently. This is already fine in practice — just noting that the assertionFailure message ("Unexpected response case after error handling") is the only diagnostic. Worth turning into a throw CloudKitError.invalidResponse comment that names the unexpected case so logs are more actionable.

Lots of redundant doc comments that narrate what the code already says

Throughout CloudKitResponseProcessor.swift:

/// Process lookupRecords response
/// - Parameter response: The response to process
/// - Returns: The extracted lookup response data
/// - Throws: CloudKitError for various error conditions

These describe the method signature, not the non-obvious behaviour. The more useful thing to document is which CloudKit error codes each endpoint can return, or edge cases like the stuck-marker or 413 behaviour. Not blocking, but adds noise to DocC output.


Positive highlights

  • Per-call credential resolution (CloudKitService+ClientDispatch.swift) is clean — a single service can now serve S2S + web-auth routes without a second instance.
  • paginationLimitExceeded carrying accumulated records is a good ergonomic choice.
  • CloudKitError.invalidPrivateKey(path:underlying:) giving diagnostically useful context on PEM load failures is appreciated.
  • The #if swift(>=6.2) gate on the @available(*, unavailable) cascade is the right fix for the 6.1 compiler difference.
  • CredentialAvailability in the error payload is a nice touch for tooling that wants to distinguish "not configured" from "preference violated".

Summary

Severity Issue
Minor/Security processQueryRecordsResponse logs response with shouldRedact: false
Bug boolValue silently returns wrong result for out-of-range int64 in release
UX Silent partial result from stuck-marker in queryAllRecords
Perf PEM file re-read on every request in makeTokenManager
API 413/400 codes misused for client-side precondition failures
API Credentials.init assert + throw asymmetry

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.

2 participants