Skip to content
← Documentation

docs/semantic-ownership.md


Semantic ownership

Renderers are ignored here. The unit of audit is the semantic quantity, not the visual property.

Invariant:

A semantic quantity should have exactly one canonical computation. Every projection should consume that computation rather than reproduce it.

The distinction is no longer agreement versus disagreement. It is computed semantics versus projected semantics. A projection may transform representation, geometry, interaction or presentation. It may not determine organizational meaning.

The eight duplicated quantities are therefore not implementation duplication. They are semantic ownership violations.


Verification is bounded by its semantic root

verifyWorld(world: World, facts: WorldFacts): Violation[]

No assertion added inside this function can ever prove coherence with CityMap, because the second derivation never enters its input. This reframes what a verifier is:

A verifier does not establish truth. It establishes truth relative to one semantic root. Once a quantity is reconstructed elsewhere, the guarantee ends unless that reconstruction is independently verified.

Coverage of the second root: 0%, and not fixable by more rules.


The ownership table

Quantity Canonical Independent computations Consumers Verifier coverage Class
weight build.ts:176 2 (state.ts:407, identical formula) 3D, panel, API / CityMap 1 of 2 Accidental
load / workload build.ts:205 2 (state.ts:462, different formula) 3D, panel, API / CityMap 1 of 2 Accidental — DIVERGED
status build.ts:440 2 (state.ts:452, same ternary) 3D, API / CityMap 1 of 2 Accidental
accent build.ts:438 2 (page.tsx:82, no reserved branch) 3D, API / CityMap 1 of 2 Accidental — DIVERGED
queue build.ts:173 2 (state.ts:436, mayor aggregation) 3D, panel, API / CityMap 1 of 2 Accidental — DIVERGED
blocked build.ts:179 2 (state.ts:448) panel, beacon / CityMap 1 of 2 Accidental
focus build.ts:188 2 (state.ts:463) inspect / CityMap 1 of 2 Accidental
lastCompleted state.ts:278 1 CityMap only 0 of 1 Unowned by the World
health build.ts:88 1 3D, API, verifier 1 of 1 Structural
activity build.ts + substrates 1 3D, API, verifier 1 of 1 Structural

Two of ten quantities are Structural. Every other operational quantity is computed twice.


Three live divergences — three distinct failure modes

The significance is that none required a bug in new code. The architecture permitted each.

1. load/workload — drift after a one-sided repair

Article IV.4 was repaired in build.ts (linear → logarithmic). state.ts kept the original. Beyond peak ≈ 600 an agent with real work reads 0.0100 in CityMap and 0.3041 in the World.

Failure mode: a correct repair applied to one implementation.

2. accent — a special case added to one implementation

build.ts:438 returns UNDEVELOPED_HUE for reserved agents. page.tsx:82 is unconditional hueFor(id). A role nobody holds renders as a staffed agent in the SVG city, violating Article I.5 — while hue-provenance.test.ts passes, because it observes the World.

Failure mode: a correct special case added to one implementation.

3. queue — an aggregation rule present in one implementation

// state.ts:433
const mayorQueue = all.filter((i) => i.state === "blocked");

build.ts contains zero mayor special-cases. So in CityMap the mayor holds every blocked item in the organization; in the World the mayor holds only items assigned to them.

Because five further quantities derive from queue, this single divergence propagates to blocked, weight, workload, status and focus for that agent. The mayor is a different organizational entity in the two projections.

Failure mode: an aggregation rule defined in one implementation only.


What focus shows about method

focus is computed twice by visibly different code — build.ts scans for a best item; state.ts sorts and takes queue[0]. Both order by priority ascending, then title, so they agree.

Measured, not assumed — and it does not change the class. focus remains Accidental: two implementations that currently agree, with nothing preventing the next one-sided change. Agreement was checked and found; ownership was still violated.


Target state

Every semantic quantity is computed exactly once. Everything else is a projection.

Eight quantities move from Accidental to Structural by deleting the second computation, not by reconciling formulas. lastCompleted is the reverse case — computed only in the second root, so the World cannot currently express it; it needs promoting rather than deleting.

Only after that property holds does projection coherence become a useful optimization target, because only then does a disagreement necessarily indicate a rendering defect rather than semantic drift.

APPLIED 2026-08-03 — six second origins eliminated

CityNode now reads the World. toCityNodes(world, agents) in hq/view.ts is a pure function of the canonical semantic model, extracted from page.tsx so the rule is checkable rather than conventional.

Quantity Before After
accent hueFor(id) in page.tsx World.agents[].accent
workload state.ts linear normalisation World.agents[].load
status state.ts ternary World.agents[].status
blocked state.ts filter World.agents[].blocked
queue state.ts, with a mayor rule World.agents[].queue
focus state.ts queue[0] World.agents[].focus
weight state.ts deleted — read by nothing

All three live divergences close as a class, not as instances. weight was measured dead: assigned once, consumed by no component, while a doc comment claimed it drove road pulse.

Still second origins, and recorded as such: metrics, products and lastCompleted are telemetry-derived and the World does not own them.

Finding 3.2 remains untouched per standing instruction.