Inventory is DB-canonical. The Supabase Postgres database is the single
source of truth for items, assets, kits, and settings; the office UI
and its service-role RPCs are the only writers. There is no automated sync
from any spreadsheet.
This replaces an earlier sheet-canonical design. If you're looking for the
iOS staff app's offline sync (its outbox, idempotency keys, APNs), that's a
different, deferred surface — see ios-staff-app.md
and rest-api.md. This doc only
covers inventory provenance and the emergency re-import.
History — what the sheet sync was, and why it's gone
Inventory was originally Google-Sheet-canonical: a human edited a sheet, an
Apps Script onEdit trigger POSTed to a webhook (/api/sync/inventory), the
event landed in a sync_events queue (with a sync_errors dead-letter), and a
background worker was meant to re-read the row and upsert it into the DB.
That worker was never built, and the studio moved to editing inventory directly
in the office. With the DB canonical, the whole queue was dead weight, so it was
removed in migration 0005_office_and_accounts.sql (folded; was 0006_drop_sheet_sync.sql):
- Dropped: the
/api/sync/inventoryroute, thesync_eventsandsync_errorstables, thesync_event_statusenum, thesettings.inventory_canonical_sourceswitch (+ itsinventory_sourceenum), the "sync alerts" dashboard stat, the Google Apps Script sender (scripts/google-apps-script/), and the one-shotscripts/import-inventory.ts+scripts/curate-inventory.sql.
Kept deliberately
audit_log.sourceand theaudit_sourceenum (user,api,agent,system). This is how any row is attributed to its origin — web-office actions carryuser, the iOS/REST surfaceapi, automated or AI-agent data workagent, and trigger-emitted changessystem. See Provenance below.
Provenance — "where did this row come from?"
The audit log answers it for any row, the same way regardless of origin
(both mechanisms shipped in 0001_baseline.sql):
- Trigger (
fn_emit_audit) — an AFTER INSERT/UPDATE/DELETE trigger on the inventory tables (items,kits,settings,assets) writes oneaudit_logrow per change with the full{old, new}snapshot inpayload. It reads the actor from theapp.actor_user_idsession GUC and leavessourceat its'system'default. writeAudit(src/lib/office/audit.ts) — purposeful rows for operational mutations, carrying the actor,source(defaults touser;/api/office/*routes passapi), and lifecyclefrom_state/to_state.
So "show me the history of this asset" is one audit_log query: source
(where), actor_user_id/payload.actor (who), payload/from_state/
to_state (what), at (when).
Emergency re-import (if the DB is lost)
There is no import script anymore — but the sheet still exists, and the
exact sheet → DB column mapping is documented in
the archived sheet-era schema. To rebuild items +
assets from the sheet after a catastrophic loss, follow that mapping by hand
(or have an agent drive it):
- Key each
itemsrow on the sheet's UUID column (column O →items.id), so re-importing reuses the original IDs rather than duplicating. - Expand each row's
Quantityinto Nassets(first asset takes the serial). - Map money to USD cents,
Location→assets.location, etc., per04.
This is a deliberate, supervised recovery operation — not something that runs automatically.
Open question
- Multi-location representation. How a single SKU with assets in both
MDE (Medellín) and LAS (Las Vegas) should be represented is still
open —
assets.locationis a per-asset enum (MDE|LAS|other), but whether/how that surfaces in catalog and quoting is undecided. See open-questions.md.