Skip to content

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.

BusinessLevel 1Level 2Level 3
Car dealershipKalyani MotorsThane showroomRavi, sales consultant
Salon chainLakmé Kandivali groupAndheri branchPriya, stylist
Clinic groupApex PolyclinicPowai clinicDr Mehta
Coaching instituteVidya ClassesDadar branchSir Kulkarni
Real-estate brokerageSai Properties(often none)Agent
Retail / restaurant chainBrandOutlet(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.

WorkspaceLocation
IsA business unitA 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
ExampleA showroom · a salon branch · an agentA 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: TIME

Note 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:

EntityNeeds 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:

WhoScopeCan
Org adminwhole orgbilling, plans, all workspaces, all seats, org-wide features & analytics
Showroom managerThane subtreeThane's agents, Thane's inventory, Thane's analytics. Not billing. Not Andheri.
Agentown workspaceown 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 ​

OptionWhy rejected
One Enterprise subscription per showroomThe 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 locationsForces 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 treeCannot 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 pathCorrect but placed inside every RLS policy on every shareable table. QRS-382's trap, amplified.
workspace_groups for showroomsGroups 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 ​

CustomerShape under this model
Solo Ganapati stall vendor1 workspace, no org, no parent, no seats. Nothing about the tree is visible to them.
Dairy with 2 collection points1 workspace + 2 locations. Still not an enterprise.
Salon chain, 4 branches, 12 stylists with own cards1 org, 17 workspaces (1+4+12), 12–16 seats
Car dealership, 8 showrooms, 30 agents, 1 service centre1 org, 40 workspaces, ~34 seats, one subscription
Franchise network, 50 franchisees1 org, 51 workspaces; franchisee subtrees member_owned so a departing franchisee keeps their card
Multi-brand retail group1 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:

  • workspaces becomes 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.