Domain model — the knowledge graph
Derived from the Supabase migrations (monet snapshot apps/dashboard/supabase/migrations/ plus the newer migrations at Lovable origin/main:supabase/migrations/), cross-checked against hooks and the real REST contract. Everything in this file is VERIFIED against a migration or service file unless marked otherwise. Backing legend (used in the diagram and the tables): GREEN = backed by a real Fibretrace REST endpoint wired in monet today; AMBER = a real endpoint exists but is partial (aggregate-only, read-only, roster-only, or unwired in monet); RED = Supabase-only, zero real REST equivalent.
Entity-relationship diagram
1. Identity and infrastructure
| Entity | Business-relevant fields | Relationships | Backing |
|---|---|---|---|
| companies | name, sharing_enabled, confidence_score/rating/summary/drivers (0-100, bucketed Moderate/Good/Excellent by trigger), unclaimed + invite_token/email/sent_at/claimed_at (partner-CSV placeholder companies) | hub of everything | AMBER — GET /public-companies (is_public catalog) + GET /company (own); no full registry, private partners resolve “Unknown” in monet |
| company_details | 1:1 companies: company_code (3-letter), company_type, supply_chain_tier (free text, self-declared), registration/billing/tax fields, hide_from_partner_directory, logo_url | 1:1 companies | GREEN — GET/POST /api/monet/v1/company |
| company_users | name, email, role (owner/admin/auditor/pending/user), phone, blockchain_public_key | N:1 companies | GREEN — /team/members and invitations |
| company_partners | requester/addressee company, status (pending/accepted/rejected), per-side reference labels; CHECK no-self; UNIQUE unordered pair | N:M companies | GREEN — /partners CRUD + pending-invitations |
| company_modules | module_name (billing, verified_cta, verifications_menu, scanners_menu, program_owner, legacy sdu), enabled; UNIQUE(company,module) | N:1 companies | GREEN in monet (mapper wired) |
| company_api_access, api_keys | enabled flag; key_value (REVOKEd from clients, key_last4 generated), description, last_used_at | N:1 companies | GREEN — /data-partner/api-keys (v2) |
| signup_requests | applicant + company_name/code/type, tier, first facility fields, status, email_verified, mfa_code, approved_company_id/user_id, claim_company_id | bootstraps companies/details/facility/first-admin on approval | RED |
| facilities | name, country, facility_type, address, status, is_free, os_hub_id, ewr_gin_id/holder_id, expires_at, lat/long | N:1 companies; sessions and production happen here | GREEN — full /facilities CRUD |
| scanners | serial_number UNIQUE, model, status (+ scanner_facility_assignments join) | assigned to facilities | GREEN reads/PATCH — but no real delete or facility-assign endpoint; admin CRUD is Supabase fiction |
| facility_licenses | company_id UNIQUE, license_count, subscription_status — count-based, no cost/period fields | consumed by non-free active facilities | RED (real side: GET /license-summary + POST /license-enquiries, a different shape) |
2. Programme and capacity
| Entity | Business-relevant fields | Relationships | Backing |
|---|---|---|---|
| fibre_programmes | name, status enum (hidden/active/expired/deleted), dates, pigment_id, fibre_type, country_of_origin, default_reservation_term_months (DEFAULT 18), heatmap_enabled, logo_url, commercial_description | pigment N:1; children below | AMBER — GET /fibre-programs(+id) deployed 2026-08-03, tenant-scoped, unwired in monet |
| program_owners | UNIQUE(programme, company) — max one owner per programme via delete-then-insert | 1:1-ish programme->company | RED |
| programme_participants | role CHECK (producer/participant/manufacturer), UNIQUE(programme, company, role) | N:M programme<->company | AMBER — returned inside /fibre-programs/{id} detail |
| programme_producers | ceiling_mt, scans_per_mt, pigment_supplied_kg, scan_volume_total (=ceil(ceiling x scans_per_mt), trigger), scans_completed, produced_mt (capped at ceiling), production window; UNIQUE(programme, company) | N:M; facilities via programme_producer_facilities (manual_logging_enabled; same-pigment-overlap trigger guard) | AMBER — in detail response |
| programme_facility_scans | session_id UNIQUE — one programme association per scan session | links sessions -> programme_producers | RED |
| programme_reservations | quantity_mt (signed; zero forbidden), quantity_cu (=MT x 1000, sync trigger), notes, expires_at NOT NULL (defaulted created_at + term months by trigger) | N:1 programme, N:1 company | AMBER — only the summed funnel GET /claim-positions/{id}; no row-level GET; real BE has no expires_at anywhere |
| reservation_requests | quantity_cu, status (pending default), decision_note, reviewed_at/by, approved_reservation_id, expires_at | approval inserts the real reservation | RED (real side: write-only POST reservations/enquiry email) |
| programme_volume_adjustments | kind CHECK (reserved/claimed), delta_cu signed, resulting_cu, reason, admin_email | admin ledger | RED |
| program_certificates / certificate_types / program_certificate_assignments | owner-uploaded credentials, status CHECK (active/revoked), expiry | programme-level | RED (read-only certificates[] array exists on /fibre-programs/{id}) |
| program_documents / program_document_assignments | owner-uploaded documents | programme-level | RED (documents[] in detail response) |
| program_record_items | section CHECK (fibretrace/program/wording_supported/wording_notyet), title, body, visible | the owner mini-CMS | RED |
| explore_fibre_programs | marketing catalog, status CHECK (available/coming_soon) | admin CMS for discovery cards | RED |
3. Production and hardware
| Entity | Business-relevant fields | Relationships | Backing |
|---|---|---|---|
| production_records | quantity, unit, weight_kg, status, source (manual/sdu/ewr/sdu_demo/ewr_demo), excluded, short_code (COMPANYCODE+6 digits, trigger), scans_required (1 for SDU/EWR, else ceil(quantity x 0.05)), scans_completed | N:1 company/programme/facility; production_files 1:N | RED |
| pigment_ids | label UNIQUE, tau_value UNIQUE, int_value, tolerance | programmes reference; pigment_company_assignments | RED |
| pigment_orders | company, programme, status, quantity_kg | producer pigment procurement | RED |
| sliver_delivery_units | serial, device_id, model, firmware, status enum (feeding/ended/idle/starting/paused/blocked/fault), bale counters, dose_g_per_kg, env metrics, sliver_remaining_pct, speed_bph | N:1 company/facility; sdu_activity_logs 1:N | AMBER — real read-only GET /api/v1/sdu/devices family; zero write endpoints |
4. Scans and verification
| Entity | Business-relevant fields | Relationships | Backing |
|---|---|---|---|
| collections | company, programme, status, guid (auto) — the /verify/:guid anchor | 1:N verifications | GREEN — real orders/collections domain |
| verifications | collection_id (nullable), name, status | 1:N sessions | RED as a resource (vocabulary exists only client-side against real BE) |
| sessions | session_code, conducted_at, fibre_detected, scan_count, facility_id, production_record_id, direction + material_direction enum (inbound/outbound), scanner_serial, pigment_id, partner_company_id, carrier, shipping_doc_id, linked_outbound_session_id, shipper, shipping_id, counterparty_company_id, programme_id | N:1 verification/facility; chain-of-custody pairing via shipping_doc_id | RED — no real sessions resource; monet fakes session_id from the audit id |
| audits | session_id, result, ft_id, blockchain_guid, notes | N:1 sessions | GREEN — GET /sat-audits + POST /sat-audit (but blockchain_guid has no source: always null against real BE) |
| session_shares | UNIQUE(session, shared_with_company) | session -> company grants | RED |
| sessions_v / audits_v (views) + mask_ftid() | masked session_code/ft_id/blockchain_guid unless claim-linked or setting off; base columns REVOKEd from clients — masking is server-side in Supabase | read layer over sessions/audits | RED (no real equivalent; session_code_masked flagged as unresolved BE requirement) |
| verification_connections / connection_deliveries / connection_interest | type CHECK (csv/url/webhook), secret (REVOKEd), delivery status (queued/sent/failed); interest = insert-only marketing signal | data-connections surface | RED |
| evidence_requests / evidence_request_shares | requester/recipient company, programme, quantity_mt, scans_requested, sku, order_reference, lot, status | retailer asks supplier for evidence | RED |
5. Nominations, claims, evidence
| Entity | Business-relevant fields | Relationships | Backing |
|---|---|---|---|
| product_categories | ref, kind (primary/custom), hs_codes[], baseline_net_weight_kg, default_blend_pct, loss_multiplier (internal-only), gsm; client scoping via product_category_clients | feeds PO line defaults | RED |
| purchase_orders | company_id (retailer/issuer), partner_company_id (manufacturer), po_reference (UNIQUE per issuer+partner while unarchived), sku, programme_id, volume_mt (CHECK >= 0), volume_claimed_mt, volume_cu/volume_claimed_cu (Tier 0 source of truth, MT synced x1000), status enum (draft/shared/linked/ready/proposed/partially_claimed/fully_claimed/closed), archived, linking_due_at, last_reminder_at, supplier_closed_at/note | N:1 both companies + programme | RED — no real PO resource anywhere; real /orders is Collections |
| purchase_order_lines | line_no, sku, quantity, hs_code, product_category_id, net_weight_kg, blend_pct, loss_multiplier, cu_total (summed to header by trigger), quantity_claimed, overridden, excluded | N:1 PO; N:1 category | RED |
| purchase_order_verifications | session_id UNIQUE — a scan session links to at most one PO ever; DB trigger enforces partner-facility-or-shared and blocks when PO fully_claimed/closed; bumps PO to ready | PO <-> session | RED |
| purchase_order_verification_lines | tags scans to specific PO lines | join | RED |
| production_claims | programme_id, company_id (the RETAILER/PO issuer), purchase_order_id, quantity_mt/quantity_cu, status CHECK (proposed/confirmed/rejected) — app also writes revoked with revoked_at/by/reason, see open question Q10 — plus confirmed/rejected metadata, rejection_reason, shipment_ref, shipped_on | N:1 programme/company/PO | AMBER — POST /api/monet/v1/claims exists (single-step, different model); no list/lifecycle endpoints |
| production_claim_lines | per-line allocation snapshot: quantity, cu, cu_per_piece, net_weight_kg, blend_pct, loss_multiplier | N:1 claim -> PO line | RED |
| production_claim_sessions | UNIQUE(company_id, session_id) — a company cannot claim the same scan twice | claim <-> session | RED |
| claim_verifications | claim_id, session_id UNIQUE — manual (non-PO) claim-to-scan links; dropped in the April reset, re-created 2026-07-22 | claim <-> session | RED |
| purchase_order_evidence_packs | PO, claim, volume_mt, is_partial; insert trigger meters volume against PO remaining and recomputes claimed/status | created on confirm | RED |
| public_verification_tokens | token UNIQUE; references at least one of session/verification/claim; enabled toggle; seeded for trade/sustainability/procurement on confirm | public /c/:token access | RED |
| Evidence pack registry (code) | 8 pack types with sections and shareability: verification-record, sustainability, procurement, retailer, assurance, regulator, dpp, supplier | src/lib/evidencePacks.ts | RED (data side) |
| company_certificates / company_certificate_facilities / po_scope_certificates | company-level certs with all-or-specific-facility scoping; po_scope_certificates: PO UNIQUE -> certificate | settings certificates + PO scoping | RED (real /certificate-types is staff-only admin CRUD) |
6. Admin, settings, messaging
| Entity | Fields | Backing |
|---|---|---|
| app_settings | key/value jsonb; seeded billing_enabled_site_wide, obfuscate_verification_ids, chatbase_widget | GREEN in monet as a mapper; the obfuscation key itself has no real BE home |
| cms_strings, help_pages, admin_banners (+dismissals) | admin CMS content; banner severity CHECK | RED |
| notifications / notification_templates / notification_preferences | server-generated rows; real BE notifications are read-only GET + mark-read (no client insert, no link field) | AMBER — GET /user/notifications real; Lovable’s client-insert pattern has no equivalent |
| onboarding_tasks | company task list with due dates | RED |
| invoices | invoice_number, purchase_order (free text), billing_period, amount, currency, status | GREEN — /company/invoices |
Cardinality highlights worth memorising
One owner company per programme. One producer row per company per programme. One reservation row per adjustment (uniqueness deliberately dropped 2026-08-16 to enable the ledger). One partnership per unordered company pair, never self. A scan session appears at most once in each of: programme_facility_scans, purchase_order_verifications, claim_verifications (UNIQUE session_id), and at most once per company in production_claim_sessions. A PO reference is unique per issuer+partner while unarchived. A public token must point at something (CHECK session/verification/claim not all null). All VERIFIED (schema section 6 of the migration sweep).