> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stackra.figentra.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 0351 — IAM conditions on a dimension another service owns: declared dimensions and projections

> Accepted · 2026-10-24

> [!NOTE]
>
> Amends
> [0208](https://docs.stackra.figentra.com/adr/0208-iam-model-scope-tree-roles-conditions-deny-delegation-and-permission-names.md)
> (decision 3: what a condition may name) and
> [0260](https://docs.stackra.figentra.com/adr/0260-cross-service-calls-through-injected-clients-and-events.md) (decision 7: a
> projection gen derives for a condition) by a Note. Reads with
> [0331](https://docs.stackra.figentra.com/adr/0331-stackra-rules-one-condition-engine-for-every-rule-consumer.md) (decision 11: the IAM
> decision point keeps its own path) and
> [0163](https://docs.stackra.figentra.com/adr/0163-reports-exports-imports-and-declared-read-models.md) (read models).

## Context

[0208](https://docs.stackra.figentra.com/adr/0208-iam-model-scope-tree-roles-conditions-deny-delegation-and-permission-names.md) 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](https://docs.stackra.figentra.com/adr/0260-cross-service-calls-through-injected-clients-and-events.md) forbids a service to query another's tables,
and [0331](https://docs.stackra.figentra.com/adr/0331-stackra-rules-one-condition-engine-for-every-rule-consumer.md) 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

1. **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 extends
   `Dimension(Model)` in a `<model>.dimension.ts` file ([0294](https://docs.stackra.figentra.com/adr/0294-programming-model-v3-grammar-folders-identities-and-naming.md)
   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.

2. **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.

3. **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
   `Projection` of that dimension's values, generated by gen when the field of decision 2 appears, in its `models/` group
   (0260 decision 7, [0163](https://docs.stackra.figentra.com/adr/0163-reports-exports-imports-and-declared-read-models.md)). The IAM decision point resolves
   the condition against the record's field and that projection, in process, and never calls the owning service.

4. **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.

5. **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](https://docs.stackra.figentra.com/adr/0349-product-scopes-package-names-and-the-module-key-registry-across-products.md)); an edition may condition
   on its base's dimensions, and a base never knows an edition's.

6. **The scope tree is not a dimension.** The scope path of 0208 decision 1 and
   [0327](https://docs.stackra.figentra.com/adr/0327-scope-package-owns-the-scope-tree-tenancy-is-a-consumer.md) 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`); a `DimensionId` field 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.

## Supersedes and sources

Supersedes: none. Amends, by Note:
[0208](https://docs.stackra.figentra.com/adr/0208-iam-model-scope-tree-roles-conditions-deny-delegation-and-permission-names.md) and
[0260](https://docs.stackra.figentra.com/adr/0260-cross-service-calls-through-injected-clients-and-events.md). Reads with
[0331](https://docs.stackra.figentra.com/adr/0331-stackra-rules-one-condition-engine-for-every-rule-consumer.md),
[0163](https://docs.stackra.figentra.com/adr/0163-reports-exports-imports-and-declared-read-models.md),
[0327](https://docs.stackra.figentra.com/adr/0327-scope-package-owns-the-scope-tree-tenancy-is-a-consumer.md),
[0294](https://docs.stackra.figentra.com/adr/0294-programming-model-v3-grammar-folders-identities-and-naming.md) and
[0349](https://docs.stackra.figentra.com/adr/0349-product-scopes-package-names-and-the-module-key-registry-across-products.md). Sources: the commerce
product's plan of 2026-10-24 (its sales-channel condition gap), restated in this ADR's own words.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.