Skip to content

The development runtime

the development runtime: which program owns the semantics of the plan, tasks, sessions, context, executions, peers, evidence and completion, the measured inventory of every one of them, the storage each is decided to keep, the two event tiers, the derivation chain anything exposed must follow, what each surface may and may not decide, and the ranked gaps between that boundary and the code

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

What owns the semantics of software development in this repository, where each one lives, which surface may decide what, and how far the code is from that boundary today.

The companion documents are DYNAMICITY.md, which decides where a fact lives, and CAPABILITIES.md, which decides how an operation is declared. This document is the third of that set: it decides where a semantic lives. The decision is ADR 0040; the rule is project.development-semantics-are-canonical@1.

Every count in this document was measured. The commands are in How this was measured; none of them is copied from prose, and a statement about behaviour that is not yet implemented is phrased as a target.

The invariant

The canonical development runtime is the capability registry of the Rust executable. Development semantics are capabilities of it. The Cockpit does not execute development semantics; it requests operations from the canonical runtime and subscribes to canonical state and events. The same sentence holds with "the CLI", "the HTTP API", "OpenAPI" or "MCP" in place of "the Cockpit".

A development semantic is any rule, derivation or transition over the plan, the active task, compiled context, a session, an execution, a peer, an event, evidence, an obligation or completion. "Is this issue ready?", "what does this task still owe?", "may this session close?", "which wave does this issue belong to?" — each has exactly one correct answer, and the runtime is where that answer is computed.

The measurement this rests on

Two executables serve this repository, and the line between them is not a layering.

bin/majordomus is a shell tool implemented under lib/. Its own help groups its commands as TASK, CONTEXT, MEMORY, RULES, PLAN and SYSTEM: start, check, finish, session, checkpoint, evidence, handover, decision, question, plan, adr, usecase. That list is the development lifecycle, and lib/ is the only writer of what it produces — lib/plan.sh:514-515 is the one place an issue or milestone YAML is written.

bin/majordomus-cli is the Rust executable. It owns the capability registry and every projection of it: MCP, HTTP, OpenAPI, Swagger, the Cockpit, the command line, the generated reference. Its help states the split in the negative: "The task lifecycle (init, start, check, finish, doctor, ...) is the shell tool bin/majordomus in the same repository; this executable does not implement those commands."

These are the measurements that make the consequence exact. Each states what is true, not how many; the command that reproduces the tally is in How this was measured.

MeasurementWhat it shows
the registrybuilt-in operations, plus one resource per object of the layer; capabilities list prints both tallies in its summary
built-in capabilities that can mutate anythingonly executions.start, executions.cancel and peers.announce. Every other capability of the runtime is a query or a resource
the command graphthree origins — executable, workflow and tool
tool-origin commands reaching any machine surfacenone. Every one of them carries a projections.withheld reason
the shape of the tool half of the graphevery tool node is a group at the first level, so majordomus plan done <id> is not a node at all; the executable half nests to its subcommands
durable executionsnone — executions list answers "an execution lives in the process that accepted it"
exposure of the built-in capabilitiesevery one reaches HTTP; all but health.live and health.ready reach MCP; fewer than half reach the command lineobjects.get, repository.info, every plan.* and graph.*, and health.report have no CLI projection

The graph gives four withheld reasons: "asks the person something; a request/response surface would hang", "effect RepositoryMutation is above the machine ceiling LocalMutation", "no capability declares this command line, so no machine surface has a typed input schema to execute from", and "groups other commands; nothing to execute". Most of the graph is withheld from every machine surface, and the commonest reason is the third.

So the runtime, today, cannot change a single development object, and none of the lifecycle is reachable from the Cockpit, MCP or HTTP.

The Cockpit is not the offender. Its trees reference the layer exactly once, as a display label in apps/majordomus-cli/src/cockpit/pages.rs:1787; it reads no state and writes none. It is already the pure projection that COCKPIT.md and ADR 0013 require. The defect is on the other side: the runtime has no development semantics to offer, so a Cockpit asked to become a development surface has two options — reimplement the lifecycle, or shell out to bin/majordomus. Both create a second implementation, and the second one drifts.

The canonical model, in repository-native names

The repository already has a name for every stage a development surface needs. Introducing a synonym for one of them is a defect, not an extension. The mapping below is the model; a worker extending any stage converges on the name in the middle column.

StageRepository-native nameCanonical owner (target)What exists today
Issue / Milestoneissue, milestonethe planplan capability module8 read-only capabilities; the write half is lib/plan.sh
Development tasktask — the existing active task. No new noun.a task capability modulelib/start.sh, check.sh, finish.sh; readable only through continuity.state
Compiled contextcontext — the context builder and its freezea context capability modulelib/context.sh, lib/context_docs.sh; the Rust half is directories.list, which reports contracts, not compiled context
Development sessionsession — the existing episodea session capability modulelib/session.sh; closed records are readable as session resources, the open one through continuity.state
Workflow executionexecution (the plane) over a command of the graphexecutions moduleexists, and holds 2 of the 3 mutating capabilities; keeps nothing across a process
Agent / peer activitypeer, and Actor inside an executionpeers moduleexists, in-memory; peers.announce is the third mutating capability
Capabilitiescapabilitycapabilities moduleexists and is canonical
Events + evidenceevent (two tiers), evidence, obligationledger vocabulary + obligations moduleledger is durable and local; the execution stream is live and in-process; the two are disjoint
Completion evaluationobligation closure and the finish contractobligations moduleobligations.closure reads it; lib/finish.sh decides it

Two stages the mandate's pipeline names separately are one thing here, and saying so matters more than the diagram: a development task is the existing task, not a new object beside it, and a workflow execution is an execution of a command of the command graph, not a third scheduler.

Ownership boundaries

                    ┌───────────────────────────────────────────────┐
                    │  THE LAYER  .ai/repo (shared) + .ai/local     │  storage
                    │  typed by share/kinds.yaml,                   │
                    │  discovered by .ai/repo/knowledge/sources.yaml│
                    └───────────────────────┬───────────────────────┘
                                            │  reads and writes
                    ┌───────────────────────▼───────────────────────┐
                    │  THE CANONICAL RUNTIME                        │
                    │  apps/majordomus-cli/src/capability/          │
                    │  one capability! per operation, typed in/out  │  semantics
                    │  exposure derived by command_graph/policy.rs  │
                    └──┬────────┬────────┬─────────┬────────┬───────┘
                       │        │        │         │        │         projections
                  ┌────▼──┐ ┌───▼───┐ ┌──▼───┐ ┌───▼───┐ ┌──▼─────┐
                  │Cockpit│ │HTTP+WS│ │OpenAPI│ │  MCP  │ │  CLI   │  surfaces
                  └───────┘ └───────┘ └───────┘ └───────┘ └────────┘

The layer owns storage. Every development object is a typed file of .ai/. Its kind is declared in share/kinds.yaml, whose declarative kinds the registry projection lists, its contract in share/schemas/majordomus/<kind>/<kind>.v1.schema.json, and its location in .ai/repo/knowledge/sources.yaml as a :(glob) pathspec. Three files agree or the object has no type and no authority.

The runtime owns semantics. One capability! declaration per operation, with a typed input and output. Where it is exposed is derived from its effect and interactivity by command_graph/policy.rs, never chosen per surface.

A surface owns presentation and nothing else. It renders capability output and posts capability input. It computes no status, decides no transition, reads and writes no file of the layer, and spawns no program to obtain a semantic. A surface that shells out owns the argument construction, the exit-code interpretation and the error rendering — three semantics, in the surface, undeclared.

Surface responsibilities

SurfaceMay doMay never do
Cockpit (src/cockpit, share/cockpit)render capability JSON; post capability input; subscribe to /eventsread or write .ai/**; compute a status or a transition; spawn a majordomus process
HTTP + WebSocket (src/http)route to capabilities; carry the typed event streamhold a route no capability declares
OpenAPI / Swaggerdescribe the registrydescribe an operation the registry does not have
MCP (src/mcp)expose capabilities as tools and resourcesexpose a tool that is not a capability
CLI (src/cli.rs)the one projection declared twice, reconciled by projection-closuregrow a command with no capability behind it
Shell tool (bin/majordomus, lib/**)target: invoke runtime capabilities, as lib/context.sh and lib/capture.sh already do through lib/rust_bin.shremain the only writer of a development object

Storage: decided, and reused

No new datastore. Every decision below is the storage the repository already has.

WhatCanonical storageShared?Why this one
Issues, milestones.ai/repo/project/{issues,milestones}/*.yamltrackedalready the canonical plan; schema majordomus.{issue,milestone}/v1; status is never stored, it is derived from timestamps and evidence
Active task.ai/local/state/current.yamllocala task is one checkout's claim; sharing it would claim paths for every worktree
Development session, closed.ai/repo/sessions/*.md, kind sessiontrackeda closed episode is a durable shared record of what happened
Development session, open.ai/local/state/session-current.yaml (+ sessions-open/)localan open episode belongs to the process holding it
Compiled context provenance.ai/local/session-contexts/<episode>localit names this machine and freezes a projection at one moment; .ai/README.md forbids publishing it
Durable events.ai/local/state/ledger.jsonl, vocabulary share/events.yaml (20 names)local, append-onlyalready the canonical record; ledger order is load-bearing for mj_record_rank
Live execution eventsthe in-process store, streamed over /events (15 typed messages)neitherbounded by design; a second durable log would reproduce the defect share/events.yaml was introduced to fix
Actor / peer statethe in-memory board (src/peers.rs)neithera peer is a connection; its durable trace is the ledger envelope's by and the session record's worker
Completion evidenceevidence[] inside the issue or milestone it dischargestrackedevidence that lives away from the obligation it discharges is evidence nobody joins
Obligation vocabularyshare/obligations.yamlshippeda contract, so it stays literal
Artifactsdocs/generated/**, site/data/** via generate::Targettrackedone writer, transactional, --checkable

The two event tiers, named. A durable state change is a ledger event whose name is registered in share/events.yaml. The typed execution stream is the live view of a runtime in motion and is never the record. A development capability that mutates state appends to the ledger; the stream carries the same change as it happens. Neither tier may hold a durable fact the other cannot see — which today it does, in both directions.

Executions stay non-durable. executions list visibly has nothing to read, and the temptation is a store. Rejected as written: the durable record already exists and is the ledger. The execution store stays bounded and in-process; durability is the ledger's job.

Data flow, mutation flow, event flow

Read. A surface asks a capability. The capability reads the index — objects discovered from sources.yaml, typed by kinds.yaml, validated against the kind's schema — or a derivation over it (plan.rs, graph.rs, product.rs, why.rs, obligations.rs). No surface reads a file.

.ai/** ──discovery──▶ index ──derivation──▶ capability ──projection──▶ surface

Mutate. Target flow. A surface posts a typed input to a capability of kind command. The capability validates against its input schema, applies the transition to the object in its canonical storage, appends a registered ledger event, and returns the new state. The same call over MCP, HTTP, the CLI and the Cockpit is one code path.

surface ──typed input──▶ capability ──▶ object in .ai/**  ──▶ ledger event ──▶ /events

Actual flow today. For the lifecycle's mutating commands, the surface is a terminal and the capability does not exist: bin/majordomus dispatches into lib/<name>.sh, which writes the object and appends the ledger line itself. That is the debt this document records, ratcheted in .ai/repo/development-semantics-baseline.txt.

Events. Durable: mj_ledger_append refuses a name that share/events.yaml does not declare and one missing a required field; history --event refuses to filter on an undeclared name; history --validate reports a stored line no reader recognises. Live: ExecutionStore::publish applies an event to the snapshot under one lock, refusing a transition ExecutionState::may_move_to does not allow, assigns the sequence, retains the event and hands it to subscribers — so a client reading the snapshot and a client reading the stream cannot disagree.

Consistency guarantees

GuaranteeMechanismWhere
One typed definition per operationthe registry refuses to build on a capability composed outside its module, a duplicate id, or a benchmark policy contradicting the kindcapability/registry.rs
A projection cannot disagree with the registrycapabilities validate, generate --check, and the projection-closure gate over the one interface declared twice (the CLI)capabilities/, scripts/ci/projection-check
An object cannot be untypedthree files must agree — manifest section, sources.yaml pathspec, kinds.yaml kinddiscovery/, metadata/
Status cannot drift from factsno status is stored anywhere; lib/project.awk derives BLOCKED / READY / ACTIVE / VERIFY / DONE / CANCELLED from timestamps, evidence and dependencies.ai/repo/project/project.yaml
An unknown key is an errorgenerated per-kind allow-lists under share/allow/share/allow/*.txt
A durable event cannot be mistypedthe registered vocabulary, enforced on append and on readshare/events.yaml
Snapshot and stream cannot disagreeone lock, one publish, refused illegal transitions, monotone sequenceexecution/store.rs
A generated file cannot be hand-editedtransactional generation plus a fingerprint or diff against a fresh generationgenerate.rs, the pre-commit hook
Live state cannot outlive its ownerthe lease is read once; a server nobody owns has a bounded lifelease.rs, ADR 0035

What is not guaranteed today, stated so the list above is not read as more than it is: a development object written by lib/ is not validated against its capability's input schema, because there is no capability; and a fact that reaches the ledger does not reach the execution stream, or the reverse.

The derivation chain every exposed object follows

canonical declaration            capability!  under src/capability/builtin/<module>.rs
        │                        (or, for a layer object, its kind in share/kinds.yaml)

serialization schema             CanonicalSchema from the typed input/output
        │                        (or share/schemas/majordomus/<kind>/<kind>.v1.schema.json)

HTTP route + OpenAPI             src/http/router.rs, docs/generated/openapi.{json,yaml}


MCP tool or resource             majordomus://<kind>/<identity>, or a tool by capability id


Cockpit contract                 the page that renders that capability's JSON

Every link is generated. Nothing on the chain is written by hand, and a hand-written entry anywhere on it is a violation of project.rust-canonical-declaration@1 (ADR 0004) as much as of ADR 0040. The chain is checked by majordomus capabilities validate, majordomus generate --check, and the projection-closure and rust-generated gates.

For a layer object the chain begins one step earlier, at the document schema: a Markdown kind is described in <kind>.v1.proto (Header = front matter, Body = sections), from which the JSON Schema, the front-matter allow-list under share/allow/ and the body section requirements under share/sections/ are all generated.

Extensibility: adding a development semantic

Short by design. If this list grows, the architecture has regressed.

  1. Declare one capability! in its module's file under apps/majordomus-cli/src/capability/builtin/, with its typed input, typed output and the input's BenchmarkCases. A new module is one module! plus one name in compose_modules!.
  2. If it mutates, give it kind: command, and append a ledger event — adding its name to share/events.yaml with the command that writes it and the payload keys it requires.
  3. Run majordomus generate, then majordomus capabilities validate. Every surface — HTTP, OpenAPI, MCP, the Cockpit page, the generated reference, the benchmark target — follows without a second registration.
  4. Run majordomus usecase impact and close any coverage gap it names with a use case that executes.
  5. If a shell command fronted the semantic, point it at the capability and remove its entry from .ai/repo/development-semantics-baseline.txt. The baseline may shrink and may not grow.

No other file is edited for the operation to exist everywhere it should.

The inventory

Measured, not recalled. Owner is who decides the semantic today; Mutation path is the only thing that may write it. A concept whose owner is lib/*.sh is debt against ADR 0040, not an architecture.

The development lifecycle

ConceptCanonical owner todayStorageSchema / typeDiscoveryConsumersMutation pathEventsTestsDocsDrift / duplication
issueread src/plan.rs; write lib/plan.sh.ai/repo/project/issues/*.yamlmajordomus.issue/v1sources.yaml class issueplan.* queries, one resource per issue, the site, the GitHub projectionlib/plan.sh:514-515 onlyplan_start, plan_verify, plan_evidence, plan_donetest/cases/*, tests/PLANNING.mdwrite half unreachable from every machine surface
milestoneas issue.ai/repo/project/milestones/*.yamlmajordomus.milestone/v1class milestoneplan.model, plan.roadmap, one resource eachlib/plan.shas issueas issueROADMAP.mdas issue
task (active)lib/start.sh, check.sh, finish.sh.ai/local/state/current.yamlmajordomus.current/v1not indexed (local)continuity.state, obligations.closure, scopelib/ onlytask.started, task.checkpoint, task.evidence, task.finishedtest/cases/32_refusal_lifecycle.shCONTINUITY.mdno capability; a refused finish writes no event at all
session (closed)lib/session.sh.ai/repo/sessions/*.mdsession/v1class session12 resources, objects.*, sitelib/session.shsession.closedtest/cases/*CONTINUITY.mdno Rust module; write unreachable
session (open)lib/session.sh.ai/local/state/session-current.yaml, sessions-open/majordomus.session-record/v1not indexedcontinuity.statelib/session.shsession.started
session contextlib/session_context.sh only.ai/local/session-contexts/majordomus.session-context/v1not indexedthe briefing, a personlib/ only.ai/README.mdno Rust owner and no capability at all
checkpointlib/checkpoint.sh.ai/local/state/checkpoints/not indexedcontinuity.state, contextlib/ onlytask.checkpointCONTINUITY.mdno capability
handoverlib/handover.sh.ai/local/state/handovers/not indexedcontinuity.state, contextlib/ onlytask.handed_overno capability
decision (local)lib/decision.sh.ai/local/state/decisions.mdnot indexedcontext, adr proposelib/ onlydecision.recordedtest/cases/99_adr.shDOCTRINE.mdtwo names for one concept: lib/decision.sh vs lib/adr.sh, and release/changelog.rs still references .ai/repo/decisions/
questionlib/question.sh.ai/local/state/open-questions.mdnot indexedcontinuity.state counts blockers onlylib/ onlyquestion.opened, question.resolvedCONTINUITY.mdno capability
evidencelib/evidence.shevidence[] in the plan objectinside issue/milestone schemavia the objectplan.*, obligations.closurelib/evidence.sh, lib/plan.shtask.evidence, plan_evidencePLANNING.mdno capability
obligation / closurecapability/builtin/obligations.rs (read) + lib/finish.sh (decide)share/obligations.yaml + local stateshipped vocabularyregistryobligations.*, finishlib/finish.shtask.finishedtest/cases/32CONTINUITY.mdthe read and the decision are two implementations of one contract
ADRlib/adr.sh.ai/repo/adrs/????-*.mdadr/v1class adrone resource each, graph.get?id=adrs, release.changeloglib/adr.shadr.proposedtest/cases/99_adr.shDOCTRINE.mdno capability; number allocation is unguarded — 0039 is claimed twice across branches today

The runtime

ConceptCanonical ownerStorageSchema / typeDiscoveryConsumersMutation pathEventsTestsDocsDrift
capabilitysrc/capability/composed at process startCanonicalSchema per capabilitycompose_modules!every surfacesource onlytests/projections.rsCAPABILITIES.mdcanonical; the model
commandthree declarations composed: cli.rs, share/commands.yaml, the justfiletrackedcommand/v1 for the shell halfcommands.graphCLI, workflow bridge, completion, docssource onlytest/cases/34_command_fixtures.shCOMMANDS.mdthe tool half is depth 1: plan done is not a node
executionsrc/execution/in-process onlytyped input per capabilityregistryexecutions.*, /events, Cockpitexecutions.start, .cancelthe stream's typed messagestests/EXECUTIONS.mdnothing durable; the 15 messages are in no ledger vocabulary
event (durable)share/events.yaml + mj_ledger_append.ai/local/state/ledger.jsonla registered vocabularyregistry walkhistory, continuity.state, obligationslib/ onlyitselftest/cases/*SCHEMAS.mddisjoint from the execution stream in both directions
peer / actorsrc/peers.rs, execution/model.rs::Actorin-memorytypedpeers.*, Cockpit, check --overlappeers.announcetests/AGENTS.mdan announcement belongs to a connection and is lost on reconnect
worktreesrc/worktree/derived from git; lock under <git-common-dir>/majordomus/locks/typed topologygit worktree list --porcelainworktree.*, the pre-commit guard, Cockpitworktree create/migrate/repairtests/, test/cases/*WORKTREES.mdtwo legacy layouts still recognised, deliberately
context (compiled)lib/context.sh, lib/context_docs.shassembled per callbudgeted assembly.ai/**/README.md, kind contexta worker, the briefing— (read-only)test/cases/*CONTEXT.mdthe Rust half (directories.list) reports contracts, not compiled context; DYNAMICITY.md already marks the provider table a target
repository / indexsrc/repository.rs, src/index.rs.ai/manifest.yaml + the trees it namesmanifest/v1manifest, then sources.yamleverythingtests/SCHEMAS.mdbenchmarks, ci, providers and workspaces are on disk but in no manifest section, alongside several loose baseline files
shared server / leasesrc/lease.rs, src/shared.rs.ai/local/state/mcp/server.jsonLeaseDocumentevery attached clientserve, mcptest/cases/108ENTRY.md, ADR 0035one server per checkout, not per repository

Governance and surfaces

ConceptCanonical ownerStorageConsumersMutation pathDocsDrift
rule / doctrine.ai/repo/rules/{project,vendor}/tracked Markdown, identity id+versioncheck, finish, doctor, watch, the site, one resource eachlib/rules.sh (vendor update)DOCTRINE.mdthe effective set is the vendored baseline plus this repository's own, each blocking or advisory; every tool-enforced rule is vendored and no project rule is tool-enforced — the doctrine, not a defect: a project rule gets a CI gate. majordomus rules list --json prints the split
policy / profile.ai/repo/policy.yaml, profiles/*.yamltrackedprovider projections, finish, contexthand-edited + update.ai/README.md
skill.ai/repo/skills/<id>/SKILL.mdtrackedone resource each, lib/skills.shlib/skills.sh.ai/repo/skills/README.mdno Rust module; only skill→skill edges in graph.rs
knowledge.ai/repo/knowledge/trackedobjects.*lib/knowledge.shcompiler is shell-only, no capability
use case.ai/repo/use-cases/*.mdtrackedone resource each, the coverage gate, the sitelib/usecase.shUSE_CASES.mdlib/usecase.sh is a second writer of docs/generated/
artifactgenerate::Target (15 targets)docs/generated/**, site/data/**site, docs, artifacts.listmajordomus generateDYNAMICITY.mdtwo writers: src/generate.rs and a shell set including lib/usecase.sh, bin/majordomus, scripts/derive, scripts/generate-site-data
Cockpitsrc/cockpit/nonea personCOCKPIT.mdclean — one layer reference, a display label at pages.rs:1787
quality / gates.ai/repo/ci/gates.yamltrackedvalidate.yml, just gatehand-edited + ci-plan --checkCI.mdci-plan --check prints the gate and class tallies; the cases live in test/cases/ and apps/majordomus-cli/tests/

Gaps against the target pipeline

Ranked by what blocks a development surface most.

  1. No mutating development capability exists. Only the execution and peer planes declare a capability of kind command. Every issue transition, session lifecycle event, checkpoint, handover, evidence attachment and completion decision is unreachable from MCP, HTTP and the Cockpit. Closing it: a kind: command capability per transition, ratcheted off .ai/repo/development-semantics-baseline.txt.
  2. The command graph does not see the lifecycle's subcommands. Every tool node is a group at the first level; majordomus plan done <id> is not a node. So the exposure policy has nothing to judge and the completion engine nothing to offer. Closing it: declare the shell subcommands in share/commands.yaml, or make each one a capability and let the graph derive it.
  3. Most lifecycle commands are interactive. The graph withholds them as "asks the person something; a request/response surface would hang". Interactivity is not a property a surface can work around. Closing it: project.commands-run-non-interactively@1, currently advisory.
  4. Two disjoint event models. A durable ledger vocabulary and a live stream vocabulary, with no bridge between them. A Cockpit cannot show one activity feed. Closing it: a mutating development capability appends a registered ledger event and publishes the same change to the stream; neither tier holds a durable fact the other cannot see.
  5. Completion is decided in two places. obligations.closure reads the contract; lib/finish.sh decides it. Two implementations of one contract. Closing it: the decision moves into the obligations module and finish asks it.
  6. Compiled context has no capability. directories.list reports context contracts; the compiled, budgeted context a worker actually gets is lib/context.sh only, and DYNAMICITY.md already lists the provider table as an entity with no owner.
  7. Executions are not durable. By decision, not by omission — but it means the Cockpit cannot show what ran before the current process. Closing it: the ledger, per §4.
  8. Two writers of docs/generated/. src/generate.rs plus a shell set. Existing debt, already in HARDCODING_LEDGER.yaml territory, and it will bite any new generated development artifact.
  9. The CLI is the least complete consumer, not the most. Fewer than half the built-in capabilities have a CLI projection — objects.get, repository.info, every plan.* and graph.* and health.report have none. So a person at a terminal reaches less of the canonical runtime than an MCP client does, which inverts the usual assumption that the CLI is the reference surface and the others catch up. Closing it: the exposure policy decides this from effect and interactivity; a query withheld from the CLI is either a policy decision worth stating or an omission worth fixing, and today nothing says which.
  10. Concepts with a shell owner and no runtime capability at all: session contexts, knowledge, skills, prompts, questions, history, search.
  11. ADR number allocation is unguarded. 0039 is claimed by two different files on two branches today. A development surface that proposes decisions makes this worse.

Enforcement

The rule is project.development-semantics-are-canonical@1, class: blocking. Per this repository's own doctrine — and consistent with every project rule, none of which carries an x-majordomus block — it is enforced by a CI gate rather than a lib/ validator.

scripts/development-semantics-check is the gate, wired as development-semantics in the structure job of .ai/repo/ci/gates.yaml. It reads tracked files only and needs no build. It decides two things:

  • Backing. A public command of share/commands.yaml whose class mutates and whose writes names a development object tree, for which docs/generated/registry.json declares no capability of kind command in the matching module.
  • Surface purity. A reference to .ai/repo, .ai/local or a majordomus executable inside apps/majordomus-cli/src/cockpit/** or share/cockpit/**, excluding a generator's own provenance header.

Every finding it reports today is in .ai/repo/development-semantics-baseline.txt: the unbacked mutating commands — adr, checkpoint, decision, evidence, finish, handover, init, migrate, plan, question, rules, session, start, update and usecase — and the one Cockpit display label. A finding not in the baseline fails; a baseline line matching nothing also fails, so the ratchet tightens instead of rotting.

Two things the gate does not decide, so that its silence is not read as a pass:

  • Whether a derivation in a surface is a semantic or presentation. A template that pluralises a count is not a second implementation; one that computes readiness is. A gate that guessed would be unauditable. A reviewer decides, exactly as majordomus.decision-threshold@1 leaves its own threshold to a reviewer.
  • Who writes a development object. A write in shell has no reliable syntactic shape, and a grep for the object trees would report every reader as a writer. Deciding it would take the writers declared as data — share/commands.yaml's writes field extended to name the writing function, reconciled against lib/ the way exit_codes already is. That is an open item, not a check.

How this was measured

# the two programs and the split between them
bin/majordomus --help
bin/majordomus-cli --help

# what the registry holds, which capabilities can mutate, and the module/kind breakdown
bin/majordomus-cli capabilities list --format json > caps.json
jq -r '{count, summary}' caps.json
jq -r '.capabilities[] | select(.provenance.source=="builtin")
        | "\(.id)\t\(.kind)\t\((.exposure|keys)|join(","))"' caps.json
jq -r '.capabilities | group_by(.module)
        | map("\(.[0].module)\t\(length)") | .[]' caps.json

# the commands, their origins, every withheld reason, and the depth per origin
bin/majordomus-cli commands graph --format json > cg.json
jq -r '.commands|group_by(.origin)|map("\(.[0].origin)\t\(length)")|.[]' cg.json
jq -r '[.commands[].projections.withheld|select(.)]
        | group_by(.)|map("\(length)\t\(.[0])")|.[]' cg.json
jq -r '.commands|group_by(.origin)
        | map({o:.[0].origin,
               d:(map(.path|length)|group_by(.)|map({d:.[0],n:length}))})|.[]' cg.json

# executions keep nothing across a process; the stream's message vocabulary
bin/majordomus-cli executions list
bin/majordomus-cli executions protocol

# the effective rules, the class split, and which of them the tool enforces
bin/majordomus rules list --json

# the durable ledger event names
grep -oE '^\s+- id: [a-z._]+' share/events.yaml

# the generation targets
bin/majordomus-cli generate --help

# the web surfaces and their mounts
bin/majordomus-cli web list

# who writes what
grep -rln 'project/issues\|project/milestones' lib/ bin/ scripts/
grep -rln 'docs/generated' lib/ bin/ scripts/
grep -rn '\.ai/\|bin/majordomus' apps/majordomus-cli/src/cockpit/ share/cockpit/*.js

# sizes and the test surface
cat lib/*.sh | wc -l ; ls test/cases/*.sh | wc -l
ls apps/majordomus-cli/tests/*.rs | wc -l

# the gate over this document's own rule
scripts/development-semantics-check

Counts of anything in this repository go stale; measure rather than trust a number written here, including these.