Appearance
Glossary
Rewritten 2026-08-08. The previous version defined Tier as a directory under
src/tiers/, Pillar as one of three products including BioLink, and UI Hybrid System as the UI model — all three belong to the retired Vite SPA (legacy/) or to retired product framing. Index of truth: current architecture state.
Platform model
| Term | Meaning |
|---|---|
| Setu Card | The universal public identity for every user, at qrsetu.com/:slug. The platform's core differentiator, not a module. Supersedes "Service Card" and retires "BioLink" (QRS-172, 2026-07-23). |
| Industry | Who you are — dairy, salon, car_sales. One industries row with a stable TEXT key, never an integer (integer ids promoted across environments caused QRS-249). Open set: adding one is a row. |
| Archetype | What you sell. Exactly three: goods (a stocked item) · time (a slot) · expertise (an enquiry). ecommerce_cart and catalog_informational were compositions, not archetypes (QRS-392). |
| Primitive | How you run. The closed set of eleven that carries all the platform's cost: Catalogue · Party · Schedule · Recurrence · Fulfilment · Ledger · Balance · Location · Resource · Asset · Campaign. A new primitive requires a written justification that it serves ≥3 industries — the governing gate of the whole architecture. |
| Composition | An industry's enabled_primitives[]. Dairy includes Recurrence, boutique does not — which is why archetype-only gating fails, and why applicability is derived from composition rather than stored in a matrix. |
| The cost equation | O(primitives), not O(industries). Eleven primitives compose into ~45 industries. Never build a dense industry × feature matrix. |
Users and tenancy
| Term | Meaning |
|---|---|
| Principal | The user. One users row per person, id = auth.users.id. There is no second account type. |
| Workspace | The business tenant — the thing that owns a card, a catalogue, and money. Named workspaces and not businesses because the tree legitimately contains non-businesses (a region, a division, a brand shell). The UI still says "your business". |
| The three user categories | Distinguished by membership count: consumer 0, solo owner 1 (member_owned), enterprise employee 1 (org_owned). See user ecosystem. |
primary_context | A preference on users choosing the landing route. Never an authorization input — a user-mutable column that grants access is a privilege-escalation primitive. Renamed from account_type for exactly that reason. |
ownership_model | member_owned | org_owned. Does three jobs: seat-revocation behaviour, who keeps the leads, and the oversight privacy boundary. |
| Location | An address, not a business unit. If a place needs its own card and its own P&L it is a workspace; if it is only an address it is a location. |
| Sharing vs oversight | Sharing flows DOWN (opt-in share_* flags, default off — a child may use an ancestor's resources). Oversight flows UP (an ancestor may read, never write, org_owned descendants). Two mechanisms, never symmetric. |
| Seat | A licence for a USER, not a workspace. 8 showrooms + 30 agents = 39 cards but 30 seats. |
Feature control
| Term | Meaning |
|---|---|
| The three axes | Applicability (does this workflow apply at all — structural) AND entitlement (is it unlocked, and capped how — commercial) AND availability (is it actually shipped). effective = all three. |
| Grant scope | One of 8 kinds in feature_grant_scopes, precedence 10→80: platform · archetype · industry · plan · workspace_group · workspace · workspace_member · user. |
| Sparse grants | feature_grants holds deviations only. Defaults come from composition. ~30 industries × ~40 features = 1,200 cells nobody maintains — so that matrix is never built. |
| Feature grant ≠ RBAC permission | Is this workflow available vs can this role do this action on it. "Agents may view stock but not edit prices" is RBAC (ADR-0024), not a grant. |
on_exceed | What happens at a limit: block_new · soft_warn · degrade. A cap with no defined overflow behaviour is an outage waiting for a busy Saturday. |
Data access
| Term | Meaning |
|---|---|
| The one hard rule | supabase.from() is banned in app code. Keeps table names off the network wire. |
packages/data | The data seam: a typed {Domain}Service interface + a stub impl per domain. Presentation depends only on the interface. |
| RPC | A Postgres function called via supabase.rpc('get_…') from a service, never a component. Read-only. SECURITY DEFINER + SET search_path = public + revoked from public/anon/authenticated by name, then granted explicitly. |
| Edge Function (EF) | A Deno/TS function called via functions.invoke() from a service. Writes, secrets, external HTTP, multi-step. The primary write-enforcement layer; RLS is defense-in-depth. Both always required. |
| Type A / Type B / Webhook EF | Type A = user-facing, JWT verified, domain-action. Type B = cron/internal, --no-verify-jwt, domain-noun-verb. Webhook = HMAC + timestamp window + constant-time compare + idempotency via a UNIQUE event id. |
EDGE_FN map | A per-domain *_EDGE_FN constant exported by the service. EF names are never inline strings at a call site. |
idempotencyKey | Required, not optional, on every mutation. Generated when the user commits and reused across retries — a fresh key per retry defeats the mechanism. |
| Outbox | The transactional outbox table + worker. How a write reaches Cloudflare cache purge without a distributed transaction. Also how a scheduled campaign boundary purges. |
my_* function | An RLS helper scoped to auth.uid(), called from inside a policy: workspace_id = any (my_workspace_ids()). |
SQL naming — five families
| Family | Means | Examples |
|---|---|---|
get_* | Retrieve stored rows | get_industries · get_public_setu_card |
resolve_* | Derive by computation over several sources | resolve_features · resolve_workspace_plan |
is_* | Predicate, bare boolean | is_slug_reserved |
my_* | RLS helper scoped to auth.uid() | my_workspace_ids · my_oversight_workspace_ids |
{table}_{action} | Trigger function, named for what it guards | workspaces_maintain_path · setu_cards_slug_write_once |
Anything outside these five is a naming violation, not a sixth family. Table names must be domain-specific: setu_cards not cards, platform_plans not plans, process_primitives not primitives. The test is "will a second thing plausibly want this name?"
Process and governance
| Term | Meaning |
|---|---|
| Design-governance asymmetry | Systemic surface (packages/tokens/** + apps/*/src/ui/**) is design-first, no exceptions. Screen composition is code-first, freely. Record every divergence in the drift ledger in the same PR (ADR-0015, check:design fails closed). |
| Parity | A feature ships on Android native · iOS native · Web PWA together. The exception path is retired — there is no pre-approved deferral. Scope is the pressure valve: a feature that cannot reach all three does not enter the release. |
| Divergence seam | Anything backed by a native module or browser-only API — camera, push, storage, share, clipboard, deep links, offline. Named at G0, not discovered at G3. |
| Expand-contract | The only safe migration shape: add-nullable → backfill → switch → drop. Never a breaking change in one step. |
| Change Record | Every production change, declared in release.json. deploy-prod.yml refuses to apply one that is not — documentation is load-bearing, not a chore. |
QRS-### | A permanent tracker id for every confirmed issue / risk / debt / decision. See the Dev Tracker. |
| Promotion | Deliberately applying every migration / EF / secret / storage / cron change to both Supabase projects, Dev first. Dev/UAT does not self-sync. |
| Wave 1 / Wave 2 | Wave 1 = the schema Store needs (identity, tenancy, cards, catalogue, grants, media, outbox). Wave 2 = Party · Schedule · Recurrence · Fulfilment · Ledger · Balance · Asset · Campaign · RBAC · billing · analytics. |
docs:gen | node tools/generate-docs.js — regenerates this portal's auto-generated pages (currently the EF index). |
Retired vocabulary — do not reintroduce
| Retired term | Replaced by | Retired on |
|---|---|---|
BioLink · bio_pages · bio_links · /b/:slug | The Setu Card at /:slug | 2026-07-23 (QRS-172); tables dropped 2026-08-08 |
Studio / My Setu website builder · /w/:slug · setu_pages · studio_* | Not in R1. The Setu Card carries the public presence | Dropped 2026-08-08 |
| Pillar (three products) | One platform, one card, industries × archetypes × primitives | 2026-08-08 |
profiles · profile_items · business_domain_id (integer) | users · workspaces · catalog_items · industry_key (text) | ADR-0020, 2026-08-07 |
capabilities · get_my_capabilities() | features · feature_grants · resolve_features() | ADR-0021 |
subscription_tiers · user_subscriptions · profiles.subscription_tier | platform_plans + feature_grants at plan scope | ADR-0021; tables dropped 2026-08-08 |
public_page_ops_* | The transactional outbox | Dropped 2026-08-08 |
UI Hybrid System (src/components/ui + per-tier overrides) | apps/mobile/src/ui (RN primitives) + shadcn/ui in apps/web, both over @qrsetu/tokens | ADR-0011 |
Tier as a src/tiers/ directory | Two stacks first, tiers inside each (see tier system) | ADR-0011/0012 |
Vitest / Vite / import.meta.env / VITE_* in app code | jest-expo (mobile) · vitest (web) · node --test (packages) · EXPO_PUBLIC_* | ADR-0012 |
| Green baseline | The gate layers in CLAUDE.md § Commands | — |