Skip to content

zones/changes may use metaSyncToken, not syncToken — pagination possibly broken #430

Closed

Description

@leogdion

Summary

Apple's archived reference documents zones/changes as taking and returning a metaSyncToken, but MistKit sends and reads syncToken. If the documentation is right, fetchZoneChanges / fetchAllZoneChanges pagination is silently broken — the server would ignore the unrecognized key and return the first page forever, which a stuck-token guard would mask as "no more changes" rather than surface as an error.

Surfaced during #386 verification and reiterated in #429. Deliberately not changed in either PR, because the evidence is genuinely contradictory and this needs a live response to settle, not a doc-driven guess.

Evidence

For the change — Fetching Zone Changes (zones/changes) names the field metaSyncToken.

Against — the same Apple page contradicts itself: its moreComing description refers back to "the included syncToken key".

In this repo, openapi.yaml shows the same split personality — the description says "meta-sync token" while the property is named syncToken:

# openapi.yaml — zones/changes
      summary: Fetch Zone Changes
      description: Get all changed zones relative to a meta-sync token
      ...
              properties:
                syncToken:
                  type: string
                  description: Meta-sync token from previous operation

metaSyncToken appears nowhere in the repository (grep -rn metaSyncToken . → no matches), so if Apple's field name is correct, this has never worked.

Why it may not have been noticed

zones/changes is deprecated by Apple in favor of changes/database (see #401), so it is plausibly under-exercised. As of #429, fetchZoneChanges / fetchAllZoneChanges are marked @available(*, deprecated) and fetchDatabaseChanges / fetchAllDatabaseChanges are the supported path — which lowers the severity here but does not resolve the question.

How to settle it

This needs a live call against a real container, not more doc reading:

  1. Call zones/changes with no token, note the returned token key name in the raw response body.
  2. Make a zone-level change, then re-request passing the token back as syncToken.
  3. If the same zone set is returned unchanged, the key is wrong; retry as metaSyncToken and compare.

MistDemo can drive this — the middleware logs full request/response bodies at .debug (com.brightdigit.MistKit.middleware), so the raw key names are directly observable.

Outcome

  • If metaSyncToken is correct → fix openapi.yaml, regenerate, and add a note that the deprecated endpoint's pagination was previously broken.
  • If syncToken is correct → fix the misleading "meta-sync token" wording in openapi.yaml so this doesn't get re-raised.

Either way the spec should stop describing a field by a name it doesn't use.

Related: #401, #386, #429

Activity

  1. added this to the v1.0.0-beta.4 milestone on Aug 28, 2026
  2. leogdion commented on Aug 28, 2026

    @leogdion
    MemberAuthor

    Confirmed by live container test — metaSyncToken is correct

    Ran against iCloud.com.brightdigit.MistDemo / development / private DB, POST zones/changes, web-auth credentials.

    1. The response uses metaSyncToken, not syncToken:

    {
      "moreComing": false,
      "metaSyncToken": "AQAAAaBJidqD",
      "zones": [ { "zoneID": { "zoneName": "…", "ownerRecordName": "…", "zoneType": "REGULAR_CUSTOM_ZONE" }, "deleted": true }, … ]
    }

    Top-level keys are exactly [moreComing, metaSyncToken, zones]. syncToken is absent.

    2. Round-trip proves the pagination is broken. Same baseline token sent back under each key:

    Request body Zones returned Result
    baseline (no token) 40, token AQAAAaBJitS1 —
    {"syncToken": …} — what MistKit sends today 40 again key ignored, page 1 replayed
    {"metaSyncToken": …} 0 token honored, advanced correctly

    So fetchZoneChanges/fetchAllZoneChanges have never paginated. The server accepts the request (HTTP 200) and silently discards the unrecognized key.

    Note the failure mode differs from this issue's prediction: the stuck-token guard at CloudKitService+FetchAllZoneChanges.swift:108-115 requires zones.isEmpty, so on a non-empty database this presents as duplicate zones accumulating until maxPages trips zonePaginationLimitExceeded — not a silent 'no more changes'.

    Fix: rename the wire key to metaSyncToken in openapi.yaml (request and response) and regenerate. Keep the Swift-level names (ZoneChangesResult.syncToken, the syncToken: argument labels) unchanged — MistKit imports the generated module as internal import, so a wire-key rename is not source-breaking for MistKit consumers. Also update the hardcoded "syncToken" JSON literals in Tests/MistKitTests/CloudKitService/FetchZoneChanges/…+Helpers.swift.

    Reopening scope: this stays in v1.0.0-beta.4.

  3. leogdion commented on Aug 29, 2026

    @leogdion
    MemberAuthor

    Fixed in #443 — zones/changes wire key renamed to metaSyncToken (verified against live container).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions