Skip to content
← Documentation

docs/ownership-remainder.md


The three remaining exceptions — classified

Invariant, now supported by evidence rather than motivating it:

A semantic quantity has exactly one origin. Every projection consumes it. No projection determines organizational meaning.

The asymmetry that emerged:

  • Semantic ownership flows downward. The World owns meaning.
  • Presentation ownership flows upward. CityMap, WorldScene, the answers panel and any future renderer own only presentation.

Materially enforced for six quantities. Three remained unclassified. Measured, they are three different cases, and only one wants the obvious repair.


metrics — presentation over a second-origin aggregate

metrics: Array<{ label: string; value: string; note?: string }>

value is a string: String(readable.length), loc.toLocaleString(), formatBytes(n). Labels, notes and number formatting are unambiguously presentation and belong exactly where they are.

But metricsFor recomputes the aggregates it formats — repos.reduce((n, r) => n + r.loc, 0) — while World.measurements already carries the same quantities as MeasureFacts with units and evidence.

Verdict: split. The formatting is presentation and stays. The aggregate is a second semantic origin and should read World.measurements.

Not the same risk class as the six repaired quantities. Those fed geometry; these feed an inspection panel. Drift here misreports a number to a reader rather than silently altering the shape of the world.


products — meaning, but already single-origin

productsOwnedBy(agent.id) reads estate.ts:39:

owner: AgentId;

This is a declared relation, not a computation. It is written once, in the estate register, and read. types.ts contains zero references to owner, so the World does not carry it — but nothing recomputes it either.

Verdict: meaning, single origin, not a drift risk. Promoting it into the World would make the projection uniform and would not eliminate a second origin, because there is only one. It is an ownership gap, not an ownership violation.

Recorded as optional, and deliberately not urgent.


lastCompleted — meaning, but the derivation is constitutionally forbidden

The instruction was to promote it if it is genuinely organizational meaning. It is. But it must not be promoted as it stands.

// state.ts:279
// A commit is the most honest "completed work" signal available today.
const repos = reposOwnedBy(t, agent).filter(…);
return { title: `Commit to ${newest.name}`, when: … };

lastCompleted is derived from commits. 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.

The article governs height specifically, but the principle it states is about the quantity: a commit is written, not done. lastCompleted is literally "last commit" wearing the name of completed work — the same inversion measured earlier, where the world renders what was written and is blind to what was done.

Promoting it would import that inversion into the canonical model, and the World is the one place it must not reach. A wrong fact in a projection misleads one renderer; a wrong fact in the World misleads every projection and every future one.

What the lawful source can and cannot supply

The completed measure now emitted from hq_task.state='done' is the lawful Article II.2 source. It supplies a count, not a last item — there is no "most recently completed task" fact, because completion is aggregated at emission.

Verdict: do not promote. Do not delete. It is organizational meaning whose canonical owner does not yet exist. The blocker is observation, not ownership: nothing records which item completed most recently.


Summary

Quantity Kind Origins Action
metrics presentation over an aggregate 2 (aggregate recomputed) read World.measurements; keep formatting
products meaning 1 (estate register) optional promotion; no drift risk
lastCompleted meaning 1, but unlawful do not promote — blocked on observation

Two of the three do not want the obvious repair, and the reason is different in each case. That is the ownership audit behaving as a roadmap rather than a checklist: it says what to do and what not to.

The narrow remaining work:

  1. Point metricsFor at World.measurements — the last aggregate-level second origin.
  2. Record a per-subject "most recent completion" fact if that distinction is wanted; only then can lastCompleted be promoted lawfully.
  3. Continue auditing for second semantic origins rather than individual inconsistencies.

Canonicalization follows truth, not convenience

Recorded as the governing rule the lastCompleted case produced. A quantity becomes canonical only when all three hold:

  1. it represents the intended organizational phenomenon;
  2. it has a lawful observational source;
  3. multiple projections need it.

lastCompleted fails (1) and (2). Promoting it would make a known-wrong phenomenon canonical instead of local — reducing semantic integrity rather than improving it.

Two kinds of gap, two different remedies

Gap Remedy
Multiple semantic origins eliminate duplication
Missing lawful observation improve observation

The six repaired quantities were ownership problems. lastCompleted is an observation problem. Solving an observation problem with an ownership change would repeat exactly what Article II.2 has warned against from the start.


Task 1 measured: metricsFor cannot read the World from where it stands

Structural blocker, measured. page.tsx runs:

const state = deriveState(telemetry);   // metricsFor runs here
const world = await deriveWorld(state); // the World is derived FROM state

metricsFor lives in state.ts, upstream of the World. It cannot read World.measurements without a circular dependency. The fix is therefore not "point it at the World" — it is relocate it downstream into the projection, which is where presentation belongs anyway.

But relocation is only partly possible. Measured, per branch:

Branch Quantities available downstream Missing
repos 4 / 5 codeBytes
content 0 / 4 modules, lessons, totalMinutes, emptyLessons
security 1 / 4 findings, missingEnv, database
finance 2 / 4 activeSubscriptions, coursePurchases
total 7 / 17

Ten of seventeen quantities the panel formats do not exist in the World at all. Moving metricsFor downstream today would lose them.

So task 1 splits, and the split follows the same two-gap distinction:

  • 7 quantities — an ownership problem. Relocatable once the move is made.
  • 10 quantities — an observation problem. The World does not carry them; no ownership change can supply them.

Not applied. A partial relocation would leave metricsFor reading two sources — the World for seven quantities and telemetry for ten — which is a worse ownership position than one clean upstream origin, because it would create the split-brain the audit exists to remove.

The honest sequence is: extend the World's measurement vocabulary to cover what the panel needs, then relocate the whole function downstream in one move. Until then metricsFor is a single-origin presentation function reading telemetry, which is coherent even though it is not canonical.


Coverage versus readiness

Two different questions, and conflating them is what makes partial migration look safe:

  • Coverage — how much of this projection's semantics is represented canonically?
  • Readiness — can it become a pure function of the canonical model?
Projection Consumes Coverage Ready?
WorldScene world + UI props 6/6 yes — already pure
deriveAnswers world yes
verifyWorld world + facts yes — facts are the World's own inputs
/api/world world yes
toCityNodes world + AgentState[] 6/9 NO — reads a second source
metricsFor telemetry 7/17 NO — upstream of the World

The migration rule: a projection moves atomically. If it cannot become a pure function of the canonical model, it does not move yet.


Self-assessment: the last change was a partial migration

toCityNodes(world: World, agents: AgentState[]) takes a second source. I created that in the previous commit, and the rule now records it as the thing not to do. Stated plainly rather than defended.

What it did and did not do:

Dual semantic roots eliminated 6 (accent, workload, status, blocked, queue, focus)
Dual semantic roots created 0 — no quantity gained a second origin
Projection made pure no — three fields still come from AgentState
Verifiable from the World alone no

So it improved coherence and left the projection impure.

The tension worth weighing

Applied strictly, the atomic rule would have prevented that change — and measurement now shows the blocker is observation (10 of 17 quantities absent from the World), so the wait would be long. The three live constitutional divergences would still be live.

Proposed refinement, offered as argument rather than measurement: the atomic unit is the quantity, not the projection.

  • Moving a quantity to its canonical origin is always safe — it strictly reduces the number of origins and can never create a dual root.
  • Moving a projection wholesale requires full coverage, because a projection that reads two sources cannot be verified against either alone.

Under that reading both hold simultaneously: the six quantities moved atomically and correctly; toCityNodes remains not ready, and stays where it is until coverage reaches 9/9.

That also preserves the rule's real content — never split a single quantity across two roots — while not blocking repairs that eliminate roots one quantity at a time.

The distinction to keep: hybrid sourcing (a projection reading two places) is a verifiability cost. Dual ownership (one quantity computed twice) is a correctness cost. The audit has been eliminating the second; the first is what toCityNodes now carries, and it is recorded rather than hidden.