Amended 2026-10-27 (STA-1307), by an owner decision of that date: the lint rule of decision 5 is
live. The Enforcement section names it. Decisions 1 to 6 stand.
Amended 2026-10-24 (STA-348): the 0327 note below is corrected.
ScopeRef stays in
@stackra/contracts, where decision 2 first placed it, as a type { id: PublicId<"scp">; path: string }; @stackra/scope owns its semantics and the resolver, and imports it
(0327 as amended the same
day). Decision 2 is read as written, with “port” meaning the type and the resolver together.Amends 0213: the names
of the settings scope levels in decision 2 move to tenancy, and the other packages name a scope
through the
ScopeRef port. The order of the cascade is unchanged. The note on 0213 records the
change.Amended 2026-10-24 by 0327: 0327
names the package that owns
ScopeRef, which is @stackra/scope, in its contracts entry. There,
ScopeRef is a type, the public id PublicId<"scp"> with the node’s path, and not a port. The port
that resolves a request to a scope is the resolver of 0327. Decision 2 of this ADR says “a port in
@stackra/contracts”, and it is read in that way. The other decisions stand.Context
The scope tree is one tree in the IAM model and in tenancy: a recursive tenant tree with one lifecycle per request (0208, 0210, 0240). A tenant is one kind of node in that tree, and 0088 was the earlier tenancy model, and 0210 superseded it. Other packages, settings included, have been writing the wordtenant in their
identifiers and types, so each of them depends on tenancy’s vocabulary even when it only needs a scope.
Decision
- Where the vocabulary is declared. An identifier that contains
tenant(a type, class, enum case, variable, column or file name) is declared only in@stackra/tenancy, and in the scope code of@stackra/iam, where tenancy is the subject. Other packages import and use the names that tenancy exports, such asBelongsToTenant, and that is allowed. - How other packages speak of a scope. Every other package speaks of a scope through
ScopeRef, a port in@stackra/contracts. The shared primitives are placed there (0117). - The tenant column trait.
BelongsToTenantis exported by@stackra/tenancy(0321). - Settings scope levels are data. Tenancy contributes the tenant level of settings as data. An app writes
TenancyScopes.Tenant, and not a settings-owned case such asSettingScope.Tenant, the shape that this rule removes. The levels keep their order in the cascade of 0213 decision 2. - The lint rule. A lint rule refuses a declaration of an identifier that contains
tenantoutside the packages named in decision 1. Imports and uses of names that tenancy exports are allowed. The rule does not apply to user-facing text, lang files, documentation, comments or generated code. - The existing code. The template and the existing code are changed to match this rule.
Consequences
- A package outside tenancy names a scope through
ScopeRef, so its code does not change whether the scope is a tenant or another node of the scope tree. - The scope tree stays one tree in tenancy and IAM (0240). This ADR moves vocabulary, and it does not change the tree.
- The rule touches the template and the existing code, and many identifiers are renamed. This ADR does not set the order or the dates of that change.
- A reviewer checks a new package for a declaration that contains
tenant. Such a declaration in any other package is refused. A use ofBelongsToTenantorTenancyScopes.Tenantis not.
Enforcement
- automated —
stackra/no-tenant-vocabulary(owner decision R-59) in@stackra/coding-standard(packages/developer/coding-standard/src/core/constants/no-tenant-vocabulary-rule.constant.ts), set toerrorinpackages/tooling/oxlint/src/base.jsonc. It reports only the name a declaration introduces, outside@stackra/tenancyand the IAM scope code. Imports, re-exports, references and member accesses are never reported. - planned — the template and existing code conform to decision 6. Nothing checks this today.