sprig and prose docs classic docs
Reference› ADRs› ADR004 NestedDimensions

ADR004 NestedDimensions

Title: Nested dimensions

  • Status: Deferred
  • Decided: D2026-08-22
  • Affects: Dimension, Parser

Context

A sketch in examples/nested-dimensions/universe.prose wants a child dimension that is only a kind word inside instances of its parent:

universe NestedDimensions {
  dimension application {
    dimension component { }
  }

  application PatientSearch {
    component Filters { }
    component DataGrid { }
    component PatientDrawer { }
  }
}

Today that file fails at parse with SP005. A dimension body may only contain describe, title, relationship, and relationships. The inner dimension component { } parses; the outer dimension's allowlist then rejects it. The validator has never seen this AST.

Prose already has two independent trees. Concept nesting is where things sit (dotted ids; ADR 002). A kind word is what they are, resolved by walking enclosing concept folders (SP010 / SP021). Docs say where a concept sits does not change which dimension it belongs to: food Bread inside a place is still universe food.

Sibling dimensions plus nested instances already compile:

dimension application { }
dimension component { }

application PatientSearch {
  component Filters { }
}

Here component is a universe-level kind, available everywhere below that folder. Declaring dimension component { } inside each application instance is not the same as the sketch: each instance would mint its own dimension id and its own relationship set.

The question is whether to let a dimension declare child dimensions, so a kind word can be in scope because of the enclosing instance's dimension, not only because of the enclosing folder.

Decision

Do not ship nested dimensions yet. Keep SP005. Keep the sketch as a sketch.

If the feature returns, the contract that was actually considered is:

  • Additive visibility. component becomes usable inside application instances. Other in-scope kinds (place, concept, sibling dimensions) still work there. The dimension body is not a schema of allowed children.
  • Shared child, instance-like scope. Nesting dimension component under dimension application should feel like declaring dimension component on every application instance, except it is one dimension (NestedDimensions.application.component), not a copy per instance.
  • Parser stays dumb. Allow nested dimension in a dimension body; keep rejecting instances and concept there. Do not enforce what may nest inside an instance at parse time. Kind lookup and SP021 belong in the validator.
  • SP021 on conflicting words, not on sharing. The same nested dimension appearing twice in a chain (application inside application) is not a redeclaration. Two different dimensions that both want the kind word component in one chain is SP021. An explicit dimension component { } on an instance that already inherits component is SP021.
  • Document it as lexical scoping for kind words, never as "applications contain components," unless instance bodies are later checked against nested child kinds.

This record is "not now," not "never." Revisit when one of the conditions in Consequences holds.

Consequences

What stays easy. Folders remain where things sit. Dimensions remain a flat-per-scope vocabulary. Kind words still resolve on one axis. The sibling workaround still says the truth it compiles: component is as available as application.

What the sketch would have cost. A second lookup axis. A bare word like component would also depend on the enclosing instance's dimension. SP021 and ADR 002 exist to keep that class of surprise out of name resolution; nested dimensions would introduce it for kinds. Two declaration sites for the same keyword would mean different visibility (universe sibling vs nested under a dimension). Top-level component Filters { } would become SP010 with a new hint class: "I declared it, why not here?"

The syntax would teach a lie if it stayed additive. dimension application { dimension component { } } reads as "applications contain components." Additive visibility only means the word component is in scope inside application instances. place Bakery { } inside PatientSearch would remain legal. Implied structure that is not true is worse than no structure.

What it would actually buy, that you cannot do today.

  • Hide component from anywhere that is not under an application.
  • One shared child dimension, with one relationship set, for every instance of the parent.

That is a narrow win against a permanent cost.

When this decision should change. Nested dimensions improve the language when at least one of these is true:

  1. Schema. Instance bodies are checked against nested child kinds, so the syntax matches the meaning. Additive visibility alone is then a stepping stone, not the feature.
  2. Demonstrated pollution. Real universes have so many kind words that people instantiate the wrong one in the wrong folder, and sibling dimensions cannot absorb that.
  3. Associated types. application.component and place.component need to be different kinds that share a word. Then the SP021 footgun is accepted on purpose.

Until then, do not widen the dimension-body allowlist in isolation. A parser-only change would turn today's SP005 into SP007 or SP010, which is worse DX.

Alternatives considered

Ship additive nested dimensions now. Rejected. The implementation is tractable (allowlist, Dimension.dimensions, parent-first placement, kind lookup that consults the enclosing instance's nested dims). The language change is not earning that complexity yet.

Restrictive schema now. An application instance could only contain nested child kinds of application (plus describe / title / relationships / concept). That would make the syntax honest. Rejected for this round: it is a new kind of rule, and Prose has never validated what may nest inside an instance. Worth considering if the feature returns as (1) above.

Direct-child-only lookup. component Filters would be valid only as a direct child of an application instance, not inside application { concept Group { component Filters { } } }. Rejected even as a future default: it would not match how dimensions already behave when declared on an instance.

Leave the sketch compiling via sibling dimensions. That is what authors should write today. It does not encode "component is a kind of application." That missing encoding is the whole proposal, and also why the proposal waits.

Evidence

The nested-dimensions example is SP005 at universe.prose:3. The inner dimension is fully parsed before the allowlist fires, so the parser change is one list entry; every assumption that dimensions are leaves lives in the validator (Dimension has no child dimensions; resolveConceptDimension walks concept scopes only; navigateToScope does not walk into dimensions when placing dims).

No real universe has yet shown kind-word pollution that sibling dimensions cannot absorb. The failing-universe work that produced ADR 003 needed cross-dimension relationship pairs, not nested kinds.