Skip to content

Current architecture state — the single index ​

Read this first, and trust it over any other page. Maintained 2026-08-13. Purpose: one place that states what is decided, what is built, and what is open — so no document can silently become the source of truth for something it does not govern.

The reason this page exists: the three-user-category principle was verified on 2026-08-07 to exist in exactly five files and to have reached no ADR and no overview page 24 hours after being decided (QRS-384). Across 124 portal documents, per-document freshness is not achievable by discipline. A single authoritative index plus supersession banners is.

⚠ AND THIS PAGE GOES STALE TOO — check the date above against git log before trusting a specific claim. On 2026-08-12 it still read "Maintained 2026-08-08" while 13 migrations had landed since, and it stated that chat and messages "are all absent" when both had shipped the previous day. That is this page's own thesis applied to itself, not an argument against it: one index drifting is far better than 124 pages drifting privately. The operating rule: read the Maintained date, then diff it against git log and the newest release.json change records. Where they disagree, the migrations and the change records win, and updating this page is part of the change that outdated it.

📏 A full ground-up re-measurement of Dev was taken on 2026-08-26 and lives at Admin Portal — backend readiness. It is newer than this page's Maintained date and every claim in it was read from the live project (55 tables · 45 policies · 69 functions · 10 Edge Functions · 0 views · zero table grants to anon/authenticated). Where the two disagree, that page wins on backend facts — including three corrections to already-tracked rows (QRS-694 is closed in substance, QRS-643's symptom is fixed) and the headline finding that there is no platform-admin identity, no admin RPC, no admin policy, and audit_log has zero rows and no admin writer (its one writer is set_my_primary_context).

🟢 Since 2026-08-13 the COUNTABLE half of that discipline is automated. npm run check:claims (QRS-642) measures the repo on every push and fails if CLAUDE.md's generated inventory disagrees — Edge Functions, migrations, real-vs-stub data seams, gates, workflows, ADRs, merchant features, @/ui primitives, portal page counts, and the merchant-mobile screen ledger. So a stale number is now a failed push rather than something a reader has to notice. ⚠ A stale JUDGEMENT still is not — no gate can decide whether a decision recorded here is still the right one, so the date check above remains necessary. Arithmetic is automated; reasoning is not.

Delta since the 2026-08-12 pass, all verified against Dev on 2026-08-13: the reminders backend is live · sign-in was BROKEN for five days on every surface and is fixed (QRS-636 — the auth-context read called an RPC that exists only in _archive_pre_v2/) · slug claim was broken and is fixed (QRS-640), with the validate-user-input Edge Function deleted and availability moved to resolve_setu_card_slug_status(text) · Google OAuth verified end-to-end · provision_merchant_workspace verified atomic by a rolled-back live probe. Still broken: email OTP (QRS-285, SMTP 535) and dark mode on the web export (QRS-639).

⭐ ADDED 2026-08-25 — this page is 12 days stale, and here is exactly where

A whole-product revalidation was run on 2026-08-25 and measured this page against the repo. Product & architecture revalidation is the result; it holds the commercial and prioritisation half. Six corrections belong here, so they are recorded here rather than left in a sibling section:

This page says🧮 Measured 2026-08-25
FACETS are "unsolved and retrofit-expensive" (the 2026-08-10 banner, QRS-498)✅ Solved. catalog_items.attributes jsonb with @> containment matching and jsonb_each_text facet counting. Multi-select buyer filters run on real data
"Not built: payouts … R2 media"✅ Both built. payout_accounts, payout_transfers and platform_settlements exist; R2 upload and card render were verified working (QRS-833)
The eleven primitives are listed as the closed set with no build status⚠ 3 of 11 have table substrate (Catalogue, Fulfilment, Location), 2 are partial (Recurrence is user-owned; Resource is stock-commitment only), and 6 have none. So the time and expertise archetypes have no substrate at all — full table
Card analytics "should be read as not working" (QRS-734)⚠ Still true, seven days on. The beacon in apps/web/src/tiers/public/features/setu-card/analyticsBeacon.ts still names track-card-event, which lives only in _archive_pre_v2/ and is not among the 10 live functions
The decision ledger cites ADR-0029⚠ QRS-887 — ADR-0030 (tenant communication identity and prepaid credits) also exists and is not in the ledger
No row for the desktop console⚠ QRS-886 — 4 of 16 designed desktop merchant-console screens are built in DOM — a second implementation of the merchant product, against Accepted ADR-0011. Unreconciled; see desktop-journey-readiness and the challenge

Two absences worth adding to §2's "not built" list, because nothing anywhere currently names them as blockers: 🧮 RBAC is 0% (no roles, permissions, user_roles, platform_admins, no is_admin(); workspace_members.role_key is text held to five values by an interim CHECK, owner · admin · manager · member · viewer (20260808210000_v2_production_hardening.sql:210-211), with no FK and no roles table) and 🧮 there is no scheduler at all (zero cron.schedule calls, and outbox is drained by nothing) — QRS-885.

🔎 The pattern, which is this page's own thesis one level up: check:claims now catches stale numbers on every push, so every item above is a stale claim — a decision that moved, an enumeration that got shorter, a gate asserted and never wired. Arithmetic is automated; judgement is not. That is the argument for revalidating on a trigger rather than on an intention.

1. Decision ledger — what governs what ​

AreaGoverning decisionStatus
Source of truthqr-setu-dev is greenfield. Prod is disposable and is never a design reference. The ..._baseline_schema_from_prod squash is retiredCLAUDE.md § "Second rule" · QRS-374
User categoriesThree — solo owner · enterprise · individual consumer — plus QRSETU staff. Six user types, four surfacesuser ecosystem · CLAUDE.md
Platform schemaPrincipal = user · workspace = business tenant · cards is its own public table · text keysADR-0020
Feature control8 scopes × 3 axes · applicability derived from composition · grants sparse · source · on_exceedADR-0021
Org sharingResolution over the org as a union, never duplication · per-type flags default offADR-0022
Org hierarchyWorkspace tree + materialized path (depth 6) · seats license users · location ≠ business unitADR-0023
Enterprise opsSharing down, oversight up · customer at the showroom · Asset primitive · divisions are tree nodesADR-0024
CampaignsCentral, targeted, scheduled, measured · first-party offer ≠ inert promo_slot · scheduled purgeADR-0025
Industry scope~45 industries · 3 archetypes · 11 primitives · the ≥3-industry gateindustry scope
Setu Card renderOne DOM renderer · manifest templates · palette axis · lossless switchingADR-0019
Public exposureanon reads cards and nothing else · no policy grants by role aloneADR-0014 + ADR-0020 D2/D4a
CommunicationsWhatsApp via Meta Cloud API direct, no BSP · one envelope, one dispatcher · consent fails closed · nothing built (2026-08-21)ADR-0029 · communications

⭐ ADDED 2026-08-18 — the commercial half of the index now exists

This page has always answered "what is decided, built and open" about the architecture. It has never answered the same question about the market, and until 2026-08-18 nothing in the portal did: ONDC, Justdial, WhatsApp Business, Khatabook, Dukaan and Vyapar appeared nowhere across the 139 pages that existed before it. Strategy: competitor analysis & market positioning is now the index for that half, and it carries its own revisit triggers.

Three of its findings change items on THIS page, so they are recorded here rather than left in a sibling section:

  • ⚠ QRS-734: card analytics record NOTHING. The beacon posts to track-card-event, which exists only in _archive_pre_v2/; a live list_edge_functions probe on 2026-08-18 returns nine ACTIVE functions and it is not among them, and sendBeacon swallows the failure. This page's "Card analytics taxonomy + beacon (M11)" row should be read as not working, not merely as an unwired sink.
  • ✅ Two rows below are now out of date in the GOOD direction (both verified 2026-08-18): setAnalyticsSink is called, by apps/web's card beacon; and place-public-order has client callers — the buyer order panel shipped in a45b21f/06c6880. Payments status's blocker 5 is closed.
  • ⚠ No reviews/ratings table exists anywhere, and reputation is not among the eleven primitives — argued as a genuine scope gap in product gaps G3.

The eleven primitives (the closed set — a new one needs ≥3 industries in writing): Catalogue · Party · Schedule · Recurrence · Fulfilment · Ledger · Balance · Location · Resource · Asset · Campaign.

2. What is actually BUILT — verified, not inferred ​

⭐ THE WAVE 1 BASELINE IS APPLIED ON DEV — 2026-08-08, verified not assumed

The owner ran v2_apply_all.sql. Measured on Dev afterwards: 26 tables · 16 functions · 22 policies · 11 triggers · 77 indexes · 15 migration rows, RLS on every table, versions matching filenames with no orphans, and zero tables directly readable by anon/authenticated. Seeds all present (753 reserved slugs, 11 primitives, 3 archetypes, 14 industries, 3 plans, 18 features, 8 scopes, 34 grants). The resolver was probed live and the dairy-vs-boutique applicability derivation holds with zero grant rows.

One defect found and fixed (QRS-420): touch_updated_at() shipped with EXECUTE granted to PUBLIC and a mutable search_path. Fixed by 20260808220000_v2_touch_updated_at_hardening.sql; check:sql's Rule 3a was widened to all functions (measured: zero new noise) so the class cannot recur.

Store is unblocked. The remaining work is application-side, not schema-side.

Everything in §1 except the last two rows is DESIGN. Do not read a decision as a feature.

Built & running on DevNotes
Onboarding (mobile), Google sign-inEmail OTP is broken — SMTP 535, QRS-285/076
Public Setu Card route + manifest renderer (apps/web)M14: a11y, layout-invariants, visual gates
Catalog editor (M8) — manage-item EF, RPCs, mobile list/add/editOn profile_items, which the baseline replaces
Capability gating (S1)Superseded by ADR-0021; client contract useFeature() survives
feature_flags (S0) + useFlag()⚠ Table dropped on Dev 2026-08-08 (QRS-412). Both consumers fail safe and all flags were seeded dark, so zero behaviour change — but the platform now has no revert lever until Wave 1's availability axis lands. Theoretical while nothing is in production; must be back before anything ships.
setu_card_templates registry, palettes, check:setu-card-templates⚠ Registry table dropped on Dev 2026-08-08 (QRS-411). The card still renders — ADR-0019 kept content in the repo and only metadata in the DB. check:setu-card-templates is pure file validation and unaffected.
Card analytics taxonomy + beacon (M11)setAnalyticsSink still never called — every track() is a no-op

Not built, despite appearing in docs: payouts · CRM/leads · scheduling · admin panel · enterprise anything · campaigns · R2 media. (⚠ "cache invalidation on any write path (G1/G2)" was on this list and is now WRONG — see the 2026-08-12 banner below. It is wired into manage-item and manage-setu-card.)

⭐ ADDED 2026-08-12 — chat + reminders shipped to Dev, and cache invalidation is real

Built and DEPLOYED to qr-setu-dev since the 2026-08-10 banner below (Dev was 13 migrations behind and is now current, so orders/payments/chat are live there too, not merely written):

Now builtNote
chat + messagesShipped 2026-08-11 (CR-26.0.1-22/23/24). ⚠ The banner below saying they "are all absent" is superseded — it was written before they landed.
Reminders backend — reminders, reminder_occurrences, reminder_categories, get_reminders, get_reminder_categories, manage-reminder EFShipped and deployed 2026-08-12 (QRS-564, CR-26.0.1-27/28). remindersService is the 9th real packages/data service. Reminders are USER-owned, the only v2 feature that is — workspace_id is a nullable tag, because a category-3 consumer has zero workspaces and an employee's personal notes must stay outside the ADR-0023 oversight path.
Cache invalidation (G1)_shared/cardCache.ts is called by manage-item and manage-setu-card, purging Cache-Tag: card-{slug}. ⚠ Two gaps remain, so G1/G2 is NOT closed: Cloudflare credentials are unset on Dev (QRS-306) so only the failure branch is ever exercised, and apps/web served no Cache-Tag at all until QRS-569. Both halves had to work and neither did.

Four defects found on 2026-08-12, all previously invisible to every gate:

  • QRS-568 — apps/web did not render at all; every SSR route 500'd on two React copies (Expo's exact 19.2.3 pin vs ^19.2.7). Fixed by ssr.noExternal: ['react-router']; resolve.dedupe was measured not to fix it. Root cause (two React versions) untouched — a dependency-policy decision.
  • QRS-569 — the public card served no Cache-Control and no Cache-Tag: React Router drops loader headers from a document response without a headers export. Edge caching and the whole ADR-0027 purge-by-tag design were both silently inert.
  • QRS-573 — _shared was writing to dropped public_page_ops_* tables: logging.ts on every log line in every EF (one spurious error per real line for four days) and cardCache.ts on every card write. Both removed; stdout was always the real path.
  • QRS-570 — a Docs-Impact: trailer waived the documentation debt of all 82 commits in a push, not just its own. Now scoped per commit.

⚠ QRS-572: never run npm audit fix --force here — it proposes expo → 53.0.27 and react-native → 0.72.17 (back four major SDK versions) to patch a build-time image-size that has no fix at any version. Triage Dependabot by manifest, not severity: of 43 alerts, ~14 were in retired legacy/ and exactly one reached a shipped artifact.

⭐ ADDED 2026-08-10 — the transactional layer started, and the CONSUMER side is now specified

Built since: orders/order_items/payments/payment_events + offline providers (QRS-480, QRS-484), 73 pgTAP assertions green. ⚠ payments is INERT until payout_accounts exists.

⚠ SUPERSEDED 2026-08-12 (see the banner above): chat and messages shipped 2026-08-11. As written on 2026-08-10 this said: Of the five consumer actions, only orders exists. Still absent: bookings, appointments, enquiries and parties are all absent, and notification transport is broken on every channel (push unbuilt, email 535) — which makes transport a hard prerequisite of the consumer surface, not an adjacent workstream (QRS-504).

Consumer Marketplace — approved design, nothing built (QRS-490…QRS-507), spec + send-ready prompts at Consumer Marketplace Spec:

  • It lives IN the app (apps/mobile, a third tier beside user/admin), which overview/user-ecosystem.md already said. The public web directory is a separate, additional surface — acquisition and SEO, versus in-app engagement and retention. Not alternatives.
  • ⚠ The app must never render a vendor's card TEMPLATE. The manifest is a repo file, so the database cannot supply layout at all. apps/web resolves a section list; native renders composition + brand and not per-block variants or ornament, with mandatory degradation plus "View full card" (QRS-501). check:parity R8 guards this.
  • Discovery mode is an INDUSTRY property, not an archetype one — archetype gives the action verb, and expertise splits (QRS-505). The unfiltered landing is category-first.
  • ⚠ Unsolved and retrofit-expensive: FACETS (QRS-498). Typed columns per facet is O(industries) which ADR-0020 forbids; attributes jsonb is unfilterable which ADR-0010 forbids. Two standing rules point opposite ways. Decide before the item feed exists.
  • ⚠ No discovery read of any kind exists. Every public read is slug-keyed, so a marketplace is a new access class with a new scraping surface, and no policy may be widened to TO authenticated (QRS-491). Location storage exists (locations has pincode + coordinates) and is not publicly projected (QRS-492).

The rest of the consumer ecosystem is PARKED, deliberately, with its reasoning intact — see the tracker's "Upcoming features — the consumer ecosystem" section (QRS-528…QRS-533): consumer subscriptions and scheduled life, QR tools as an acquisition surface, create-and-share (biodata, invitations), a Shared hub, and targeted promotional placement. Highest priority for the phase after the Ganapati season; none of it is R1. ⚠ Do not read the parking as deferral by default: QRS-528 carries the flywheel argument (consumers → marketplace → vendor value → subscription adoption → enterprise leverage), and QRS-531 is supply-free and therefore the only consumer-acquisition channel that works before the marketplace has inventory.

Dev dropped to 37 tables on 2026-08-08 by the owner (QRS-410, QRS-411) — public_page_ops_*, the subscription tables, bio_pages, bio_links, setu_card_templates, feature_flags. All were on the baseline's drop list. One test breaks and is deliberately not patched: anon_least_privilege_test.sql asserts the bio tables exist; it is rewritten wholesale by the baseline, since ADR-0020 D2 makes that whole apparatus unnecessary rather than better-tested.

Orphaned as of 2026-08-08 (QRS-410): the four public_page_ops_* Edge Functions are deployed and referenced in supabase/config.toml but their tables were dropped on Dev by the owner. They are retired by the v2 baseline — the transactional outbox replaces the cache-ops mechanism — so they are removed rather than repaired. manage-reminder's quota lookup degrades to its default and is rewritten when plans/subscriptions land.

3. Decisions — resolved 2026-08-08, and what remains ​

The three blockers are settled (owner delegated 1 and 2, confirmed 3 on 2026-08-08):

#DecisionResolution
1Plan / tier vocabularyfree · pro · enterprise, plans.key text PK, with rank 0 / 100 / 300. ⚠ The rank gaps are the point — inserting a tier later (a business step between Pro and Enterprise for a multi-outlet dairy) is one row, with no renaming and no data migration. The three prior vocabularies are retired: none was in real use. plans.audience (solo | organization) keeps seat-based pricing out of a solo merchant's picker.
2workspaces, not businessesThe tree contains nodes that are demonstrably not businesses — a region, a division ("Thane Sales"), a brand shell — so businesses would be actively wrong for those, and a schema should read like the domain it models. The UI still says "your business": that is presentation, and @qrsetu/i18n already mediates every string, so the table name and the label need not match.
3Three archetypesgoods (a stocked item) · time (a slot) · expertise (an enquiry). ecommerce_cart and catalog_informational were compositions, not archetypes (QRS-392) — free to collapse only while greenfield.

Still open, none of them blocking Wave 1:

DecisionBlocksStatus
Launch date — 15 Aug is incompatible with the redesignRelease planning🟠 Assumed superseded; confirm
MLM / direct-selling vs the payment aggregator's restricted listWhether that vertical can take payments🟡 Verify before payments
Cloudflare R2 provisioning (bucket, Images toggle, scoped token)Store media only, not Wave 1 schema🟡 Owner-provisioned

4. Pre-Store validation — the answer to "what must happen first" ​

⚠ The decisive finding: Store cannot meaningfully resume before the baseline ​

Every table the Store touches is being replaced. Resuming on today's schema means writing it twice:

Store depends onTodayAfter baseline
The item tableprofile_itemscatalog_items
The ownerprofilesworkspaces
Gatingget_my_capabilities()resolve_features()
Public renderget_public_profile_by_slugcards
Media(none)media + R2
Stocka counterper-period, location-aware

M8's editor UI is largely salvageable; its data layer is not.

But Store does NOT block on the whole baseline — only ~60% of it ​

The primitives split cleanly. Store needs Wave 1; Party, Schedule, Recurrence, Fulfilment, Ledger, Balance, Asset and Campaign are Wave 2 and can follow.

Wave 1 — required before StoreWhy
users · workspaces (+parent_id/path) · workspace_membersThe owner of every catalogue row
cards (+ card_links, card_hours)Where the catalogue renders publicly
industries · archetypes (+ composition)Which Store features apply
features · feature_grant_scopes · feature_grants + resolve_features()Gating, with the axes separated
catalog_items · catalog_categories · catalog_item_variants · catalog_item_mediaThe Store itself
media (two-phase pending→ready) + R2 presignProduct images
locationsStock is per-location or the multi-outlet case breaks
outbox (+ scheduled_for)Cache invalidation — G1, else edits appear to do nothing
RLS + (select auth.uid()) discipline + indexesQRS-382
Baseline-critical columns that are unfixable latertax (QRS-387) · UoM + numeric qty (QRS-388) · ledger.unit (QRS-405) · analytics_events.campaign_id + source_campaign_id (QRS-403)

Foundational gaps by the owner's own categories ​

CategoryRequired before Store?What
Client-side architecture🟡 PartialuseFeature() public API survives; its service and resolver are replaced. Screens repointed from profileService→workspace services
Navigation / feature gating🟡Action launcher + /create retirement (S6) is independent of the baseline — can proceed either side
Archetype / industry mapping🔴 YesText keys, 3 archetypes, compositions per industry. Decision #3
Feature entitlement infra🔴 Yesfeatures + feature_grants + resolver. Blocked on decision #1
Enterprise / location hierarchy🟠 Structure yes, features noparent_id/path/locations columns must exist (retrofitting is a rewrite); org sharing and oversight can follow
Database schema🔴 YesWave 1 above
Edge Functions🔴 Yesmanage-item rewritten onto catalog_items + assertFeature; new manage-item-media
Storage / media🔴 YesR2 + presign + media two-phase + orphan sweeper. Owner must provision first
Permissions / RLS🔴 YesRelationship-scoped policies, consumer-isolation pgTAP, index discipline
Subscription / billing🟢 Noplans/billing_accounts/subscriptions/seats are Wave 2 — Store works ungated at first
Other🟠Sonar baseline bootstrap (QRS-256) before apps/web grows; release.json target paths (QRS-343) before G2
  1. Settle decisions #1-#3 — done 2026-08-08.
  2. 🔴 OWNER ACTION: run supabase/scripts/v2_reset_dev_schema.sql in the Dashboard SQL Editor. The full audit (QRS-413) found zero of Dev's 37 tables should be kept, and a cascade drop is required because 37 functions would otherwise be orphaned (PL/pgSQL bodies are not dependency-tracked). drop schema public cascade is refused by the auto-mode classifier, so this one step is yours. The script carries a project-confirmation pre-check and a gen_random_uuid() post-check.
  3. Wave 1 baseline — AUTHORED 2026-08-08 (QRS-414): 11 migrations, 24 tables, 15 functions, a 36-assertion pgTAP suite (QRS-415), check:sql and check:readmes both EXIT=0. Not yet applied — it needs step 2 first. — archive the old series, author the new one, rebuild Dev, pgTAP both directions. ~8–9d.
  4. Confirm the foundation — gates green, live probes on Dev, three-surface pass.
  5. Resume Store on the new schema — M8's UI adapted, full-field editor, media, location-aware stock. ~3d.
  6. Wave 2 as the roadmap demands — Party/Ledger/Balance for CRM and khata, Schedule/Fulfilment for services, Campaign for Enterprise.

Do not build Store first and migrate it. That is the position the repo is already in with profile_items, and it is what this whole review sequence exists to avoid repeating.

5. Documents superseded, and where the truth now lives ​

Stale documentSuperseded by
overview/target-end-users.md "two audiences"user ecosystem — banner added
ADR-0001 tenancy shapeADR-0020 D1/D4 — banner added
ADR-0007 entitlement storageADR-0021 — banner added
ADR-0009 capability storage + "config not code" promiseADR-0020/0021 + industry scope §A1 — banner added
ADR-0011 missing the 4th tier (org-admin portal)Amendment owed — banner added
ADR-0014 authenticated populationADR-0020 D4a — banner added
ADR-0016 scope (platform recurrence, not reminders)QRS-380 — banner added
ADR-0004 first-party vs third-party promoADR-0025 D1
architecture/database.mdREWRITTEN 2026-08-08 — now carries the real 26-table v2 ER diagram
features/catalog.mdThis page + ADR-0020 — banner added
releases/26.0.1/*Re-scoped once decision #4 lands

The 2026-08-08 documentation audit (QRS-416) ​

The owner noticed stale BioLink references and asked for a broader audit rather than a single-page fix. That was the right call: the BioLink mentions were the smallest finding.

Rewritten from scratch (each had been describing the retired architecture as current): architecture/tiers.md · overview/platform.md · overview/glossary.md · index.md · architecture/database.md · overview/target-end-users.md (retired to a pointer). Extended: overview/user-ecosystem.md — five parser-validated diagrams mapping every user type to real tables. Corrected: overview/product-vision.md, both Claude Design prompts, guides/coding-standards.md. 18 supersession banners added, incl. ADR-0005/0006/0012.

Three findings worth carrying, because each says something this page did not:

  1. architecture/tiers.md had no warning at all and was linked from the portal home as step 4 of "Start here". So an index is necessary but not sufficient — this page tells a reader where truth lives; it cannot stop a stale page from being recommended to them. Both are needed, and only one is enforceable.
  2. A stale prompt is worse than a stale page. design-system/pdpr-prompt.md described BioLink as something to design for — and it is sent to Claude Design, so it does not merely mislead a reader, it manufactures designs for a deleted product.
  3. Two overview pages contradicted each other about the flagship vertical.overview/target-end-users.md made restaurants/cafés flagship Wave 1; overview/industry-scope.mdexcludes them, with a reason. Fixed by retiring the older page rather than reconciling two lists — two industry lists is the QRS-249/284/287 duplicate-source-of-truth defect applied to docs.

Now gated: npm run check:docs (QRS-417) — 12 retired-vocabulary groups, historical records and banner text exempt by design, ratchet baseline with a reason per file, in pre-commit and CI. Also found: one portal diagram that had never rendered (QRS-418).

Anything not listed here that contradicts §1 should be treated as stale and reported, not followed.