Skip to content

Invariants

This catalog defines the observable conditions that every conforming Synchro protocol version 3 implementation MUST satisfy.

MUST and MUST NOT are normative. Failing a MUST, observing behavior forbidden by a MUST NOT, or accepting an invalid state identified below is a conformance violation. The Accepted ADRs define the exact shapes, bindings, ordering rules, matrices, and dispatch behavior referenced here and MUST NOT be weakened by an implementation-specific interpretation.

For every release, every level-three invariant heading in this document maps to exactly one requirement record, and every requirement maps through exactly one normative reference to a level-three invariant here. Supplemental references do not replace that binding. Orphaned, duplicate, or non-invariant mappings are invalid.

Every wire timestamp MUST use the canonical RFC 3339 UTC representation assigned to its field, including the exact microsecond Z form for portable datetime. Emitting or accepting a non-UTC or noncanonical timestamp is a violation. The field representations are defined under Portable type system and wire JSON in ADR 005.

Every protocol-envelope JSON integer MUST remain in the exact portable range -9007199254740991..9007199254740991, and every field declared nonnegative or positive MUST satisfy that narrower range. Server counter allocation MUST fail before overflow. Emitting, accepting, rounding, wrapping, or sign-changing a value outside its range is a violation of the protocol integer contract; opaque-token decimal-string counters are not envelope integers.

Clients MUST preserve each non-null server-issued cursor byte-for-byte and MUST NOT parse, order, alter, or assign meaning to its encoding. null MUST mean no usable resumable state, and a server MUST authenticate every non-null token before use. Client reinterpretation or server acceptance of a forged or misbound token is a violation under the cursor rules in ADR 003.

server_version MUST be a server-generated equality token of 1 through 64 octets. Each octet MUST be printable ASCII from 0x20 through 0x7E other than " and \, so its canonical JSON string is its length plus 2 octets. Clients and adapters MUST NOT parse, sort, increment, timestamp-compare, or generate it. A client MAY use the length bound only to compute a worst-case request size. Any such operation, a token outside the bound, or any server version derived from client data, is a violation of the version rules in ADR 001 and ADR 002.

Only the server MUST determine the authoritative scope set for an authenticated client. The set is the union of the default user scope, registered shared scopes, explicit grants, and registered assignment function results. A client-authored scope instance or replication predicate MUST be rejected, and accepting one is a violation of the assignment rules in ADR 003 and ADR 004.

Connect and pull MUST compute the authoritative scope set with the same rule. Pull MUST reconcile that set after every request validation passes and before it builds scope_updates. A changed set MUST advance scope_set_version by exactly one and MUST appear in scope_updates of that response. An unchanged set and a failed pull MUST write no assignment state. Rebuild MUST NOT reconcile assignment. A stale set in a successful pull, any other version step, or an assignment write from a failed pull is a violation of Scope assignment in ADR 004.

A native client MUST send pull in each normal sync cycle, also when it knows no scope. That pull carries an empty scopes map. The client MUST apply each scope that the response adds, rebuild it, and materialize its rows in the same cycle. A cycle that skips pull because the local scope set is empty is a violation of Sync ordering in the Client Contract.

At most one registered assignment function MUST contribute scopes. Registration MUST reject a function that breaks a rule in Scope assignment in ADR 004. The registration function MUST keep its (text, integer) identity, default bound of 1000, and void result. Registration and removal MUST require synchro_operator permission. A NULL function name MUST return SQL NULL and MUST change no registration, also when the bound is NULL or invalid. Each non-NULL bound MUST be an integer from 1 through 1000. SQL NULL MUST select unbounded evaluation of the complete DISTINCT result set through operator-only registration. That selection MUST remove only the assignment-result count limit. Authorization, function restrictions, definition hash checks, result validation, and reconciliation MUST remain required. Each evaluation MUST confirm the registered definition hash. Both modes MUST reject a NULL scope result, a scope that is blank after trim, or a scope that starts with user:. A bounded evaluation MUST reject more distinct results than max_scopes. A rejected evaluation MUST fail the complete connect or pull with 500 sync_integrity_failure and no assignment change. Treating a failure as an empty set, truncating results, or evaluating a changed definition is a violation.

scope_set_version MUST advance strictly when, and only when, the client’s authoritative assigned scope set changes. Reusing a version for a different set, moving backward, or advancing for an unchanged set is a violation of Normal scope movement in ADR 003.

The server MUST select schema compatibility and the resulting action from the immutable schema lineage, and clients MUST execute that action without substituting a local compatibility decision. The fresh sentinel MUST be accepted only for a client identity with no prior generation or installed schema; a continuing client cannot use it to bypass unsupported or explicit reset. Continuing normal sync after unsupported, accepting a continuing-client fresh sentinel, or choosing a different action is a violation of Protocol 3 Schema Dispatch in ADR 004.

Push MUST accept only insert, update, and delete; pull MUST emit only upsert and delete. Accepting push create, push upsert, an implicit resurrection, or any other operation is a violation of Push vocabulary in ADR 002.

Applications MUST be able to perform every synced CRUD operation by reading and writing local SQLite. Requiring a separate application write API for any registered synced table is a violation of the native client boundary defined by ADR 002.

Synchro MUST define and execute create, update, and delete synchronization for every registered table through its canonical mutation contract. Delegating those semantics to an application-specific transport contract is a violation of the operation and outcome rules in ADR 002.

Every captured local mutation MUST remain durably pending or reach exactly one durable accepted, server-rejected, superseded_before_send, cancelled_before_send, blocked_by_predecessor, or exceeds_push_limit outcome. The four local outcomes MUST remain inspectable. superseded_before_send, cancelled_before_send, and blocked_by_predecessor MUST also remain linked to the normalized or predecessor intent. Removing intent without one of those states or explicit client policy is a violation of the queue and outcome rules in ADR 002 and ADR 004.

When a mutation outcome contains an authoritative row state or server_version, the client MUST reconcile its canonical local base to that state while preserving any later local intent. Marking the mutation terminal while retaining a conflicting canonical base or version is a violation of Complete outcome partition in ADR 002.

One logical local action MUST create one stable mutation identity and MUST NOT be applied or enqueued again because of request replay, response replay, or authoritative echo delivery. A duplicate queue entry or duplicate local transition is a violation of Exact replay in ADR 002.

Applying pull, rebuild, seed, or push-response state MUST suppress local change capture for that server-originated apply. Any new client mutation caused solely by applying authoritative state is a violation of the client apply boundary in ADR 002 and ADR 005.

Accepted Writes Advance Canonical Row Version

Section titled “Accepted Writes Advance Canonical Row Version”

Every accepted authoritative row transition MUST allocate a new opaque server-generated version through the version-fence path. The version MUST NOT come from client_version, a writable updated_at value, or an unchanged prior version. Observing any such ownership or reuse is a violation of Opaque server versions and atomic compare-and-swap in ADR 002.

Scope removal and rebuild pruning MUST remove stale local provenance and unjustified materialization without enqueuing a delete unless the server supplied an authoritative tombstone. A cleanup-created mutation or invented tombstone is a violation of Normal scope movement in ADR 003.

A completed batch replay with the same scoped identity and fingerprint MUST return the original HTTP status and exact canonical response without executing mutation work; a different fingerprint MUST return 409 idempotency_conflict without mutation work. Any changed response field or repeated side effect is a violation of Exact replay in ADR 002.

When a mutation identity appears in a different new batch, an equal mutation fingerprint MUST contribute its exact stored outcome without re-execution, and a different fingerprint MUST fail the whole request with 409 idempotency_conflict. Reclassifying or reapplying that mutation is a violation of Exact replay in ADR 002.

Batch and mutation ledger entries MUST NOT expire because of age, inactivity, schema change, cursor retention, or compaction. Deletion is permitted only after the permanent marker for an irreversible client retirement is durable, so deletion or client ID reuse outside that path is a violation of Ledger retention and irreversible client retirement in ADR 002.

A first push execution MUST commit its batch response, every mutation outcome, every accepted source write, every server-version transition, and every write fence in one database transaction, or roll all of them back. A retryable operational response beside any committed subset is a violation of First-execution transaction boundary in ADR 002.

An HTTP 200 first execution MUST place every requested mutation exactly once in accepted or rejected, preserve request-relative order within each array, and use only the statuses and required fields defined by ADR 002. An omitted, duplicated, cross-listed, or retryable per-mutation outcome is a violation of Complete outcome partition.

A first execution of a push request with atomic: true MUST commit the source writes of every mutation or of none. The server MUST stop at the first outcome that is not applied and roll back every source write, version transition, and write fence of the group. The failing mutation MUST keep its status and code, and a failing conflict MUST carry committed state read after the rollback. Every other mutation MUST receive rejected_terminal with atomic_batch_rejected. A partial group commit, a conflict row from rolled-back state, an accepted-write epoch advance after rollback, or atomic_batch_rejected in a non-atomic response is a violation of Atomic mutation groups in ADR 002.

A push unit is one mutation of a non-atomic request or the complete group of an atomic request. Before the first unit, the server MUST fail the complete call with SQLSTATE 25000 when a relation with a deferrable trigger has a pending trigger event in the calling transaction, and it MUST NOT fire that event. Each unit MUST start with every deferrable constraint deferred and MUST check every deferrable constraint before the unit ends. A failed check at a unit end MUST roll back only that unit and produce rejected_terminal with validation_failed. In an atomic request, the last mutation of the group carries that outcome. The last mutation of a unit MUST own each synced write that the unit-end check causes. Each accepted outcome MUST report its row state after the unit-end check. A check at transaction commit, a caller event fired in the call, or a constraint mode that depends on the unit position is a violation of Push unit constraint checks in ADR 002.

Every mutation outcome MUST carry the exact immutable outcome_schema used for classification and row checksum encoding. First execution uses the current request schema; exact and cross-batch replay preserve the original stored binding. A client MUST verify historical rows under that manifest and MUST NOT apply an unsafe historical row as current-schema state. Missing, rewritten, unavailable, or checksum-inconsistent binding, or loss of later local intent during historical reconciliation, is a violation of Exact replay and Complete outcome partition in ADR 002.

Update and delete MUST lock the row and row-version identity, verify byte-for-byte base_version equality, and perform the source transition and version-fence transition atomically; insert MUST atomically reserve an absent row identity. An unlocked check followed by a write, a write after a failed comparison, or two successful reservations is a violation of Opaque server versions and atomic compare-and-swap in ADR 002.

The first lock of the authoritative source row MUST use the row lock mode that PostgreSQL takes for the source statement before its BEFORE row triggers run. A hard delete MUST lock the row FOR UPDATE. An update or a soft delete MUST lock the row FOR UPDATE when its updated column set contains a key column of the partition that holds the row. Otherwise it MUST lock the row FOR NO KEY UPDATE. A key column is a key attribute of a live unique index that has no predicate and no expressions. The updated column set contains each assigned column and each generated column that PostgreSQL computes again for the statement. The server MUST compute the mode for each update and each soft delete from the relation cache entry that the statement uses. It MUST compute the mode after it holds ROW EXCLUSIVE on each relation from the registered relation to the row partition, and after it accepts invalidation messages. An insert, a reread after an insert that applied no row, and a reread after an atomic group rollback MUST lock the row FOR NO KEY UPDATE.

A stronger lock blocks each foreign key check of a child row until the push ends. A weaker lock makes the statement upgrade the lock while it holds the weaker lock, which can cause a deadlock. As in a native statement, a BEFORE trigger that changes a key column can make PostgreSQL take the stronger lock at the write. An index that a concurrent build or drop changes after the mode computation can also change the lock at the write. A deadlock returns SQLSTATE 40P01, and the push fails with a retryable error.

Of concurrent update and delete attempts using the same current base_version, at most one MUST apply and every loser MUST receive the current authoritative conflict outcome. Two successes, a missing outcome, or loss of the winning transition is a violation of the complete operation and row-state matrix in ADR 002.

client_version MUST remain a diagnostic RFC 3339 UTC timestamp and MUST NOT affect conflict choice, row version, source updated_at, or write order. Different outcomes caused only by equal, past, or future client time are a violation of Exact mutation shapes in ADR 002.

A schema_incompatible outcome MUST retain the original mutation UUID, complete authored payload, authored schema, base version, client version, and local order in a durable inspectable blocked state. Dropping or rewriting an incompatible field, deleting the intent, or automatically retrying it is a violation of Schema-incompatible queued intent in ADR 002.

A batch MUST be sealed before first transmission, and retries after response loss, HTTP 429, or HTTP 503 MUST reuse the same batch identity, mutation order, and canonical content. Reusing the batch ID with changed content or retrying operational failure as a different execution is a violation of Scoped immutable identity and Whole-request failures in ADR 002.

Schema or application reconciliation MUST give changed intent a new mutation UUID and MUST leave the original intent inspectable; an unchanged queued mutation MUST retain its UUID and complete authored content. Folding a successor into the original mutation or deleting either without explicit policy is a violation of Schema-incompatible queued intent in ADR 002.

Before transmission, a same-row insert, update, and delete chain without a legal server precondition MUST be normalized or blocked exactly as defined by ADR 002. Every original local action MUST remain linked and inspectable, no dependent mutation may be sent with a fabricated base version, and an insert followed by delete before sealing MUST create no transient server row.

Every mutation captured inside one atomic write transaction MUST carry that transaction’s atomic group identity, and no other mutation MAY carry it. The client MUST reject and roll back a group that fails a commit-time rule in Atomic write groups in the Client Contract. The client MUST send a group only as one complete sealed batch with atomic: true that contains no other mutation. When one unsent group member becomes blocked_by_predecessor, every unsent member of that group MUST become blocked. A split group, a mixed batch, or a committed invalid group is a violation.

Every committed change affecting registered data MUST remain eligible until its complete pull-visible representation is durable. If representation fails, the source transaction MUST be durably quarantined and MUST block later processing and acknowledgement. Dropping the change or advancing or serving later progress is a violation of the WAL decision in ADR 001.

Direct writes and accepted push writes MUST become pull-visible only through durable WAL materialization. A synchronous push changelog, membership update, or other second publication path, or a pull-visible effect without a WAL replay identity, is a violation of Decision in ADR 001.

The worker MUST order source transactions by commit LSN and events by their pre-filter source ordinal within one stream generation, MUST represent an empty generation with generation_start, and MUST represent every fully processed transaction with its transaction-end point. Ordering or refining pull visibility by sequence, timestamp, notification, table name, or poll order, or issuing a boundary with no defined point, is a violation of Commit-safe stream position in ADR 001.

One committed source transaction MUST be one atomic replay unit with stable transaction and event identities, and retry MUST reproduce exactly one materialized result. Duplicate versions, edges, effects, or a transaction split into independently durable replay units is a violation of Transaction and event replay identity in ADR 001.

The logical slot MUST advance only after materialization commits and only to a durable processed boundary before which every source transaction is completely durable. Acknowledging an event, batch maximum, later noncontiguous transaction, incomplete transaction, or a position after an unprocessed source transaction is a violation of Slot acknowledgement in ADR 001.

A source transaction that cannot be decoded, validated, or materialized MUST create a durable redacted quarantine record and MUST block later processing and acknowledgement until same-identity retry succeeds or an authorized stream reset establishes a complete verified projection baseline from a new logical slot’s exported snapshot, catches it up through an activation barrier, atomically creates a new generation, and forces rebuild. Reusing incomplete projections, taking an independent live snapshot, exposing pre-baseline catch-up incrementally, logging and skipping, partial publication, or acknowledgement around the poison transaction is a violation of Poison-event quarantine and recovery in ADR 001.

A stream reset MUST use a permanent candidate logical slot created through the replication protocol, import its exported snapshot while the creating connection remains valid, persist its exact slot and boundary lifecycle, and atomically switch a durable active-slot pointer only after complete catch-up and verification. Activating a temporary, missing, wrong-database, wrong-plugin, gapped, or unrecoverable slot, or losing WAL continuity across process death, is a violation of Causally Bound Projection Baseline And Stream Reset in ADR 001.

Each source row event MUST use exactly the latest validated registry generation whose activation precedes that event in source order. An activation precedes an event when an earlier source transaction commits it, or when its activation message precedes the event in the same source transaction. Activation by notification alone, or a generation switch at any position other than an activation message, is a violation of Registry activation and relation drift in ADR 001.

Every registry, catalog, publication, decoder, hydration, and scope-function lookup MUST use the immutable logical identity and exact schema-qualified physical identity, including OID drift detection. Bare-name or search_path resolution, or automatic rebinding after relation drift, is a violation of ADR 001 and Logical and Physical Relation Identity in ADR 004.

An active registered relation MUST have exactly one non-null declared primary-key column of portable type string, int, or int64, with REPLICA IDENTITY DEFAULT backed by that key. The primary key MUST NOT be deferrable, because PostgreSQL uses only an immediate key as the replica identity. A registration with push policy enabled MUST NOT use a database-generated key. The key column type MUST be one of the built-in key types in Primary key and replica identity contract in ADR 004, because every component identifies a row by the text form of its key. Synced mutation validation and the source trigger MUST reject any update that changes the key value before fence or row effects. The capture fence MUST also reject a key change with SQLSTATE 23514, including a key change by a later BEFORE UPDATE trigger and a partition row move. Accepting any other key shape, key type, or replica identity, continuing capture after drift, or capturing a key-value update as one event is a violation of Primary key and replica identity contract in ADR 004.

Capture MUST report ready only for the configured database when the worker, heartbeat, validated registry, required triggers, exact publication, usable slot, poison state, and contiguous progress checks in ADR 001 are healthy. A database ping alone, a worker on another database, or ready status with any missing or unhealthy required check is a violation of Readiness.

Every actual registered source row operation MUST emit exactly one row-level AFTER fence. In each row identity, which is the relation identity and the registered key, the k-th row operation in source order MUST correlate with the fence that has the k-th lowest registered-DML ordinal. Each pair MUST match by registration kind, unique fence ID, relation identity, operation, keys, and transition version. Row operations and fences of different row identities MAY interleave in any order. In each row identity, the fences MUST fire in the order of the row operations. A statement fires its row fences at its end, but a nested statement fires its fences first. If a nested statement changes a row before the fence of an earlier change of that row fires, that fence MUST fail the source transaction with SQLSTATE 27000. Thus, a same-row AFTER trigger whose name sorts before synchro_capture_fence fails when it changes its row. COPY ... FREEZE into a registered table MUST fail with SQLSTATE 0A000, because a frozen row does not keep the command ID that the check needs. Repeated same-row DML remains distinct; ON CONFLICT DO UPDATE emits only its final update fence and DO NOTHING emits none. A synced fence requires table identity, while a capture-dependency fence forbids it and uses registered capture-key identity. Any missing, duplicate, extra, reordered within one row identity, reused, or malformed correlation MUST block capture. Pull or rebuild while an accepted fence lacks WAL materialization or activated reset-baseline coverage MUST return 503 capture_pending without progress.

During reset, every accepted fence visible in the imported candidate-slot snapshot MUST have immutable verified baseline coverage and complete projected row/version state, while every later fence MUST reconcile through candidate-slot WAL. Activation MUST wait until every accepted fence through the barrier has exactly one of those coverage modes. The projected row/version state is the final state in that snapshot. It is separate from historical coverage. For a row with a pending fence in that snapshot, the final source-owned version MUST come from a covered fence of that row. If the snapshot does not contain a row and its source version is live, the baseline has no row or version for that row. The reset MUST NOT invent an event or tombstone for such a row. A deletion version in that snapshot MUST remain staged, including a version that a soft-delete update sets. Clearing a fence by LSN guess, fabricating an event, omitting a pre-boundary accepted write, or leaving a covered fence permanently pending is a violation of Causally Bound Projection Baseline And Stream Reset in ADR 001.

Repeated evaluation of the same authoritative row and dependency state MUST produce the same normalized scope set. A different set, or treating evaluation failure as an empty set, is a violation of Deterministic Scope Membership in ADR 004.

Membership and impact functions MUST satisfy the exact registered signatures and restrictions in ADR 004, including schema qualification, LANGUAGE SQL, STABLE, SECURITY INVOKER, declared dependencies, capture-only dependency registrations, commit-ordered projection access, and positive bounds. A function that reads a live application relation, or activation after any contract violation, is a conformance violation.

Assignment functions MUST satisfy the separate signature and restrictions in Scope assignment in ADR 004. Their result count bound MUST be 1 through 1000, or SQL NULL for administrator-selected unbounded evaluation.

Every declared dependency change, including a same-relation sibling dependency, MUST be captured through a synced or capture-only dependency registration, use its captured old and new image to identify every affected source row, and evaluate membership against the final commit-ordered projected state of that source transaction. Missing an affected row, reading later live state, or treating impact failure as no impact is a violation of Function restrictions in ADR 004.

A membership rule, dependency, composition, or controlled relation change MUST mint the affected membership generation, stale only affected cursors and rebuild sessions, and require affected scopes to rebuild. Interpreting an old cursor under changed rules or invalidating unrelated scopes is a violation of Registry and Membership Generations in ADR 004.

Membership reconfiguration MUST build and catch up a complete staged edge set before atomic activation, while retaining the prior generation as required by active continuation state. Rebuilding live edges in place or exposing a partially backfilled generation is a violation of Staged membership backfill in ADR 004.

Causally Bound Class 3 Projection Bootstrap

Section titled “Causally Bound Class 3 Projection Bootstrap”

When Class 3 requires any table, field, dependency, or projection absent from active materialization, the server MUST use one permanent candidate logical slot and imported exported snapshot, stage complete pending source, dependency, version, membership, and integrity projections, replay every later transaction through a barrier also reached by the main worker, and atomically activate the manifest, generations, projections, and affected-scope invalidation. Early manifest exposure, an independent live query, omitted catch-up, partial activation, mismatched barriers, or process-death recovery from another slot is a violation of Staged membership backfill and Causally Bound Projection Baseline in ADR 001 and ADR 004.

Before local schema DDL, the client MUST persist the complete verified target manifest and a versioned deterministic typed migration plan bound to source and target references, action, affected scopes, and phase. Restart MUST resume from that journal without schema fetch, avoid repeating committed DDL, preserve queues and local-only data, and enter explicit error for inconsistent physical, reference, body, plan, or phase state. Missing journal inputs, raw unvalidated SQL, network-dependent recovery, mixed physical and active schema, or false completion is a violation of Crash-safe local schema application in ADR 004.

For one read-consistent pull boundary, the server MUST resolve and deduplicate each valid scope’s complete eligible candidate stream before applying the global page limit. Applying the limit to raw global rows so that an eligible scope candidate is hidden is a violation of Per-scope eligibility before the limit in ADR 003.

The server MUST merge per-scope candidate streams by the exact typed tuple in ADR 003, compute has_more from all remaining stream heads, and make finite-boundary progress starvation-free. Different candidate selection or order for the same fixed streams and boundary, premature completion, or indefinite bypass of an eligible candidate is a violation of Deterministic, starvation-free merge.

Every derived pull effect MUST have a deterministic persisted delivery position (commit_lsn, event_ordinal, effect_ordinal) within its scope, and a page cursor MUST be able to stop and resume between effects from one source event. An earlier deduplication-suppressed effect MAY be covered by the retained later candidate for the same typed scope key, but an issued cursor MUST remain before that retained candidate until it is explicitly delivered. Replaying the first selected candidate forever, advancing past an undelivered retained or sibling candidate, deriving ordinal order nondeterministically, or using the effect ordinal as WAL replay identity is a violation of Commit-safe stream position and Deterministic, starvation-free merge.

Every selected pull candidate MUST yield one explicit canonical upsert or delete from its WAL-captured projection, or the whole page MUST fail without cursor, checksum, or checkpoint advancement. Silent omission, live-row hydration of a historical effect, or partial-page success is a violation of Explicit candidate outcomes and captured projections in ADR 003.

Pull deduplication MUST key on (scope_id, logical_table_id, typed_primary_key) and retain the greatest eligible stream position for that key within its scope. String concatenation, cross-scope collapse, or progress consumption in another scope is a violation of Scope-inclusive typed deduplication in ADR 003.

A client MUST advance a scope cursor only after every corresponding incremental outcome has been validated and durably applied for that scope. Cursor advancement before apply completion, or advancement caused by another scope, is a violation of Pull response rules in ADR 003.

The server MUST advance a durable scope checkpoint only when a later successful client request presents a valid cursor for that scope, and MUST do so monotonically. Selection, response construction, token issuance, rebuild paging, or an older token MUST NOT advance it, as specified by Cursor issuance and server acknowledgement in ADR 003.

Every incremental cursor MUST authenticate all identity, generation, scope, schema, retention, stream-position, issue-time, and key bindings listed in ADR 003. Accepting a token with an omitted or mismatched binding, or failing to distinguish stale authenticated state from forged or misbound state, is a violation of Incremental cursor bindings and Stale versus forged cursor dispatch.

Rebuilding one scope MUST preserve unrelated synced scope materialization and local-only data. Wiping or rematerializing unrelated data solely because another scope rebuilds is a violation of the single-scope rebuild contract in ADR 003.

The first rebuild request MUST atomically stage one complete immutable snapshot and digest from WAL-materialized projections exactly through boundary B, and every later page MUST read only that stage. A source snapshot not causally bound to B, requerying live rows or edges, changing staged order or content, or exposing a partial session is a violation of Durable immutable staged rebuild snapshot in ADR 003.

Every rebuild continuation MUST authenticate all common and rebuild-specific bindings in ADR 003, including session, rebuild ID, boundary, ordinal, page limit, and fixed 24-hour expiry. Accepting a forged, cross-context, resized, or expired token, or extending expiry on use, is a violation of Rebuild HMAC token bindings and 24-hour expiry.

Writes, deletes, and membership changes after rebuild boundary B MUST be excluded from the staged snapshot and remain eligible for normal incremental delivery after the client presents the final cursor. Inserting them into the snapshot or omitting them from subsequent pull is a violation of Durable immutable staged rebuild snapshot in ADR 003.

Repeating a valid first, intermediate, or final rebuild page request MUST return its exact stored page without a new snapshot, live query, ordinal change, digest recomputation, or checkpoint effect, but only while its accepted-write epoch remains current. Any changed replay output or side effect is a violation of Idempotent first, intermediate, and final replay in ADR 003.

Every rebuild session and continuation MUST bind the client’s durable accepted-write epoch. A first execution accepting any source write increments that epoch atomically; rejected mutations and exact replay do not. Every page and stored replay MUST check it before returning data, and a mismatch MUST return 409 rebuild_restart_required without records or progress. Replaying a snapshot staged before a later accepted write, including after that write is WAL-materialized, is a violation of the rebuild snapshot contract in ADR 003.

A rebuild request MUST modify only its session, immutable stage, and replay outcomes, and MUST NOT change any incremental checkpoint, another scope’s state, or the scope-set version. Any such state change before a later pull presents the final cursor is a violation of Rebuild checkpoint and scope isolation in ADR 003.

A valid cursor MUST prove only resumable progress and MUST NOT by itself mark local materialization healthy. Treating a scope as healthy without independent authoritative digest verification is a violation of the checksum contract in ADR 005.

Every terminal pull page MUST include authoritative scope checksums, and a client MUST verify each applicable local scope before treating it as healthy or installing terminal progress. A terminal page accepted without that verification is a violation of Wire checksum objects and terminal maps in ADR 005.

A pull page with has_more: true MUST omit checksums; a terminal page MUST include a map whose keys exactly equal the complete active scope set after scope updates, including scopes marked for rebuild and excluding removed scopes. Missing, extra, malformed, or wrong-bound entries MUST fail the whole page without cursor advancement under ADR 005.

A client MUST complete a rebuild only after every page is durably applied, stale provenance is pruned, zero-provenance rows are removed, the immutable snapshot digest matches, and the final cursor is atomically installed. Declaring completion before any one condition is a violation of Rebuild response rules in ADR 003.

Rebuild Does Not Erase Valid Overlapping Data

Section titled “Rebuild Does Not Erase Valid Overlapping Data”

Rebuilding a scope MUST NOT remove a local row while any other valid scope provenance edge still justifies it. Deleting such a row is a violation of Normal scope movement in ADR 003.

A scope with no usable cursor MUST use the same authenticated, snapshot-bound, digest-verified rebuild contract for first materialization as for recovery. Incrementally resuming or installing unverified state for that scope is a violation of ADR 003.

The same immutable manifest and complete authoritative row MUST produce identical canonical typed bytes on every implementation, and receivers MUST reject unknown, duplicate, omitted, mistyped, or primary-key-mismatched fields before apply. Manifest type aliases, alternate casing, and PostgreSQL or SQLite physical type names MUST be rejected rather than normalized. Divergent bytes or accepted malformed rows violate Canonical typed binary grammar in ADR 005.

Every row-bearing push outcome, pull change, rebuild record, and seed record MUST carry an opaque server version and a SHA-256 row digest that binds the exact schema hash, logical table, typed primary-key identity, complete canonical row, server version, and domains defined in ADR 005. A changed bound value with an unchanged accepted digest, a missing or stale version, outcome reconciliation or cursor advancement after digest failure, or a checksum without a row is a violation of Row identity and row digest.

Every authoritative scope digest MUST be the standard streaming SHA-256 over the exact schema, scope ID, cardinality, and canonically sorted row-identity and row-digest pairs defined in ADR 005. Accepting a duplicate, omission, changed pairing, changed cardinality, or implementation-defined aggregate with the same claimed digest is a violation of Scope digest.

Queued mutations, immutable identities, authored content, and ordering metadata MUST be persisted before first transmission and survive process death and restart. Losing or regenerating any of them after restart is a violation of Scoped immutable identity in ADR 002.

Rebuild session, page-apply, provenance, and verification state MUST survive process death without installing a false final cursor or checksum. Restarting from an unverifiable partial state as though rebuild completed is a violation of the client obligations in ADR 003.

Local cursor advancement MUST commit atomically with successful validation and local apply for that scope. A crash state containing the new cursor without all corresponding data, or all data without its transactionally paired cursor decision, is a violation of ADR 003.

Rejected mutations and their canonical reasons MUST remain durably inspectable across restart until explicit client policy clears them. Automatic deletion, including deletion during schema reconciliation, is a violation of Schema-incompatible queued intent in ADR 002.

For a multi-scope table, local metadata MUST record exactly the active scopes that currently justify each row. Missing, stale, or cross-row provenance, or removal of a row with nonempty valid provenance, is a violation of Normal scope movement in ADR 003.

Assignment removal, seed cleanup, and rebuild reset or final pruning MUST delete only server provenance, continuation state, and zero-provenance cache materialization that is not protected by unresolved authored local intent. When pending or inspectably blocked intent addresses a row, every cleanup path MUST preserve its application-visible local row state outside server provenance until normal push-first reconciliation or explicit application policy resolves it. Removing that row before its outcome, converting cleanup into a mutation, or treating lost assignment or rebuild as authorization to discard intent is a violation of Scope assignment changes and Rebuild finality in the client contract.

A published schema manifest body, hash, positive version, class, compatibility floor, composition, table IDs, and field IDs MUST be stored atomically and MUST NOT change in place. Serving a live-catalog body under an existing hash or version is a violation of Immutable Schema Manifests in ADR 004.

Connect MUST apply the exact per-client Class 1 through Class 4 action dispatch and explicit incompatible-schema reset in ADR 004, including replace for Class 3 with no affected assigned scope and queue-preserving reset after unsupported; push, pull, and rebuild MUST require the exact current manifest, and protocol version 3 MUST NOT emit fetch. A different action, an empty rebuild_local, no recovery path, or partial work after schema mismatch is a violation of Protocol 3 Schema Dispatch and Exact Current Schema Gate.

Every schema-changing connect MUST replace each otherwise valid historical-schema cursor for an unaffected existing scope with a server-issued current-schema token at the exact same position, while returning null for an affected or otherwise stale scope. Replacement MUST require a known compatible lineage, no Class 3 effect on that scope, and current identity, generation, membership, retention, stream, and resume-floor bindings. The client MUST install the replacement only with successful target-manifest activation and MUST NOT relabel the old token. Rebuilding an unaffected scope solely because the manifest hash changed, accepting an old-schema cursor on pull, or carrying an affected scope forward is a violation of Protocol 3 Schema Dispatch in ADR 004.

Each scope MUST retain a durable inclusive resume floor and retention lineage even when no effects remain. A cursor below the floor MUST rebuild, a cursor at or above an ordinarily advanced floor MUST remain eligible if its other bindings are valid, and inferring validity from an empty log is a violation of Per-Scope Retention in ADR 004.

Client expiry MUST use the finite positive stale interval and cursor-acknowledgement activity defined in ADR 004; reconnect MUST issue a new generation with null affected cursors and preserve the durable mutation queue and idempotency ledgers. A first-execution generation rejection MUST allocate no ledger state, after which unchanged authored mutations move to a new successor batch and retain their mutation IDs while the old sealed batch remains immutable and unsent. Reusing the expired generation, editing or resending the rejected batch, changing content under an existing mutation ID, or deleting its idempotency ledgers is a violation of Explicit client generation and expiry.

Compaction MUST use the minimum acknowledged position among nonexpired clients assigned a scope, or that scope’s durable high-watermark when none are assigned, honor active rebuild pins, and commit effect deletion atomically with monotonic floor advancement. A global or unrelated checkpoint, inferred floor, or one-sided commit is a violation of Scope-local compaction in ADR 004.

Every request, push body, mutation count, mutation size, column count, compaction, backfill, fanout, and impact limit named in ADR 002 and ADR 004 MUST be within its positive fixed or configured maximum. Push byte and count limits MUST fail before ledger allocation or semantic work. Defaulting, clamping, truncating, or doing work after an invalid value is a violation of Strict Positive Limits.

A seeded database MUST enter the normal authenticated connect, delta, checksum, and rebuild protocol and MUST NOT use a separate authority or sync mode. Treating seeded contents or metadata as authorization is a violation of Snapshot-bound portable seed export in ADR 005.

An exported seed MUST contain only scopes explicitly declared portable shared scopes by the server and MUST NOT contain auth-scoped user data. Inferring portability from row presence or exporting an undeclared scope is a violation of Snapshot-bound portable seed export in ADR 005.

Every seeded portable scope MUST contain verified continuation metadata for the exact seeded schema, generations, boundary, cardinality, and digest. Treating seeded rows as incrementally resumable without that metadata is a violation of Seed continuation receipt in ADR 005.

All manifest, scope, row, digest, boundary, retention, and receipt reads for one seed artifact MUST occur on one physical connection in one enforced SERIALIZABLE READ ONLY DEFERRABLE transaction over WAL-materialized projections exactly through its visible durable boundary. Mixing transactions, connections, source-table state not causally bound to that boundary, or live post-snapshot reads is a violation of Snapshot-bound portable seed export in ADR 005.

Seed page tokens MUST authenticate every export, transaction, scope, generation, boundary, ordinal, and limit binding in ADR 005, and seed receipts MUST become client-bound cursors only through authenticated connect after current assignment and state validation. Accepting a cross-context token or using a receipt directly as a runtime cursor is a violation of Seed page token and Seed continuation receipt.

The exporter MUST verify every row and scope digest, page sequence, receipt, SQLite integrity property, queue and provenance condition, and final artifact hash required by ADR 005 before atomic publication. Any failed or skipped check MUST produce no artifact, so publishing a partial, corrupt, sidecar-dependent, or unverified seed is a violation of Seed verification and artifact publication.

Native clients with a verified bundled or seeded protocol 3 manifest MUST accept local SQLite writes before first successful connect and preserve them in the same durable mutation queue used later. A portable-data seed MUST install each verified opaque row version as local compare-and-swap base before writes are exposed, while withholding runtime cursors until authenticated receipt conversion. Unassigned-scope cleanup or stale-receipt rebuild MUST preserve pending authored intent and local row state. A client without a verified manifest MUST delay synced-table writes because it has no authoritative table or field identities. Losing eligible offline intent, fabricating a base version, treating a version as authorization, or storing intent outside the durable queue is a violation of the queue contract in ADR 002, ADR 004, and ADR 005.

Retryable transport or availability failure MUST preserve queue, schema, assignment, cursor, rebuild, and apply state except for durable retry bookkeeping, and a failed page MUST expose no partial success. Any other durable transition is a violation of Whole-request failures in ADR 002 and Error rules in ADR 003.

Every non-retryable protocol or local contract failure MUST enter an explicit typed error or blocked state and MUST NOT continue silent degraded synchronization. Continuing push, pull, or rebuild as though the failure succeeded is a violation of the dispatch rules in ADR 002, ADR 003, and ADR 004.

Under default client configuration, a server-required scope rebuild MUST proceed without application approval or app-owned orchestration. A default client that waits indefinitely for mediation is a violation of the rebuild contract in ADR 003.

A client MUST permit only the complete state adjacency map in ADR 005; unknown states and unlisted transitions MUST fail as non-retryable local contract errors without mutating durable sync state. Accepting any other transition, including stopped -> pulling or error -> ready, is a violation of Legal client state transitions.

Application and minimum-client versions MUST parse and compare by Semantic Versioning 2.0.0 exactly, while protocol negotiation MUST remain the separate integer 3. Accepting malformed syntax, a leading v, or precedence that mishandles prerelease or build metadata is a violation of SemVer 2.0 precedence in ADR 005.

Authorization, Readiness, And Operations Invariants

Section titled “Authorization, Readiness, And Operations Invariants”

The fixed synchro schema and every extension-owned object MUST revoke PUBLIC authority and grant only explicitly required schema, type, and function capabilities. Any public execution or direct adapter, seed, or monitor access to metadata tables or sequences is a violation of PostgreSQL schema and authorization in ADR 005.

Owner, adapter, seed, monitor, operator, and worker SQL authority MUST remain split among the six least-privilege NOLOGIN, NOREPLICATION group roles in ADR 005. Exactly one dedicated LOGIN REPLICATION NOINHERIT worker principal MAY belong only to the worker group and MUST use explicit SET ROLE for SQL access. Its internal background-worker connection does not use HBA authentication. External worker connections MUST use restricted HBA and deployment-managed credentials. A runtime role inheriting owner or another runtime role, broad function grants, or any other replication-capable login is a violation of Database role separation.

The adapter MUST derive readiness from the canonical extension result for its configured database, and overall status MUST be ready only when every required tri-state check is ok. Reporting ready for failed, unknown, absent, inconsistent, or query-error state, or exposing the detailed check map publicly, is a violation of Fail-closed readiness in ADR 005.

Worker heartbeat age, WAL lag bytes, and oldest-unmaterialized-commit age MUST be known, finite, nonnegative, and no greater than configured finite positive maxima. A missing, zero, negative, non-finite, or unparseable maximum, or a missing, negative, non-finite, unparseable, or exceeded observation, MUST make readiness unready under ADR 005.

Logs, metrics labels, traces, readiness output, and quarantine text MUST NOT contain synced values, primary keys, user or client IDs, scope instances, mutation bodies, tokens, credentials, raw requests, raw WAL, SQL text, or unbounded errors. Observing any prohibited value is a violation of Logging and failure-state redaction in ADR 005.

pgrx annotations and extension_sql! declarations in the Rust extension source MUST be the sole authored source for extension DDL, grants, revokes, comments, and membership, except for the reviewed update scripts synchro_pg--<from>--<to>.sql in extensions/synchro-pg/sql. Shipping hand-maintained parallel SQL or generated SQL that does not regenerate byte-for-byte is a violation of Installation and migration baseline in ADR 005.

Version 0.3.1, the first published Protocol 3 release, MUST be the first supported extension migration baseline, proven by a clean PostgreSQL 18 install and non-superuser role checks. Each later version MUST be reachable from 0.3.1 through one chain of update scripts. A 0.3.1 installation updated through the chain MUST have the same extension objects as a clean installation of the current version. Claiming an in-place update from deleted or experimental pre-0.3.0 state or from the unpublished 0.3.0 candidate, using IF NOT EXISTS as migration logic, or shipping an update chain whose result differs from a clean installation is a violation of Installation and migration baseline in ADR 005.

Each Go sync endpoint MUST delegate its semantic operation to the canonical extension function and MUST NOT implement independent scope, conflict, rebuild, or synced-write decisions. A second semantic implementation or outbound sync-service dependency in the adapter is a violation of the extension authority established by ADR 001 through ADR 005.

React Native MUST delegate queue, cursor, conflict, apply, and rebuild behavior to the native clients on both platforms and MUST NOT implement a third sync engine in JavaScript. Any divergent JavaScript-owned semantic transition is a violation of the cross-language boundary required by ADR 005.

Native clients MUST own local SQLite integration, durable queueing, scheduling, verification, and safe apply while accepting server authority for assignment, schema, conflict outcomes, cursors, and rebuild state. A native client overriding a server decision or delegating durable semantics to JavaScript is a violation of ADR 002, ADR 003, and ADR 004.

No Mandatory Custom Backend Write Contract

Section titled “No Mandatory Custom Backend Write Contract”

A client MUST be able to synchronize create, update, and delete through local SQL and the canonical Synchro protocol without an application-defined upload endpoint. Requiring such an endpoint for any registered table is a violation of Push vocabulary and First-execution transaction boundary in ADR 002.

No Arbitrary Client-Defined Replication Model

Section titled “No Arbitrary Client-Defined Replication Model”

Sync requests MUST accept only server-assigned scope identifiers and MUST NOT accept client-authored predicates or ad hoc scope SQL. Executing such input is a violation of Deterministic Scope Membership in ADR 004.

No Extra Hot-Path Network Hop For Core Sync Logic

Section titled “No Extra Hot-Path Network Hop For Core Sync Logic”

The production core sync request path MUST call the PostgreSQL extension directly and MUST NOT make an outbound RPC to a separate sync service for core semantics. Observing that extra dependency in a connect, push, pull, or rebuild path is a violation of the extension-authoritative execution defined by ADR 001 through ADR 005.