Skip to content
← Documentation

docs/open-decisions.md


The two open decisions — evidence and options

Prepared after AMD-0003 was vetoed and the gate reopened. Both were recorded as governance decisions; measurement changed the shape of the first one, so it is presented as re-framed rather than as previously stated.

Nothing here decides anything. Each option is stated with its consequence so the decision can be made against evidence rather than against my preference.


Decision 1 — Floor and tier units (blocks II.2, II.3, tripwire W5)

How it was recorded

docs/tripwires.md W5: "II.2 asks which metric feeds height, II.3 asks in what units. Both wait on one decision."

What measurement shows

That framing is incomplete in a way that matters. Four measurements:

  1. Height currently derives from code-size. src/lib/world/structure.ts:266heightRule: { kind: "measure", metric: "code-size", base: 5, scale: 3.6 }. Article II.2: "It is forbidden to derive height from code size, file counts, or any measure of how much has been written rather than done." This is a direct textual violation, in source, today.

  2. Height is continuous. The civic hall stands at 11.399999999999999 floors. W5 measured 2001 distinct heights from 2001 inputs.

  3. No cumulative-output metric exists in the fact vocabulary. WorkItemFact.state is "open" | "blocked" — there is no terminal state, so completion is never expressed as a fact.

  4. But the evidence exists. db/schema.sql hq_task carries state IN ('open','blocked','done','cancelled') and completed_at TIMESTAMPTZ. Cumulative output is recorded; it is discarded at the fact boundary.

The consequence for the decision

The decision cannot be taken onto anything yet. A unit quantises a metric, and the metric II.2 requires does not currently reach the world. Choosing units today would quantise a forbidden metric — making the violation discrete rather than removing it, and W5 would close while II.2 stayed violated.

So this is not one decision. It is one decision plus one prerequisite, and the prerequisite is implementation that needs no governance at all.

Options

Action Consequence
A (recommended) Surface completions as facts first — extend WorkItemFact with a terminal state, emit a cumulative completed measure per subject — then decide units against the observed distribution The units decision becomes evidence-based: band edges chosen from real data rather than invented. II.2 and II.3 close together. Requires no amendment; facts.ts is not constitutional
B Decide units now, in the abstract, against code-size W5 closes and II.2 remains violated, now permanently encoded in a declared unit. The Legend would assert a source the Constitution forbids
C Amend II.2 to permit code size as a height source Honest if the intent has changed, but it inverts the article's stated purpose — height means done, not written. Requires an Article X amendment

Recommendation: A. It is the only option under which the units are measured rather than chosen, and it is unblocked today.

MEASURED 2026-08-03 — step 2 done, step 3 returns a degenerate distribution

Preservation is fixed: substrate/runtime.ts now emits a per-subject completed measure. It had been running the query, typing the result and discarding it.

The distribution was then measured against the live database, read-only and in aggregate:

Source Result
hq_task WHERE state='done' 0 rows. Totals: 3 open, 3 cancelled, 0 done
hq_agent_run WHERE state='succeeded' 9 rows, all for one agent (z) — n=1 subject, min=max=9, 1 distinct value
hq_handoff, hq_meeting, hq_memory 0 rows each

The floor-unit decision is still blocked, and the reason has changed. It was blocked because the evidence was discarded at the fact boundary. That is fixed. It is now blocked because the evidence has not accumulated: no task has ever completed, and the only cumulative output that exists is one subject with one value.

Band edges cannot be derived from a single point. Choosing a unit now would fit it to one observation — the same defect that withdrew X.3, where "a governance rule that swings that far on one data point is not ready".

A second candidate source surfaced and is recorded rather than adopted. hq_agent_run.state='succeeded' is also cumulative operational output, is per-agent, and unlike hq_task it has actual rows. It is arguably the better II.2 source — it records work executed rather than work marked done by an operator. Whether height should represent completed tasks or successful runs is a governance judgement about what "operational output" means, not something measurement settles. It is not wired to anything.

What unblocks step 3: completed work accumulating across several subjects. Until then any proposed unit is speculative, and the honest state is waiting for data, not waiting for analysis.

Note on scope: the db→fact boundary is outside every enumerator in the system. The enumerators cover src/lib/world; the connector that drops state='done' is not in the object space. That is a coverage boundary this decision surfaced, and it is not yet recorded as one.


Decision 2 — Gap surfacing design (blocks F6b)

What measurement shows

  1. core/ingest produces Gaps. IngestResult = { facts, gaps }; every observation a connector cannot map becomes a Gap with a reason and detail.
  2. Nothing consumes them. No component, page, or lib module reads gaps. They are produced and discarded — the channel exists and terminates.
  3. repoTodos is deliberately unmapped, keyed repo-todos-<name>, with no migration by design: a TODO marker is untriaged text with no correct condition, so it has no correct mapping either.

The consequence for the decision

Gaps are the system's own record of what it observed and could not represent — which is the same category as UNWITNESSED and the coverage boundary, and the one the architecture treats as load-bearing. Producing them and discarding them is the failure mode the ledger exists to prevent, occurring in the runtime rather than in the documentation.

The decision is where a Gap becomes visible, and it is genuinely a design choice with no measured answer.

Options

Surface Consequence
A (recommended) A count plus reasons in the existing runtime panel — the same place source failures already appear Cheapest, consistent with how sources[].ok already reports substrate failure. Makes "observed but unrepresentable" visible without inventing a new surface
B Gaps become incidents, appearing in the world as markers Highest visibility, but conflates the system failed with the system could not interpret. Article I.3 territory: an incident is an operational fact, a Gap is a representational one
C A dedicated Gap view Most faithful, most work, and unjustified until the volume is known — nothing currently measures how many Gaps a real ingest produces

Recommendation: A, with a measurement first: instrument the count before choosing a surface, because C cannot be justified without knowing the volume and A may be sufficient permanently.

repoTodos needs no decision — it stays unmapped, which is the correct treatment of untriaged text, and F6b retires only its legacy identity, not its unmapped status.


Decision 3 — a DOM environment for scene-graph observation (new, 2026-08-03)

What was authorised, and what measurement then showed

@react-three/test-renderer was authorised and installed on the reasoning that it unlocks the largest remaining unobservable object. It is necessary but not sufficient, and that was measured rather than assumed:

Step Result
Import renderer and WorldScene works
Render with a hand-built World DOMRect is not defined
+ DOMRect, ResizeObserver stubs document is not defined
+ minimal document stub canvas.getContext is not a function
+ custom gl mock via RenderProps fails with an empty error

The renderer calls document.createElement('canvas') and expects a real canvas. It ships no mock, declares no DOM dependency, and its only peers are react, @react-three/fiber and three.

composeWorld also cannot be used to build the fixture — build.ts is server-only, and here that marker is correct: the module holds deriveWorld, which does database IO. Genuine privilege, so the restriction belongs. The fixture must be hand-built, which is a real cost: the test then observes WorldScene given a World, not the pipeline that produced it.

Options

Action Consequence
A (recommended) Add jsdom (or happy-dom) as a second dev dependency Completes the capability. Scene structure — positions, materials, hierarchy — is trustworthy under jsdom, because three.js builds the scene graph independently of the GL context. Anything requiring actual rasterisation stays unobservable, and that limit should be recorded rather than glossed
B Hand-stub the DOM and WebGL in test setup No new dependency, but the instrument would observe a scene built under a heavily faked environment. Divergence from the real one would be undetectable — an instrument whose fidelity cannot itself be measured
C Revert @react-three/test-renderer; keep scene-graph on the boundary Honest and cheap. Leaves six of eight known rendering violations permanently unreachable

Recommendation: A. Every criterion given for the first dependency applies unchanged — dev-only, unlocks the largest unobservable object, no measured zero-dependency path. B is the one to avoid: it produces an instrument that looks like scene-graph observation while measuring something else, which is the substitution defect the observable-object rule exists to prevent.

The installed dependency is retained rather than reverted: it is a genuine prerequisite for A, and unused only until that decision is taken.


Sequencing

Both recommendations are measure-then-decide, which is why neither asks for a decision today:

  1. Instrument the Gap count (unblocked, no governance).
  2. Surface completions as facts (unblocked, no governance).
  3. Decide units against the observed completion distribution.
  4. Decide the Gap surface against the observed Gap volume.
  5. Only then P4 — the Legend amendment — with both units and surfacing settled, so the first visible constitutional artifact after the gate reopened carries measured values rather than chosen ones.