surfaces-never-disagree — Every surface projects the same state, says when it was observed, and keeps no list of its own
A reader on any surface sees the same value, the time it was observed and whether it is stale; a capability added to or removed from the registry appears or disappears everywhere with no consumer edit; a page served beside the write routes runs only script it shipped.
ACTIVE 7 of 13 issues done
- ready
- 6
- blocked
- 0
- active
- 0
- verify
- 0
- done
- 7
- cancelled
- 0
Problem
The core projection holds: the live server's 181 /api/v1 routes equal the 181 OpenAPI operations, which equal the committed docs/generated/openapi.json, and the executor is the only dispatch path (apps/majordomus-cli/src/capability/executor.rs:156). Around it: freshness is not uniform, since dashboard/overview, episodes, lifecycle/runtime, plan/status, served and mesh carry no observed_at, the overview badge is permanently stale on an idle repository (apps/majordomus-cli/src/index.rs:44, apps/majordomus-cli/src/live.rs:250-252) and GET / said stale: null while serving a build 12 commits behind HEAD. The parity property compares HTTP only for GET routes and excludes the coordination read models (apps/majordomus-cli/tests/properties.rs:166-168,300), and nothing compares CLI JSON with HTTP. /swagger has no CSP and no SRI on the same origin as the write routes (apps/majordomus-cli/src/http/swagger.rs:52,79). The Cockpit and the site keep at least nine consumer-side catalogues of what the registry declares, and ADR 0004 says nothing has a second registry. Measured in the runtime baseline audit of 2026-10-09 at master 1fb8d6be44 (plan/RUNTIME_BASELINE.md).
Outcome
A reader on any surface sees the same value, the time it was observed and whether it is stale; a capability added to or removed from the registry appears or disappears everywhere with no consumer edit; a page served beside the write routes runs only script it shipped.
Current state
Implemented: routes, OpenAPI, MCP and the generated registry dataset are registry-derived and drift-checked. Partial: freshness, parity, Cockpit data access (five pages read ctx.registry directly, .ai/repo/ui-integrity-baseline.txt:9-13). Second registries: the clap tree (checked both ways by apps/majordomus-cli/src/capability/closure.rs), cli/local.rs LOCAL (89 commands), native web surfaces (apps/majordomus-cli/src/web/discover.rs:150), Cockpit routes (three lists, cross-checked), and the consumer copies listed in plan/RUNTIME_BASELINE.md.
Desired state
Every state-reporting answer carries observed_at and source, enforced over the registry; the parity property covers writers and the CLI; no Cockpit or site file names a value an enum, a capability or HttpExposure already declares; /swagger and every GET refuse what a hostile page or rebinding host could do.
Scope
- apps/majordomus-cli/src/capability/builtin
- apps/majordomus-cli/src/cockpit
- apps/majordomus-cli/src/http
- apps/majordomus-cli/src/live.rs
- apps/majordomus-cli/src/index.rs
- apps/majordomus-cli/src/site.rs
- apps/majordomus-cli/src/web/discover.rs
- share/cockpit
- site/templates
- apps/majordomus-cli/tests/properties.rs
- apps/majordomus-cli/tests/dashboard.rs
- test/cases
- docs/SURFACES.md
- docs/COCKPIT.md
- .ai/repo/adrs/0004-canonical-architecture-and-performance-truth.md
Out of scope
- Removing the clap tree or cli/local.rs LOCAL; both are measured second declarations (SURFACES F-1, F-4) and this milestone records them rather than rewriting the CLI.
- Writing a static site that claims live state; the site stays a picture labelled as one.
- Reporting a replaced executable, which is I1502.
Acceptance criteria
- A test over the registry fails when a state-reporting capability answers without observed_at
- The parity test covers every capability with an HTTP route and every capability with a CLI path
- GET /swagger answers with a CSP and integrity hashes, and a foreign Host is refused on GET
- A gate fails on a literal /api/v1/ path or a copied enum value in Cockpit or site code
Validation
- bin/majordomus plan validate
- cargo test --test properties --test projections --test dashboard --test http_serve --test http_host
- bin/majordomus-cli generate --check
- MJ_TEST_JOBS=4 bash test/run.sh 545_every_answer_says_when_it_was_observed 406_one_declaration_every_surface
Evidence required
- uniform_freshness
- parity_with_writers_and_cli
- no_consumer_catalogue
Risks
- Adding observed_at to capability outputs changes their schemas and so the public surface; version-surface will price it as a change, which is correct.
- Extending parity to POST routes runs writers in tests; each must run against a synthetic repository, never the checkout.
Issues
Each one is an execution contract with its own acceptance criteria, validation command and required evidence. The wave column is the layer of the dependency graph it sits in.
| issue | status | wave | depends on | title |
|---|---|---|---|---|
| I2151 | DONE | 0 | — | A page served beside the write routes runs only script it shipped, and a rebinding host reads nothing |
| I2653 | DONE | 0 | — | Every diagram on the site is one interactive, linkable viewer whose node links are inferred, and a derived table can be drawn as a chart |
| I2654 | READY | 1 | I2653 | A chart is held to its table by a gate, and the tables that are measured series are drawn |
| I2655 | READY | 1 | I2653 | The homepage decides whether it carries a derived figure, within its declared story and budget |
| I2656 | READY | 1 | I2653 | The diagram viewer serves the Cockpit and majordomus ui, and a node that is a repository file links to its source |
| I2770 | DONE | 0 | — | majordomus ui and the Cockpit wear the site's navbar: one renderer, the same styles and scripts, and a browser comparison that refuses a difference |
| I2842 | READY | 0 | — | The live site is verified on a schedule, and its last verification is evidence |
| I2943 | DONE | 0 | — | A mesh this process is not on is said as not observed, never as zero or no |
| I3007 | DONE | 0 | — | A commit that a record or the site's data names is a commit the history holds |
| I3008 | DONE | 0 | — | A homepage link with a fragment resolves to an element its page carries |
| I3009 | READY | 0 | — | No record's prose and no board announcement holds a template delimiter |
| I3010 | DONE | 0 | — | The mesh manual says the edit guard is not wired, and how to wire it by hand |
| I3318 | READY | 0 | — | Every decision and curated note is read on /ui, with the records it names and the records that name it |
The graph
flowchart LR
I2151["I2151<br/>A page served beside the write routes runs only script it shipped, and a rebinding host reads nothing"]:::done
I2653["I2653<br/>Every diagram on the site is one interactive, linkable viewer whose node links are inferred, and a derived table can be drawn as a chart"]:::done
I2654["I2654<br/>A chart is held to its table by a gate, and the tables that are measured series are drawn"]:::ready
I2655["I2655<br/>The homepage decides whether it carries a derived figure, within its declared story and budget"]:::ready
I2656["I2656<br/>The diagram viewer serves the Cockpit and majordomus ui, and a node that is a repository file links to its source"]:::ready
I2770["I2770<br/>majordomus ui and the Cockpit wear the site's navbar: one renderer, the same styles and scripts, and a browser comparison that refuses a difference"]:::done
I2842["I2842<br/>The live site is verified on a schedule, and its last verification is evidence"]:::ready
I2943["I2943<br/>A mesh this process is not on is said as not observed, never as zero or no"]:::done
I3007["I3007<br/>A commit that a record or the site's data names is a commit the history holds"]:::done
I3008["I3008<br/>A homepage link with a fragment resolves to an element its page carries"]:::done
I3009["I3009<br/>No record's prose and no board announcement holds a template delimiter"]:::ready
I3010["I3010<br/>The mesh manual says the edit guard is not wired, and how to wire it by hand"]:::done
I3318["I3318<br/>Every decision and curated note is read on /ui, with the records it names and the records that name it"]:::ready
I2653 --> I2654
I2653 --> I2655
I2653 --> I2656
classDef done stroke:#16a34a,fill:#dcfce7,color:#14532d,stroke-width:2px
classDef active stroke:#2563eb,fill:#eff6ff,color:#1e3a8a,stroke-width:2px
classDef verify stroke:#7c3aed,fill:#f5f3ff,color:#4c1d95,stroke-width:2px
classDef ready stroke:#0891b2,fill:#ecfeff,color:#164e63,stroke-width:2px
classDef planned stroke:#0891b2,fill:#ecfeff,color:#164e63,stroke-width:2px
classDef blocked stroke:#b45309,fill:#fffbeb,color:#78350f,stroke-width:2px
classDef cancelled stroke:#6b7280,fill:#f9fafb,color:#374151,stroke-width:2px
classDef superseded stroke:#6b7280,fill:#f9fafb,color:#374151,stroke-width:2pxLinked from this diagram (13)
- I2151 A page served beside the write routes runs only script it shipped, and a rebinding host reads nothing
- I2653 Every diagram on the site is one interactive, linkable viewer whose node links are inferred, and a derived table can be drawn as a chart
- I2654 A chart is held to its table by a gate, and the tables that are measured series are drawn
- I2655 The homepage decides whether it carries a derived figure, within its declared story and budget
- I2656 The diagram viewer serves the Cockpit and majordomus ui, and a node that is a repository file links to its source
- I2770 majordomus ui and the Cockpit wear the site's navbar: one renderer, the same styles and scripts, and a browser comparison that refuses a difference
- I2842 The live site is verified on a schedule, and its last verification is evidence
- I2943 A mesh this process is not on is said as not observed, never as zero or no
- I3007 A commit that a record or the site's data names is a commit the history holds
- I3008 A homepage link with a fragment resolves to an element its page carries
- I3009 No record's prose and no board announcement holds a template delimiter
- I3010 The mesh manual says the edit guard is not wired, and how to wire it by hand
- I3318 Every decision and curated note is read on /ui, with the records it names and the records that name it
Canonical record: .ai/repo/project/milestones/surfaces-never-disagree.yaml. Read it back with majordomus plan show surfaces-never-disagree.