Context
The scope tree is the hierarchy that settings (0213), IAM (0208), feature flags and the other configuration consumers resolve against. 0240 placed the tree in@stackra/iam and @stackra/tenancy, so two packages held one structure, and a consumer that needed only a
scope depended on tenancy. 0323
moved the tenant vocabulary out of the other packages. The owner decided in the final structure review of 2026-10-24
that the tree is one package of its own, and that tenancy is one of its consumers. Tenant isolation is a different
matter, and it stays where it is.
Decision
-
One package.
@stackra/scopeis a free package in thesecuritycategory. It owns the hierarchical scope tree that settings, IAM, feature flags and the other configuration consumers resolve against. 0240 placed the tree in@stackra/iamand@stackra/tenancy. It moves to@stackra/scope, and neither package holds it. Depth stays a plan entitlement (0240, 0242). - Levels are data. A deployment declares its levels in order. The defaults are platform, product and tenant. The tenant level is recursive, so child tenants sit under their parent in the tree of 0210. A deployment may declare further levels as data, as 0208 decision 1 allows for levels that a product declares. Adding a level is a change to that data. It needs no code and no migration.
-
Nodes. A node has a
scope_id, typedPublicId<"scp">, and a materialisedscope_pathonltreewith a GiST index, as 0240 decision 5 states. - Aliases. A deployment may give a level a display name of its own. An organisation is a parent tenant (0243), so it is shown as an alias of the tenant level, for example “organisation”, and it is not a level of its own. Each alias is a lang key, so each locale has its own label.
-
ScopeRefis a type. Thecontractsentry of@stackra/scope, imported as@stackra/scope/contracts, exportsScopeRef, which carries the public id and the path of a node. It is a type and not a port. The port of this package is the resolver of decision 7. 0323 names this package as the owner ofScopeRef. - Current scope in the action context. The current scope is part of the action context (0136). A code block may run in a scope other than the current one, for emulation or impersonation. Each such switch is audited.
-
Resolver chain. The scope of a request is resolved by a chain, in this order: a request header (
X-Scope-Path, accepted only when its path is one the caller holds a grant for, and dropped otherwise), a token claim, tenancy, and a root fallback. Each step is a driver behind a resolver port (0142). The chain runs in the tenancy step of the action pipeline (0138). -
Tenancy is a consumer.
@stackra/tenancymaps each tenant to a scope node, and it contributes the tenant level and its alias. Tenant isolation does not change. Postgres row-level security ontenant_id(0239) andBelongsToTenantstay in tenancy. Scope is for the configuration hierarchy, and it is not for row isolation. -
Not adopted.
- No generic key-value table of values in
@stackra/scope. Each consumer keeps its own typed table, keyed byscope_id. Settings already has one, and 0227 rejects a JSONB-for-everything store. - No automatic application-level
WHEREfilter on models with a bypass decorator. Isolation stays in the database, through row-level security.
- No generic key-value table of values in
-
Entries. The package has the
contracts,core,node,reactandnativeentries of the v3 layout (0294 decision 15). It addsconfigandtestingwhere it needs them, as that decision allows, and an entry is exported once its folder holds a file. Thereactentry provides a current-scope hook and a scope switcher.
Consequences
- Settings, IAM and flags resolve against one tree, and a level added for a deployment needs no code change.
- Tenancy keeps tenant isolation and gives up the tree, so tenant row isolation reads as it does today.
- A consumer that stores values per scope writes its own typed table with
scope_id. The package adds no filter to its rows. - A switch into another scope leaves an audit record, so impersonation is never silent.
- The tenancy rule, the workspace-layout rule and the constitution’s tenancy and security pages are updated with this
ADR, so no document still places the tree in
@stackra/iamor@stackra/tenancy.
Enforcement
- planned — tests in
@stackra/scopefor a level added as data with no code change, for the resolver order of decision 7, and for the depth check against the plan entitlement at tree creation (0240). - planned — the lint rule of 0323
decision 5 refuses a
tenantidentifier declared in@stackra/scope, so tenancy’s contribution stays in tenancy. - No check exists today. The package is not written yet.
Supersedes and sources
Supersedes: none. Amends: 0240 (decision 1) and 0323 (the owner ofScopeRef).
Notes added to 0127, 0202, 0208,
0210 and
0213.
Sources: the owner’s final structure review of 2026-10-24, restated in this ADR’s own words.