Context
0208 decision 3 gives grants structured conditions, with deny over allow and no expressions. A condition names a field of the record being checked or an attribute of the principal. The commerce product’s plan of 2026-10-24 needs grants that depend on a sales channel (“may manage orders of the online channel only”), a dimension its channels module owns, while the records live in other modules and other services, and other products will want the same for a location, a market or a legal entity. 0260 forbids a service to query another’s tables, and 0331 decision 11 keeps the IAM decision point on its own path for performance and security, so a cross-service call inside a permission check is out. This ADR says who owns a dimension, how a condition names it, and how the check resolves it.Decision
-
A dimension is declared by its owner. A module that owns an enumerable attribute other grants may condition on
(a channel, a location, a market, a legal entity) declares it in its
access/group as a class that extendsDimension(Model)in a<model>.dimension.tsfile (0294 decision 13 gains the row by Note). Gen records the dimension in the manifest: its key, its id type and its label key. Only the owning module creates, renames or retires a dimension’s values. -
A record names the dimension by id. A model whose records fall under a dimension carries a field typed as the
dimension’s id (
channelId!: DimensionId<"channel">), and the condition tests that field. A condition names only dimensions the manifest registers; the structured-conditions rule of 0208 decision 3 is unchanged and no expression appears. -
The owner publishes, the checker projects. The owning module emits integration events for a dimension’s values
(created, renamed, retired). A module whose records carry a dimension another service owns gets an event-fed
Projectionof that dimension’s values, generated by gen when the field of decision 2 appears, in itsmodels/group (0260 decision 7, 0163). The IAM decision point resolves the condition against the record’s field and that projection, in process, and never calls the owning service. - Fail closed. A value not yet projected, a retired value, or a dimension the manifest no longer registers denies, because deny wins (0208 decision 3). The lag of the projection is the lag of the events, and it is part of the contract; a retirement event also marks every grant that named the value, so an access review of 0208 decision 7 sees it.
- One vocabulary across products. The dimensions of the OS, of a product and of its editions appear in one list for the grant editor, read from the registry’s manifests (0349); an edition may condition on its base’s dimensions, and a base never knows an edition’s.
- The scope tree is not a dimension. The scope path of 0208 decision 1 and 0327 stays the place for hierarchy; a dimension is flat and enumerable, and a tenant that wants a hierarchy of channels models it as scope nodes, not as a dimension.
Consequences
- A grant can say “orders of the online channel” with one structured condition, and the check stays in process.
- Each dimension another service owns costs a small projection table in the consuming service, fed by events it already receives.
- Retiring a dimension’s value is visible: it fails the grants that named it instead of silently widening or narrowing them.
- The grant editor’s dimension list is as current as the registry.
Enforcement
- planned — gen (STA-340): a condition that names a dimension the manifest does not register fails
(
dimension_unknown); aDimensionIdfield whose dimension has no projection in the module fails (dimension_not_projected); a module that writes a dimension it does not own fails. - planned — an IAM conformance test: a grant conditioned on a value not yet projected denies; a retired value denies and marks the grant.
Alternatives
- A call to the owning service inside the permission check. Not chosen: 0260 decision 1 and 0331 decision 11; the check would depend on another service’s availability and latency.
- Copying the dimension’s table. Not chosen: a projection is the copy, fed by events, as 0260 decision 7 already rules; nothing reads another service’s tables.
- Expressions in conditions. Not chosen: 0208 decision 3 keeps conditions structured.