Wiki · Architecture & Decisions
ADR-0010 — Room state is contamination; room purpose is function
How to read this page
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.
A Plain and a Clear version of this page have not been written yet. What follows is the document itself.
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: PROPOSED — NOT ADOPTED. This document is not in force.
- Date: 2026-08-01
- Deciders: Michael (operator), Veritas, Custos
- Subject on
/decide:not_mine[8]inevidence/remediation/phase9_plan.json
Context
The founding specification asked the control plane to be, in the operator's own words, "the operating room … the laboratory … the airlock system … the green room … the clean room, and the sterile room."
Five of those six exist and are enforced. SP.ControlPlane.Room (lib/sp/control_plane/room.ex:47)
declares @states [:green, :clean, :sterile] as a strict ladder, with a two-key airlock condition
(keys_condition/1 — at least two distinct parties, at least one of them an operator) and receipt
plus contamination gates on the sterile crossings. Five tests encode that ladder.
Two are missing: operating room and laboratory. The obvious move is to add them to @states.
That move is wrong, and the reason is worth writing down rather than rediscovering.
They are not the same kind of thing. green → clean → sterile is a contamination axis: what
has been proved about a space, in order, each step paid for with keys and receipts. "Operating
room" and "laboratory" are function labels: what happens in a space. The two axes are
orthogonal, and a single enum cannot carry both:
- A sterile operating room would need two values at once, which the enum cannot express.
- Or
:operating_roomwould replace:sterile, and the room would silently stop carrying the only fact the ladder exists to carry.
The function is already held, twice, and better. ADR-0003 rules that the Control Plane is the
lab — laboratory is the body's identity, not a state it enters. And viewer/lab/rooms.cjs already
models named functional rooms (the-gate-floor, the-pending-gates, the-airlock-to-air) with a
three-valued door — open / sealed_by_rule / no_door — which is strictly more informative
about why you cannot enter than a fourth state would be.
And a fourth state is not cheap. Each state in the ladder carries its own ordering rule, its own entry conditions, its own receipt kind and its own refusal messages. A fourth without all of those is decoration; a fourth with all of them invents a contamination step nobody can define.
Decision
Do not add a room state. Add a purpose field that gates nothing.
SP.ControlPlane.Room gains @purposes [:floor, :laboratory, :operating_room, :airlock] and a
purpose field defaulting to :floor, set at Room.new/2 and recorded on both sides of every
crossing (prior and resulting). The operator gets the vocabulary he asked for. The contamination
axis stays uncorrupted.
The field is inert by construction, and a test keeps it that way.
test/sp/control_plane/room_purpose_never_gates_test.exs is a source scan over the functions that
decide a crossing — conditions, enter, order_condition, keys_condition, receipt_conditions,
all_met, in_order, not_already, known_state, next_of — and fails if any of them so much as
mentions purpose. It is a source scan and not a behavioural test on purpose: a behavioural test can
only prove the field does not gate the cases someone thought to write.
It carries its own negative control: the same scan logic is run over a copy with purpose
injected into keys_condition/1, and must catch it. A scan that has only ever been run against a
clean file has proved nothing about itself.
Also asserted: a state may not be used as a purpose (Room.new("x", :sterile) is refused), two
rooms differing only in purpose face byte-identical conditions, and every room still starts green
whatever it is called — naming a room :operating_room proves nothing about it.
Consequences
- The specification's vocabulary is honoured without weakening the ladder.
- A room's function is now in the evidence record, where a reader can see what a space was for as well as what was proved about it.
purposecan never become a second, undeclared authorization axis — which is the real risk, since a label is settable by anyone who can name a room, while a state must be earned through keys and receipts.- "Operating room" as a state is now a closed question. If a future reader wants it, they must first define its contamination meaning and its receipt, and argue with this document.
Alternatives considered
- Add
:operating_roomand:laboratoryto@states. Rejected: makes an illegal state expressible, or drops the contamination fact. See Context. - A second enum,
room_kind, gating its own conditions. Rejected: that is exactly the undeclared second authorization axis this ADR exists to prevent. - Nothing — document that green/clean/sterile plus ADR-0003 already satisfy the intent. Rejected as the weaker option: the operator asked for the words, the words cost one inert field, and refusing them without offering anything reads as a refusal of the request rather than of the design.
Falsifier
Any transition condition whose result depends on purpose. Concretely: two rooms identical
except for their purpose that receive different conditions/4 output, or different enter/4
refusals. room_purpose_never_gates_test.exs fires on exactly this, and its negative control proves
it can.
A second falsifier: a room whose recorded prior.purpose and resulting.purpose disagree within one
crossing. Purpose is carried, not changed, by a threshold.
sha256 19b6fff27252e8e0 — of the original file, so what was ingested stays checkable.