sprig and prose docs classic docs
Reference› ADRs› ADR012 FileScopeUniverse

ADR012 FileScopeUniverse

Title: File-scope 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 required in 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.