Skip to content

Add records/resolve and records/accept share operations (#41, #42) - #428

Draft
leogdion wants to merge 4 commits into
v1.0.0-beta.4from
41-42-records-resolve-accept
Draft

Add records/resolve and records/accept share operations (#41, #42)#428
leogdion wants to merge 4 commits into
v1.0.0-beta.4from
41-42-records-resolve-accept

Conversation

@leogdion

Copy link
Copy Markdown
Member

Summary

Implements CloudKit Web Services' two record-sharing endpoints:

Public API:

let infos = try await service.resolveShares([
  ShortGUID(value: "", shouldFetchRootRecord: true, rootRecordDesiredKeys: ["title"])
])
let accepted = try await service.acceptShares([ShortGUID(value: "")])

Auth: no database: parameter

Apple's reference fixes both paths' database scope to public, and both operations act on behalf of the current user. So — following the existing fetchCaller() precedent — they hard-code .public(.requires(.webAuth)) and expose no database: parameter. This is not a silent policy default (cf. feedback_no_silent_policy_defaults): there is no valid alternative for the caller to choose, so the choice is removed from the API rather than defaulted.

Both endpoints validate the request as a whole — a bad short GUID fails the entire call rather than producing a per-item error — so there is deliberately no RecordResult-style per-item failure variant (same reasoning as assets/rereference).

How the wire format was verified

Both endpoints are absent from .claude/docs/webservices.md (and from Apple's current online docs), so per project memory reference_cloudkit_archived_endpoints.md the shapes were confirmed against Apple's archived CloudKit Web Services Reference:

Source Confirms
FetchingRecordInformation.html POST …/public/records/resolve, request { shortGUIDs: [ShortGUID] }, response { results: [ShortGUIDResult] }
AcceptingShareRecords.html POST …/public/records/accept, same request/response shapes
Types.html ShortGUID Dictionary, ShortGUID Result Dictionary, Share Participant Dictionary, and the share-related Record Dictionary keys — every key name, type, required flag, and enum value modeled here
.claude/docs/webservices.md:1610-1672 Share creation keys (createShortGUID, forRecord, publicPermission, participants) and the share response keys
.claude/docs/cloudkitjs.md (CloudKit.RecordInfo, CloudKit.Share, CloudKit.ShareParticipant) Cross-check of the share/participant field sets

Changes

openapi.yaml (regenerated via ./Scripts/generate-openapi.shSources/MistKitOpenAPI/ was never hand-edited):

  • New paths records/resolve (operationId: resolveShortGUIDs) and records/accept (operationId: acceptShares).
  • New schemas ShortGUID, ShortGUIDResult, ShortGUIDResultResponse, ShareParticipant, ShareReference, ShareTargetReference.
  • Per the Accepting Share Records (records/accept) #42 gap analysis: share request keys on RecordRequest (createShortGUID, forRecord, publicPermission, participants) and share response keys on RecordResponse (shortGUID, share, publicPermission, participants, owner, currentUserParticipant).

Domain models (Sources/MistKit/Models/Sharing/): ShortGUID, ShareRecordInfo, ShareInfo, ShareParticipant, SharePermission, ShareParticipantType, ShareAcceptanceStatus, ShareDatabaseScope, SharePotentialMatch.

ShareInfo lifts the share-specific keys off a cloudKit.share record, because RecordInfo models a plain record and intentionally carries no sharing metadata. ShareDatabaseScope is deliberately separate from Database — it is a plain descriptor CloudKit returns, carrying no PublicAuthPreference. Environment gains Codable (it is already a String raw-value enum) so ShareRecordInfo can synthesize its conformance.

Operations: CloudKitService+ShareOperations.swift, CloudKitResponseProcessor+Sharing.swift, plus the two Operations.*.Output error-mapping extensions and OperationInputPath conformances.

Docs: CLAUDE.md/AGENTS.md operations table + a "Share Operations" section; README roadmap moves both issues into a new v1.0.0-beta.4 section.

Verification

Check Result
swift build ✅ Build complete
swift test 571 tests in 178 suites passed (baseline 568; 11 sharing operation tests + model tests added)
mise exec -- swift-format ✅ Applied, clean
./Scripts/lint.sh (swiftlint + swift-format lint + header.sh + periphery) 0 violations, 0 serious in 408 files; "No unused code detected"
./Scripts/generate-openapi.sh re-run ✅ Idempotent — committed output matches openapi.yaml

Tests assert the serialized request body (that shortGUIDs is sent in order, that shouldFetchRootRecord/rootRecordDesiredKeys are carried, and that omitted optionals are not emitted as nulls), full response-field mapping, the potentialMatchList ambiguous-caller path, ShareInfo extraction, and top-level BAD_REQUEST handling for both operations.

Not verified

  • No live-service call. Everything is verified against archived documentation and mock transports; neither endpoint was exercised against a real CloudKit container. Apple's archived pages carry no verbatim JSON examples for either endpoint, so field names come from the Types.html dictionaries rather than from a sample payload.
  • potentialMatchList is the least-documented part of the response — Types.html describes participantId + contactInformation{emailAddress, phoneNumber} but shows no example. Modeled as described; the fields are all optional so an unexpected shape degrades rather than throws.
  • Share creation is not wired into a MistKit convenience. The createShortGUID / forRecord / publicPermission / participants request keys are modeled in the schema (as Accepting Share Records (records/accept) #42's gap analysis asks) and are reachable via MistKitOpenAPI, but no curated "create a share" API is exposed on CloudKitService — that is a larger surface deserving its own issue.
  • No MistDemo integration phase was added for these endpoints, since they need a real share URL from a second iCloud account to exercise.

Closes #41
Closes #42

🤖 Generated with Claude Code

leogdion and others added 3 commits August 20, 2026 14:16
Adds two project memory files plus their MEMORY.md index entries:

- project_beta4_worktree_layout: the branch/worktree split for the
  remaining v1.0.0-beta.4 issues, the grouping rule (shared openapi.yaml
  path family => shared branch, to avoid Sources/MistKitOpenAPI/
  regeneration collisions), and why #407 was excluded.
- project_419_fixed_in_beta3: #419 is already fixed by 5a58120 and
  verified building on macOS Swift 6.3.2; do not re-implement.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Implements CloudKit Web Services' two sharing endpoints, both documented
only in Apple's archived CloudKit Web Services Reference:

- `records/resolve` (#41) — resolves share short GUIDs into information
  about the shared records: root record, `cloudKit.share` record, owner
  identity, and the caller's participation.
- `records/accept` (#42) — accepts shares on behalf of the current user,
  returning the same result shape with the caller's resulting
  participation.

Both take `{ shortGUIDs: [ShortGUID] }` and return
`{ results: [ShortGUIDResult] }`. Apple's reference fixes the path's
database scope to `public`, and both act on behalf of the *current*
user, so — like `fetchCaller()` — they hard-code
`.public(.requires(.webAuth))` and expose no `database:` parameter.
Both validate the request as a whole (a bad short GUID fails the entire
call), so there is no per-item RecordResult-style failure variant.

Spec changes (openapi.yaml, regenerated via Scripts/generate-openapi.sh):
- New paths `records/resolve` + `records/accept`.
- New schemas `ShortGUID`, `ShortGUIDResult`, `ShortGUIDResultResponse`,
  `ShareParticipant`, `ShareReference`, `ShareTargetReference`.
- Share request keys on `RecordRequest` (`createShortGUID`, `forRecord`,
  `publicPermission`, `participants`) and share response keys on
  `RecordResponse` (`shortGUID`, `share`, `publicPermission`,
  `participants`, `owner`, `currentUserParticipant`), per the #42 gap
  analysis.

Domain models land in Sources/MistKit/Models/Sharing/. `ShareInfo` lifts
the share-specific keys off a `cloudKit.share` record, since `RecordInfo`
models a plain record and carries no sharing metadata. `Environment`
gains `Codable` so `ShareRecordInfo` can synthesize it.

Verified: swift build, swift test (571 tests, 178 suites, all passing),
swift-format, and ./Scripts/lint.sh (0 violations, no unused code).

Closes #41
Closes #42

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

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3af8f8d3-6b94-47ea-8642-8aa69f0374e7

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

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.

@claude

claude Bot commented Aug 20, 2026

Copy link
Copy Markdown

Review: records/resolve / records/accept share operations

Went through the new sharing surface (CloudKitService+ShareOperations, CloudKitResponseProcessor+Sharing, the Sources/MistKit/Models/Sharing/ types, openapi.yaml, and the test suites). Overall this is a clean, well-documented addition that follows the repo's established patterns closely (public-DB/web-auth hardcoding mirrors fetchCaller(), exhaustive error-case switches mirror the other Operations.*.Output extensions, request/response separation matches the FieldValueRequest/FieldValueResponse split described in CLAUDE.md).

Potential bug: ShareInfo.sharedRecordName likely never resolves correctly

Sources/MistKit/Models/Sharing/ShareInfo.swift:86:

self.sharedRecordName = record.share?.recordName

This is documented as "The record name of the shared record this share governs," but ShareInfo.init(from:) is only ever invoked with the cloudKit.share record itself (ShareRecordInfo.swift:98-100 passes schema.share, which per ShortGUIDResult's schema is the full cloudKit.share RecordResponse). So record.share here is that same share record's own share key, not a pointer to the root record being shared.

Per .claude/docs/webservices.md:1660-1672, the share record's own response does carry a share key — but reading that key off the share record itself either resolves to nil in practice or, at best, is self-referential (yielding the share record's own name rather than the root record's). Either way it can't produce "the shared record's name" as documented.

This is untested: ShareModelTests.swift:145 only asserts sharedRecordName == nil for an empty-constructed ShareInfo, and the mock share-record fixture in CloudKitServiceTests.Sharing+Helpers.swift (shareRecord(for:)) never populates a share key, so no test exercises a non-nil value. ShareRecordInfo.rootRecordName (sourced directly from ShortGUIDResult.rootRecordName) is the reliable way to get this today — worth double-checking against a live response whether ShareInfo.sharedRecordName should just be removed, or wired from a different source.

Minor / non-blocking

  • ShareRecordInfo.swift:89-91 falls back to ZoneID.defaultZone.zoneName when zoneID.zoneName is absent. Other conversions in the codebase (e.g. SubscriptionInfo+Schema.swift:141) don't need this fallback because the schema already guarantees zoneName. If CloudKit really can omit it here, a code comment on why would help; otherwise consider matching the stricter pattern.
  • SharePermission/ShareParticipantType/ShareAcceptanceStatus each hand-write 2-3 near-identical init(from:) overloads for the different generated payload enums (ShareParticipant.permissionPayload, ShortGUIDResult.participantPermissionPayload, RecordResponse.publicPermissionPayload, etc.). This is a direct consequence of the OpenAPI generator emitting distinct nested enums per call site rather than a design choice in this PR, so not asking for a change — just noting the duplication in case a future schema tweak (e.g. a shared $ref'd enum component) could collapse it.

Test coverage

Good breadth: request-body ordering/omitted-optionals, full field mapping, missing-root-record, potentialMatchList ambiguous-caller path, shareInfo == nil for a non-share record, and top-level BAD_REQUEST handling for both endpoints. The one gap is the sharedRecordName case noted above.

Security / performance

No concerns — no new secrets/logging, no unbounded loops or allocations, and the hardcoded .public(.requires(.webAuth)) routing correctly removes a database: choice that has no valid alternative (consistent with the project's "no silent policy defaults" convention).

🤖 Generated with Claude Code

@codecov

codecov Bot commented Aug 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.16318% with 2 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (v1.0.0-beta.4@d295c30). Learn more about missing BASE report.

Files with missing lines Patch % Lines
...PI/Operations/Operations.acceptShares.Output.swift 93.75% 1 Missing ⚠️
...erations/Operations.resolveShortGUIDs.Output.swift 93.75% 1 Missing ⚠️
Additional details and impacted files
@@               Coverage Diff                @@
##             v1.0.0-beta.4     #428   +/-   ##
================================================
  Coverage                 ?   77.60%           
================================================
  Files                    ?      171           
  Lines                    ?     4000           
  Branches                 ?        0           
================================================
  Hits                     ?     3104           
  Misses                   ?      896           
  Partials                 ?        0           
Flag Coverage Δ
mistdemo-spm-macos 10.62% <0.00%> (?)
mistdemo-swift-6.2-jammy 10.62% <0.00%> (?)
mistdemo-swift-6.2-noble 10.62% <0.00%> (?)
mistdemo-swift-6.3-jammy 10.62% <0.00%> (?)
mistdemo-swift-6.3-noble 10.62% <0.00%> (?)
spm 75.55% <99.16%> (?)
swift-6.1-jammy 75.84% <99.16%> (?)
swift-6.1-noble 75.84% <99.16%> (?)
swift-6.2-jammy 75.76% <99.16%> (?)
swift-6.2-noble 75.69% <99.16%> (?)
swift-6.3-jammy 75.49% <99.16%> (?)
swift-6.3-noble 75.59% <99.16%> (?)

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

☔ View full report in Codecov by Harness.
📢 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.

Flip ResolveCommand off PendingStub, add AcceptCommand, wire integration
phases and web routes so records/resolve and records/accept can be tested.

Co-authored-by: Cursor <cursoragent@cursor.com>
@claude

claude Bot commented Aug 20, 2026

Copy link
Copy Markdown

Code Review — PR #428 (Standards + Spec)

Reviewed via two independent parallel passes: does the diff follow this repo's documented conventions (Standards), and does it faithfully implement issues #41/#42 (Spec)?

Standards

No hard violations of documented CLAUDE.md rules found — ACLs are explicit throughout, every import carries internal/public, Environment correctly picks up Codable for ShareRecordInfo, the .public(.requires(.webAuth)) hard-coding in CloudKitService+ShareOperations.swift matches the existing fetchCaller() pattern, and the openapi.yaml diff lines up with the generated MistKitOpenAPI types (no hand-edits detected).

Judgement-call smells (baseline, not standards breaches — repo conventions already override where relevant):

  • Duplicated code: AcceptCommand.swift's printSummary (~L203-221) and ResolveCommand.swift's (~L317-335) are byte-identical 18-line blocks. Minor — matches the existing one-file-per-command convention.
  • Duplicated code: SharePermission.swift has three near-identical init(from:) overloads re-switching the same four cases (mirrored in ShareAcceptanceStatus.swift/ShareParticipantType.swift). Understandable given generator-produced distinct nested types per schema usage, but ~30 lines could collapse into a shared raw-value mapping helper.
  • Feature envy / odd coupling: AcceptConfig.swift:483 calls ResolveConfig.parseShortGUIDs(from:)AcceptConfig depends on a sibling command's config type for core parsing rather than a shared helper. Works, but reads oddly under the codebase's DI conventions.
  • Copy-paste artifact: CloudKitServiceTests.Sharing+Accept.swift:2 and ...+Resolve.swift:2 both carry the header comment CloudKitServiceTests.Sharing.swift, a leftover from splitting one file into three. Scripts/header.sh should normally fix this.

Spec

(a) Missing/partial requirements: none. Both endpoints, both schema groups, request-side share-creation keys, and response-side share fields are all present. Both curated methods (resolveShares(_:), acceptShares(_:)) exist with the documented no-database:-parameter, .public(.requires(.webAuth)) design applied consistently to both.

(b) Scope beyond the issues' literal text (not flagged as a defect): the MistDemo AcceptCommand/ResolveCommand (+ Config/Error types), WebServer+Shares.swift, WebRequests+Shares.swift, CloudKitService+WebBackend+Shares.swift, and integration test phases go beyond "add the endpoint + schemas + curated methods." ResolveCommand was already a pending CLI stub this PR fills in, and AcceptCommand follows the established one-command-per-operation convention — reasonable dogfooding infrastructure, not gratuitous creep.

(c) Implementation accuracy: endpoint paths/operationIds match (records/resolveresolveShortGUIDs, records/acceptacceptShares). Schema names diverge from the issue's suggested names (RecordInfo/RecordInfosResponseShortGUIDResult/ShortGUIDResultResponse), but field content is a superset of what was asked — nothing is actually missing, only renamed. Share creation is reachable only via MistKitOpenAPI (no curated CloudKitService convenience), matching the PR's own stated "left out deliberately" caveat. potentialMatchList is modeled all-optional as described. No incorrect-implementation defects found.

Summary

Standards: 0 hard violations, 4 judgement-call smells (worst: the SharePermission/ShareAcceptanceStatus/ShareParticipantType triple-duplicated init(from:) switches — minor, generator-shape-driven).
Spec: 0 gaps, 0 wrong implementations, 1 noted-but-reasonable scope note (MistDemo dogfooding infra). Both endpoints correctly implement #41/#42 including the deliberate no-database:-parameter design.

Both self-flagged verification caveats from the issue threads (no live-service call made against a real CloudKit container; potentialMatchList shape unverified) still stand as open risk for both endpoints — not something this review can close, since it requires a live container.

🤖 Generated with Claude Code

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.

1 participant