Skip to main document
[NOTE 02] · AUGUST 2025 · 8 MIN READ
SYSTEM: EDGE DATA ARCHITECTURE

Offline-first is a data-model problem, not a sync button

"Putting a 'Sync Now' button in the corner of your interface is an admission that your underlying architecture treats local work as an aberration rather than the primary state. Real resilience requires re-engineering how state is born."

Local Queues Schema-per-Tenant Causal Ordering Tombstones PostgreSQL Sync

Most web and SaaS applications are designed around optimistic online assumptions: the client displays a spinner, sends an HTTP POST to a remote REST endpoint, waits 150 milliseconds for the cloud database to return an autoincrement ID, and updates the UI.

When teams attempt to retrofit "offline support" onto this foundation, they invariably install an HTTP caching layer and slap a "Sync Changes" button into the header. The moment a store runs offline for eight hours, this approach collapses: autoincrement IDs collide, deleted records re-emerge like ghosts, foreign keys break, and cashiers are presented with baffling modal dialogs asking them which version of a receipt to discard.

The Cashier Reality: If your system requires an exhausted cashier to resolve a three-way database conflict while ten customers wait in line, you do not have an offline architecture — you have an unfinished engineering project.

1. The Append-Only Mutation Queue

In NoreOS, state is never modified in-place over the network. Instead, every user interaction is modeled as an immutable mutation event written immediately to a local disk-backed log (SQLite or IndexedDB on the edge terminal):

interface MutationEvent {
  id: string;              // Client-generated UUIDv7
  tenantId: string;
  terminalId: string;
  type: 'SALE_COMPLETED' | 'ITEM_VOIDED' | 'STOCK_ADJUSTED';
  payload: Record<string, unknown>;
  lamportClock: number;    // Monotonic sequence per register
  occurredAt: string;      // ISO-8601 UTC
  status: 'PENDING_SYNC' | 'COMMITTED' | 'CONFLICT';
}

Because mutations are append-only, the register can commit checkouts with sub-millisecond local latency. Thermal receipt printers fire immediately over USB or Bluetooth ESC/POS protocol. As far as the customer and cashier are concerned, the transaction is finished and permanent.

2. The Problem with Deletes: Tombstones Are Mandatory

In a naive relational database, deleting a row means executing DELETE FROM items WHERE id = ?. In an offline-first distributed system, an in-place physical delete is fatal.

Consider this sequence: Register A is offline. Register B deletes an obsolete item. Register A reconnects and syncs its state. If the record on Register B was simply removed, Register A sees no record of the deletion, assumes Register B missed the item, and re-uploads it back to the cloud. The item has risen from the dead.

To prevent resurrection, all deletions must be modeled as explicit Tombstone records:

interface Tombstone {
  entityId: string;
  entityType: 'ITEM' | 'CATEGORY' | 'CUSTOMER';
  deletedAt: string;
  deletedByTerminalId: string;
  version: number;
}

When Register A reconnects, it queries the server for all tombstones registered since its last sync watermark. It deletes its local copies and purges pending mutations targeting those entities.

3. Causal Ordering Without Wall-Clock Synchrony

You cannot rely on device system clocks. A cashier's tablet battery might die, causing the CMOS clock to reset to January 1, 1970 upon reboot. If your reconciliation logic sorts events by occurredAt, all new offline transactions will be sorted behind historical records, corrupting account balances.

Instead, NoreOS pairs UUID v7 identifiers (which embed a millisecond timestamp prefix with high random entropy) with a monotonic Lamport Sequence Counter incremented strictly on local writes. When reconciling on the backend, the server sorts by sequence number first, using server arrival time as a tiebreaker.

4. Schema-per-Tenant Isolation as a Scaling Primitives

Many SaaS architectures use a shared database with a tenant_id column on every table. In high-concurrency offline sync, this introduces severe lock contention: when fifty offline stores reconnect simultaneously after a storm, fifty concurrent batch syncs acquire write locks across the same table indexes, stalling the database.

In NoreOS, every merchant is allocated a dedicated PostgreSQL schema. Sync workers write exclusively to the tenant's isolated tables. A storm of reconnecting merchants results in completely partitioned database operations, eliminating index lock contention and allowing seamless horizontal database sharding.

The Core Lesson: Offline resilience is not a feature you add to your user interface with service workers or local storage polyfills. It is a fundamental commitment to event-sourced mutation logs, deterministic keys, and asynchronous reconciliation semantics in your core schema design.