v0.1.0
Protocol Spec

Change Tracking

Design rationale for grant-relative incremental sync via changes_since cursors, not canonical changelog streams.

Date: 2026-07-07 (revised from 2026-04-06)

Scope: Design rationale and decision history for grant-relative incremental sync. The normative mechanics live in spec-core: Section 4 defines stream semantics, the snapshot model, and tombstones; Section 8 defines the changes_since query surface.

Decision

Change tracking in PDPP uses grant-relative incremental sync, not canonical changelog streams.

There are no {stream}_changes streams in the protocol. Change tracking is a property of the resource server query API: a client passes a changes_since cursor and receives only records that changed within its grant-authorized field projection since that cursor.

This design was chosen over a canonical CDC-style changelog after analysis of prior art (Microsoft Graph delta queries, OData change tracking, Google Calendar incremental sync). The key finding: a canonical stored changelog cannot be made privacy-safe without becoming grant-relative anyway. Two clients with overlapping grants (one authorized for fields A+B, another only for A) cannot share a single changelog without leaking metadata about field B to the A-only client. Grant-relative delta queries solve this cleanly.

The consequence that shaped the normative text: eligibility for a changes_since response must be computed on the grant-authorized projection, not the full record. Selecting rows on full-record changes and projecting afterward leaks that hidden fields changed. spec-core Section 8 states this as a conformance requirement.

Normative surface (pointers, not restatement)

  • Stream semantics (append_only, mutable_state), internal version history, the snapshot model, and the tombstone envelope: spec-core Section 4.
  • The changes_since / next_changes_since token space, its separation from cursor / next_cursor, session-horizon anchoring across pages, projection-safe eligibility, and cursor expiry (HTTP 410 cursor_expired, full re-sync): spec-core Section 8.

Why not canonical {stream}_changes streams

The March 2026 draft defined {stream}_changes as a companion stream generated by the resource server. It was rejected for three reasons:

  1. Privacy leakage. A canonical changelog stored once for all clients cannot be filtered per-grant without becoming grant-relative anyway. The changed_fields list alone leaks that a field changed, even if the field value is redacted.

  2. Conceptual confusion. {stream}_changes looks like a first-class stream but has different semantics from real streams (it is derived, not collected). Calling it a stream creates a false equivalence.

  3. Prior art alignment. Microsoft Graph, OData, and Google Calendar all model change tracking as query-relative delta views, not as canonical streams. The pattern is established and well understood.

What this does not cover

  • Point-in-time reconstruction. Reconstructing the full state of a stream at a past timestamp is not supported in v0.1. It requires materializing historical state from version history, which is expensive. Deferred.

  • Field-level change subscriptions. "Notify me when display_name changes" is a push/webhook concern. Deferred.

  • Changes between collections. The resource server can only track changes it has observed. If a value changed and changed back between two collection runs, the net change is zero and no delta is returned. This is an inherent limitation of pull-based collection, not a protocol flaw; resource server implementations should document it.