Skip to content

Outbox ordering for entity repositories and payload-carrying upload crash reports - #1

Merged
3 commits merged into
mainfrom
hotfix/sync-422-divergence
Aug 16, 2026
Merged

Outbox ordering for entity repositories and payload-carrying upload crash reports#1
3 commits merged into
mainfrom
hotfix/sync-422-divergence

Conversation

@austin047

Copy link
Copy Markdown
Collaborator

Problem

SyncEntityRepository.put()/post()/delete() only wrote the pending local change when the remote call was skipped or failed with UnavailableException. Any other error — e.g. a server validation rejection — propagated before the change was enqueued, permanently orphaning the record: saved locally, invisible to the sync loop, never retried, never quarantined, absent from sync history. This is how the trakli mobile app lost transactions in the field on v1.0.5.

Changes

  • v0.3.1 — outbox ordering: the pending change is enqueued before the remote attempt; success removes it, failure records the error on it (handing retry/backoff/quarantine to the synchronizer), and remote errors no longer rethrow to callers. Deleting a never-synced entity concludes immediately instead of calling the server, and a queued delete replaces a queued create for the same entity.
  • v0.3.2 — richer crash reports: upload_local_change reports now include the attempt number and a truncated excerpt of the queued payload, so unmarshal/serialization failures are diagnosable from the report alone.

Verification

12 new tests in sync_entity_repository_test.dart cover success, rejection, network-unavailable, unauthenticated, and delete-replaces-put paths; 111 tests passing. Note: constructor_test.dart's "sync() throws on concurrent sync" was already failing on main before this branch — it asserts the pre-0.2.0 behavior that the 0.2.0 idempotency change replaced, and is left for a separate cleanup.

Tags drift_sync_core-v0.3.1 and drift_sync_core-v0.3.2 are published and consumed by trakli/mobile#214.

…epositories

put/post/delete previously only wrote the pending local change when the
remote call was skipped or failed with UnavailableException; any other
exception (e.g. a 422 validation rejection) propagated before the change
was enqueued, permanently orphaning the record locally with no retry,
no quarantine, and no sync-history entry.

The pending change is now written before the remote attempt. Success
removes it, failure records the error on it so the synchronizer's
existing backoff and quarantine classification take over, and remote
errors no longer escape to the caller. Deleting a never-synced entity
concludes immediately instead of hitting the server.
… reports

A type-cast failure during unmarshal carries no context of its own;
without the queued payload the report cannot show which field was at
fault.
…aits

A queued payload that fails to unmarshal throws UnmarshalException,
classified permanent: the payload is immutable, so it is quarantined
and surfaced on the first attempt instead of burning the ten-attempt
unknown budget. A change deferred by shouldPersistRemote records a
transient DependencyPendingException on the row, giving sync history a
reason instead of a silently pending change.
@austin047 austin047 closed this pull request by merging all changes into main in afb8fce Aug 16, 2026
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