ADR012 FileScopeUniverse
Title: File-scope universe
- Status: Accepted
- Decided: D2026-08-17
- Recorded: D2026-09-07
- Followed by: ADR023
ErrorNameCorrections - Introduces: SP022
DeprecatedInClause - Deprecates: SP006
ConceptWithoutUniverseError, SP009DuplicateDeclaredParentError - Affects:
Concept,Dimension,Parser,Relates,Relationship,Universe
Context
Prose used to require every top-level declaration to name its universe:
concept Rose in Garden { }
A top-level declaration without that clause was SP006
(concept-without-universe), which told the author to add one.
But a repository describes one world. There is exactly one universe per
compile, and the compiler already enforced that from both directions —
SP013 when none is declared, SP014 when more than one is. So in Garden asked the author to restate the only fact the compiler could not
possibly be wrong about, in every satellite file, forever.
The clause did say something real in one case: placement under a nested
parent, as in in Meridian.Oldgate. Nesting already expresses that, and had
since the March 2026 rewrite around in-clause placement.
That left in doing two jobs of very different value. One was pure
repetition. The other was a second way to say what nesting says.
Decision
A declaration written at file scope belongs to the universe.
concept Rose { } in any satellite file resolves to Garden.Rose, exactly as
concept Rose in Garden { } did. Children of such a declaration resolve under
it the same way. This holds for concept, dimension, relationship and
relates alike.
Placement under another concept is expressed by nesting. The in clause is
removed from the language.
SP006 is retired and its code is never reused. A universe that was never declared is SP013.
The removal shipped in two steps on purpose. In prose-parser 0.4.0 the
clause still worked and reported SP022 (deprecated-in-clause) as a
warning, once for the whole compile rather than once per occurrence. In 0.8.0
SP022 became an error and the clause stopped placing anything.
Consequences
Authoring. Satellite files stop repeating the universe name. A file that describes part of a world reads as part of that world rather than as a series of cross-references to it.
Cost. Every universe that used in had to change, and the two-step gave
them exactly one release where both spellings worked. A project that skipped
0.4.0 entirely met the change as a hard error with no warning release in
between, which is the cost of shipping the deprecation and the removal eight
releases apart rather than eight months.
One diagnostic, not many. SP022 was reported once per compile, keyed to the
earliest declaration that used the clause. A universe with forty in clauses
got one message. Repeating it forty times would have said nothing new after the
first.
A retired code stays retired. SP006 is not reallocated, so the next code to allocate is SP026. The reasoning is that a code which has appeared in somebody's build output should keep meaning what it meant, even after the behaviour it described is gone. This costs one number and buys the property that an old log is still readable.
What this does not do. It does not change how a path resolves once you write one; that is ADR 002. It does not change nested placement, which worked before and works now. It does not make satellite files independent — there is still exactly one universe, and it must still be declared somewhere.
Alternatives considered
Keep in as an optional synonym, forever. Rejected. Two spellings for one
meaning, and the redundant spelling is the one that can be wrong: in NotTheUniverse is a mistake that file scope makes unrepresentable. An optional
clause also has to be taught, and the teaching is "you may write this, and it
never helps."
Keep in only for nested placement. Rejected. Nesting already does that
job, so the clause would survive as a second syntax for an existing capability.
A keyword that works in one narrow case is harder to explain than a keyword
that does not exist.
Reuse SP006 for the deprecation warning. Rejected, and this is the choice
the retirement note exists for. SP006 meant "add in UniverseName," which
became advice that would break a universe. Pointing an old code at new
behaviour makes every previously printed occurrence of it a lie. SP022 is a new
code for a new fact.
Remove the clause in one release. Rejected at the time. The warning release gave existing universes a version where both spellings compiled, which is what made the change mechanical rather than a migration. Whether eight releases was the right gap is a fair question; zero was not.
Evidence
The retirement reasoning is written into the compiler rather than only into
this record, at packages/prose-parser/src/errors.js:
SP006 (
concept-without-universe) is permanently retired, not free to reuse. It told the reader a top-level declaration requiredin UniverseName, which stopped being true once a declaration at file scope belonged to the universe; having no universe is SP013. A code that has appeared in someone's output should keep meaning what it meant, so the next code to allocate is SP026.
The two-step is recorded in the canonical fixtures. success-deprecated-in
existed while SP022 was a warning and the clause still placed declarations;
error-in-clause replaced it when SP022 became an error. That pair is the
migration history, and it is why the deprecation cannot be mistaken for having
always been fatal.
The once-per-compile behaviour is deprecatedInClauseDiagnostic() in
packages/prose-parser/src/index.js, which resolves the earliest declaration
using the clause and reports a single diagnostic carrying the occurrence count.