Appearance
ADR-0023 · Organization hierarchy, locations & seat licensing
Status: 🟡 Proposed — authored 2026-08-08, awaiting owner approval · Extends: ADR-0022 (sharing now resolves up an ancestor path, not to a single parent) · Amends: ADR-0020 (tenancy), ADR-0001 · Depends on: ADR-0006, ADR-0021 · Related: QRS-385 (locations), QRS-393 (sharing)
The one-line thesis
A workspace TREE, not a fixed Organization → Location → User hierarchy. A showroom needs its own card, catalogue, leads and analytics — that makes it a business unit, which is what a workspace already is. Making Location the hierarchy level would either duplicate Workspace or force every two-outlet dairy into the enterprise model. And seats license USERS, not workspaces — a dealership with 8 showrooms and 30 agents needs 39 cards and 30 seats, and any model that couples them bills for 39.
Context
A car dealership with 5–10 showrooms across cities, multiple sales agents per showroom, buying one Enterprise subscription. The owner's expectation — one dealership = one enterprise organization with multiple locations underneath, rather than a separate subscription per showroom — is correct in substance. This ADR evaluates the shape it should take, because one part of the intuitive hierarchy is the wrong level.
The same structure recurs across the target market, and the recurrence is the strongest evidence for the abstraction: brand → place → person.
| Business | Level 1 | Level 2 | Level 3 |
|---|---|---|---|
| Car dealership | Kalyani Motors | Thane showroom | Ravi, sales consultant |
| Salon chain | Lakmé Kandivali group | Andheri branch | Priya, stylist |
| Clinic group | Apex Polyclinic | Powai clinic | Dr Mehta |
| Coaching institute | Vidya Classes | Dadar branch | Sir Kulkarni |
| Real-estate brokerage | Sai Properties | (often none) | Agent |
| Retail / restaurant chain | Brand | Outlet | (usually none) |
Depth varies — 2 levels or 3. That single fact rules out a fixed-depth schema.
The decisive distinction: a Location is not a business unit
This is the crux, and getting it wrong is what would force the redesign later.
| Workspace | Location | |
|---|---|---|
| Is | A business unit | A physical address |
| Has its own Setu Card | ✅ yes | ❌ no |
| Has its own catalogue / leads / analytics | ✅ yes | ❌ no — it qualifies them |
| Has its own archetype | ✅ yes | ❌ no |
| Example | A showroom · a salon branch · an agent | A dairy's second collection point · a showroom's separate service entrance · a warehouse |
The rule, stated so it can be applied without judgement:
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.
Had Location been the hierarchy level, one of two bad things follows: either Location grows a card, a catalogue, leads, analytics and an archetype — at which point it is a workspace under a different name, and we maintain two tenancy concepts — or every multi-outlet business is pushed into the enterprise model, and a dairy with two collection points is not an enterprise. Both are the duplicate-concept failure this repo has hit at QRS-249/284/287.
So locations (QRS-385) stays, unchanged and useful, as an attribute of a workspace. It is what a solo dairy needs. It is not the enterprise hierarchy.
Decision
D1 — workspaces forms a TREE within an organization, with a materialized ancestor path
workspaces.parent_id uuid NULL + workspaces.path (materialized, trigger-maintained) + workspaces.organization_id. Depth capped (4 is ample for brand → region → place → person).
Kalyani Motors becomes:
organizations: Kalyani Motors ← billing + admin tenant
workspaces (tree):
Kalyani Motors (corporate) card: /kalyani-motors archetype: Expertise
├─ Thane showroom card: /kalyani-motors-thane archetype: Expertise
│ ├─ Ravi (agent) card: /ravi-kalyani
│ └─ Sneha (agent) card: /sneha-kalyani
├─ Andheri showroom …
└─ Service centre card: /kalyani-service archetype: TIMENote the service centre. It is a sibling workspace with a different archetype in the same organization on the same subscription — which works only because archetype lives on the workspace, not on the organization (ADR-0022 D-validation).
Why a materialized path rather than a recursive CTE. Ancestor resolution appears in every RLS policy on every shareable table (ADR-0022 D1). A recursive CTE per row-security check is the QRS-382 performance trap raised to a power. A path column makes ancestry a single indexed prefix operation, and it pays a second dividend: org-level analytics roll-up becomes one indexed range scan over path, rather than a join against a membership tree.
D2 — SEATS LICENSE USERS. Not workspaces, not cards
seats(subscription_id, user_id, status, cost_center_workspace_id NULL).
This is the correction with the largest commercial consequence. ADR-0020 keyed seats on workspace_id with an optional user. Applied to a dealership: 8 showroom workspaces + 1 corporate + 30 agents = 39 workspaces, and the dealership would be billed for 39 seats when it has 30 people.
Decoupling them is also just how enterprise software is licensed — you pay per person:
| Entity | Needs a card? | Needs a seat? |
|---|---|---|
| Corporate workspace | ✅ | ❌ nobody operates it |
| Showroom workspace | ✅ | ❌ it is a place, not a person |
| Agent | ✅ | ✅ |
| Showroom manager | ❌ | ✅ |
Workspace count is limited separately, by an entitlement — feature_grants.limit_value at plan scope, which is exactly what that column exists for. So "Enterprise includes 10 workspaces and 30 seats" is two grants, not a schema concern.
cost_center_workspace_id is optional and exists because enterprises ask "how many seats is Thane consuming?" — internal cost allocation, not a licensing constraint.
D3 — ADR-0022's sharing union walks the ANCESTOR PATH, not a single parent
ADR-0022 resolved own workspace ∪ organization. The three-level case breaks it: agent Ravi must see his own listings ∪ Thane's stock ∪ corporate's stock, and must never see Andheri's stock.
visible = own workspace ∪ { ancestors on my path where sharing is enabled for that type }Two properties that make this correct rather than merely working:
- Sharing flows DOWN the tree only. A showroom's stock is visible to its agents; an agent's private listing is never visible to the showroom, and Thane's stock is never visible to Andheri. Sibling isolation is the default and needs no rule.
- Exclusivity still holds by construction — one row per car, wherever it is owned, so whoever sells it marks the single row.
D4 — Roles and grants are assignable to a SUBTREE, so "showroom manager" is expressible
role_assignments(user_id, role_key, scope_workspace_id NULL) — null means the whole organization. feature_grants likewise gains subtree semantics at workspace scope.
That makes the three admin tiers the owner asked about real:
| Who | Scope | Can |
|---|---|---|
| Org admin | whole org | billing, plans, all workspaces, all seats, org-wide features & analytics |
| Showroom manager | Thane subtree | Thane's agents, Thane's inventory, Thane's analytics. Not billing. Not Andheri. |
| Agent | own workspace | own card, own leads; sees shared inventory per D3 |
Answering the question directly: yes — management at both organization and showroom level, with one mechanism. A showroom manager is not a new role type; it is an existing role assigned to a subtree.
D5 — Every workspace in the tree owns its own card, catalogue, leads and analytics
Nothing special is needed for this: they are workspace-scoped already. Centralised management comes from D4 (scoped roles) and central roll-up from D1 (path range scan). Local autonomy and central control are therefore not in tension — they are different queries over the same tree.
Options considered and rejected
| Option | Why rejected |
|---|---|
| One Enterprise subscription per showroom | The owner's instinct to reject this is right. Eight subscriptions means eight billing relationships, no consolidated analytics, no shared inventory, and a per-showroom plan change. It also misprices: the dealership is one customer. |
| Organization → Location → User as the fixed hierarchy (the intuitive model) | Location would have to grow a card, catalogue, leads, analytics and archetype — at which point it is a workspace under another name. And a fixed depth cannot express both the 2-level retail chain and the 3-level dealership. |
One workspace per dealership, showrooms as locations | Forces one card for eight cities — a Thane customer gets Andheri's address and hours. Requires location_id on cards, members, leads, catalogue and analytics: i.e. re-deriving the tree, badly. And it leaves agents with no card, contradicting the stated requirement. |
| Flat: all workspaces directly under the org, no tree | Cannot express "Thane's stock is visible to Thane's agents but not Andheri's" without a grouping concept — which is a tree with extra steps. |
| Recursive CTE for ancestry instead of a materialized path | Correct but placed inside every RLS policy on every shareable table. QRS-382's trap, amplified. |
workspace_groups for showrooms | Groups are for cohorts (beta, enterprise bundles, custom segments) — an orthogonal, many-to-many concern. Overloading them with structural hierarchy conflates two meanings on one table. |
Long-term scalability
| Customer | Shape under this model |
|---|---|
| Solo Ganapati stall vendor | 1 workspace, no org, no parent, no seats. Nothing about the tree is visible to them. |
| Dairy with 2 collection points | 1 workspace + 2 locations. Still not an enterprise. |
| Salon chain, 4 branches, 12 stylists with own cards | 1 org, 17 workspaces (1+4+12), 12–16 seats |
| Car dealership, 8 showrooms, 30 agents, 1 service centre | 1 org, 40 workspaces, ~34 seats, one subscription |
| Franchise network, 50 franchisees | 1 org, 51 workspaces; franchisee subtrees member_owned so a departing franchisee keeps their card |
| Multi-brand retail group | 1 org, brand-level workspaces as regions, outlets beneath |
The same three tables serve all six — the difference is tree depth and which sharing flags are on. That is the test of whether this is a foundation or a feature.
Extensibility that stays additive: a region level is another tree node · a franchise is ownership_model on a subtree · shared campaigns are another ADR-0022 sharing type · path-form enterprise URLs (/kalyani-motors/thane) were already reserved by the plan's enterprise decision #5 and become natural once the tree exists.
Consequences
Schema (additive, baseline): workspaces.parent_id + path + trigger + depth CHECK · seats re-keyed to user_id with optional cost_center_workspace_id · role_assignments.scope_workspace_id · ADR-0022's union clause becomes a path predicate · analytics roll-up reads path.
pgTAP must prove the negatives, since these are the failure modes that matter: an agent of Thane cannot read Andheri's inventory · a showroom manager cannot touch billing or a sibling subtree · a parent cannot read a child's private listings · sharing off yields nothing shared · a cycle in parent_id is refused · depth beyond the cap is refused.
Cost: ~+1.0d in the baseline (~+0.25d of it is the ADR-0022 union becoming a path predicate, which was going to be written anyway).
Accepted costs, stated plainly:
workspacesbecomes the most structurally important table in the schema, carrying tenancy, hierarchy, archetype and sharing. It earns a hard depth cap, a cycle-prevention trigger, and its own pgTAP suite.- Slug pressure rises: 40 cards per dealership all need globally-unique write-once slugs. Path-form enterprise URLs are the eventual relief, and they are already reserved.
- QRS-382's RLS discipline is now unconditionally mandatory. Path-prefix predicates must be index-backed, or the largest customers get the slowest reads — the same trade ADR-0022 flagged, now load-bearing rather than marginal.
Recommendation
Adopt D1–D5 in the baseline. The owner's instinct — one dealership, one organization, one subscription, locations underneath — is right; the one correction is that the level underneath is a workspace, not a location, because a showroom is a business unit and a location is an address. That distinction plus user-keyed seats is the difference between billing a dealership for 30 people and billing it for 39 workspaces.
Everything else in the multi-location case already follows from decisions taken for other reasons, which is the strongest available signal that the tenancy model is sound and needs one structural addition rather than a rethink.