Conflict Resolution Strategy

The following defines the conflict resolution model for the Vessel Vanguard platform using

  • PostgreSQL (authoritative relational database)
  • GraphQL Delta Sync (push/pull sync cycle)
  • RxDB (local offline database using IndexDB/SQLite).

Because the system is offline-first and multi-user, conflict resolution must be deterministic, predictable, and
domain‑aware. This solution balances simplicity (AS), practicality (PFM), and domain intelligence (DSR) to
create a robust offline-first platform. This specification ensures:

  • Accurate merging of concurrent edits
  • Preservation of critical data
  • Minimal user friction
  • Predictable outcomes
  • Full offline capability
  • Strong consistency guarantees

Core Conflict Resolution principals

Server is the Authority

The backend (PostgreSQL + GraphQL resolver logic) is the final source of truth.

All merges occur on the server.

All Records Must Have Standard Metadata

Every relational table must include:

id UUID PRIMARY KEY

updated_at TIMESTAMP WITH TIME ZONE

updated_by UUID

_deleted BOOLEAN DEFAULT FALSE

version INTEGER (optional but recommended)

Why?

  • updated_at enables delta sync
  • updated_by enables conflict audits
  • _deleted supports soft deletes
  • version adds deterministic ordering

Types of Conflicts

Conflicts occur when:

  • A row is edited locally AND edited on the server before sync
  • A row is deleted on one client and edited on another
  • Two clients update different fields of the same record
  • A user updates data that is now invalid according to business rules

Conflict Resolution MOdes

There are three merge modes, chosen per-entry.

Authoritative Server Merge (AS)

The server always wins. Client changes are applied only if :

  • client.updated_at > server.updated_at AND
  • client changes do not violate domain rules

Outcome: Predictable, safe, stable

Per-Field Merge (PFM)

If both client and server changed different fields, the merge keeps both updates. Example:

  • Client: task.title = “Replace bilge pump”
  • Server: task.due_date = “2025-07-12”

Domain-Specific Rules (DSR)

Used when conflicts require higher-level logic. Examples:

1. Task Completion vs Task Edit

If one client marks a task complete
and another edits the task details:
Rule: Completion override wins.
Merged state = completed task + updated details (if allowed by workflow).

2. Document Expiration

If one user changes the expiration date and another changes status:
Rule: Date change always takes precedence. Status recalculated.

3. Checklist Answer Conflicts

Field-level merge only if answers do not contradict.
If contradiction: server logs conflict and promote user

Full Merge Algorithm (Server Side)

Input

client_version

server_version

changed fields

Updated timestamps

STEP 1: Determine if conflict exists

if client.updated_at <=

server.updated_at:

conflict = true

else:

conflict = false

STEP 2: Choose merge mode

Each entity/table has assigned merge mode:

Vessel = PFM

Task = DSR

Checklist Answer = AS or DSR

Document metadata = PFM

Document blobs = AS

STEP 3: Apply merge logic

Depend on merge mode:

AS: accept server row; optionally override server with client field if approved

PFM: merge fields individually

DSR: apply domain rule

STEP 4: Save merged result

Server writes authoritative row or Postgres:

Updated updated_at set server-side

STEP 5: Respond to client

  • Return merged authoritative row to Postgres
  • Client updates local RxDB record

Entity-by-entity conflict rules

Vessels (PFM)

  • Most fields are independent
  • Last-write-wins for single-field collisions
  • Field-level merge otherwise


Tasks (DSR)

Rules:

  • Completed tasks override edits
  • Edits override informational fields
  • Due date changes override reminders
  • Assigned user conflicts logged but allow merge


Checklists – (DSR)

  • Answers merged if non-conflicting
  • Conflicting answers: server flags record and requires user confirmation


Documents (Metadata) – PFM

  • Title, tags, categories merged per-field
  • Status follows AS
  • Expiration follows DSR


Document Files – AS

Binary documents ALWAYS follow server for consistency.


Users & Permissions – AS

Only server can authoritatively modify permissions

Soft Delete Conflict Rules

  • If client deletes a row offline and server updates it:
    Rule: Deletion overrides client changes unless server-side logic forbids deletion.
  • If server deletes a row and client edits it:
    Rule: Deletion wins. Client receives _deleted=true.

Versioning strategy

Each row optionally includes:

version INTEGER

This increments on every update.

If client.version < server.version → conflict.

This allows deterministic reconciliation.

Client-Side (RxDB) Behavior

  • Always apply server response as authoritative
  • Never overwrite server merges
  • Outbox entries removed only when server acknowledges
  • Conflicts may be surfaced to UI where appropriate

User Messaging Strategy

Conflicts should rarely be visible, but when they are:

  • Silent resolutions for:
    • per-field merges
    • authoritative server merges
    • system-generated fields
  • User alerts for:
    • contradictory checklist answers
    • major task state conflicts
    • deleted records they attempted to update

Auditing & Logging

Every conflict event logged in Postgres:

table

record id

client vs server versions

resolved values

updated_by client

updated_by server

timestamp

Allows:

Traceability

Debugging

Analytics on conflict frequency