Skip to content

Operations-flow hardening: multi-device money-integrity invariants from the origin app #2

Description

@TiredofSleep

Companion to #1 (payments hardening). Same source: the origin app's pre-launch consolidation + adversarial review. These are the OPERATIONS invariants — what keeps a multi-terminal, local-first shop's books truthful when devices sync every few seconds. Every item below corresponds to a bug that actually happened (or was proven reachable) in production. They belong in the engine's kernel-invariants list (MODULE-LIBRARY.md already has the seed) and in whatever sync layer customPOS ships.

The #1 lesson: field edits must WIN the merge

In a last-write-wins sync, any mutation that doesn't stamp its record's modified-time can be silently reverted by the next pull while related array-adds (payments, log entries) survive. This ONE mechanism produced, in production or verified-reachable form: delivered orders snapping back to Ready with money already taken (handed out twice / re-charged), racked garments "lost" (on the shelf, screen says Assembled), un-racked garments handed out with the stain still on them, queue approvals reverting to pending and being approved twice, a paid-out refund lock reverting (double payout), settled collections re-raising as collectible, and store credit spendable twice. Recommendation: the engine should stamp centrally on mutation (or ship a one-liner touch(record) convention enforced by tests), and the docs should name this failure class. Idempotency flags (billed-once guards, consumed-once flags, already-decided guards) belong NEXT to the stamp — a reverted flag re-arms the action.

Money-recording invariants (kernel, not per-trade)

  • Ledger and drawer move together. Every real payment is BOTH a ledger entry and a drawer-visible payment row; the origin app fixed "collected money invisible to the drawer/daily report" twice before consolidating. One recordPayment unit, not per-flow copies.
  • Every payment row carries where it was TAKEN (station/location) separate from which location's books it belongs to — otherwise cash reconciliation goes over/short across locations.
  • Cents-round at every mutation. Four of six hand-rolled balance updates in the origin app drifted unrounded.
  • Bill-once guard: a re-run flow (re-delivery, retry) must never double-bill an order or duplicate its collections record — check the ledger net before charging.
  • Debt leaves the books only as a payment or by the OWNER's hand — write-offs/A-R removal are owner-only, logged, and voiding an order must clear its receivable (the origin app's approval-queue void skipped that until audited).

Authority + audit invariants

  • One non-overridable owner predicate. Owner-only money actions must NOT route through the general permission system (a perms edit must never grant refunds). One isOwner() definition — the origin app had 54 scattered role compares before the sweep.
  • PIN lookups: one helper — always trim, always enforce active!==false (a fired employee's PIN authorized things in one code path), and LOG rejected PINs at money gates: wrong-PIN guessing at the unpaid-release/void-approval screens was invisible to the theft log until audited.
  • Fresh PIN for irreversible approvals (voids AND refunds): an unattended logged-in owner session must not be enough to pay out cash.
  • Denials get logged. The gates guarding money actions were exactly the ones showing a toast with no audit event.

Truthfulness invariants (operator trust)

  • Notification claims must reflect the attempt decision, not config toggles: return {sent, reason} synchronously from one notify gate and have modals/logs read it. The origin app told staff "Text sent ✓" for opted-out customers, so nobody called them.
  • A bad auth key must never render as an empty inbox. Server: fail-closed key check + a 401 body that says WHICH side to fix (server env vs device key) on every endpoint. Client: inbox loaders surface that error instead of "No new messages."
  • A deploy gate harness: load the real single-file app in a Node vm and assert the money math (totals/tax, merge behavior, guards). The origin app's harness (196 assertions) caught regressions in every wave; it is the single highest-leverage practice to ship as a template alongside the engine.

Own-your-data (fits the project's core pitch)

One-click full JSON export (restorable) + CSV exports (customers/orders/payments/ledger) in-app, a one-click verified backup script, and a documented rebuild-from-nothing playbook. The honest-exit story — tokens can't move (by design), everything else can — is a trust feature worth advertising.

Source: origin-app consolidation pass, 2026-07 (multi-agent adversarial review, all findings regression-tested). No proprietary data included.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions