Wiki · Architecture & Decisions
ADR-0006 — The body is `SP.ControlPlane`, and it lives in the zero-dep core
How to read this page
Three ways to read this page. Precise is the document itself, exactly as it is written in the repository. Plain and Clear were written for this website to help you meet that document — they are about it. They are not it, and they are not evidence.
This is the design record of the part of the estate whose job is to stop the rest of it claiming more than it has measured. It runs to architecture notes, numbered decision records, phase plans, and the results written after each phase ran.
It is for anyone curious how a decision was actually reached — what was considered, what was refused, and what the refusal cost. The decision records are the most durable part: each names one choice, the alternatives, and the consequence accepted in exchange. The failure-modes page is worth the detour, because it states each refusal as something you could go and test rather than as a promise.
Read the architecture page first. It opens by declaring itself part design and part built, which sets the tone for the whole section. Then the first decision record, then whichever phase interests you — read as a pair, the plan and the results page written afterwards, including the phases where a premise turned out to be wrong and the receipt says so.
What it is not: a description of running software, and not a complete record. Three documents from this area were withheld from publication, because they describe private infrastructure or the operator declined them; they are listed as withheld rather than quietly dropped, so you can see that they exist.
Your browser cannot switch reading levels, so the document itself is shown.
Precise — the source document
This is the document. Rendered from the repository at the commit above, with nothing rewritten for the web. A gate re-renders it on every deploy and fails the build if a single byte differs.
- Status: Accepted
- Date: 2026-07-25
- Deciders: Michael (operator, decided the name), Veritas, Custos
Context
An earlier draft placed this body in a Phoenix app with its lab view at ui/lib/sp_ui_web/live/lab_live.ex. A deeper inventory of the canonical repo (UNI.Minecraft, branch gen2-runtime, HEAD cdf73c89, on THINKER) showed that wrong on two counts.
Both names were already taken, in this codebase, meaning different things:
lib/sp/producer.ex:1-3— "The Producer UNI host — the live show-running control plane. A singleton GenServer that every beat assembles ALL telemetry…"lib/sp/lab.ex:1-3— "The Stratified Palimpsest Hard-Science Lab — a bounded, deterministic, zero-dependency set of pure physical/biochemical models…" (bioenergetics, physics, planetary_data, radiation, solar_energy)
Building a third body under both names would collapse three distinct things — the failure this architecture exists to prevent.
The placement violated a written contract:
mix.exs:30-33— the root app has zero deps by design, "so thatmix testis fully offline and deterministic (no hex fetch required)". EvenJasonis deliberately absent:test/gate_registry_integrity_test.exs:34uses stdlibJSON.decode/1with the comment "this repo is deliberately zero-dep, so Jason must not become available here".ui/mix.exs:26-29— "the UI consumes it as a path dependency and only ever READS its state / the evidence log."
A verdict-authoring surface had been placed inside the app contractually forbidden from writing.
Decision
Name. The module namespace is SP.ControlPlane. It is free — SP.Producer uses the phrase only in prose, never as a module. The operator's word is kept, and the collision is resolved by explicit disambiguation in both directions rather than by renaming either existing thing:
SP.Produceris the show's control plane — camera, narration, cast, broadcast.SP.ControlPlaneis the science's control plane — gates, runs, verdicts, receipts, rooms.SP.Labremains the hard-science model namespace. The room isSP.ControlPlane.LabView/SpUiWeb.LabLive, both free.
A one-line clarification to SP.Producer's moduledoc is part of this work, not an afterthought.
Placement. SP.ControlPlane.** lives in the root zero-dep app: pure, offline, deterministic, stdlib JSON only, tested with the existing hand-rolled SP.Prop (test/support/sp_prop.ex). It owns Ledger, GateRow, Command, Registry, Verdict, Run, Pair, Room, Key. The lab view renders in ui/ and proposes; every write is performed by the core. See ADR-0007.
Consequences
Positive. The write path is testable offline with no Phoenix in the loop, matching the repo's strongest convention. Three bodies keep three names. Supervision has a proven slot — SP.Show.Supervisor (rest_for_one, permanent children, hosted by SpUi.Application, env-gated) is the pattern to follow.
Negative. Zero-dep means no JSON Schema library; gate_row.schema.json must be enforced by hand-written validation in Elixir. Mitigated by the fact that test/gate_registry_integrity_test.exs already does exactly this with stdlib JSON, and should be extended rather than duplicated.
Neutral. The name SP.ControlPlane will read oddly beside SP.Producer's moduledoc until that one-line clarification lands. It lands in the same change.
Alternatives considered
A new top-level name to avoid the collision entirely (SP.Adjudicator, SP.Chamber). Rejected by the operator: the body is the control plane and it is the lab; renaming it to dodge a prose collision loses the meaning the operator set.
Put it in ui/ with the rest of the web stack. Rejected: violates the read-only contract, and makes the write path untestable without Phoenix and hex deps.
A third Mix project. Viable, and the fallback if the core ever needs a dependency. Rejected for now: the logic is pure and belongs with the engine it governs; a third project adds a boundary with nothing on the far side of it.
Falsifier
If the core Control Plane requires a hex dependency, the placement was wrong. If authoring a verdict requires ui/ to write a ledger, gate row or receipt directly, the placement was wrong. If a reader confuses SP.Producer with SP.ControlPlane after the disambiguation lands, the naming was wrong.
sha256 36a8cfddf2326a31 — of the original file, so what was ingested stays checkable.
Plain — written for this website, not the source document
Two small-sounding things turned out to matter, and a dated record settles both: what the new body is called, and which part of the codebase it lives in.
An earlier draft had put it inside the web application under a name already in use. A closer look at the real repository showed both choices were wrong. The name was taken twice over, meaning different things — one existing piece is the show's control plane, another is a set of pure physical models. Building a third thing under those names would have collapsed three distinct ideas into one, which is the failure this architecture exists to prevent.
The placement was worse. The root application is deliberately dependency-free so that its tests run offline, and the web application is contractually allowed only to read. A surface that authors verdicts had been placed inside the app forbidden from writing.
The decision keeps the operator's chosen name and disambiguates the collision in both directions rather than renaming anything. The new body lives in the dependency-free core; the lab view renders in the web app and only proposes.
Plain · written 2026-08-01 by claude-opus-5 · not yet checked by a person · about the document whose sha256 is 36a8cfddf2326a31
Clear — written for this website, not the source document
A naming question and a placement question, settled together in one dated decision record because they turned out to be the same mistake seen twice.
The context is an earlier draft that placed this body inside the web application, under a name already used elsewhere in the codebase. A deeper inventory of the real repository showed that both were wrong. Two existing pieces already carried the words in question, meaning different things. One is the live show's control plane, a singleton that assembles telemetry every beat. Another is a bounded, dependency-free set of pure physical and biochemical models that give the same answer every time. Building a third body under both names would have collapsed three distinct things.
The placement broke a written contract. The root application is documented as having no dependencies by design, so that its tests run fully offline and repeat exactly. The record notes that even a common encoding library is deliberately absent, with an existing test using the standard library instead and a comment saying so. The web application, meanwhile, is documented as consuming the core as a path dependency and only ever reading its state and its evidence log. A verdict-authoring surface had been placed inside the app that is forbidden from writing.
The decision on naming keeps the operator's word. The collision is resolved by explicit disambiguation in both directions rather than by renaming either existing thing. One namespace is the show's control plane, covering camera, narration, cast and broadcast. The new one is the science's control plane, covering gates, runs, verdicts, rooms, and receipts — the files recording what was run. The third remains the hard-science model namespace. A one-line clarification to the existing module's documentation is treated as part of the work, not an afterthought.
The decision on placement puts the new body in the dependency-free root application: pure, offline, repeatable, using only the standard library, and tested with the hand-rolled property tool already in the tree. The lab view renders in the web application and proposes; every write is performed by the core.
The consequences are stated both ways. In favour: the write path is testable offline with nothing web-related in the loop, which matches the repository's strongest convention, and the three bodies keep three names. Against: having no dependencies means no schema library, so the row schema has to be enforced by hand-written validation — reduced by extending an existing test that already does this, not duplicating it. Neutrally, the new name will read oddly beside the old one until the clarification lands, and it lands in the same change.
Three alternatives were rejected. A fresh top-level name would have sidestepped the collision, but the operator rejected it because it loses the meaning he set. Putting the body in the web application with the rest of that stack was rejected because it breaks the read-only contract and makes the write path untestable without the web dependencies. And a separate project is viable and kept as the fallback if the core ever needs a dependency, but is rejected for now.
What would show this wrong has three limbs. If the core ever requires an external dependency, the placement was wrong. If authoring a verdict requires the web application to write directly to the ledger — the list of results that is only ever added to — or to write a row or a receipt, the placement was wrong. And if a reader still confuses the two similarly-named things after the clarification lands, the naming was wrong.
Clear · written 2026-08-01 by claude-opus-5 · not yet checked by a person · about the document whose sha256 is 36a8cfddf2326a31