Docs.

Rental House — Inventory Provenance (the sheet sync was retired)

architecture/inventory-provenance.md

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/inventory route, the sync_events and sync_errors tables, the sync_event_status enum, the settings.inventory_canonical_source switch (+ its inventory_source enum), the "sync alerts" dashboard stat, the Google Apps Script sender (scripts/google-apps-script/), and the one-shot scripts/import-inventory.ts + scripts/curate-inventory.sql.

Kept deliberately

  • audit_log.source and the audit_source enum (user, api, agent, system). This is how any row is attributed to its origin — web-office actions carry user, the iOS/REST surface api, automated or AI-agent data work agent, and trigger-emitted changes system. 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):

  1. Trigger (fn_emit_audit) — an AFTER INSERT/UPDATE/DELETE trigger on the inventory tables (items, kits, settings, assets) writes one audit_log row per change with the full {old, new} snapshot in payload. It reads the actor from the app.actor_user_id session GUC and leaves source at its 'system' default.
  2. writeAudit (src/lib/office/audit.ts) — purposeful rows for operational mutations, carrying the actor, source (defaults to user; /api/office/* routes pass api), and lifecycle from_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 items row on the sheet's UUID column (column O → items.id), so re-importing reuses the original IDs rather than duplicating.
  • Expand each row's Quantity into N assets (first asset takes the serial).
  • Map money to USD cents, Locationassets.location, etc., per 04.

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.location is a per-asset enum (MDE | LAS | other), but whether/how that surfaces in catalog and quoting is undecided. See open-questions.md.