Writing Backend correctness

Don’t invent a user at push time

Minting a fake “Cashier” so the foreign key passes orphans every real person’s sales.

A sale leaves the device with a cashier id. The cloud has never seen that person. Sync invents a shared user named Cashier so the foreign key passes. Reports light up under one ghost. Alice and Bob never own a row.

That is not a missing column. It is attribution satisfied locally and meaningless in the roster. The counter of “who sold this” looked fine on the tablet. Push is where the lie became permanent.

Invariant: if a sale names a cashier, that id must already be a real roster user. Otherwise the device is unlinked for attribution and must not push as if it were.

Why a valid FK is still wrong

Nothing crashed. The cloud accepted the insert. The join from sale → user succeeds. The wrong model is: the tablet needs someone on the row, so inventing “Cashier” at push time is fine.

Person and device are not the same thing. A tablet setting, a shared PIN, or a placeholder name can satisfy a NOT NULL. None of them are a person on the team list. Every later report that groups by cashier collapses onto the ghost.

Three paths in one lab

I ran node lab/synthetic-cashier-push.mjs in this repo on 10 August 2026. One in-memory cloud. Roster starts with Alice only.

PathWhat we needResult
mint “Cashier” on pushFK always passesghost on roster; report under Cashier; Alice orphaned
stamp Alice’s roster idattribution sticksreport under Alice; no synthetic user
unknown cashier iddo not invent a personaccepted: false, reason: "unknown_cashier"; no sale stored

Trimmed report:

{
  "synthetic": {
    "createdSynthetic": true,
    "ghostName": "Cashier",
    "reportByCashier": { "Cashier": 1 },
    "aliceOrphaned": true
  },
  "roster": {
    "reportByCashier": { "Alice": 1 },
    "syntheticCount": 0
  },
  "unknown": {
    "accepted": false,
    "reason": "unknown_cashier",
    "salesOnCloud": 0
  },
  "allPassed": true
}

The interesting failure is not that the synthetic insert works. It is that Alice was on the roster and still got zero sales. Push never asked who was active. It manufactured a sink.

Roster first, then stamp

Pull (or cache) the team list before you treat a device as ready to sell under a name. Active cashier is a roster UUID. Switching cashier changes the stamp on new sales — it does not mint a new cloud user.

Push may append sales and sessions. It must not upsert a synthetic staff row to make foreign keys happy. Unknown id → reject (this lab) or leave the device unlinked until someone signs in. Do not “fix” attribution with a shared ghost.

function pushSale({ cashierUserId, ...payload }) {
  if (!cashierUserId || !roster.has(cashierUserId)) {
    return { accepted: false, reason: "unknown_cashier" };
  }
  // insert sale stamped with cashierUserId — never create the user here
}

You still need a real sign-in / PIN gate on the device. Rejecting an unknown id does not create a roster. It only refuses to invent one.

Decision boundary

Rejected: device role as cashier_user_id, and auto-create "Cashier" on push. Both satisfy storage and destroy “who sold this.”

This note is the wrong tool when you truly have a single operator, no per-person reports, and no multi-cashier audit — you still want one real roster row for that owner, but the urgency is lower than a multi-person till.

What this does not solve

Shared one-PIN-for-everyone is a sibling footgun; fixing push does not fix it. An empty offline roster needs an explicit unlink / block-sell policy — not covered beyond reject. Void and override rules are separate from who is stamped on the sale.

Offline writes need identities they can mint without coordination for events, and people they must not mint to paper over a missing roster. Sales can be append-only with UUIDs and still lie about the cashier. Boring on purpose. The synthetic user is the exciting bug.

A public notebook

Notes from real delivery: racey quotas, sync when a device is offline, messaging APIs, and the gap between a clean local demo and production.

Not a product catalog, a tutorial syllabus, or a course funnel. If a post names a tool, I used it. If it describes a failure, it happened.

About Contact