sprig and prose docs classic docs
Reference› ADRs› ADR002 NestedNameResolution

ADR002 NestedNameResolution

Title: Nested name resolution

Context

In January 2026, while prose still had a fixed ontology (series, book, chapter), names walked the container chain: current container, then the parent, then outward to the universe. First match won. That rule was written down on the names-and-scoping reference page and implemented as resolveNameInScope in the old graph builder.

In March the compiler was rewritten around in-clause placement. A name became a path: concatenate it onto the enclosing container, or treat it as absolute when the first segment is the universe, or re-anchor when the first segment equals an ancestor name. Relationship targets reused that placement helper. The live docs kept saying "names resolve locally first; if not found, prose looks upward," which describes January, not the compiler that shipped.

The question is whether to restore the January walk for references. Nested universes make that expensive to get wrong: a short name that is missing locally can bind to a distant Well, App, or Item at universe scope. Same-level ambiguity does not catch that. The dangerous case is one outer match the author was not thinking about.

Decision

Keep the March path rule. A bare name is a child of the enclosing container. A path may start at the universe (absolute) or at an ancestor name (re-anchor). Qualification is how you leave the folder.

From Fizz, Foo.Buzz resolves because Foo is an ancestor:

universe Universe { }

relationship a and b { }

concept Foo {
  concept Bar {
    concept Fizz {
      relationships {
        a { Foo.Buzz }
      }
    }
  }

  concept Buzz { }
}

Bare Buzz means Foo.Bar.Buzz and fails. Same-parent siblings still resolve with a short name: GrindFlour may write Flour when both sit on the universe.

Consequences

Cost. Uncle references need a path (Foo.Buzz, or Universe.ApplicationName from a nested vocabulary). Nested folders are more verbose than January.

Safety. A missing local name is an error, not a surprise bind. Nested containers stay namespaces. Nesting remains the tree; relationships remain the graph; a path is the explicit way out.

Docs. The live Scope section must describe this rule. "Looks upward" is not what the compiler does.

What this does not freeze. The compiler's resolveScopePath helper is unchanged. This record is the language rule, not a new implementation.

Alternatives considered

Restore January's bare ancestor-sibling lookup. Buzz from Fizz would find Foo.Buzz without naming Foo. Rejected for now, for the second time. The failure mode is an unintended outer bind in a large universe, not two matches at the same level. Same-parent siblings already work without a walk. We are not opposed to adding that lookup later if authoring pain shows up in real universes. This record is "not now," not "never."

Universe-wide unique-name fallback. If exactly one Buzz exists anywhere, bind it. Rejected. It makes nesting irrelevant and turns every new homonym into a breakage.

Evidence

The re-anchor rule is the one authors already use when they write Foo.Buzz rather than a fully qualified Universe.Foo.Buzz. The failing case people trip on is the bare uncle (Buzz, or ApplicationName from inside CommonAbbreviations), which is exactly the bind this decision refuses to make quietly.