Zum Inhalt springen
NavTrax Expeditions-Betriebssystem

Architecture · offline-first synchronization

Consistency without a connection.

This page describes the mechanism, not the marketing. If you are evaluating NavTrax for an operation where a lost write has a cost, this is the document to read.

Durable op logCausal replayField-level mergeGroup relay4 transports

The failure mode we are designing against.

A party of six spends nine days in a drainage with no cellular service. Over those nine days they create waypoints, edit the route twice, log three water sources as dry, record two injuries, and change the check-in schedule. Two members carry a mesh radio. One member walks to a ridge on day four and gets two bars for eleven minutes.

An online-first application handles this by queueing requests in memory and retrying. That fails in four specific ways, all of which we have seen in field software:

  1. The queue does not survive a process kill. The operating system reclaims a backgrounded app after a day of GPS recording, and the in-memory queue goes with it. The user has no way to know.
  2. Retries replay in arrival order, not causal order. A waypoint deletion lands before the creation it refers to, the server rejects it, and the deletion is silently dropped.
  3. Conflicts resolve by whole record. Two members edit different fields of the same trip; last-write-wins discards one of them entirely rather than merging two non-overlapping changes.
  4. Only the member on the ridge syncs. The other five carry nine days of unsynchronised state down the mountain.

The fabric described below exists to make all four impossible.

Four transports. One queue.

Every mutation takes the same path out of the device regardless of which radio eventually carries it. The transport is a late binding, chosen per attempt.

Device → mesh → LoRa → satellite → cloud Ziehen zum Schwenken · ⌘/Ctrl + Scroll zum Zoomen · Knoten ziehen zum Verschieben
On-device Transport Resolution Cloud

Writes are operations, not state.

The device does not queue "the new version of trip 41". It queues the operation that produced it. Each entry carries:

{
  "seq":        18342,              // monotonic, per-device, never reused
  "op":         "trip.waypoint.add",
  "entity":     "trip:41",
  "parent":     18339,              // the op this one causally depends on
  "actor":      "user:88",
  "device":     "dev:a9f1",
  "at":         "2026-08-05T14:22:09Z",
  "lamport":    [88, 18342],        // tiebreak pair for concurrent edits
  "payload":    { "lat": 47.4318, "lon": -121.8121, "label": "Water — dry" },
  "attempts":   0,
  "transports": []                  // which channels have been tried
}

Durability

The log is written to IndexedDB on web and to SQLite on mobile before the UI acknowledges the action. A process kill loses nothing. On relaunch the queue depth is shown in the sync badge, so an unsynchronised nine-day trip is a visible number rather than a silent condition.

Causal ordering

parent makes the dependency explicit. The server applies an operation only when its parent has already been applied, and holds it otherwise. A deletion can never arrive before the creation it refers to, because the deletion names the creation.

Idempotency

(device, seq) is the idempotency key. A satellite link that acknowledges a frame the device never receives causes a retry, not a duplicate: the server has already recorded that pair and returns the same result.

The cheapest channel that is actually up.

Transports are ranked by cost per byte and by payload ceiling, then filtered by live availability. The selector runs per attempt, not per session, so a party that walks in and out of coverage does not need to do anything.

Satellite is deliberately last and deliberately restricted. Iridium airtime is billed by the byte; sending a full trip diff over it because the code did not know better is a real bill for a real customer. Over satellite the fabric sends a compacted digest — the operations that change safety-relevant state — and holds the rest for a wider channel.

Transport characteristics as the selector models them
TransportPayloadCostRank
Wi-FiUnboundedFree1
CellularUnboundedMetered data2
Mesh 868/915 MHz240-byte framesFree3
LoRa store & forward240-byte frames, hours of latencyFree4
SatelliteCompacted digest onlyPer byte5
Sneaker-net bundleUnboundedFreeManual

One person gets a signal. Six people sync.

Group relay is the part of the fabric that most changes what an expedition feels like. Every member's queue is replicated to their mesh peers as a compacted digest. When any member acquires an IP-bearing channel, that member's device pushes every digest it holds — not only its own — and pulls the group's deltas back down the mesh.

Three properties make this safe rather than merely convenient:

  • Digests are signed by the originating device. A relaying peer cannot alter another member's operations; it can only carry them.
  • Relaying is opt-in per member and visible. Privacy settings decide what leaves your device through somebody else's radio, and the app shows what was carried on your behalf.
  • Store-and-forward has a horizon. Unattended relay caches hold traffic for a bounded window and report what they are still holding, so "it will sync eventually" is a number rather than a hope.

Merged by field. Never by record.

Conflicts are resolved at the granularity of the field, using the Lamport pair as the tiebreak. Two members editing different fields of the same trip produce no conflict at all — both edits survive.

Rules, in order

  1. Disjoint fields merge. No user is asked about a conflict that is not one.
  2. Same field, different values: higher Lamport pair wins. Deterministic, and identical on every device — the resolution does not depend on which device runs it.
  3. Additive collections union. Waypoints, journal entries and observation logs are append-only sets; concurrent additions all survive.
  4. Safety-critical fields never auto-resolve. Check-in schedules, emergency contacts and medical notes raise an explicit review with both versions shown. A silent merge of a medical note is worse than a prompt.
  5. Deletions are tombstones. A delete concurrent with an edit keeps the record, marks it deleted, and surfaces it for review. Nothing is destroyed by a race.

When there is no channel at all.

For deployments where no radio may be used, the same operation log exports as a signed bundle to removable media. The bundle is the queue: it carries the operations, the digests, and the device signatures. Importing it on a connected node applies it through exactly the same ingest, resolver and audit path as a cellular sync — there is no second code path with different guarantees.

This is what makes the no-cloud mode described on the defense pages a configuration rather than a separate product.

If a lost write has a cost in your operation, read the mesh protocol next.

The transport layer under this fabric is documented to the same depth, including the self-healing behavior and the key rotation scheme.