Skip to content

How Majordomus derives its surfaces

how the system fits together: canonical state, discovery, typed registries, derived models, governance and evidence, and every surface projected from them — what is authored, discovered, derived, observed and inferred, a capability and a rule traced end to end, the generation matrix, a claim audit with file references, and the architecture debt that remains

Rendered from docs/SURFACES.md — the same Markdown GitHub shows.

Majordomus is a control plane that lives inside the repository it governs. This document explains it as one system, not as a list of features. Facts are written down, discovered or observed. They become typed models. Those models are validated and checked against evidence. Then every interface a person or an agent uses is derived from them: the command line, the HTTP API, the OpenAPI document, Swagger UI, MCP, the Cockpit and this website.

Most of what follows is already described somewhere else, one subsystem at a time: CAPABILITIES.md covers the operation model, DYNAMICITY.md covers where a fact lives, DEVELOPMENT_RUNTIME.md covers where a semantic lives, DOCTRINE.md covers rules and proof, EVIDENCE.md covers recorded executions, GITHUB_PAGES_ARCHITECTURE.md covers the website, MESH.md covers discovery, and PLANNING.md covers the plan. This document connects them, and it says plainly where the code does not do what the design wants yet.

How to read the claims. Every structural claim was checked against the code and against the running executable, at the commit this document was written on. Claims carry labels in brackets, such as [C4], which point into the claim audit. The audit gives the file and line, or the command, that proves each one. Some statements are qualified with a word:

  • partial: only part of the statement is implemented;
  • not implemented: nothing in the tree does it;
  • debt: the repository knows about the gap. It is listed in Architecture debt.

No numbers about the repository's contents appear here (project.no-counts-in-prose). Wherever a count matters, the command that measures it is given.

Contents

  1. The invariant
  2. Two programs share one name
  3. Where the truth is: five kinds of fact
  4. Discovery, and how it differs from inference
  5. Typed registries, canonical ids and display names
  6. Schemas: from a Rust type to OpenAPI and MCP
  7. The surfaces are projections
  8. The generation pipeline
  9. Governance: doctrine, rule, policy, ADR
  10. A rule existing is not the same as a rule being enforced
  11. Evidence, freshness and claim drift
  12. Publication: built, deployed, verified
  13. Doctor and drift: detect, repair, regenerate
  14. Tests that hold the surfaces together
  15. Sessions, context, handover, planning, environment
  16. Mesh and the peer board
  17. Walkthroughs
  18. What is not generated
  19. Zero registration: the ideal and its exceptions
  20. Why generation matters, and the theses

Appendices: A architecture map, B surface map, C generation matrix, D governance and mesh maps, E claim audit, F architecture debt.

1. The invariant

Discover and model a fact once, and derive every interface from it.

This is the mechanism that keeps the interfaces from drifting apart. A repository that describes itself in several places builds up descriptions that disagree: a README, an API reference, a tool list for an agent, a dashboard, a website. Each can be correct on the day it is written, and each goes stale on its own schedule. Majordomus avoids this by keeping one declaration and generating the other descriptions from it. Where it cannot generate a description, it checks that description against the declaration.

The repository names this invariant several times. It is project.interfaces-are-projections and project.rust-canonical-declaration in .ai/repo/rules/project/. It is ADR 2, ADR 4 and ADR 5 for operations, and ADR 40 for development semantics. What matters is how far the code actually honours it:

  • Operations (capabilities) honour it best. MCP tools and resources, HTTP routes, the OpenAPI document, the benchmark targets, the generated reference and the site's registry pages are built by walking one registry [C1, C5, C6, C7].
  • The command line is declared a second time, in clap. It is checked against the registry rather than derived from it [C8, C9].
  • The Cockpit is partial. Its generic capability, object and graph pages are driven by the registry. Its area pages and the areas themselves are written by hand [C12, C13].
  • Development semantics (tasks, sessions, handovers, checkpoints, ADR allocation) are still mostly owned by shell code under lib/. For the mutating half there is no capability at all, and a ratchet file records that debt [C30].

2. Two programs share one name

majordomus is two executables. This fact is easy to miss, and every later section depends on it.

ProgramEntryImplementationOwns
the shell toolbin/majordomuslib/*.sh, dispatched per commandstart, check, finish, context, handover, session, doctor, update, adr, the plan writes, the doctrine dispatcher
the Rust executablebin/majordomus-cli (a launcher that builds when needed)apps/majordomus-cli/the capability registry, serve, mcp, generate, rules, evidence, devcontext, mesh, env, worktree, the Cockpit

For a command it does not know, the shell tool points you at the Rust executable. The registry, and so every derived surface, belongs to the Rust executable. Anything owned only by lib/ reaches no HTTP route, no MCP tool and no Cockpit page, unless a Rust capability reads its files. DEVELOPMENT_RUNTIME.md is the measured inventory of that boundary, and scripts/development-semantics-check enforces its ratchet.

3. Where the truth is: five kinds of fact

The source of truth is the tracked tree. The .ai/ layer is named by .ai/manifest.yaml and classified by .ai/repo/knowledge/sources.yaml. The Rust sources under apps/majordomus-cli/src/capability/builtin/ declare the operations, and share/ holds kinds, schemas and vocabularies. Everything else is one of the following:

Kind of factExamplesWho creates or changes itAuthorityCan it overwrite canonical state?How it is invalidated
authoreda rule file, an ADR, policy.yaml, a capability! block, docs/*.md, site/data/nav.toml, an issue record, a handover bodya person, or an agent acting for one, in a commitcanonicalit is canonical stateby the next commit that edits it
discoveredthe index of .ai/** objects, the git worktree topology, the effective rule set, the environment snapshot's tool versionsthe executable, reading tracked files and git on each runas authoritative as its inputs; never stored as a separate truthnorecomputed on each read; the index carries a fingerprint, and the shared server rebuilds it when the tree moves
deriveddocs/generated/**, site/data/**, site/content/**, .gitattributes' managed block, AGENTS.md/CLAUDE.md, issue status, waves, rule proof statesgenerators (majordomus generate, scripts/generate-site-data, scripts/gitattributes, majordomus update), or a pure function at read timenone of its own: it is a projectionno; a hand edit is refused or reported (generate --check, update's stamp)an input hash or a byte comparison (scripts/derive-check, the pre-commit scripts/pages current)
observedthe peer board, mesh nodes, a service answering on a port, an execution in progress, a recorded test run in the evidence ledgera running process: the shared server, a mesh provider, majordomus evidence recordawareness only; the ledger is the exception because it is committedno; project.mesh-is-observation-not-authority says so for the mesh, and the peer board "enforces nothing"TTL and session timeout for presence; the evidence ledger's digest and diff checks for runs
inferredthe intent match in devcontext, a derived handover body, the implied release bump, a proposed ADR from adr proposea deterministic heuristic, never a model callthe lowest; always markedno; an extracted ADR that claims accepted fails validationrecomputed; confidence is carried in the value

Two consequences follow, and the rest of this document depends on them.

  • Derived output never feeds back as input. scripts/derive states that no stage reads its own output [C15]. The shell dataset generator leaves its own documents out of its input list [C16].
  • Observation never becomes permission. A peer's claim is advisory. A mesh node's trust verdict changes labels, not permissions [C40, C42].

4. Discovery, and how it differs from inference

Discovery is enumeration governed by a declaration. The Rust executable finds the repository root. It reads the manifest, loads the kinds and schemas (share/kinds.yaml, share/schemas/, and repository additions under .ai/repo/knowledge/), and then enumerates files through sources.yaml classes, from the version-control index by default (--discovery vcs). Each file becomes a typed object or a diagnostic with a stable code. An unreadable manifest or kinds file is an error. An object that does not validate is excluded, the index reports degraded, and the surfaces still serve; --strict refuses a degraded index. CAPABILITIES.md ("Lifecycle and failure policy") describes this, and test/cases/76_capabilities_projections.sh adds a repository-defined kind and asserts that it appears on every surface.

Inference is different. It guesses at relevance or intent. It lives in a few named places and marks itself:

  • devcontext selection. Every selector except IntentMatch is declared() and carries confidence 1.0. The intent match scores the share of intent terms found in an object's title, description, identity or tags, and never in its body [C45].
  • Derived handover bodies. They are built from git, the ledger and open questions, and they end with a footer saying that no model wrote them [C47].
  • adr propose. It writes proposed and refuses any other status. Acceptance is a person's act [C34].

No surface runs a language model to produce a fact.

5. Typed registries, canonical ids and display names

Every addressable thing has a canonical identity that is not its display name.

  • Capabilities. The id is namespace.local (rules.report). The title ("Every rule against the proof there is for it") is display text. The registry refuses duplicate ids, MCP tool names, MCP resource URIs, HTTP routes and CLI paths, stores entries in BTreeMaps and iterates by id [C4].
  • Declarative objects. The id is <kind>.<identity>, and the identity comes from the kind's identity rule: id@version for a rule (project.english-only@1), a number for an ADR (adr-0044). The MCP resource is majordomus://<kind>/<identity>.
  • Rules are referenced as id@version in depends_on. A new version is a new file (conventional-commits.v2.md).
  • Worktrees are identified by their branch. The path <repo>-wt/<branch> is derived from git and never registered (WORKTREES.md).
  • Peers get a board position (p1, p2) that is reassigned on reconnect. It is not an identity: the durable half of a peer's identity is the checkout it carries (ADR 44).

Ordering is canonical too. apps/majordomus-cli/src/order.rs defines one total order: group, rank, label compared naturally, then identity. scripts/ci/order-check refuses case-folded comparators outside it, sort keys that render, and unpinned shell sort. It also ratchets the remaining crate sort sites in .ai/repo/order-baseline.txt [C19].

6. Schemas: from a Rust type to OpenAPI and MCP

The schema chain for an executable capability is real code, and a person writes each piece of it only once:

flowchart TD
  ty["Rust input and output types<br>serde + schemars JsonSchema derive<br>doc comments become descriptions"]
  cs["CanonicalSchema::of<br>schemars::schema_for, $schema removed,<br>rustdoc translated to CommonMark"]
  mcp["MCP tool inputSchema and outputSchema<br>for_mcp: inline, $defs kept"]
  oas["OpenAPI 3.1 operation<br>for_openapi: $defs hoisted into components,<br>refs rewritten; a clash of names is refused"]
  sw["Swagger UI<br>loads /openapi.json, embeds nothing"]
  gen["docs/generated/openapi.json and openapi.yaml<br>site /docs/api/"]
  ty --> cs
  cs --> mcp
  cs --> oas
  oas --> sw
  oas --> gen

CanonicalSchema::of::<T>() calls schemars::schema_for! [C2]. OpenAPI component names come from the schema title. That namespace is flat, so two different types with the same title fail openapi::document with "schema component … is defined twice with different content" [C3]. The fix is to rename a type; nothing warns about it before that point (debt F-11).

A declarative kind starts one step earlier. Its document schema lives under share/schemas/majordomus/<kind>/. A .proto description there generates the JSON Schema, the front-matter allow-list under share/allow/ and the section requirements (DEVELOPMENT_RUNTIME.md, "The derivation chain"). Unknown front-matter keys are errors (project.unknown-keys-are-errors).

7. The surfaces are projections

7.1 One declaration

An executable capability is one capability! block in apps/majordomus-cli/src/capability/builtin/<module>.rs. It lists id, kind, title, description, input, output, stability, exposure, tags, cache, benchmark and handler. The macro also classifies availability, visibility and the execution policy from the kind and the exposure, and takes the benchmark cases from the input type's BenchmarkCases [C1]. exposure sets each surface explicitly: Exposure { mcp, http, cli }. None means the capability is not exposed there, and nothing infers an exposure [C4]. Modules are composed with module! and compose_modules! in builtin/mod.rs.

Two post-macro modifiers declare what the kind alone cannot say. .cancellable() marks a capability whose run can be cancelled. .writes_repository() marks one that writes a tracked record. Two capabilities served over HTTP and MCP carry it today, plan.transition and recover.orphans; list them with a search for .writes_repository() under builtin/ [C31].

7.2 MCP

src/mcp/surface.rs says of itself "Nothing is declared here". compute_tools() takes every capability with an mcp.tool exposure. compute_resources() takes every capability with an mcp.resource exposure, and every declarative object is one [C5]. A tool carries the canonical input and output schemas, _meta.majordomus.id and a readOnlyHint derived from the kind. A call resolves through by_mcp_tool and runs through the shared executor.

The tools agents use to coordinate are ordinary declared capabilities: majordomus_announce is peers.announce, majordomus_peers is peers.list, and majordomus_capabilities is capabilities.list. They are not hand-written tools. Three pieces of code still know about them by name, though: the initialize instructions text, the stdio-to-HTTP bridge (which replays an announcement after a reconnect), and the peers.announce handler (which refuses a caller that is not MCP) [C6].

7.3 HTTP API

The router resolves a capability route with registry.by_http(method, path). For GET it binds query parameters, coerced by schema type; for POST it parses a JSON body. It calls the executor and maps errors to 400, 404, 422 and 500 [C7]. The router adds transport behaviour and holds no business logic: coercion, error mapping, refusing cross-origin non-GET requests, the route index at /api/v1, and a per-generation cache of the OpenAPI document. Its non-capability routes are infrastructure: /, /openapi.json, /swagger, /mcp, the /events WebSocket, /cockpit/** and generated static mounts such as /docs/ (WEB.md). There is no hand-written peers route: /api/v1/peers is peers.list.

7.4 OpenAPI

src/http/openapi.rs builds OpenAPI 3.1 from the same registry. operationId is the capability id and the tags are the modules. Parameter examples are the capability's own benchmark cases, so a field no case sets has no example. The x-majordomus-* extensions carry id, kind, stability, provenance, benchmark, cache, MCP and CLI; infrastructure routes sit under x-majordomus.infrastructure [C7]. Maps are sorted and carry no timestamp. The live document and docs/generated/openapi.json are the same computation.

7.5 Swagger UI

src/http/swagger.rs renders one static HTML shell that points SwaggerUIBundle at /openapi.json. A unit test asserts that the shell embeds no paths, openapi or components. The UI's own JavaScript and CSS load from the pinned swagger-ui-dist on unpkg, so this is the one surface that is not offline [C10]. It is a pure UI projection.

7.6 The command line: declared twice, and checked

The clap tree in apps/majordomus-cli/src/cli.rs is written by hand. That is deliberate: clap owns parsing, help, defaults and value sets ("The one projection that can drift" in CAPABILITIES.md). A capability's CliExposure is therefore a claim about a command declared somewhere else, and the claim is checked in both directions:

  • capability/closure.rs fails when a claim names a command clap does not have, or one that only groups other commands. It runs in tests/projections.rs and in capabilities validate [C8].
  • cli::LOCAL (src/cli/local.rs) gives the reason for each command no capability claims. There are exactly four structural reasons: process lifecycle, writes the repository, session-local, or renders a named capability for a terminal. There is also an alias form. quality::parity checks it, and the projection-closure gate (scripts/ci/projection-check) fails on a command that gives no reason [C9]. capabilities projections lists both sets live: the CLI-only commands as unbacked, and commands with no reason as unclassified.

What a CLI command still takes by hand [C11]:

  • a clap variant, and a dispatch arm in commands/mod.rs;
  • a runner in commands/<name>.rs, which maps flags to the input JSON (--findings becomes findings_only) and calls ctx.execute(id, input) after registry.by_cli;
  • a text renderer. --format json prints the capability's output unchanged; --format text is the command's own. Some exit semantics live only in the renderer: rules report --check exits 10 there, not in the handler;
  • an entry in cli::EXAMPLES, a single hand-written CommandExamples const. cli::validate refuses a runnable command with no example, and tests/cli_examples.rs runs every example against the built binary.

Shell completion is not clap_complete. commands/completion.rs answers from the command graph, which composes the clap tree, the shell tool's share/commands.yaml and the workflows (COMMANDS.md).

7.7 The Cockpit

The Cockpit (src/cockpit/) is HTML rendered on the server in Rust; there is no template engine. The /cockpit path matters more than any feature, so this section separates what is derived from what is written:

  • Derived. /cockpit/capabilities/<id> is one generic page for every capability, with a runner form built from the input schema. /cockpit/object?uri= renders any object; /cockpit/graphs/<id> renders any derivation. The sidebar's catalogues come from registry.modules(), grouped by the product model's module_area, plus index.kinds() and graph::ids(), all in canonical order [C12].
  • Written by hand. The route table is a match in cockpit/mod.rs. The areas list (Overview, Capabilities, Mesh, …) is nav::areas(), and its module comment gives the reason. Area pages ask for a capability by its literal id, such as ask(ctx, "health.report", …) or ask(ctx, "mesh.status", …) [C13]. So the sentence in CAPABILITIES.md that "nothing in it names a capability" is not accurate (debt F-20).
  • The frontend is not an authority. The JavaScript under share/cockpit/ does presentation and requests only. runner.js calls the capability's real route from data-mj-method/data-mj-path; palette.js and the graph views read /api/v1/*. Nothing in the browser computes readiness, status or proof. The development-semantics gate refuses any reference to .ai/repo, .ai/local or a majordomus executable inside the Cockpit trees [C14].

7.8 The surfaces compared

CLI (majordomus-cli)HTTP APIMCPCockpit
Declared byclap, by hand, checked against CliExposurederived from HttpExposurederived from McpExposureroute table and areas by hand; generic pages derived
Reachesthe capabilities that claim a CLI path, plus local commandsevery capability with an HTTP exposureevery capability with a tool or resource exposure; every declarative object as a resourceevery capability (generic page); selected areas
Inputflags mapped by hand in the runnerquery parameters coerced by schema, or a JSON bodyarguments validated against inputSchemaa runner form generated from the input schema
Output--format json (the capability value) or text (a renderer)JSON value, typed errorsstructuredContent plus textHTML over the same value
Logic of its ownrenderers, some exit codes, local commands (serve, generate, worktree create)transport onlytransport only; announcement replay in the bridgepresentation only
Measure itcapabilities projections (unbacked)GET /api/v1tools/list, resources/listGET /cockpit

Coverage is uneven. Fewer capabilities have a CLI path than an HTTP route. peers.list, plan.*, objects.*, health.report and environment.status have no CLI projection of their own; the env commands are CLI renderings recorded in cli::LOCAL. Run majordomus-cli capabilities projections --format json and compare the cli and http fields (debt F-4).

8. The generation pipeline

8.1 The generators, in order

scripts/derive is the one command that brings every committed projection up to date. Its stages run in this order [C15]:

flowchart TD
  build["build step<br>cargo build, or MAJORDOMUS_BIN"]
  a["stage A: majordomus generate --strict<br>code-only projections: openapi, registry, cli,<br>capabilities.md, modules/*.md, share/allow, provider bootstraps"]
  b["stage B: scripts/generate-site-data<br>site/data/generated, site/content, docs/SITE_CLAIMS.md,<br>docs/PLAN_STATUS.md, docs/PAGES_STATUS.md"]
  c["stage C: majordomus generate<br>projections over the index stage B changed:<br>site/data/registry, benchmarks, artifacts manifest"]
  d["stage D: scripts/gitattributes<br>the merge=derived block, from the artifact manifest<br>and the published content tree"]
  check["scripts/derive-check<br>generate --check, generate-site-data --check,<br>gitattributes --check: 0 current, 10 stale, 15 refused"]
  build --> a --> b --> c --> d
  d -.-> check
  • majordomus generate (src/generate.rs) writes to docs/generated/ and site/data/registry/ from one plan(). The same plan runs for writing and for --check. Its targets are the GenerateTarget enum in cli.rs, and the output manifest (docs/generated/artifacts.json) lists every artifact with its source, schema and hash.
  • scripts/generate-site-data is the shell half. It hashes a declared list of inputs (INPUTS) under LC_ALL=C, writes into a staging directory and moves the results into place, so a failed run changes nothing. It projects docs/*.md into site/content/docs/ [C16].
  • There are two writers of generated trees, src/generate.rs and the shell generator, and both write site content (debt F-14).

8.2 Freshness is enforced at commit time and in CI

  • .githooks/pre-commit runs bin/majordomus doctor, the worktree guard and scripts/pages current. That last command compares generate-site-data --fingerprint with source_hash in site/data/generated/source.json, and then runs generate --check [C17]. If the executable is not built, the registry half of the check is skipped with a message rather than failed (debt F-16).
  • In CI, core-check runs generate-site-data --check, and the generation-converges gate runs generate followed by generate --check: the generator must be a fixed point [C18].
  • .gitattributes marks every derived path merge=derived. scripts/merge-derived keeps "ours" and prints that the file is now stale, so a merge never hand-merges a projection; derive regenerates it afterwards. doctor fails a clone that has not registered the driver.

8.3 How a document becomes a page

Each document is registered by one row in the first table of docs/README.md. The shell generator reads those rows and makes /docs/<slug>/. It generates the front matter: the title from the first heading, the description from the row, the weight from the row's position. It rewrites links, and it refuses any docs/*.md that is in no table [C16]. The site navigation (site/data/nav.toml) is authored, and scripts/ci/nav-check verifies that the navbar and footer templates derive from it. Nothing adds a document to the navigation automatically. That is by design: the file says the navbar is not a sitemap.

8.4 Generator and renderer

A generator turns canonical input into a committed or served artifact, and is judged by --check: majordomus generate, scripts/generate-site-data, majordomus update, scripts/gitattributes. A renderer turns a value that already exists into a presentation, and holds no fact: a CLI text renderer, a Cockpit page, a Zola template, the environment banner (src/environment/render.rs, "a pure view with no discovery"). When a renderer computes something a generator should own, it becomes a second implementation. The development-semantics gate says it cannot decide this mechanically, so a reviewer does.

8.5 Generators are tested

  • tests/projections.rs: every declared projection exists and none is an orphan; a change to a type or description reaches MCP, OpenAPI and the reference; generated artifacts are byte-identical when generated twice and carry no absolute path [C18].
  • tests/generate_check.rs: --check agrees with what a write would produce, detects tampering and never writes.
  • test/cases/98_cli_reference.sh compares two generations with cmp. test/cases/28_no_hardcoded_values.sh asserts that input order does not depend on locale. test/cases/12b_site_data_transactional.sh asserts that a failed run leaves artifacts byte-identical. test/cases/51_derived_artifacts_committed.sh asserts that derive-check passes and that no derived file embeds the current commit.

9. Governance: doctrine, rule, policy, ADR

This repository defines the four terms as follows. The definitions come from DOCTRINE.md, the rule files and .ai/repo/policy.yaml.

TermWhat it is hereWhereLifecycle
rulea Markdown file with front matter: id, version, title, statement, status (active or deprecated), class (blocking or advisory), depends_on, and an optional x-majordomus block naming a validator, tests or a reviewed_because.ai/repo/rules/project/, plus the vendored package in .ai/repo/rules/vendor/majordomus/ (hash-pinned)edited in a commit; a new meaning is a new version file; no acceptance step
doctrinetwo meanings in this repository (debt F-25). In DOCTRINE.md, a doctrine is a rule whose x-majordomus block names a validator the shell dispatcher calls (mj_doctrine_dispatch), and the registry of doctrines is derived from the rule set. On the site's method pages, "doctrine" is the principle layer above policyDOCTRINE.md; site/content-src/method/derived from rules
policyone repository's configuration of the tool: context budgets, verification.finish_requires, handover sections, enforcement wiring for hooks.ai/repo/policy.yamledited by hand; provider projections are regenerated by majordomus update
ADRa decision record: schema: adr/v1, status (proposed, accepted, superseded or rejected), reciprocal supersedes/superseded_by, typed related references, provenance.originone file per decision under .ai/repo/adrs/, named by its number and slugadr propose writes proposed; acceptance is a person's act, enforced only for records whose origin is extracted [C34]

The chain from a rule to evidence is:

flowchart TD
  rule["rule file<br>class, depends_on, x-majordomus"]
  mode["enforcement mode, from the block's shape<br>dispatched: validator / gated: tests / reviewed: reason"]
  val["validator<br>mj_validate_name in lib/, run by the dispatcher<br>at the commands enforced_by names"]
  gate["CI gate<br>.ai/repo/ci/gates.yaml: id, job, runs, path classes"]
  test["behavioural case or crate test<br>test/cases/*.sh, apps/majordomus-cli/tests/*.rs"]
  ledger["recorded execution<br>.ai/repo/evidence/ledger.json"]
  state["proof state per rule<br>crate::rules, rules report"]
  surf["surfaces<br>CLI rules report, GET /api/v1/rules,<br>majordomus_rules, generic Cockpit page"]
  rule --> mode
  mode --> val
  mode --> test
  test --> gate
  val --> test
  test --> ledger
  ledger --> state
  gate --> state
  state --> surf

One join in that chain is inferred rather than declared. gates.yaml does not say which rules a gate proves. crate::rules scans each gate's runs: line (a line reader, not a typed parse) and binds a rule's named path to a gate in three cases: the command is that path; the path is a test case and the command runs test/run.sh; or the path is a crate test and the command contains rust-check [C27]. A suite gate is assumed to run every case; nothing checks that it actually selected the case (debt F-7).

10. A rule existing is not the same as a rule being enforced

A rule can be in any of eight conditions, and each is a different claim. The table maps each condition to what the repository can actually show today.

ConditionWhat proves it hereImplemented?
declareda file under .ai/repo/rules/yes
parsedlib/rules.sh and src/rules/mod.rs read it; unknown keys are errorsyes (two readers)
validresolution refuses missing dependencies, cycles and duplicate identities; the loader refuses a half-declared blockyes
validator existsrules report marks a named validator present; a missing one makes the rule danglingyes, for dispatched rules
wireddoctor walks the dispatcher chain for doctrines; rule-proof-check refuses a named path that is not in the treeyes
gate executesa gate in gates.yaml whose runs: covers the named pathinferred from runs: (§9)
passeda recorded passing execution the rule's inputs have not moved pastpartial: only local runs are recorded; CI records nothing (debt F-6)
evidence captured.ai/repo/evidence/ledger.json, joined on every readpartial: the same limit

The machine-readable form of this ladder is the rule proof graph (crate::rules, majordomus-cli rules report | show | proves, GET /api/v1/rules, MCP majordomus_rules). It gives each rule one of ten states: proven, inputs unchanged, stale, gated, failing, not run, reviewed, unrunnable, dangling, unproven. A rule's state is the weakest of its parts [C23].

  • Enforcement modes come from the block's shape [C24]. A dispatched rule names a validator. A gated rule names tests and no validator. A reviewed rule names a reviewed_because sentence and neither. An executable proof always wins. scripts/ci/rule-proof-check, the rule-proof gate, refuses a dangling path outright and ratchets blocking rules that name nothing in .ai/repo/rule-proof-baseline.txt [C25].
  • A finding is a class the state does not support. dangling is a finding for any class. For a blocking rule, unproven, failing and unrunnable are findings. gated, reviewed and not run are weaker states: they are counted but are not findings [C26].
  • Nothing gates on the proof graph itself. No script, gate or workflow runs rules report --check (debt F-8).
  • A failing run can be hidden. The per-rule state is max() over its tests, and the enum orders Failing before NotRun. A blocking rule with one failing case and one case that never ran therefore reports not run, which is not a finding. Reading the code shows this; it has not been observed in data (debt F-9) [C26].

project.never-reported-is-not-green shows the whole problem in one rule. Its statement is "a check that has not reported is unknown, never passing". It is class: advisory, has no x-majordomus block, and says it is decided "by review and by the record, not by a gate". The proof graph reports it as unproven, and that is not a finding, because an advisory rule owes no proof. The rule exists, its ideas shaped evidence-check (see EVIDENCE.md), and it is not mechanically enforced. The graph says exactly that.

To measure it yourself:

$ bin/majordomus-cli rules report --format json | jq '.states, .coverage'
$ scripts/ci/rule-proof-check
$ bin/majordomus-cli rules show project.never-reported-is-not-green

11. Evidence, freshness and claim drift

The ledger. .ai/repo/evidence/ledger.json holds the latest recorded execution of each test. Each entry records the test id, runner, source, outcome, seconds, commit, working-tree state, a sha256 digest of the test's own source, the time, the origin (local, ci or release) and the command that reproduces it. majordomus evidence record writes it from a report the run already produced. It keeps no history and no output (EVIDENCE.md).

Freshness is derived on every read [C28]:

  • a run that did not pass is failing;
  • when git cannot diff, the run is stale, because not knowing is not proof;
  • when the test source or the rule file changed after the recorded commit, or the digest no longer matches, the run is stale;
  • when nothing else changed apart from the ledger itself, the run is proven;
  • otherwise the run is inputs unchanged, meaning no known invalidation but not proof against this commit.

Claims. docs/CLAIMS.yaml binds each public claim to a source, an implementation and a test, with an authored status from a vocabulary declared in the same file. Every claim has a page under docs/claims/. Three readers check for drift:

CheckQuestionGate
scripts/generate-site-data --checkdo the named paths exist? does every claim have a page?core-check
scripts/ci/claim-proof-checkdoes the named test name the claim back? (ratcheted)claim-proof
scripts/evidence-checkwhat is each claim's proof state against the ledger? (ratcheted on lost or withdrawn proof)evidence-check, always on

How proof reaches the Cockpit and the website, honestly. Rule and claim proof states are served as capabilities (rules.*, evidence.*) over the CLI, HTTP and MCP, and each has the generic Cockpit capability page. The Cockpit has no dedicated rules, evidence or peers area. The website's /evidence/ page renders data/generated/claims-graph.json, which is built from CLAIMS.yaml: it shows the authored links between claims, sources and tests, not ledger outcomes. Its guarantees pages show the authored status, not measured proof [C29]. CI does not record evidence either: EVIDENCE.md says "nothing runs that step today" [C29]. So proof of function reaches the API and MCP as a derived state. It does not yet reach a page a person browses as anything more than a mechanism (debt F-6, F-10).

12. Publication: built, deployed, verified

The website is a projection, and three different states can each be called "published":

StateWhat establishes itWhere
builtscripts/pages build proves that the committed data is current by fingerprint, then runs site-build --no-data; a stale tree is refused, not regenerated.github/workflows/pages.yml, build step
deployedscripts/site-deploy --skip-build pushes gh-pages; GitHub then runs its own Pages build, which scripts/pages built readspublish step and the step that reads GitHub's build
verified publicscripts/pages verify --commit $GITHUB_SHA polls the live /build.json (written by scripts/site-build with commit, source hash and fingerprints). It is continue-on-error: a report, not a gatepublic step
verified, as a gatescripts/ci/pages-check, the pages-live gate: the published commit is on master, no publication is owed beyond the deploy window, the live site serves what gh-pages holds, and GitHub's build did not error. It is at-finish, so finish asks it.ai/repo/ci/gates.yaml [C21]

This is project.never-reported-is-not-green in practice. A deploy job that reports cancelled because it waited too long for the CDN is not proof that nothing was published, so the question is answered by reading what the site serves.

13. Doctor and drift: detect, repair, regenerate

VerbToolWrites?
detectmajordomus doctor (shell, read-only): provider projection states (absent, region_absent, malformed, unstamped, hand_edited, ok), the doctrine wiring chain, hook and merge-driver wiring, the always-loaded file's budget and hard-coded counts. majordomus-cli worktree doctor, mesh doctor, capabilities validate, scripts/derive-checkno
regeneratemajordomus generate, scripts/derive, majordomus update (provider files; renders every target first, and refuses to overwrite a hand edit without --force)yes, derived files only
repairmajordomus-cli worktree repair and worktree migrate (fingerprint-verified), just derive-merge-driveryes, local state or configuration

doctor has no --fix [C22]. Each finding names the command that fixes it. A finding that does not name one is refused by the finding-reproduce gate. Freshness of site/ and docs/generated/ is not checked by doctor; the pre-commit pages current and derive-check check it.

14. Tests that hold the surfaces together

TestWhat it compares
test/cases/76_capabilities_projections.shcapabilities validate passes for registry, MCP, HTTP and OpenAPI; one capability (objects.get) is traced to its tool, route, operationId and reference row; the OpenAPI operation count equals the HTTP exposure count; a tampered generated file makes generate --check exit 10; no closed == false rows; a declarative rule and a repository-defined kind reach every surface
apps/majordomus-cli/tests/http_serve.rsa real socket: mcp_and_http_answer_the_same_capability_with_the_same_result for objects.get, objects.search and capabilities.describe; the_release_analysis_is_one_value_on_every_surface; every route answers its benchmark cases
apps/majordomus-cli/tests/projections.rsprojections exist with no orphans, including the clap tree; a mutation of a type reaches every projection; generation is byte-identical
test/cases/91_canonical_architecture.shregistry.json ids equal the live describe; benchmark coverage is complete; many MCP requests leave the startup counters unchanged
test/cases/92_openapi_reference.shOpenAPI tags equal the modules; examples equal the benchmark cases
test/cases/46_cross_surface.shplan issues, READY status and edges agree across the CLI, the Mermaid diagram and the GitHub projection. This is the plan, not capabilities
test/cases/99_plan_capabilities.shthe shell plan engine and src/plan.rs produce byte-identical answers

Value equality across HTTP and MCP is tested for a sample of capabilities, not generated for each one. No test asserts that --format json from the CLI equals the HTTP value for each CLI-exposed capability (debt F-12).

15. Sessions, context, handover, planning, environment

Each of these follows the same pattern: an authored or recorded state, a derivation, and projections. The owner of each derivation differs, and that is where the debt is.

Sessions: a persistent record plus ephemeral presence. A session episode belongs to the provider session. It is recorded under .ai/local/state/sessions-open/ while open and closed into .ai/repo/sessions/ as a tracked record. That is the persistent half, written by lib/session.sh and lib/capture.sh, and triggered by the provider hooks in .claude/settings.json (session start, end, compact and prompt submission). Presence is the peer board, held in memory by the shared server and lost with the process (§16). CONTINUITY.md states the separation: "The episode file records no process, the peer board records no episode." The typed Rust session domain (src/session/) reads; the shell still writes (ADR 47, ADR 52).

Context compilation: two engines.

  • majordomus context (lib/context.sh) is the briefing. It has fixed sections in authority order (git, peers, task, profile, context documents, questions, decisions, checkpoint, handover, files, history, prompt), a line budget from policy (context.builder_budget_lines), and a declared drop order. Git, the task, the profile and open questions are never dropped, and every dropped section is listed under EXCLUDED. It is deterministic, and no model is called.

  • devcontext.compile (src/devcontext/) is the Rust compiler. It is a capability, so it reaches the CLI, HTTP and MCP [C44]. The algorithm:

    • discovery: seeds, a walk over the composed graph with edge weights multiplied along each path, the effective scope paths (including an issue's declared scope), governance, session records and local handovers and checkpoints, and unresolved references;
    • relevance: a floor (DEFAULT_FLOOR) and a depth limit (DEFAULT_MAX_DEPTH);
    • authority: tiers ordered Task, Governance, Source, Decision, Knowledge, History;
    • freshness: a local record whose git divergence is untrustworthy has its relevance halved and gets a stale_record diagnostic, and deprecated or superseded objects are excluded;
    • budget: tokens estimated as bytes divided by BYTES_PER_TOKEN, first fit in the order tier, then relevance, then URI; seeds and policy or scope objects are never dropped, and over_budget is reported if they alone exceed the budget [C45].

    Only the intent match is inferred (§4). Two engines for one concept is debt F-15.

Handover. The required sections come from policy (handover.required_sections: Objective, Current State, Next Action), and a missing or empty section is refused. The front-matter identity fields (branch, head, worktree, changed files, …) are always computed, and a body that tries to author one is refused. With --derive, each section is generated from the ledger, git and open questions by mj_derive_sec_<slug> in lib/derive.sh [C47]. --resolve picks the newest handover for the same worktree and branch, then for the same branch, never repository-wide, and labels its git divergence.

Planning. Milestones and issues are authored YAML under .ai/repo/project/. Status is derived and never stored: a status key is a validation error. Validation builds the dependency DAG with cycle detection, then execution waves; two ready issues in one wave whose scopes contain each other produce WARN scope_conflict and are serialised [C46]. lib/project.awk is the authoritative engine. src/plan.rs mirrors it and is held byte-identical by case 99; plan.* exposes it over HTTP and MCP, and plan.transition writes one record through the model (debt F-17). docs/PLAN_STATUS.md is generated by stage B.

RepositoryEnvironment. environment::RepositoryEnvironment (majordomus/repository-environment/v1) is one typed snapshot: project, repository, VCS, toolchains, layer, workflows, providers and services, with a provenance entry for each value. environment.status and environment.explain are capabilities (HTTP, MCP). The env commands and the shell banner are renderings of it; the banner "is a pure view with no discovery". One limit is honest to state: a service is drawn as available when a TCP connect succeeds, and nothing more [C48] (debt F-18).

16. Mesh and the peer board

There are two distinct mechanisms, and there is no third.

The peer board (src/peers.rs, ADR 44) holds the sessions working on this repository now. It is one in-memory board per shared server; peers.list gathers every checkout's board by reading the other checkouts' leases and asking each one once, and reports complete and boards. A peer is attached through an MCP session, and HTTP MCP sessions expire after SESSION_IDLE_TIMEOUT. majordomus_announce attaches a claim (intent, scope) to the connection. The board "enforces nothing", and a peer that reconnects loses its place on it [C41].

The mesh (src/mesh/, ADR 50, docs/MESH.md) discovers running Majordomus instances across machines. Its implemented providers are UDP multicast, UDP broadcast and HTTP rendezvous (POST /api/v1/mesh/register), each an implementation of MeshProvider that only hands bytes to the manager. The manager verifies Ed25519-signed envelopes on one path into one MeshRegistry, bounded by MAX_NODES, with presence from PRESENCE_TTL and records kept for RETENTION. Trust (deny_unknown by default) labels records and grants nothing. The mesh is off unless a committed declaration enables it, and this repository's declaration (.ai/repo/mesh/majordomus.yaml) says enabled: false [C42]. The governing sentence is "Discovery creates awareness, not authority."

What does not exist, so that nobody goes looking for it: a blackboard separate from the peer board; Tailscale, mDNS or DNS-SD discovery (named as possible future providers only); any cross-machine journal, link or claim replication on master; a Cockpit page for the peer board. ADR 50 keeps the two apart on purpose: "The peer board remains the only cooperation surface."

Scope overlap is derived, and advisory. Three mechanisms compute overlap, and none of them locks anything:

MechanismReadsResult
majordomus start / check --overlap (lib/start.sh mj_report_overlap)the other worktrees' .ai/local/state/current.yaml task scopes, by path containmentINFO overlap lines; exit 0
peers::claims_meetthe claims announced on the boardoverlap reported beside a peer
the plan's scope_conflictthe issues' declared scopes within a waveWARN; the issues are serialised

check --overlap reads task records only, not the board and not branches. It also has a defect you can reproduce: the loop runs on the right side of a pipe, so its finding counter is lost, and it prints "no other worktree claims an overlapping path" directly after listing overlaps [C43] (debt F-13). What actually refuses a commit is the task's own scope, checked at finish and by the pre-push hook.

SurfacePeer boardMesh
CLInone for peers.listmesh identity, mesh doctor (capabilities); mesh status, mesh nodes (local renderings)
HTTPGET /api/v1/peers, POST /api/v1/peers/announceGET /api/v1/mesh, /mesh/nodes, /mesh/identity, /mesh/doctor; POST /api/v1/mesh/register
MCPmajordomus_peers, majordomus_announcemajordomus_mesh, majordomus_mesh_nodes, majordomus_mesh_identity, majordomus_mesh_doctor, majordomus_mesh_register
Cockpitno area (generic capability page only)/cockpit/mesh, from mesh.status and mesh.nodes

17. Walkthroughs

17.1 One capability through every surface: rules.report

StepWhereAutomatic or by hand
input type RulesReportInput, output type, BenchmarkCasessrc/capability/builtin/rules.rsby hand (the types)
capability! with id: "rules.report", mcp("majordomus_rules") plus resource majordomus://rules, get("/api/v1/rules"), cli: ["rules", "report"]builtin/rules.rsby hand, once
JSON SchemasCanonicalSchema::ofautomatic
MCP tool majordomus_rules with readOnlyHintmcp/surface.rsautomatic
GET /api/v1/rules?class=&state=&namespace=&findings_only=http/router.rsautomatic
OpenAPI operationId: rules.report, tag rules, x-majordomus-cli: rules report, examples from caseshttp/openapi.rsautomatic
Swagger UI entry/swaggerautomatic
clap RulesCommand::Report, flags --state --class --namespace --findings --checkcli.rsby hand
runner: flags to input, by_cli, execute; text renderer; --check exits 10commands/rules.rsby hand
example rules-report-jsoncli::EXAMPLES in cli.rsby hand
closure: the claim rules report resolves to a runnable clap commandcapability/closure.rsautomatic check
/cockpit/capabilities/rules.report with a runner formcockpit/pages.rs capabilityautomatic
docs/generated/capabilities.md, docs/generated/modules/rules.md, registry.jsonstage Aautomatic
/registry/capabilities/rules-report/, /docs/cli/rules/report/stage B (scripts/generate-site-data)automatic

To reproduce: bin/majordomus-cli capabilities describe rules.report, curl -s localhost:8742/openapi.json | jq '.paths["/api/v1/rules"].get.operationId'.

17.2 One rule from its file to its evidence: project.conventional-commits

  1. Source. .ai/repo/rules/project/conventional-commits.v2.md, class: blocking, with x-majordomus: tests: [test/cases/274_commit_policy.sh, scripts/ci/commit-policy]. It names tests and no validator, so its mode is gated.
  2. Gate. commit-policy in .ai/repo/ci/gates.yaml, runs: scripts/ci/commit-policy. The judge inside is the executable's commit capability; the gate chooses a range and ratchets debt in .ai/repo/commit-policy-baseline.txt.
  3. Mutation proof. test/cases/274_commit_policy.sh adds a non-conventional commit and asserts that both the command and the gate exit 10, that new debt fails after --write-baseline, and that --strict fails.
  4. Evidence. The ledger holds a local passing run of suite:274_commit_policy. The rule's gate path is unrunnable as evidence, because no runner records a gate verdict, so the rule's state is gated: the mechanism is shown, not a result.
  5. Surfaces. bin/majordomus-cli rules show project.conventional-commits, GET /api/v1/rules/rule?rule=project.conventional-commits, MCP majordomus_rule, and the generic Cockpit page for rules.show. The site's /doctrines/ pages are generated from the doctrine registry (doctrines.json in stage B) and do not show ledger state.

17.3 One doctrine: majordomus.use-case-coverage

It is vendored at .ai/repo/rules/vendor/majordomus/rules/use-case-coverage.v1.md. Its block names validator: use_case_coverage, enforced_by: [doctor, check, finish], policy_key: use_cases_covered, exit_code: 10 and a test (test/cases/94_use_cases.sh). The dispatcher runs mj_validate_use_case_coverage (lib/usecase.sh) whenever one of those commands runs, using the coverage policy in policy.yaml. finish selects doctrines by verification.finish_requires, and this repository's list does not name use_cases_covered, so here the doctrine is enforced at doctor and check but not at finish. The proof graph reports the validator present and the case not run.

17.4 One mesh example

The mesh is implemented and disabled in this repository, so the honest live example is the refusal:

$ curl -s localhost:8742/api/v1/mesh | jq '{active, reason}'
$ curl -s localhost:8742/api/v1/mesh/nodes | jq .count

The first answers active: false with the reason "the mesh declaration is disabled"; the second answers zero nodes. test/cases/130_mesh.sh and apps/majordomus-cli/tests/mesh.rs exercise the mesh in disposable fixture repositories rather than on this one.

17.5 Adding a feature: what actually happens

For a new read-only operation:

  1. Write the input and output types with serde and JsonSchema, plus BenchmarkCases for the input (or declare a benchmark waiver). By hand.
  2. Write one capability! block with its exposure. A new module also needs module! and a line in compose_modules!. By hand.
  3. Make sure no schema title collides with another component name, or OpenAPI generation fails. By hand; nothing warns about it earlier.
  4. If it has a CLI path: add a clap variant, a dispatch arm, a runner with argument mapping, a text renderer and a CommandExamples entry. If it is CLI-only, add a cli::LOCAL entry. By hand, and checked by cli::validate, tests/cli_examples.rs, closure and parity.
  5. A dedicated Cockpit area needs a route arm, a page function and an areas() entry. By hand. The generic page needs nothing.
  6. Special path classes in .ai/repo/ci/gates.yaml name specific files (commit, design, rules). A new builtin file is already covered by the rust class; a new subsystem directory may need a class. By hand, and not documented as a step.
  7. Run scripts/derive and commit the projections. Automatic generation; staging them is by hand.
  8. MCP, HTTP, OpenAPI, Swagger, the generic Cockpit page, benchmark targets, the reference and the registry pages follow. Automatic.

CAPABILITIES.md says a new capability is "one capability! block … Nothing else". That is true only for a capability without a CLI path and without a dedicated page.

17.6 Adding a rule: "every public capability has executable verification"

StepExists today?
write .ai/repo/rules/project/<name>.v1.md with class: blocking and x-majordomus.testsyes: the format, loader and validation exist
have rule-proof-check refuse it if it names no proofyes
a check that joins every capability to a behavioural testno. Nearby pieces: benchmark coverage (every exposed capability has a benchmark target on each transport; timing, not correctness), scripts/ci/command-furnished (public commands have fixtures and cases), scripts/ci/surface-coverage (surfaces have tests, by file presence), use-case coverage (required for commands; MCP tools are advisory, and "named" counts as "executable")
a gate in gates.yaml running that check, and a mutation case proving it failswould be new
the proof graph binding rule to gateautomatic once the gate's runs: names the script
a recorded passing executionlocal recording exists; CI recording does not
a Cockpit or site page showing the resultnot implemented (§11)

18. What is not generated

Generation stops where judgment begins. The following are canonical human input, and the machine derives nothing that could replace them:

  • intent: an issue's acceptance criteria, a milestone's outcome, a task's scope, the objective of a handover;
  • decisions: an ADR's context, decision and consequences, and its acceptance;
  • rules: the statement, the class, and the choice between a validator, a test or a reviewed reason;
  • policy: budgets, the finish requirements, what the mesh may do;
  • prose: this document and every docs/*.md;
  • declarations of taste: navigation groups, design tokens, the Cockpit's areas.

The machine derives what can be recomputed from those: status, waves, proof states, schemas, routes, references, the site, briefings and handover skeletons.

19. Zero registration: the ideal and its exceptions

The ideal is that adding a fact requires registering it nowhere else. It holds for: declarative objects (a file in a sources.yaml class), capability projections to MCP, HTTP, OpenAPI, Swagger and the generic Cockpit page, documents on the site (one index row, which the generator enforces), mesh providers, and graph derivations in the Cockpit sidebar.

The current exceptions are all recorded, and each is checked or ratcheted rather than silent: the clap tree, cli::EXAMPLES, CLI renderers, cli::LOCAL, the Cockpit route table and areas, site/data/nav.toml, gates.yaml path classes, and the shell commands in share/commands.yaml. Appendix F lists them with file references.

20. Why generation matters, and the theses

A repository that runs agents in parallel writes about itself faster than any person reads. If each surface had its own description, every merge would be a chance for them to disagree, and nobody would notice until an agent acted on the wrong one. Generation, a --check run and a ratchet turn that disagreement into a failing command.

The theses, each stated only as strongly as the evidence above allows:

  1. Majordomus is a model native to the repository, and its surfaces are derived from it. This is true for operations and declarative objects. It is partial for the command line (checked, not derived) and the Cockpit (generic pages derived, areas by hand), and it is not yet true for the mutating development semantics that lib/ owns.
  2. The surfaces are projections of one typed reality. HTTP, OpenAPI, Swagger and MCP are projections, and tests show that HTTP and MCP return equal values for sampled capabilities. The CLI and the Cockpit add renderers that hold no facts; a reviewer, not a gate, decides whether a renderer has started computing.
  3. Rules matter when their relation to validators, execution and evidence can be inspected by a machine. It can here: the proof graph derives a state per rule from the rule, the tree, gates.yaml and the ledger. What the graph can show is still limited by what is recorded, and today that means local runs only and no gate verdicts.
  4. The mesh makes distributed work visible. It is not magic orchestration. The peer board and the mesh registry observe; claims are advisory; overlap is reported, not locked; and the mesh is off in this repository.
  5. Generation keeps one repository from accumulating incompatible descriptions of itself. This holds wherever a --check or a fixed-point gate exists. Where prose describes code without a check, it drifts: F-20 to F-24 below are examples found while writing this document.

Appendix A: architecture map

flowchart TD
  subgraph canonical["canonical: tracked, authored"]
    codeN["capability! blocks<br>apps/majordomus-cli/src/capability/builtin"]
    layerN[".ai/** objects<br>rules, ADRs, policy, plan, sessions, use cases"]
    shareN["share/: kinds, schemas, commands, events"]
    proseN["docs/*.md, site/content-src, nav.toml"]
    shellN["lib/*.sh: shell tool semantics"]
  end
  subgraph discovery["discovery"]
    manifestN["manifest and sources.yaml"]
    indexN["index of typed objects<br>fingerprinted"]
  end
  subgraph model["typed models"]
    registryN["CapabilityRegistry<br>builtin plus declarative, frozen"]
    rulesN["rule proof graph"]
    planN["plan model: status, DAG, waves"]
    envN["RepositoryEnvironment"]
    devctxN["devcontext selection"]
  end
  subgraph observed["observed, in process"]
    peersN["peer board"]
    meshN["MeshRegistry"]
    execN["executions"]
  end
  ledgerN["evidence ledger<br>.ai/repo/evidence/ledger.json"]
  gatesN["gates.yaml and scripts/ci"]
  executorN["one executor"]
  subgraph surfaces["projections"]
    mcpS["MCP"]
    httpS["HTTP API"]
    oasS["OpenAPI"]
    swS["Swagger UI"]
    cliS["CLI, checked by closure"]
    cockS["Cockpit"]
    genS["docs/generated"]
    siteS["site data and pages"]
  end
  codeN --> registryN
  shareN --> manifestN
  layerN --> manifestN --> indexN --> registryN
  indexN --> rulesN
  indexN --> planN
  ledgerN --> rulesN
  gatesN --> rulesN
  registryN --> executorN
  rulesN --> executorN
  planN --> executorN
  envN --> executorN
  devctxN --> executorN
  peersN --> executorN
  meshN --> executorN
  execN --> executorN
  executorN --> mcpS
  executorN --> httpS
  executorN --> cockS
  executorN --> cliS
  registryN --> oasS --> swS
  registryN --> genS --> siteS
  proseN --> siteS
  shellN --> layerN

Appendix B: surface map

ConceptCLIMCPAPICockpitDocs
capabilitiesmajordomus-cli capabilities list / describe / projections / validatemajordomus_capabilities, majordomus_capability, majordomus_projections/api/v1/capabilities, /capability, /capabilities/projections/cockpit/capabilitiesdocs/generated/capabilities.md, /registry/
objectsnone (a gap)majordomus_get, majordomus_list, majordomus_search, one resource per object/api/v1/object, /objects, /search/cockpit/objects, /cockpit/objectkind-specific site sections
rules and proofrules report / show / provesmajordomus_rules, majordomus_rule, majordomus_rule_proves/api/v1/rules, /rules/rule, /rules/provesgeneric page onlyDOCTRINE.md, /doctrines/ (no proof state)
evidenceevidence show / claim / proves / recordmajordomus_evidence, majordomus_evidence_claim, majordomus_evidence_test/api/v1/evidence, /evidence/claim, /evidence/test (record is CLI-only: it writes)generic page onlyEVIDENCE.md, /evidence/ (authored claim graph)
planshell majordomus plan …majordomus_plan*, majordomus_plan_transition/api/v1/plan*, POST /plan/transitiongeneric pagesPLANNING.md, /plan/, docs/PLAN_STATUS.md
contextshell context; devcontext compile / explain / policymajordomus_devcontext*, majordomus_continuity/api/v1/devcontext*, /continuity/cockpit/continuityCONTEXT.md, CONTINUITY.md
environmentenv status / explain / banner / export (renderings)majordomus_environment, majordomus_environment_explain/api/v1/environment, /environment/explainoverviewENVIRONMENT.md
worktreesworktree topology / status / inspect / migrate …majordomus_worktrees, …/api/v1/worktrees*/cockpit/worktreesWORKTREES.md
peersnonemajordomus_peers, majordomus_announce/api/v1/peers, /peers/announcenoneMCP.md
meshmesh identity / doctor / status / nodesmajordomus_mesh*/api/v1/mesh*/cockpit/meshMESH.md
gates and completionshell check, finishmajordomus_gates, majordomus_completion/api/v1/gates, /gates/completiongeneric pagesCI.md

To check any row, run bin/majordomus-cli capabilities projections --format json.

Appendix C: generation matrix

"Derived" means produced by a generator or computed on read. A dash means the entity is not on that surface.

EntityAuthoredDiscoveredDerivedCLIAPIMCPCockpitDocs
builtin capabilitycapability! block, typescomposed, not discoveredschemas, route, tool, operation, benchmark targets, referenceonly with CliExposure and a hand-written commandyesyesgeneric pagegenerated reference, /registry/
declarative object.ai/** filesources.yaml classes into the indexresource capability, <kind>.<identity>–/api/v1/objectresource/cockpit/objectper kind
rulerule fileeffective set: vendor plus projectenforcement mode, proof staterules …yesyesgenericDOCTRINE.md, /doctrines/
ADRADR fileindexreference validity, graph edgesshell adras objectresourceobject pageobject resource; no site route of its own
claimdocs/CLAIMS.yaml–proof state against the ledger, claim graphevidence …yesyesgenericdocs/claims/, /guarantees/
evidence run– (recorded)–freshness stateevidence record writesreadreadgeneric–
issue and milestoneYAML under .ai/repo/project/indexstatus, DAG, waves, conflictsshell planplan.*plan.*generic/plan/, PLAN_STATUS.md
session episodehooks and shell commands write itindex (closed records)handover skeleton, briefingshellcontinuity.state, session_domain.*yes/cockpit/continuity/sessions/
compiled context–graph and indexselection, budgetdevcontextyesyes––
environment.envrc adapter, toolchain filesprobes, git, filesthe snapshotenvyesyesoverviewENVIRONMENT.md
worktree–git worktree liststanding, expected pathworktreeyesyes/cockpit/worktreesWORKTREES.md
peeran announcementMCP connectionoverlap–yesyes––
mesh nodethe declarationproviderspresence, trust labelpartialyesyes/cockpit/meshMESH.md
documentdocs/*.md and its index rowthe index (document kind)page front matter, links–as objectresourceobject page/docs/<slug>/
navigationsite/data/nav.toml–navbar and footer–––Cockpit sidebar catalogues are derived; areas are authoredsite
provider bootstrappolicy.yaml, templates–AGENTS.md, CLAUDE.mdshell update–––docs/generated/providers.md

Appendix D: governance and mesh maps

flowchart TD
  pol["policy.yaml<br>finish_requires, budgets, hook wiring"]
  vend["vendored rules<br>.ai/repo/rules/vendor/majordomus"]
  proj["project rules<br>.ai/repo/rules/project"]
  eff["effective rule set<br>additive, no override"]
  disp["doctrine dispatcher<br>lib/doctrine.sh, at doctor, check, finish, watch"]
  gy["gates.yaml<br>gates and path classes"]
  ci["validate.yml<br>planner selects gates by changed paths"]
  rpc["rule-proof-check<br>dangling refused, unproven ratcheted"]
  proofG["rule proof graph<br>ten states"]
  adr["ADRs<br>proposed, accepted by a person"]
  vend --> eff
  proj --> eff
  eff --> disp
  pol --> disp
  eff --> rpc
  gy --> ci
  eff --> proofG
  gy --> proofG
  adr -.-> proj
flowchart TD
  decl["mesh declaration<br>.ai/repo/mesh/majordomus.yaml, enabled false"]
  prov["providers<br>multicast, broadcast, rendezvous"]
  mgr["manager<br>one verification path, signatures, skew, trust label"]
  reg["MeshRegistry<br>presence TTL, retention, bound"]
  caps["mesh capabilities<br>status, nodes, identity, doctor, register"]
  board["peer board<br>per shared server, gathered across checkouts"]
  pcap["peers.list, peers.announce"]
  decl --> prov
  prov --> mgr --> reg --> caps
  board --> pcap

The two maps are separate on purpose. Nothing flows from the mesh registry into the peer board today.

Appendix E: claim audit

The references are to the tree at the commit this document was written on. A range such as :439-482 gives lines. A command in the last column is one you can run.

#ClaimEvidenceVerdict
C1one capability! builds id, schemas, provenance, classified execution and casesapps/majordomus-cli/src/capability/handler.rs:439-482verified
C2schemas come from schemars through CanonicalSchema::ofsrc/capability/schema.rs:102-119verified
C3OpenAPI components share one namespace and a clash is refusedschema.rs:234, :278verified
C4exposure is explicit per surface; the registry refuses duplicates and iterates in ordersrc/capability/model.rs:706, :755, :799, :807; registry.rs:86-170 (duplicate id, MCP name, MCP URI, HTTP route, CLI path)verified
C5MCP tools and resources are computed from exposuressrc/mcp/surface.rs:4, :263, :332verified
C6coordination tools are declared capabilities, and some code knows them by namebuiltin/peers.rs (peers.list, peers.announce); src/mcp/protocol.rs:312; src/mcp/bridge.rsverified
C7HTTP routes resolve by registry; OpenAPI operationId is the idsrc/http/router.rs:774-786; src/http/openapi.rs:81, :110verified
C8the CLI claim is checked against clap in both directionssrc/capability/closure.rs; tests/projections.rs:24; capabilities validateverified
C9CLI-only commands carry a structural reason, checked by a gatesrc/cli/local.rs:43-66, :137; scripts/ci/projection-check; capabilities projections shows unclassified emptyverified
C10Swagger is a shell loading /openapi.json, with assets from unpkgsrc/http/swagger.rs:6, :52, :74verified
C11CLI runners, renderers and examples are hand-writtensrc/cli.rs:1987 (EXAMPLES); src/commands/rules.rs:369 (by_cli)verified
C12Cockpit generic pages and sidebar catalogues are derivedsrc/cockpit/pages.rs:673, :928; src/cockpit/nav.rs:1-5verified
C13Cockpit routes and areas are hand-written; area pages name capability idssrc/cockpit/mod.rs:161-201; nav.rs:93; pages.rs:131, :135, :2580, :2584verified; contradicts CAPABILITIES.md
C14the Cockpit trees may not reference the layer or the executablescripts/development-semantics-check; DEVELOPMENT_RUNTIME.md "Enforcement"verified
C15derive runs stages A to D; no stage reads its own outputscripts/derive:6-21, :72-105verified; the header comment omits stage D
C16the site generator is transactional, hashes declared inputs, and refuses an unindexed docscripts/generate-site-data (INPUTS, .mj-stage, "is in no table" at :1279)verified
C17pre-commit runs doctor, the worktree guard and pages current.githooks/pre-commit; scripts/pages:76, :106, :515verified
C18generation is a fixed point and byte-identical.ai/repo/ci/gates.yaml generation-converges; tests/projections.rs:318, :380verified
C19one canonical order, gatedsrc/order.rs:141; scripts/ci/order-check; .ai/repo/order-baseline.txtverified
C20docs pages are generated from the docs/README.md indexscripts/generate-site-data (index awk; project_doc)verified
C21pages-live is an at-finish gate; pages verify in the workflow is continue-on-error.ai/repo/ci/gates.yaml:381-385; .github/workflows/pages.yml:213-218verified
C22doctor detects and does not repairlib/doctor.sh (read-only header; no --fix in bin/majordomus or lib/)verified
C23ten rule states, the weakest part winssrc/rules/mod.rs:466-492, :1160-1182; DOCTRINE.md "The proof graph"verified
C24the mode is the block's shape; an executable proof winsDOCTRINE.md "Three enforcement modes"; src/rules/mod.rs enforcement_ofverified; the module comment still says "two enforcement modes" (rules/mod.rs:28)
C25rule-proof-check refuses dangling paths and ratchets unproven rulesscripts/ci/rule-proof-check:1-45; gate rule-proof in gates.yamlverified
C26findings by class; Failing sorts before NotRun, so it can be maskedsrc/rules/mod.rs:1000-1023 with :466-492 and :1169-1176verified by reading; not observed in data
C27rule-to-gate binding is inferred from runs:src/rules/mod.rs:876-916verified
C28freshness is derived from outcome, git diff and digestsrc/rules/mod.rs (run state derivation); EVIDENCE.mdverified
C29CI records no evidence; the site evidence page shows the authored claim graphdocs/EVIDENCE.md:296; site/templates/evidence.html:3; .github/workflows/validate.yml has no evidence record stepverified
C30mutating development commands have no capability, and a ratchet records it.ai/repo/development-semantics-baseline.txt (backing:*)verified
C31plan.transition and recover.orphans write the repository and are exposed over HTTP and MCPbuiltin/plan.rs:643-672; builtin/recover.rs:445-465; live openapi.json has POST /api/v1/plan/transitionverified (from the declarations; no write was issued)
C34acceptance is refused only for extracted ADRslib/adr.sh:207-213verified
C40the peer board enforces nothingsrc/peers.rs module documentationverified
C41presence is session-bound; the board is gathered per checkoutsrc/http/mcp.rs:34, :190; builtin/peers.rs:15, :112verified
C42mesh: one registry, providers only hand bytes up, off by declarationsrc/mesh/provider.rs:155; registry.rs:39-45; .ai/repo/mesh/majordomus.yaml:12; live GET /api/v1/mesh gives active: falseverified
C43the check --overlap counter is lost in a pipeline subshelllib/start.sh:111-123, lib/check.sh:283-287, lib/common.sh:21; reproduced: majordomus check --overlap lists overlaps, then prints "no other worktree claims an overlapping path"verified, reproduced
C44devcontext is a capability on CLI, HTTP and MCPbuiltin/devcontext.rs:102; capabilities projectionsverified
C45declared selectors versus intent inference; token budgetsrc/devcontext/model.rs:232, :1092, :1100; select.rs:758verified
C46plan status derived; scope conflicts warned and serialisedlib/project.awk:294; src/plan.rs:554; PLANNING.mdverified
C47handover sections required by policy; derived bodies from ledger and git.ai/repo/policy.yaml:34; lib/derive.sh:255verified
C48a service reads as available on a TCP connectsrc/environment/probe.rs:82verified

Downgraded while writing. Several claims were weakened after checking:

  • "Nothing in the Cockpit names a capability": false (C13).
  • "No kind writes to the repository" (CAPABILITIES.md), and the surfaces' own self-description saying the server "never writes to the repository" (about.rs:40, mcp/protocol.rs:312): contradicted by plan.transition and recover.orphans (C31).
  • "Adding a capability is one block and nothing else": true only without a CLI path or a dedicated Cockpit page (§17.5).
  • "Proof of function reaches Cockpit and docs": it reaches the API, MCP and generic pages as a state, not as a dedicated page (§11).
  • "Cross-surface equality is tested": sampled, not generated (§14).
  • "A gate proves a rule": inferred from runs:, and no gate verdict is recorded (§9, §10).

Appendix F: architecture debt

Each item is phrased so it can become an issue. The categories are: M manually registered, D duplicated, N not derived, E not exposed on every relevant surface, T not tested, V not evidenced, F not fully enforced, P prose that disagrees with code.

IdCat.DebtWhere
F-1MThe clap tree is a second declaration of the CLI; it is checked by closure and parity, not derivedapps/majordomus-cli/src/cli.rs; src/capability/closure.rs; src/quality/parity.rs
F-2MCLI examples are one hand-written constsrc/cli.rs:1987 (EXAMPLES)
F-3MCLI runners map flags to input by hand, and some exit semantics live only in the renderersrc/commands/*.rs, for example commands/rules.rs (--check)
F-4EMany capabilities have no CLI path (peers.list, plan.*, objects.*, health.report, environment.status); no policy says which omissions are deliberatecapabilities projections --format json; DEVELOPMENT_RUNTIME.md gap 9
F-5ENo Cockpit area for rule proof, evidence or the peer board; only the generic capability pagesrc/cockpit/mod.rs:161-201
F-6VCI records no evidence, so every proof state rests on local runs and most rules read not rundocs/EVIDENCE.md:296; .github/workflows/validate.yml; rules report coverage.recorded
F-7VGate verdicts are never recorded; rule-to-gate binding is inferred from runs: by a line scan, and a suite gate is assumed to run every casesrc/rules/mod.rs:876-916; .ai/repo/ci/gates.yaml
F-8FNo gate runs rules report --check, so a rule finding does not fail CI through the proof graphscripts/, .ai/repo/ci/gates.yaml, .github/workflows/
F-9FRuleState orders Failing before NotRun; a blocking rule with one failing and one never-run case reports not run, which is not a findingsrc/rules/mod.rs:466-492, :1169-1176, :1000-1023
F-10NThe website's /evidence/ and guarantees pages render authored claim links and status, not measured proofsite/templates/evidence.html:3; scripts/generate-site-data (claims graph)
F-11MOpenAPI component names share one namespace and collisions are fixed by renaming; no earlier check or checklistsrc/capability/schema.rs:272-288
F-12TCLI JSON equal to the HTTP value is not tested per capability; HTTP equal to MCP is tested for a sampletests/http_serve.rs:202, :254
F-13Fcheck --overlap loses its finding count in a pipeline subshell and prints a contradictory "no other worktree" line; it reads task records only, not the peer board or brancheslib/start.sh:111-123; lib/check.sh:283-287
F-14DTwo writers of generated trees: src/generate.rs and scripts/generate-site-data (registry and CLI site pages are written by shell)src/generate.rs; scripts/generate-site-data; DEVELOPMENT_RUNTIME.md gap 8
F-15DTwo context engines: the shell briefing and devcontextlib/context.sh; src/devcontext/
F-16FThe pre-commit freshness check skips the registry half when the executable is not builtscripts/pages:106-118
F-17DTwo plan engines held equal by a test; plan writes are shell-owned apart from plan.transitionlib/project.awk; src/plan.rs; test/cases/99_plan_capabilities.sh; lib/plan.sh
F-18FEnvironment services read as available on a TCP connect alonesrc/environment/probe.rs:82; src/environment/render.rs
F-19NMutating development semantics (adr, checkpoint, decision, finish, handover, init, migrate, question, rules, session, start, update, usecase) have no capability, so no HTTP, MCP or Cockpit path.ai/repo/development-semantics-baseline.txt
F-20PCAPABILITIES.md says nothing in the Cockpit names a capability; area pages dodocs/CAPABILITIES.md (projection list); src/cockpit/pages.rs
F-21PCAPABILITIES.md says no kind writes to the repository, and about.rs and the MCP instructions say the server never writes; plan.transition and recover.orphans write tracked files and are serveddocs/CAPABILITIES.md (model table); src/about.rs:40; src/mcp/protocol.rs:312; builtin/plan.rs:643-672; builtin/recover.rs:465
F-22PADR 4 and CAPABILITIES.md show an old, short compose_modules! list.ai/repo/adrs/0004-canonical-architecture-and-performance-truth.md; docs/CAPABILITIES.md ("The registry")
F-23PThe rules module comment says "two enforcement modes"; there are three plus the declarative defaultsrc/rules/mod.rs:28
F-24PMesh docs say kind mesh; the code and declaration use mesh-declaration. GITHUB_PAGES_ARCHITECTURE.md calls site/content gitignored; it is committed. The scripts/derive header omits stage D and docs/PAGES_STATUS.mddocs/MESH.md; src/mesh/config.rs:28; docs/GITHUB_PAGES_ARCHITECTURE.md; .gitignore; scripts/derive:6-21
F-25P"Doctrine" means a dispatched rule in DOCTRINE.md and the principle layer on the method pagesdocs/DOCTRINE.md; site/content-src/method/; scripts/lib/method.jq
F-26FADR acceptance as "the person's act" is enforced only for extracted records; an authored file can be edited to acceptedlib/adr.sh:207-213
F-27Fproject.no-counts-in-prose has no mechanical check over docs/*.md; review only.ai/repo/rules/project/no-counts-in-prose.v1.md
F-28FUse-case coverage counts an MCP tool as executable and evidenced when a use case merely names itlib/usecase.sh:805-811
F-29NNo check joins every public capability to a behavioural test and a recorded pass§17.6
F-30ESwagger UI assets come from a CDN; the only surface that does not work offlinesrc/http/swagger.rs:52, :74
F-31MCockpit areas and routes are hand-written, and new area pages name capability idssrc/cockpit/nav.rs:93; src/cockpit/mod.rs:161-201
F-32Mgates.yaml path classes that name specific files must be edited by hand when a subsystem grows; nothing documents it as a step.ai/repo/ci/gates.yaml (classes)
F-33VExecutions are not durable, and the live execution stream and the durable ledger vocabulary are disjointDEVELOPMENT_RUNTIME.md gaps 4 and 7

Searches the titles and descriptions of every page this site publishes.