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
- Depending on the domain rules for each entity
- Sync resolves at per row level
- Merge resolves at per field level
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
- Best for:
- Status fields
- System-generated values
- Audit logs
- Permissions
- Timestamps
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”
- Merged result keeps both changes.
- Best for:
- Task metadata
- Vessel profile fields
- Document metadata
- User-entered fields unlikely to overlap
- Requirements
- Field-level diffing
- Conflict engine compares each properly
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: