Skip to content

The Cockpit as a development surface: baseline, archaeology and gap matrix

forensic finding, 2026-09-10: what the Cockpit would need in order to become a development surface — the governance preflight and its two defects, the two-program split, the system run end to end with every command and response recorded, the gap matrix, the drift audit, and the phased plan with the failures that must be fixed first

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

A forensic finding, measured on 2026-09-10 in a linked worktree at feature/development-runtime-model, branched from origin/master at 08e4bb252, against a server this worktree built and started itself. It is phase 01 of the Cockpit IDE / control-plane pack and it implements nothing: it establishes what exists, proves it by running it, and says what the smallest safe next phase is.

The architecture it measures against is ADR 0040 and DEVELOPMENT_RUNTIME.md: one canonical runtime — the capability registry of the Rust executable — and every surface a consumer of it. This document is the evidence behind that decision and the plan that follows from it.

Every claim here was produced by a command, and the command is shown. Where a number appears without a command, it is a defect in this document.

The finding in one paragraph

The Cockpit is a faithful projection of a registry that has almost nothing development-shaped in it. The only capabilities that can mutate anything are executions.start, executions.cancel and peers.announce, and the OpenAPI document the server serves has exactly those three non-GET operations to match. Every command of the development lifecycle lives in a second program, bin/majordomus, and every one of its nodes in the command graph is withheld from every machine surface. So the Cockpit cannot be made an IDE by adding pages: there is nothing behind the pages to call. The first phase of work is not UI, it is giving the runtime a mutating development surface for the Cockpit to consume.

Three further defects are load-bearing and were found by running the system rather than reading it: majordomus run executes in its own process and is invisible to the running server, so the execution plane the Cockpit shows and the execution plane a person uses are disjoint; a server serving code that no longer exists on disk reports itself ready; and /cockpit/worktrees takes 18.3 s where every other page takes 1–4 ms.

1. Governance preflight

The pack requires the repository's own mechanisms to be exercised, and any that are broken to be recorded rather than routed around.

StepCommandResult
contractread README.md, AGENTS.md, CLAUDE.md, .ai/README.mdok — AGENTS.md carries its generated stamp from .ai/repo/policy.yaml
effective rulesmajordomus rules list --jsonok — the vendored baseline plus this repository's own resolve in one order; this branch adds one
contextmajordomus contextok — well inside its line budget, working_tree clean, no active task at that moment
scoped contextmajordomus context resolve docs/DEVELOPMENT_RUNTIME.mdok — resolves to ai.layer alone; docs/ is outside .ai/ and gets the root chain only
worktree topologymajordomus-cli worktreeok — canonical, container …-wt, trunk master
collision checkscripts/collision-check --new <paths>ok — nothing else claims docs/DEVELOPMENT_RUNTIME.md, docs/COCKPIT_IDE_AUDIT.md, the ADR or the rule
task lifecyclemajordomus start "…" --scope …ok — t-20260910222833-6506, profile implementation
overlapreported by start, majordomus check --overlapfour other branches claim paths this task claims: readme-integrated-surfaces (docs, site), canonical-order (docs, site, scripts, .ai/repo/rules/project, .ai/repo/ci), doctor-passes-from-a-release (docs, site), the-subset-is-a-subset (docs)
checkmajordomus checkok — findings reported, none failing
sessionmajordomus session statusnone open in this worktree
handovermajordomus handover --resolvenone — absence, not a stale match
peer boardnot announced by this workerthe fleet's row covers it; §3 records what the board could not have told me

Two preflight defects, both real, neither routed around:

P1 — MAJORDOMUS_SHARE breaks the push hook, not only the commit hook. The variable is exported into every shell in this repository and points at the primary checkout's share/. The commit succeeded under env -u MAJORDOMUS_SHARE; the git push in the same script did not carry it and the pre-push hook refused:

majordomus: MAJORDOMUS_SHARE names ~/dev/prismatic-majordomus/share, the
distribution of ~/dev/prismatic-majordomus — another worktree of this
repository, not this one … Run with: env -u MAJORDOMUS_SHARE
error: failed to push some refs to 'github.com:korczis/prismatic-majordomus.git'

The diagnostic is excellent and the remedy is one flag, but the trap is that the variable is inherited, so every hook is a coin flip depending on how the command was invoked. The fix belongs with the hooks, not with each worker remembering.

P2 — the audit's own base is already historical. origin/master was 08e4bb252 when this worktree was created and 06fa25891 by the time the audit finished, roughly forty minutes later. Any plan this document proposes must assume master moves during the phase that implements it.

2. Current canonical architecture, subject by subject

The full per-concept inventory — canonical owner, storage, schema, discovery, consumers, mutation path, events, tests, docs and drift — is in DEVELOPMENT_RUNTIME.md and is not repeated here. This section records only what that inventory does not: the entry and projection subjects the pack names, and the two-program split that explains most of the rest.

The split that explains the rest

bin/majordomus (shell, lib/**) owns the development lifecycle — start, check, finish, session, context, checkpoint, handover, evidence, decision, question, plan, adr, usecase, rules, doctrine, knowledge, history, search. bin/majordomus-cli (Rust) owns the capability registry and every projection of it. Each program rejects the other's commands by name:

$ ./bin/majordomus worktree create feature/development-runtime-model
majordomus: unknown command: worktree
majordomus: worktree is a command of the Rust executable, not of this tool; run: bin/majordomus-cli worktree

The diagnostic is precise, which is the point: the split is deliberate and documented, and it is still the reason the Cockpit cannot reach the lifecycle. A surface served by the Rust executable can only project what the Rust executable knows.

3. Running the system

Started by the repository's supported path, in this worktree, against this worktree's own build. The primary checkout's server was left alone.

Startup, reuse and the fleet

$ ./bin/majordomus-cli serve status          # before
standing   absent
desired    127.0.0.1:8741  version 0.5.0
repository 4ca83e493da37139e9b0324377ab2c20  (this is a linked worktree)

$ ./bin/majordomus-cli serve ensure
ready http://127.0.0.1:62354 pid 44871 version 0.5.0 (started by this call)
                                                          0.45s total

$ ./bin/majordomus-cli serve ensure          # again
ready http://127.0.0.1:62354 pid 44871 version 0.5.0
                                                          0.03s total

Start is idempotent and reuse costs 30 ms. Two structural observations from the same output:

  • One server per checkout, not per repository. desired is 127.0.0.1:8741 for every checkout, so the first one wins the port and each linked worktree falls back to an ephemeral one (this one got 62354). The repository id is shared; the server is not. The doc that says "one shared server per repository" is describing the primary checkout.
  • The fleet is mostly dead. serve status enumerates every checkout it has ever seen. Almost all are absent, the ones still serving are mostly outdated, and the list includes long-gone scratch trees under /private/tmp/classify-* and /private/tmp/force-merge-*. Nothing reaps the register.

The stale-server class, measured from both sides

$ ./bin/majordomus-cli serve status
  outdated  ~/dev/prismatic-majordomus (master)  http://127.0.0.1:8741  peers 5
            the executable it was started from … has been replaced since
            (it is serving code that is no longer on disk)
  outdated  …/feature/swagger-offline           http://127.0.0.1:60204  peers 0
  outdated  …/feature/the-cockpit-shows-the-board http://127.0.0.1:53226  peers 0
  outdated  …/fix/stale-runtime-is-loud          http://127.0.0.1:8799   peers 0
  outdated  …/fix/worktrees-page-is-fast         http://127.0.0.1:8792   peers 0
            the server published no version, so it is older than this executable (0.5.0)

$ curl -s http://127.0.0.1:8741/api/v1/server        # the outdated server, asked directly
HTTP 200   standing: ready

The staleness is only visible from outside. The lease reader in a fresh executable can tell that the running binary was replaced; the running server reports itself ready and its root document carries no HEAD to check. Peers were attached to that server throughout this audit, every one of them reading a projection of code that no longer exists. This is the single most dangerous class in the audit, because every measurement anyone takes through that server is quietly wrong — including, as the coordinator of this fleet found, measurements of capability exposure.

Surfaces

Every request below was made against http://127.0.0.1:62354.

RequestStatusTypeSizeTime
GET /200application/json2 345 B0.6 ms
GET / with Accept: text/html200text/html
GET /cockpit200text/html25 913 B21.6 ms
GET /cockpit/capabilities200text/html59 528 B1.8 ms
GET /cockpit/executions200text/html17 599 B1.4 ms
GET /cockpit/worktrees200text/html78 098 B18 291.9 ms
GET /cockpit/graphs/composed200text/html1 192 144 B39.3 ms
GET /openapi.json200application/json783 220 B13.6 ms
GET /swagger200text/html6 382 B0.5 ms
GET /docs/503application/json251 B0.4 ms
GET /api/v1/capabilities200application/json1 247 611 B7.8 ms
GET /api/v1/health200application/json2 737 B0.5 ms
GET /api/v1/live, /api/v1/ready200application/json
GET /api/v1/peers, /api/v1/executions, /api/v1/plan/status200application/json
GET /api/v1/does-not-exist404application/json138 B0.4 ms

Content negotiation on / works. The 404 body is a model of what an error should say:

{"error":{"code":"not_found","message":"no route GET /api/v1/does-not-exist; the routes are listed at /openapi.json"}}

Two findings: /docs/ is 503 in any checkout that has not built the site, because the surface is served from target/web/docs, which is a build output. And the health probes are at /api/v1/live and /api/v1/ready, not under /api/v1/health/…; health.live and health.ready are the only two built-in capabilities with no MCP exposure, which the OpenAPI document and the registry agree on.

The canonical API, measured through its own document

$ python3 -c "…json.load(open('openapi.json'))…"
openapi 3.1.0 | info.version 0.5.0
non-GET operations: [('/api/v1/executions/cancel','post'),
                     ('/api/v1/executions/start','post'),
                     ('/api/v1/peers/announce','post')]

One operation per path, and the only ones that mutate are the three the registry declares as commands. The OpenAPI document is a faithful projection of the registry, and what it faithfully projects is a runtime with no development mutations in it.

MCP

$ POST /mcp initialize
protocol 2025-06-18 | server {'name':'majordomus','version':'0.5.0'} | caps ['resources','tools']
Mcp-Session-Id: 44871-1-15feeba0
$ POST /mcp notifications/initialized          → HTTP 202
$ POST /mcp tools/list                          → every tool the registry exposes
$ POST /mcp resources/list                      → every resource, in one page, no cursor
$ POST /mcp resources/read majordomus://adr/adr-0040
read ok | text/markdown | "--- schema: adr/v1 id: adr-0040 …"
$ POST /mcp tools/call majordomus_plan_status   → isError false, a full status document
$ POST /mcp tools/call majordomus_plan_done     → {"code":-32602,"message":"unknown tool: majordomus_plan_done"}

The MCP surface is complete and correct over what the registry holds — including this branch's own new ADR, served as a resource minutes after it was written. The last line is the architecture in one error message: there is no tool for a plan transition, because there is no capability for one.

Peers

The board is connection-scoped and was proved so by accident: the MCP probe above appeared as p1 / phase01-probe / http / attached: true, and once the probe's connection closed the count returned to 0. An announcement belongs to a connection, exactly as AGENTS.md warns; a worker that reconnects keeps its work and loses its place.

What the Cockpit may already change

The Cockpit is not read-only, and the shape of its write path is the invariant working rather than a violation. It carries exactly three actions, and each one is a capability of kind command invoked over its own declared route: run a capability as an execution and cancel one (share/cockpit/executions.js), and the generic runner form, which is generated from a capability's input schema and can therefore drive any command capability — today only peers.announce, at /cockpit/capabilities/peers.announce.

No page carries a hand-written <form method="post">; the buttons declare data-mj-start, data-mj-cancel-route and data-mj-effect, and the browser reads the route from the page rather than constructing it. cockpit/mod.rs refuses every non-GET Cockpit route outright, and a cross-origin state change is refused and covered by a test.

So the Cockpit's mutation surface is exactly the runtime's mutation surface, which is the rule this audit's ADR states. The problem is not the Cockpit's write path; it is that the runtime's mutation surface contains no development semantics for it to reach.

Executions — the disjoint-plane defect

$ curl /api/v1/executions                     → 0 count / 0 remembered
$ ./bin/majordomus-cli run objects.verify &   # while polling the server every second
  t+1s server sees: 0 count, 0 active, 0 remembered
  (run finished at t+1s)
run exit=0                                    → "OK read — 976 current, 0 drifted"
$ curl /api/v1/executions                     → 0 count / 0 remembered
$ curl /api/v1/peers                          → 0

An earlier run produced a named, succeeded execution — x-20260910T222800Z-2adbdce0 health.report — and the server never saw that one either.

majordomus run does not use the running server. It builds a registry in its own process, executes there, streams to its own terminal and exits. The server's execution store is a different plane in a different process. The consequences for an IDE are exact: /cockpit/executions can only ever show work started through that server by executions.start; everything a developer actually runs is invisible to it; and docs/EXECUTIONS.md's live channel, which works — GET /events returns 101 Switching Protocols and streams frames — is a window onto a plane almost nothing writes to.

Combined with the non-durability already recorded (an execution lives in the process that accepted it), the execution history an IDE needs does not exist anywhere today.

Registry, generation and health

$ ./bin/majordomus-cli capabilities validate     → validate: 0 failure(s)
$ ./bin/majordomus-cli generate --check          → generate --check: in sync
$ curl /api/v1/health                            → status "warn", 7 ok / 1 warn
$ ./bin/majordomus doctor        (via the pre-commit hook)
                                                 → doctor: 0 failure(s)
                                                   WARN budget doctor — 51055 ms, budget 3000 ms
                                                   WARN clone unpushed — 48 branches

The generation chain is honest: it refused this branch's ADR twice before accepting it, once for an unsupported YAML construct and once for the wrong heading level, and both refusals named the file and the reason. doctor reports zero failures and takes 51–74 seconds against a 3-second budget — seventeen to twenty-five times over, every commit.

4. Gap matrix

One status per capability, from the pack's vocabulary: canonical+implemented+verified, implemented but duplicated, documented only, planned, partially implemented, broken, absent. "Verified" requires a test that exercises it, named in the row. "Planned" requires a plan object; a search of .ai/repo/project/issues/*.yaml and milestones/*.yaml for the IDE keywords in this matrix returned nothing for palette, editor, diff, terminal, retries or CI status, so no row is planned — the IDE work is not in the plan at all. The only IDE-adjacent plan objects are I0927 (the Cockpit shows the deployment) and the capability-graph milestone's Cockpit issues, all already shipped.

Development

capabilitystatusevidence
capability explorercanonical+implemented+verifiedcapabilities.list/describe/projections; /cockpit/capabilities; tests/cockpit.rs::a_capability_the_repository_adds_reaches_every_cockpit_surface
command palettecanonical+implemented+verifiedshare/cockpit/palette.js; gate cockpit-probe checks the palette family
generic execution runnercanonical+implemented+verifiedpages.rs::runner_form from the input schema; tests/cockpit.rs::the_runner_form_is_generated_from_the_input_schema
streaming outputcanonical+implemented+verifiedexecutions.events + WebSocket; tests/executions.rs; test/cases/100_execution_plane.sh
terminal / REPLabsentno PTY or shell execution anywhere in src/; palette.js says in terms "the palette navigates and the terminal executes"
source / file navigatorpartially implementedobjects.list, directories.list — the .ai/** layer only; no src/** tree
source viewingpartially implementedobject page renders an indexed file body as text; no code files, no highlighting, no line anchors
safe editing workflowabsentthe Cockpit refuses every non-GET route (cockpit/mod.rs); no write path, by decision (COCKPIT.md)
diff viewingabsentdiff appears only in changelog commit ranges and worktree fingerprints; no capability, no surface
test executionpartially implementedrecipes and cases exist and commands.list projects them read-only; runnable only from a terminal
formatting / lint / checkpartially implementedoffered as text through the workflow catalogue; never executed by the runtime
build / release / deploypartially implementeddistribution.build, deploy.check/list/get describe it; the doing is shell and CI
task / workflow launchbroken for a surfacethe transitions exist only in lib/plan.sh and lib/start.sh; unreachable from MCP, HTTP and the Cockpit
reusable workflow formscanonical+implemented+verifiedschema-generated controls; the declared benchmark cases load into the runner
provider / model interactionpartially implementedproduct.providers projects provider config; no model client exists in src/

Planning and SCM

capabilitystatusevidence
repo statecanonical+implemented+verifiedrepository.info; the Cockpit overview; tests/repository_discovery.rs
branchescanonical+implemented+verifiedthrough worktree.topology; tests/worktree.rs
worktreescanonical+implemented+verified, and slowworktree.*; /cockpit/worktrees measured at 18.3 s (§3)
commitspartially implementedconventional-commit parsing and trace.commit; no commit browsing surface
diffsabsent
PRspartially implementedtrace derives issue↔PR edges; scripts/github-sync; no capability, no surface
issuesimplemented but duplicatedread plan.*; write lib/plan.sh + lib/project.awk; no Cockpit page at all
milestonesimplemented but duplicatedsame split; not in the Cockpit
issue↔milestone↔code linkagecanonical+implemented+verifiedtrace.issue/commit/report; test/cases/98_traceability.sh; registry and CLI only
release / changelog / versioncanonical+implemented+verifiedrelease.changelog, release.version; no Cockpit page

AI and runtime collaboration

capabilitystatusevidence
peerscanonical+implemented+verifiedpeers.list, peers.announce; tests/mcp_shared.rs; no Cockpit area — reachable only through the generic runner
claims / scopesimplemented but duplicatedRust repository.scope* classifies; lib/check.sh --overlap decides containment
collisionspartially implementedscripts/collision-check and check --overlap; no capability, no surface
current task / mandatepartially implementedcontinuity.state reads it; the write half is lib/start.sh only
session contextpartially implementedlib/session_context.sh only — no Rust owner and no capability
handoverpartially implementedlib/handover.sh; continuity.state surfaces blockers only
workflow statecanonical+implemented+verifiedthe execution state machine; tests/executions.rs
model / provider surfacespartially implementedconfig projection only
executionscanonical+implemented+verified, and disjointexecutions.*; in-process only, and majordomus run never reaches the server (§3)
cancellationcanonical+implemented+verifiedexecutions.cancel; a non-cancellable capability says so
retriesabsent — explicit non-goalexecution/mod.rs: "no durable queue, no retry and no scheduler"
live events / logscanonical+implemented+verifiedWebSocket handshake and frames observed in §3; disjoint from the durable ledger

Governance

capabilitystatusevidence
effective rulespartially implementedrules as objects and a rules graph; precedence resolved only in lib/; COCKPIT.md states "no rule-precedence resolution" as a decision
doctrinescanonical+implemented+verifiedlib/doctrine.sh; test/cases/17,18; no project rule is tool-enforced, by doctrine
policiespartially implementedtyped and consumed; hand-edited; no capability, no surface
ADRsimplemented but duplicatedlib/adr.sh and lib/decision.sh are two names for one concept, and release/changelog.rs still references .ai/repo/decisions/; number allocation unguarded
context resolutionimplemented but duplicateddirectories.list in Rust and lib/context.sh in shell answer one question; the compiled, budgeted context has no capability
provenancecanonical+implemented+verifiedcapability provenance and knowledge-edge provenance, both real
governance preflightpartially implementedcheck and worktree guard; the word appears nowhere in src/; nothing in the Cockpit
violations and remediationcanonical+implemented+verifiedquality.report findings carry a remedy; tests/cli_validate_violations.rs — scoped to Rust API and CLI docs only
quality gatescanonical+implemented+verified.ai/repo/ci/gates.yaml and scripts/ci-plan --check; no capability and no view of gate state
"done" obligationsimplemented but duplicatedobligations.closure reads the contract; lib/finish.sh decides it

Observability

capabilitystatusevidence
server healthcanonical+implemented+verifiedhealth.report and its check set; /cockpit/health; tests/health_server.rs
process / runtime statecanonical+implemented+verifiedserver.status; tests/server_status.rson no Cockpit page
perf counterscanonical+implemented+verifiedperf.counters; /cockpit/activity — which is not in the sidebar
phase timingscanonical+implemented+verifiedtests/hot_path.rs; a second timing system exists shell-side
cache behaviourcanonical+implemented+verifiedcache policy and counters; a separate shell cache exists
request / execution historybrokenin-process only, and the durable half is a different model (majordomus history); nothing survives a restart
diagnosticscanonical+implemented+verifiedthe health check set; doctor is a different subject by decision
CI statusabsentgates and their plan are canonical; nothing reads run state on any surface
deploy statepartially implementeddeploy.* reads the declared deployment; live state is not there
docs / site freshnesspartially implementeda CI answer, not a Cockpit one
generated artifact driftcanonical+implemented+verifiedartifacts.list, generate --check, the generated-registry health check; two writers of docs/generated/ remain

Docs and knowledge

capabilitystatusevidence
docspartially implementedbuilt and mounted at /docs503 in a checkout that has not built the site (§3); not a Cockpit area
generated referencecanonical+implemented+verifiedthe generated tree; tests/generated_documents.rs; byte-identical twice
API referencecanonical+implemented+verified/cockpit/api and the generated OpenAPI
Swaggercanonical+implemented+verified/swagger served as infrastructure; not a capability, by declaration
knowledgepartially implementedthe compiler is shell-only, no capability
ADR graph, rules graph, use-case graphcanonical+implemented+verifiedgraph.get; complete before any JavaScript loads
searchable project knowledgeimplemented but duplicatedobjects.search and /cockpit/search against majordomus search in shell
links into GH Pagesabsentthe Cockpit never links to the published site

5. Drift audit

The repository reconciles most of its mirrors, and that is the finding as much as the drift is: the CLI against the registry, every generated artifact against its source, the plan's two status engines against each other by byte equality, the dispatch table against the command registry, providers against templates, the version against its two writers. docs/HARDCODING_LEDGER.yaml is the honest register of what remains. What follows is only what is not reconciled, by the classes the pack names.

Two classes came back empty, and the searches are recorded so the negative is checkable: handwritten OpenAPI paths — none (the document is built from the registry; paths, operationId and tags have no literal sites), and workflow arrays — none (no file enumerates just recipes; the gate command-graph refuses a hand-written bridge).

classfindingcanonical ownerreconciled?
route arrayscockpit/nav.rs::areas() is a literal list, and it is already behind the dispatcher: Executions and Quality are declared in the Area enum, rendered by pages and routed, but absent from areas()cockpit/mod.rs route dispatchno — and three surfaces read the stale list: the sidebar, the product model's area validation, and the site's cockpit-areas dataset
route arrayspage URLs and API endpoints written as literals throughout cockpit/pages.rs, including a hand-written mirror of the navigation on the 404 pagenav.rs and the registry's HTTP exposureno
route arraysdocs/COCKPIT.md's route table is missing commands, continuity, directories, artifacts and qualitycockpit/mod.rsno — the public description of the product is wrong
command cataloguesbin/majordomus's usage() repeats every command's one-line summary, and scripts/generate-site-data publishes that copy as site datashare/commands.yamlmembership only (test/cases/30_command_registry.sh); the summary text is unchecked. This is the open ledger row command-surface-has-no-owner
duplicated status enumsshare/cockpit/executions.js::isFinalState decides execution finality in the browserexecution/model.rs::is_finalno — a new terminal state means a page that polls forever with every gate green
duplicated status enumsshare/cockpit/socket.js hardcodes the stream control messages although the server publishes the protocolexecutions.protocolno
duplicated schemasthe generated-artifact contracts under share/schemas/generated/ are hand-written for artifacts whose shape is a Rust structthe structone direction only: an artifact cannot violate the schema, but the schema can silently lag the type
provider arrayslib/capture.sh encodes one provider's settings file, hook names and hook paths as positional stringsshare/providers.yaml, which has no field for hooksno — the fact has no owner, so providers-check cannot see it
frontend-only logicpalette.js builds its API calls and page URLs from hardcoded paths while every neighbouring module reads them from data-mj-* attributesthe page's own datano — the one component whose purpose is "reach everything" is the one that will miss a moved mount
frontend-only logicworktrees.js filters which topology fields "matter" for a reloada server-side digestno
manual endpoint liststhe loopback address is compiled into every published curl examplethe CLI's default port constantno
docs / nav inventoriesthe site's curated nav plus a literal tile-route list in site-checkthe generated collectionsno — a new collection ships unreachable and unchecked; both are open ledger rows
hardcoded countsincluding this branch's own first draft, which stated a project-rule count that its own new rule invalidated in the same committhe thing being countedno decider exists: project.no-counts-in-prose says in terms that a reviewer decides it

The last row is the one worth dwelling on. The rule is blocking and has no enforcement, and the document that introduced a rule about canonical semantics broke it within one commit. That is the class in miniature: a mirror is created by someone who knows the rule, while writing about the rule.

6. The plan

Root causes, ranked

R1 — the lifecycle and the registry are in two programs, and only one of them has surfaces. Everything below follows from this. It is not an accident and it is documented; it is simply incompatible with the Cockpit becoming a development surface.

R2 — the runtime has no mutating development capability. CapabilityKind::Command is used by the execution and peer planes and nowhere else, so there is no typed input schema for any development transition on any machine surface.

R3 — the command graph sees the shell tool only at its top level. Every tool node is a group, so the exposure policy has nothing to judge and the completion engine nothing to offer, and the withheld reason for each is a property of the group rather than of the operation.

R4 — the lifecycle commands are interactive. Most tool nodes are withheld because they ask the person something. Interactivity is not something a surface can work around.

R5 — there is no development history anywhere. The durable tier is a local ledger with its own vocabulary; the live tier is per-process and forgets on exit; and majordomus run writes to neither the server nor, for its own execution, anything durable.

R6 — the UI-adjacent lists are the ones without keepers. The repository reconciles its mirrors well, except at the boundary this pack wants to expand: navigation areas, the Cockpit's documented route table, and semantics decided in the browser.

The target flow for one development mutation

Cockpit form / MCP tool / HTTP POST / CLI
        │  typed input, validated against the capability's own schema

capability!  kind: command    ← the only place the transition is decided

        ├─▶ the object in its canonical storage   (.ai/repo/project/issues/<id>.yaml)
        ├─▶ a ledger event whose name share/events.yaml declares   (durable record)
        └─▶ the execution stream                                   (live view)

Nothing in that diagram is new infrastructure. Every box exists; the arrows from the first box to the rest are what is missing.

Dependency graph

A. one mutating development capability, end to end

        ├──▶ B. events: a mutating capability appends to the ledger
        │            │
        │            └──▶ E. Cockpit development pages (issues, sessions, peers)
        ├──▶ C. the lifecycle capabilities (task, session, checkpoint, handover, finish)
        └──▶ D. compiled context as a capability
F. navigation and route derivation ──▶ E     (independent of A–D; do it any time)

Phases

Phase A — one vertical slice, and nothing else. Take the plan transitions (start/verify/done) and declare them as one capability! of kind command in the plan module, with a typed input, benchmark cases and a registered ledger event. Point lib/plan.sh at it, the way lib/context.sh and lib/capture.sh already call the Rust binary. Remove backing:plan from .ai/repo/development-semantics-baseline.txt.

Acceptance: the MCP tool exists and tools/call moves an issue; POST /api/v1/plan/… appears in the generated OpenAPI without anyone editing it; the Cockpit's existing generic runner can drive it with no Cockpit edit; lib/plan.sh no longer writes the YAML itself; test/cases/99_plan_capabilities.sh still holds the two engines to byte equality; the gate development-semantics reports one fewer entry and refuses the baseline's return.

This phase is the whole architecture in miniature. If it lands cleanly, every later phase is the same shape; if it does not, nothing later would have worked either.

Phase B — one event model. A capability of kind command appends a ledger event whose name share/events.yaml declares, and publishes the same change to the execution stream. Give the ledger a read capability so the Cockpit can show history at all.

Phase C — the lifecycle. task start/checkpoint/finish, session open/close, handover, evidence, in the pattern Phase A established, each one non-interactive so the exposure policy stops withholding it.

Phase D — compiled context as a capability. Owned by the parallel context-compiler work, not by this phase.

Phase E — Cockpit development pages. Issues, milestones, sessions, peers, task. Only after A–C, and only as projections: a page that computes a status is the defect this whole document exists to prevent.

Phase F — derive the navigation. Reconcile areas() against the Area enum and the dispatcher, and derive docs/COCKPIT.md's route table or check it. Independent of the rest; cheap; prevents the next unlinked page.

Non-goals

A terminal or PTY on any surface. Editing repository source in the browser. Retries and a scheduler — the execution plane states in its own module documentation that it has none, by decision. A second datastore. A second project model. A wholesale port of lib/*.sh before anything else may start. A new noun for anything the repository already names.

Risks

riskmitigation
master moves during the phaseit moved during this audit; rebase per landing, and prefer small branches
parallel workers own overlapping pathsfour branches already claim paths this task claims; check --overlap names them, and the peer board does not
a second name for an existing concept lands in parallelone has: a devtask module, which documents itself as a join over plan and trace rather than a second model and holds an equality assertion against plan.issues. It is additive and should stay, but its readiness vocabulary is a second status vocabulary derived from the canonical one — Phase A should converge the naming rather than grow a third
measurements taken through a stale serverfour of the five servers running during this audit were outdated; always measure from a freshly built executable
a loaded machine turns slowness into false failuresobserved at load 109 on 18 cores; do not read a timing failure as a defect without re-running quiet

Files likely to change

apps/majordomus-cli/src/capability/builtin/plan.rs, src/plan.rs, share/events.yaml, lib/plan.sh, .ai/repo/development-semantics-baseline.txt, and later src/capability/builtin/{continuity,obligations}.rs, lib/{session,checkpoint,handover, evidence,finish}.sh, src/cockpit/{nav,pages}.rs, docs/COCKPIT.md.

Generated files that must never be hand-edited

docs/generated/**, site/data/**, site/content/**, share/allow/**, share/sections/**, AGENTS.md, CLAUDE.md, .bb/AGENTS.md, share/design/*.css, apps/majordomus-cli/src/web/tokens.css, apps/majordomus-cli/src/design/tokens.yaml, deploy/Dockerfile, fly.toml, docs/SITE_CLAIMS.md, docs/PLAN_STATUS.md, docs/PAGES_STATUS.md. Each is written by majordomus generate or scripts/derive and checked against a fresh generation; an edit is a reported failure, not a silent divergence.

Failures to fix before any IDE expansion

#failurewhy it blocks
F1a server whose executable was replaced reports itself ready; the staleness is visible only to an outside readerevery measurement taken through it is quietly wrong, including measurements of what the IDE lacks
F2majordomus run never reaches the running serverthere is no execution history for any surface to show, and the one page that shows executions shows a plane nobody writes to
F3/cockpit/worktrees at 18.3 s and /cockpit/quality at 4.6 s against 1–4 ms elsewherean IDE is used continuously; these are already the slowest thing a person meets
F4/docs/ is 503 until the site is builta developer entering a fresh worktree meets a broken documentation surface
F5routed Cockpit pages absent from the navigationnew pages are invisible by default, and the probe crawls out of the navigation, so nothing catches it
F6doctor runs 51–74 s against a 3 s budget on every committhe lifecycle's own cost; it will be paid on every IDE-driven action
F7MAJORDOMUS_SHARE is inherited into hooks and breaks them depending on invocationit broke this audit's own push

The smallest safe next phase

Phase A, and only the done transition of it: one capability! of kind command that moves one issue, with its ledger event and its use case. It touches one module, one shell file and one baseline line; it is provable end to end through four surfaces in a single sitting; and it either establishes the pattern for everything else or shows immediately that the pattern is wrong.

How this was measured

# the supported start path, reuse, and the fleet
bin/majordomus-cli serve status
bin/majordomus-cli serve ensure

# every surface (B is the address `serve ensure` printed)
curl -s -o /dev/null -w '%{http_code} %{content_type} %{size_download} %{time_total}\n' "$B/<path>"
curl -s -H 'Accept: text/html' "$B/"

# the canonical API document, counted rather than read
python3 -c "import json;d=json.load(open('openapi.json'));print(len(d['paths']))"

# MCP over HTTP, with the session id the initialize response returns
curl -X POST "$B/mcp" -H 'Content-Type: application/json' \
     -H 'Accept: application/json, text/event-stream' \
     -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{…}}'

# the live channel, bounded
curl -s -i -N --http1.1 --max-time 4 -H 'Connection: Upgrade' -H 'Upgrade: websocket' \
     -H 'Sec-WebSocket-Version: 13' -H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' "$B/events"

# whether `run` reaches the server: poll the server while a run is in flight
bin/majordomus-cli run objects.verify & while; do curl -s "$B/api/v1/executions"; done

# the registry, the generation chain and the lifecycle
bin/majordomus-cli capabilities validate
bin/majordomus-cli generate --check
bin/majordomus start "" --scope   ;   bin/majordomus check

Counts in this repository go stale. Measure rather than trust a number written here, including these.