Guaranteed, advisory, planned
What Majordomus actually does today, row by row, with the file that implements it and the test that proves it.
A supervisory tool that overstates itself is the thing it was built to prevent. This page is generated from docs/CLAIMS.yaml; the build fails if any source, implementation or test path in it does not exist, or if a guaranteed claim names no test.
The same data, drawn as the graph it is: every claim wired to the document, the implementation and the test behind it →
Deterministic and blocking. Implemented, and a behavioural test proves it.
Projected into the worker's instructions. Whether a worker honours it is not observable from outside. Documentation, not enforcement.
Specified and not implemented. Named so the omission is visible.
Considered and deliberately refused, with the reason recorded.
guaranteed guaranteed
| claim | source | implementation | test |
|---|---|---|---|
| Every platform, artifact name and installation URL is derived from one model, and a projection that disagrees with it is refused share/distribution.yaml is the model. The installer's platform table, the release build matrix, the documentation's table, the website's dataset and the public release metadata are generated from it, and `majordomus generate --check` exits 10 naming any that has fallen behind. |
docs/DISTRIBUTION.md | apps/majordomus-cli/src/distribution/mod.rs | test/cases/84_distribution_model.sh |
| The documented one-line install command is composed from the model's parts, and a document that states a different one fails the suite The download command, the base URL, the script name and the shell are four fields; the command is their composition. README.md and the installation guide are checked against it rather than trusted. |
docs/INSTALL.md | apps/majordomus-cli/src/distribution/mod.rs | test/cases/84_distribution_model.sh |
| The installer verifies an artifact's digest and inspects its archive before anything is unpacked, and any failure leaves the previous installation working A wrong digest, a truncated download, an archive that escapes its own directory or carries a link, and an executable that reports another version are each refused by name, before the launcher is replaced. There is no warn-and-continue path and sudo appears nowhere. |
docs/INSTALL.md | share/install/install.sh.in | test/cases/85_installer.sh |
| A release publishes an artifact for every supported target or it is not published The build matrix stops at the first failure, publication begins only when every build succeeded, and the recorder refuses to write a record missing a target the model marks supported. |
docs/DISTRIBUTION.md | scripts/release-record | test/cases/87_release_pipeline.sh |
| The install command this project advertises is fetched and run from the published site on every push to the default branch, and the branch is red while it does not install a working tool The line is run as it is written, pipe included, on Linux and macOS, into a home directory of the run's own. It proves the metadata resolves, the tool reports the version that was resolved, the MCP launcher runs with no toolchain, and a repository can be initialised. A release proves the same thing once at publication; this keeps proving it afterwards. |
docs/DISTRIBUTION.md | scripts/ci/install-check | test/cases/97_install_gate.sh |
| A release archive carries every path once and carries nothing but files and directories The packer reads back what it wrote and deletes an archive carrying a repeated path or an entry that is not a file or a directory; the verifier checks the same two properties again on the runner that built it. There is no fallback packer, because an archive packed another way is not the one that was verified. |
docs/DISTRIBUTION.md | scripts/release-package | test/cases/87b_release_archive_shape.sh |
| The canonical policy is parsed and rejected if it contains an unknown key A restricted YAML subset. Tabs, anchors, flow maps and multi-line scalars are refused rather than guessed at. |
docs/SCHEMAS.md | lib/common.sh | test/cases/00_yaml_flatten.sh |
| Every profile is parsed, its unknown keys rejected, and the default profile proven to exist | docs/SCHEMAS.md | lib/doctor.sh | test/cases/02_doctor_basic.sh |
| Every enforcement the policy declares is reconciled against what actually runs The path must exist, be executable, and be invoked by the named dispatcher. A hook that invokes it and discards the exit code counts as not wired. |
docs/DESIGN.md | lib/doctor.sh | test/cases/03_update.sh |
| Exit codes are a contract, and no code means "warn and continue" | docs/CLI.md | lib/common.sh | test/cases/03_update.sh |
| Provider instruction files are generated from the one policy, deterministically Running update twice leaves the byte-identical file. The test asserts the hash. |
docs/DESIGN.md | lib/update.sh | test/cases/03_update.sh |
| Every generated instruction file carries its own stamp, and a hand edit is detected | docs/SCHEMAS.md | lib/update.sh | test/cases/03_update.sh |
| A projection can own only the region between its markers, leaving the rest of the file untouched How a repository that already has a hand-written CLAUDE.md adopts Majordomus. An edit outside the markers is never drift; an edit inside them is caught. Malformed markers are refused, not guessed at. |
docs/SCHEMAS.md | lib/update.sh | test/cases/13_region_projection.sh |
| Wiring is found in a hook or in any subhook of the directory that hook dispatches to The finding names the file that actually carries the invocation. A subhook that is not executable is reported as not wired, because the dispatcher skips it. |
docs/CLI.md | lib/doctor.sh | test/cases/14_wiring_dispatcher.sh |
| Every rule the tool enforces is declared once, as a rule object in the repository's effective set, and doctor proves each one is reached by the command that claims to run it A doctrine whose validator does not exist, a validator no doctrine declares, a command that never dispatches, a blocking rule whose command cannot exit non-zero, a missing test, and CI that swallows the suite are six separate failures with six separate messages. Each is mutation-tested. |
docs/DOCTRINE.md | lib/doctrine.sh | test/cases/18_doctrine_wiring.sh |
| A doctrine's class is what decides whether a violation stops the command, not a label beside it The test changes checkpoint freshness from advisory to blocking in a copy of the vendored rule file and asserts that check's exit code moves from 0 to 10. There are two classes and no override. |
docs/DOCTRINE.md | lib/doctrine.sh | test/cases/17_doctrine_enforcement.sh |
| Every line of the append-only ledger is a well-formed event, and a malformed line is a failure rather than a skipped record The reader skips a bad line rather than crashing, and check, doctor and watch all report it. The ledger is the one durable record nothing else can reconstruct. |
docs/DOCTRINE.md | lib/check.sh | test/cases/22_history.sh |
| An entry in the questions store that does not parse blocks acceptance exactly as an unresolved question does A gate that cannot read an entry can be bypassed by mistyping one, so an unreadable question is not a lesser problem than an open one. |
docs/DOCTRINE.md | lib/check.sh | test/cases/21_decision_question.sh |
| A declared provider hook captures the person's raw prompts below the model, and doctor proves it by running it Covers the five states a provider can be in, idempotence on the provider's prompt identity, an archive that is ignored and untracked, a record set that cannot carry model output, the record-and-rendering pair every prompt is kept as, and the schema identifier resolving to the two files that describe both halves. |
docs/DOCTRINE.md | lib/capture.sh | test/cases/29_prompt_capture.sh |
| Every repository-local prompt asset renders, and an unknown token is a failure Covers name and filename mismatch, an empty description, an unknown front-matter key, missing front matter, and a block token used inline. |
docs/DOCTRINE.md | lib/prompt.sh | test/cases/24_prompt_search.sh |
| A use case or application cannot name a command, doctrine or claim that does not exist, and the two catalogues cannot disagree about which applies to which Prose describing the tool is checked the same way the tool checks itself. Cross-references are verified in both directions, so a use case naming an application that does not name it back is a failure rather than a silent asymmetry. An application declaring no does_not_fit_when is refused, because a catalogue that only lists fits is marketing. |
docs/DOCTRINE.md | lib/usecase.sh | test/cases/28_catalogue.sh |
| A hand-edited instruction file is never silently overwritten update refuses, offers a diff, and overwrites only under an explicit flag. |
docs/CLI.md | lib/update.sh | test/cases/03_update.sh |
| The always-loaded instruction file has a line budget with a failing check Over budget, nothing is written at all. |
share/skeleton/policy.yaml | lib/update.sh | test/cases/03_update.sh |
| Every repository-relative reference in the always-loaded file is proven to resolve | docs/DESIGN.md | lib/doctor.sh | test/cases/03_update.sh |
| A hardcoded count in the always-loaded file is a failure | docs/DESIGN.md | lib/doctor.sh | test/cases/03_update.sh |
| The append-only ledger and handover store have retention caps that are checked | share/skeleton/policy.yaml | lib/doctor.sh | test/cases/02_doctor_basic.sh |
| Installing into a repository that already has an installation is refused | docs/CLI.md | lib/init.sh | test/cases/01_init.sh |
| Every finding carries the command that reproduces it | docs/DESIGN.md | lib/common.sh | test/cases/02_doctor_basic.sh |
| Nothing performs a network call, evaluates generated text, or deletes recursively | docs/DESIGN.md | bin/majordomus | test/cases/08_no_forbidden_constructs.sh |
| A task is started with a declared file scope and a profile, and only one task is active per checkout A scope path that escapes the repository root is refused. Starting a second task while one is active is refused. |
docs/CLI.md | lib/start.sh | test/cases/04_start_check.sh |
| Identity fields on every state record are computed from git and never authored A handover body that tries to set one is rejected rather than merged. |
docs/SCHEMAS.md | lib/start.sh | test/cases/04_start_check.sh |
| check reports whether state, scope, blockers and checkpoint age are consistent right now | docs/CLI.md | lib/check.sh | test/cases/04_start_check.sh |
| A touched file outside the claimed scope is a failure, not a warning | docs/DESIGN.md | lib/check.sh | test/cases/04_start_check.sh |
| State read back is labelled exact, advanced, diverged or different_context against git | docs/SCHEMAS.md | lib/common.sh | test/cases/04_start_check.sh |
| Overlap between parallel workers is computed on claimed paths in both containment directions Reported, never blocking. Work in progress is reported and never blocked. |
docs/DESIGN.md | lib/check.sh | test/cases/04_start_check.sh |
| watch reports policy, projection, state, scope, handover, verification, staleness and retention drift | docs/DESIGN.md | lib/watch.sh | test/cases/07_watch.sh |
| handover writes an append-only record with computed front matter and required sections A body with a missing or empty required section is refused and names the sections. |
docs/SCHEMAS.md | lib/handover.sh | test/cases/05_handover.sh |
| Handovers carry durable facts, never conversation transcripts | docs/DESIGN.md | lib/handover.sh | test/cases/05_handover.sh |
| finish evaluates the finish contract line by line and refuses when any line is unmet | docs/CLI.md | lib/finish.sh | test/cases/06_finish.sh |
| An outcome is a value from a closed vocabulary, not free text no_match means the work was done and the thing sought does not exist. failed means the work could not be done. They are different facts. |
docs/SCHEMAS.md | lib/finish.sh | test/cases/06_finish.sh |
| The context a worker is given is assembled from durable state in authority order, and what a profile excludes is excluded Git first, then task and profile, then blockers, then authored records, then history. The profile's context block decides what is offered; it is the only code that reads those fields. |
docs/CONTINUITY.md | lib/context.sh | test/cases/23_context.sh |
| The assembled context obeys a line budget, and every section it drops is named with its reason The count is of the printed document, not of its sections. Git, task, profile and open blockers are never dropped; record bodies degrade to a pointer before they disappear. |
share/skeleton/policy.yaml | lib/context.sh | test/cases/23_context.sh |
| A checkpoint is a capped progress record with computed identity, and a body over the cap is refused rather than truncated The cap is what keeps a checkpoint distinct from a handover. Mode 0600, created atomically, never staged. |
docs/SCHEMAS.md | lib/checkpoint.sh | test/cases/20_checkpoint.sh |
| The right prior record is resolved by worktree and branch, and an unrelated one is never offered Same worktree and branch, else same branch, else nothing. Ties within one second are broken by ledger order. Absence is reported as absence. |
docs/CONTINUITY.md | lib/common.sh | test/cases/23_context.sh |
| The ledger is readable back as operational history, filtered by task, event and time What happened, when, for which task, at which head, and what outcome was accepted. Never what anyone said. |
docs/CLI.md | lib/history.sh | test/cases/22_history.sh |
| Rotating the ledger archives the oldest lines and never deletes them Refuses to overwrite an existing archive, and does nothing when the ledger is under its cap. |
docs/SCHEMAS.md | lib/history.sh | test/cases/22_history.sh |
| A decision is recorded with its reason and its task, and is superseded by a later entry rather than edited --why is required. --supersedes refuses text matching no recorded decision. Task and head are computed. |
docs/SCHEMAS.md | lib/decision.sh | test/cases/21_decision_question.sh |
| An unresolved question blocks acceptance, and an entry the gate cannot parse is a failure rather than a silent pass A gate that cannot read an entry can be bypassed by mistyping one, so check, doctor and watch all fail on a malformed entry. |
docs/SCHEMAS.md | lib/question.sh | test/cases/21_decision_question.sh |
| A repository-local prompt renders against a closed set of state tokens, and an unknown token is an error No conditionals, loops, includes or shell. A prompt that silently renders a misspelled token as literal text is worse than one that refuses. |
docs/SCHEMAS.md | lib/prompt.sh | test/cases/24_prompt_search.sh |
| Durable records are searchable literally, across kinds, without an index A fixed-string grep in authority order. No ranking, no embedding, no index that could fall out of step with the records it describes. |
docs/CLI.md | lib/search.sh | test/cases/24_prompt_search.sh |
| Every continuity store is proven reachable through its own command, not merely present on disk doctor runs the resolver, renders every prompt asset, validates both parsed stores and builds the context. A directory of records that no command reads is the failure this tool exists to catch. |
docs/CLI.md | lib/doctor.sh | test/cases/25_continuity_lifecycle.sh |
| The context that applies to a path is composed from the layer's scoped documents, root to target, in one deterministic order with provenance, and a tree that does not validate resolves nothing A document is a Markdown file under .ai/ whose front matter carries schema context/v1; the file name is a convention and the id is the identity. Ordering is depth, then declared order, then path. Providers and audience filter; extend, replace and final compose; a duplicate id, a dangling reference, a superseded final document or a cycle is refused by name. |
docs/CLI.md | lib/context_docs.sh | test/cases/69_context_documents.sh |
| Every directory of the layer carries a context document, the exemption is declared by the contract that governs the subtree, and a descendant may narrow that requirement but never weaken it A directory inside the tree with no document is missing-contract, naming the directory and, where an ancestor made the requirement explicit, the contract that did. The exemption is children.require_contract on a subtree document; the value nearest a directory applies, absence means one is owed, and lowering an inherited true is illegal-override. The tree is the manifest's directory minus the half the manifest declares untracked; nothing else is skipped by name, and a subtree the layer carries but does not author is released by children.exempt on the contract that governs it, which may name only directories inside its own scope. |
docs/CONTEXT.md | lib/context_docs.sh | test/cases/69_context_documents.sh |
| The impact of a change set on the scoped context is reported from git — the documents, the scopes below them, moves with their ancestry, tracked sources to review, and stale projections — and an unrelated change reports nothing context affected reads the working tree, the index, or a base ref, with renames detected; a tracked source change is a WARN to review and never an exit code, because the tool cannot decide whether prose still describes code. context check-sync is validate, then every projection against its stamp, then affected; test/cases/71_context_sync.sh proves the exit codes. |
docs/CLI.md | lib/context_docs.sh | test/cases/70_context_impact.sh |
| The repository's AI layer is one directory whose manifest names every section, readable without the tool init seeds .ai/ from the skeleton and nothing else; no hook, no shell file, no tool installation. doctor fails on a section the manifest names that is absent, and on a manifest key nothing reads. |
docs/SCHEMAS.md | lib/init.sh | test/cases/01_init.sh |
| Checkout-local state lives under .ai/local/, is ignored by git, and is absent on a fresh clone or worktree The task record, ledger, decisions, questions, checkpoints and handovers never travel through git. A record that arrives in another checkout by other means names the checkout it belongs to and is enforced nowhere else, which test/cases/27_foreign_task.sh proves. |
docs/SCHEMAS.md | lib/common.sh | test/cases/01_init.sh |
| The rule baseline is vendored into the repository with a manifest naming every file and its hash, and a hand edit is detected and refused rules vendor status reports an edited, missing or stray file; rules vendor update refuses over it until --force, and never touches rules/project/. A newer executable reports a newer package and does not apply it. |
docs/DOCTRINE.md | lib/rules.sh | test/cases/67_rule_dag.sh |
| The effective rule set is resolved as a dependency graph in a deterministic order, and a set that does not resolve is applied by nothing A missing or deprecated dependency, a cycle, one identity claimed twice, a project rule in the vendor namespace, malformed front matter, an unknown key and an x-majordomus block missing its binding are each refused by name. Every dependency is listed before the rule that depends on it, and two runs agree. |
docs/DOCTRINE.md | lib/rules.sh | test/cases/67_rule_dag.sh |
| Provider instruction files are thin bootstraps that point at the AI layer and carry no rule of their own Every projection names .ai/README.md, README.md names AGENTS.md, and doctor fails on a projection that grew a rules section of its own. A person and a worker reach the same layer by the same two hops. |
docs/DESIGN.md | lib/update.sh | test/cases/03_update.sh |
| A repository on the pre-.ai layout is migrated once, explicitly, with a previewed plan and a verified backup of its local state, and the command is idempotent afterwards Every other command refuses the legacy layout and names migrate. --dry-run prints the plan and writes nothing. A file the version does not know is reported, never deleted; a destination that already exists refuses the whole run. |
docs/CLI.md | lib/migrate.sh | test/cases/66_migrate_legacy.sh |
| The tool runs read-only from any location, and two locations of one version agree about one repository A checkout copy, an unrelated absolute path and a PATH install read the same repository files and write nothing into the distribution, whose hash is the same after every command. The distribution root is derived from the entry point, never from the environment. |
docs/DESIGN.md | lib/common.sh | test/cases/65_tool_root_independence.sh |
| What the tool knows about itself is written once and derived everywhere else, and a second copy is a test failure Policy values have no reader-side defaults, claim statuses are declared in the matrix, the site fixtures derive their inputs from the generator, and every command, doctrine and claim is checked against a list read from the dispatch table or the registry rather than written into the test. |
docs/CONCEPTS.md | lib/doctor.sh | test/cases/28_no_hardcoded_values.sh |
| The public command surface is declared, and reconciled against the dispatcher The registry is authority for what a command means; bin/majordomus stays authority for what runs. A command dispatched with no entry, and an entry nothing dispatches, both fail. A command is public exactly when the usage text lists it, checked in both directions. |
docs/DYNAMICITY.md | share/commands.yaml | test/cases/30_command_registry.sh |
| Every public command has a behavioural test and a negative test, computed rather than remembered The requirement is derived from the registry, so a command added tomorrow is owed coverage from that moment. Coverage is declared in per-case headers rather than inferred, because the negative assertions that use stdin and the surface case's loop are invisible to any scan. |
docs/DYNAMICITY.md | test/cases/31_command_coverage.sh | test/cases/31_command_coverage.sh |
| The ledger's event vocabulary is closed, on the way in and on the way out share/events.yaml declares every name, its emitting command and its required payload keys. An unregistered name is refused on write, refused as a history filter, and reported by history --validate when already stored. |
docs/SCHEMAS.md | lib/common.sh | test/cases/33_event_registry.sh |
| A task record names the checkout it belongs to, and another checkout is never held to its scope The record is checkout-local and never tracked, so it does not travel through git; one that arrives by other means is recognised by the worktree it names. check, finish --check and watch report a foreign record and enforce nothing from it; finish refuses to write to it. A record from before the field existed is treated as local, so upgrading does not turn an installation red. |
docs/SCHEMAS.md | lib/check.sh | test/cases/27_foreign_task.sh |
| A blocking question keeps blocking after the work is handed to a new task The gate reads every unresolved entry in the store rather than only the active task's, so a handover cannot launder a blocker. The store is checkout-local, so the questions a gate can see are the ones asked in this working copy. Only a completed finish is refused; blocked, partial, no_match and failed are honest non-completions and are not. Any task may resolve any question. The alternatives — transferring the question with the handover, or gating on scope overlap — were rejected in M001 with the case each gets wrong. |
docs/CONTINUITY.md | lib/check.sh | test/cases/50_blocker_across_handover.sh |
| Milestones and issues are canonical repository files, and a key nobody reads is an error The same allowlist rule the policy and the profiles live under. The filename is the id; a record that disagrees with its own filename is refused rather than reconciled. |
docs/SCHEMAS.md | lib/project.sh | test/cases/40_project_model.sh |
| Issue and milestone status is derived from recorded facts and is stored nowhere An issue records started_at, verified_at, completed_at, cancelled and its evidence. BLOCKED, READY, ACTIVE, VERIFY, DONE and CANCELLED follow from those and from the state of its dependencies. `status` is not a field, so writing one is an unknown key. |
docs/PLANNING.md | lib/project.awk | test/cases/41_project_status.sh |
| A cycle, a self-dependency and a dependency on an issue that does not exist are each refused by name Kahn layering; the issues that never leave the graph are the ones reported. A malformed graph fails doctor and is reported by watch as drift. |
docs/PLANNING.md | lib/project.awk | test/cases/42_dag_waves.sh |
| Execution waves are computed from the graph, and issues in one wave that touch the same paths are reported as serialised Sharing a wave is necessary for concurrency, not sufficient. Overlapping declared scope serialises conservatively, because a false serialisation costs time and a false parallel costs a conflict. |
docs/PLANNING.md | lib/project.awk | test/cases/42_dag_waves.sh |
| An issue cannot be completed while a required piece of evidence is missing Evidence is a command and its result, or an artifact. Narrative is refused. An issue whose completed_at is set but whose evidence is incomplete derives VERIFY, never DONE. |
docs/PLANNING.md | lib/plan.sh | test/cases/41_project_status.sh |
| The roadmap is derived from milestone state, and no document may be a second authority for it The sequence is the topological rank of the milestone graph, so no list of versions exists anywhere. While a hand-written roadmap table survives, the doctrine holds it to the model in both directions; once it is gone the check holds trivially, which is the regression it exists to refuse. |
docs/ROADMAP.md | lib/doctor.sh | test/cases/49_roadmap_doctrine.sh |
| GitHub milestones and issues are generated from the canonical model, and a hand-edited generated region is reported rather than overwritten The adapter lives in scripts/ because the tool itself makes no network call, which test/cases/08_no_forbidden_constructs.sh proves. The begin marker carries the hash of the record it was generated from, which is what separates a moved plan from a hand edit; a second marker carries the record's identity, so a title a person rewrites cannot orphan it. This claim is about what the adapter renders and refuses. Whether the remote has actually received it is a different question, and github-projection-gated is the claim that answers it. |
docs/PLANNING.md | scripts/github-sync | test/cases/45_github_projection.sh |
| A projection that stops being applied turns a build red, and a gate that cannot reach GitHub reports that it cannot rather than passing The adapter has exited 11 on drift since it was written and nothing called it, so the projection decayed to a tenth of the model over five days with every build green. The gate refuses behind, edited, conflict, unmanaged, state and milestone outright, and ratchets the backlog of unprojected records against .ai/repo/ci/github-drift-baseline.txt, which may fall and may never rise. |
docs/PLANNING.md | scripts/ci/github-check | test/cases/97_github_gate.sh |
| The Rust executable serves the repository's AI layer to an MCP client over stdio, read-only, with protocol frames alone on stdout One process per client, alive as long as its pipe; diagnostics go to stderr; a session leaves the repository byte-identical. The crate's own suite, tests/mcp_stdio.rs, holds the same frames to the same rules. |
docs/MCP.md | apps/majordomus-cli/src/mcp/protocol.rs | test/cases/72_rust_mcp.sh |
| What the MCP server serves is decided by the manifest, the declared source classes and each file's front matter, so an object added to the layer is served with no change to the executable The add, remove and break sequence through the built binary is tests/external_extension.rs in the crate; the shell case proves the layer init writes is served, and that a rule added to it appears. |
docs/MCP.md | apps/majordomus-cli/src/index.rs | test/cases/72_rust_mcp.sh |
| A file of the layer the MCP server cannot read is excluded with a diagnostic naming its path and the index says it is degraded, never silently smaller Two files of one kind claiming one identity are both excluded and both named. --strict refuses to serve a degraded layer; --inspect exits 10 on one. |
docs/MCP.md | apps/majordomus-cli/src/index.rs | test/cases/72_rust_mcp.sh |
| Every capability of the Rust executable is defined once, and the registry refuses a duplicate id or a colliding projection name, naming both parties Two sources, builtin executables and the layer's objects, meet in one registry built at one place; ids, MCP names and URIs, HTTP routes and CLI paths are each unique or the registry does not build. The crate's tests/registry.rs covers every collision. |
docs/CAPABILITIES.md | apps/majordomus-cli/src/capability/registry.rs | test/cases/76_capabilities_projections.sh |
| MCP, HTTP, OpenAPI, Swagger UI, the capabilities commands and the generated reference are derived from the registry, and a change to one definition reaches every one of them No projection declares a name, a description, a schema or a route of its own; tests/projections.rs proves every declared projection present and none orphan, and that a changed input type and description reach the MCP schema, the OpenAPI operation and the reference. MCP and HTTP answer the same handler identically. |
docs/CAPABILITIES.md | apps/majordomus-cli/src/http/openapi.rs | test/cases/76_capabilities_projections.sh |
| How each declarative kind is read and which keys it may carry is data read at run time, and a repository adds a kind with its JSON Schema without a code change share/kinds.yaml and share/schemas/*.schema.json are the distribution's; .ai/repo/knowledge/kinds.yaml and schemas/ are the repository's; a name declared by both is an error naming both files. The shell tool's allow-lists are generated from the same schemas. |
docs/CAPABILITIES.md | apps/majordomus-cli/src/metadata/mod.rs | test/cases/76_capabilities_projections.sh |
| Every generated artifact declares the document it projects, the encoding it is written in, the schema its content satisfies and its source, and a structured document is written in every encoding it is committed in from one value JSON, YAML and Markdown of a document are renderings of one value, so they cannot disagree; each carries a provenance header in the form its encoding allows; the index of the whole set, docs/generated/artifacts.json, is itself generated and is read back by artifacts.list. |
docs/CAPABILITIES.md | apps/majordomus-cli/src/generate.rs | test/cases/52_generated_artifact_typing.sh |
| The committed projections, the OpenAPI document, the capability reference and the allow-lists, are regenerated from the registry and the schemas, and generate --check refuses a stale one Byte-identical on regeneration, no timestamps, no absolute paths; --check writes nothing and exits 10 naming every file that differs or is missing. CI runs it. |
docs/CAPABILITIES.md | apps/majordomus-cli/src/generate.rs | test/cases/76_capabilities_projections.sh |
| One shared server serves a repository: the first majordomus mcp binds loopback HTTP with Swagger UI and MCP over HTTP beside its stdio session and logs the URL, every later one attaches to it, and it ends when the last client leaves The election is one lease file under .ai/local/state/mcp/, created atomically and removed on exit; a stale lease is taken over; a bridged client whose server dies takes over or re-attaches without its client noticing. The crate's tests/mcp_shared.rs covers the socket side; --standalone keeps one process per client. |
docs/MCP.md | apps/majordomus-cli/src/lease.rs | test/cases/90_mcp_shared_server.sh |
| Nothing a client leaves behind locks another client out of the shared server; a corrupt, empty, stale or abandoned lease is taken over and named, a client that cannot share is served alone with the reason logged, and a signal removes the lease before the server dies The election classifies the lease on every attempt and is bounded by time; commands/mcp.rs degrades to a standalone session when the lease or the server cannot be used; SIGTERM, SIGINT and SIGHUP unlink the lease in the handler. The crate's tests/mcp_shared.rs proves each path over real processes, and case 90 plants a corrupt lease behind the launcher. The doctrine is project.shared-server-resilience. |
docs/MCP.md | apps/majordomus-cli/src/lease.rs | test/cases/90_mcp_shared_server.sh |
| Every client attached to the shared server is a peer named by its own initialize, listed by majordomus_peers, and majordomus_announce tells the others what it is working on and which paths it expects to touch Two registry capabilities, peers.list (a query) and peers.announce (the one command), projected like every other; the board is one process's memory and is gone with it. Announcements are informational; scope is enforced by the shell tool's task record, not here. |
docs/MCP.md | apps/majordomus-cli/src/peers.rs | test/cases/90_mcp_shared_server.sh |
| An MCP client opened in the repository starts the server itself, building the executable when it must, through the client configurations at the root and bin/majordomus-mcp .mcp.json (Claude Code), .gemini/settings.json (Gemini CLI) and .codex/config.toml (Codex) name the same launcher, which writes nothing to stdout and passes every argument to majordomus mcp; MAJORDOMUS_BIN, MAJORDOMUS_BUILD_PROFILE and MAJORDOMUS_NO_BUILD control the build. |
docs/MCP.md | bin/majordomus-mcp | test/cases/90_mcp_shared_server.sh |
| A majordomus:// URI resolves the same way through the MCP resource read, the majordomus_get tool and the HTTP object route, and majordomus://repository answers repository.info as a JSON document tagged builtin One function, objects::resolve, is the resolution for every projection; a file of the layer is answered tagged declarative, a query with a resource exposure tagged builtin with its answer as data and the resource text beside it; an unknown URI is not found everywhere; the registry refuses a query read as a resource whose input requires anything. The crate's tests/objects.rs covers every branch at the library level. |
docs/MCP.md | apps/majordomus-cli/src/capability/builtin/objects.rs | test/cases/72_rust_mcp.sh |
| Every gate the Rust executable must pass is one script, scripts/rust-check, and CI runs that script on every change that can reach the crate Format, lints with warnings as errors (a public item without documentation is one), the suites with the doc examples, rustdoc with warnings denied, the benchmarks building, the registry's own validation, the committed projections in sync, the benchmark coverage complete, and line coverage above the committed floor; --ci adds the benchmark check against the platform's baseline and leaves coverage to its own job, --integration runs the registry checks alone for a change outside the crate. The rust job of the workflow runs the script itself with the mode the plan chose, so there is no second list to fall out of order; the case reads the script, the workflow and the justfile and fails when they disagree; with a toolchain present it also runs the doc examples and builds the benchmarks. |
.ai/repo/rules/project/rust-cli-evidence.v1.md | scripts/rust-check | test/cases/77_rust_evidence.sh |
| Line coverage of the Rust crate is measured on every change that can affect the crate and on every push to master, and the build fails under the floor in scripts/rust-coverage-threshold, one integer read by CI, by scripts/rust-check and by just coverage cargo-llvm-cov over all targets, --fail-under-lines. The floor is 90; the case refuses a floor under 90, so lowering it is an edit to a test that a reviewer sees, not a quiet change to a number. Raising it when the figure rises is one edit to the file. The coverage job runs when the plan selects the rust-coverage gate, which the rust and share classes of .ai/repo/ci/gates.yaml select and every full plan does; a change that cannot reach the crate does not recompile it with instrumentation. |
.ai/repo/rules/project/rust-cli-evidence.v1.md | .github/workflows/validate.yml | test/cases/77_rust_evidence.sh |
| The paths of the Rust executable that scale with the repository carry criterion benchmarks that build on every push, and every executable capability declares its benchmark policy, required or waived for a typed reason The YAML subset and front matter, glob matching, the index, the registry, the OpenAPI document, one MCP listing and one capability call (benches/projections.rs); the router, the bridge and the shared server (benches/shared.rs). Numbers are reported, not asserted; no latency budget is promised until one is measured on CI. The capability! macro does not compile an executable without benchmark cases, the registry counts the required and the waived, and the case reads the policy of every executable back through capabilities list. |
.ai/repo/rules/project/rust-cli-evidence.v1.md | apps/majordomus-cli/benches/projections.rs | test/cases/77_rust_evidence.sh |
| A capability of the Rust executable is declared once and composed into its module, the root composes modules, and the registry refuses a capability outside its module's namespace capability!, module! and compose_modules! build plain values handed to the registry builder; adding a capability to an existing module touches that module's file alone, and MCP, HTTP, OpenAPI, Swagger UI, the CLI, the benchmark targets, the cache and the generated reference follow. The crate's tests/registry.rs holds the invariants, tests/projections.rs the propagation. |
docs/CAPABILITIES.md | apps/majordomus-cli/src/capability/module.rs | test/cases/91_canonical_architecture.sh |
| Every externally callable operation of the Rust executable is a benchmark target with a denominator generated from the registry, and a missing case fails the structural check Each required executable is a target directly and on every transport its exposure declares, with the cases its input type provides; the transports' own operations are targets declared once; waivers are typed and never counted. Runners time the executor, a real loopback socket and a real majordomus mcp child; results are majordomus/benchmark-result/v1; the baseline is per platform under .ai/repo/benchmarks/rust/ and the policy is data. |
docs/CAPABILITIES.md | apps/majordomus-cli/src/bench/coverage.rs | test/cases/91_canonical_architecture.sh |
| After startup, no MCP or HTTP request of the Rust executable scans the repository, builds the index or the registry, derives a schema or builds a projection, and perf.counters proves it Process-wide counters, each incremented at one named place, answered as perf.counters over every transport; tests/hot_path.rs sends hundreds of requests through the real transports and requires the startup counters unchanged; the MCP listings and the OpenAPI document are prepared once. |
docs/CAPABILITIES.md | apps/majordomus-cli/src/perf.rs | test/cases/91_canonical_architecture.sh |
| A cached capability of the Rust executable answers the same value uncached, cold and warm, a hit runs no handler, errors and commands are never cached, and the key carries the registry fingerprint One executor for every transport; CachePolicy on the descriptor; bounded per capability; the key is the canonical id, the input in canonical form and a sha-256 fingerprint of the repository content and the descriptors. tests/executor.rs and the proptest harness in tests/properties.rs iterate every cached capability with every case and generated inputs. |
docs/CAPABILITIES.md | apps/majordomus-cli/src/capability/executor.rs | test/cases/91_canonical_architecture.sh |
| The provider bootstraps the policy declares (AGENTS.md, CLAUDE.md, GEMINI.md, ...) are a target of majordomus generate, rendered from the policy and the provider templates byte for byte as the shell tool renders them, and generate --check refuses a hand-edited or stale one in CI One renderer, two entry points that agree on every stamp; the policy is the only input (its hash and the profiles' is the stamp's first hash). A target outside the repository, a provider without a template, a token the policy cannot fill, and an always_loaded target over the budget are refused with the reason, and nothing is written. Region mode keeps the host document outside the markers. apps/majordomus-cli/tests/providers_projection.rs covers the fixture side; CI runs generate --check on every push. |
docs/CAPABILITIES.md | apps/majordomus-cli/src/providers.rs | test/cases/93_rust_provider_projections.sh |
| The site's Registry page is rendered from site/data/registry/registry.json, a dataset majordomus generate site derives from the registry and the index with their fingerprints, and generate --check refuses a stale one in CI Every builtin descriptor in full with the file it was composed in, every module with its ids, every object of the layer without its content, every kind with its schema, the declared provider projections, the command line as clap declares it, the MCP tools and resources, the HTTP routes, the benchmark targets, coverage, policy and accepted baselines, and the registry and index fingerprints (majordomus-site-registry/v2); no timestamps of its own, no absolute paths, no git state. The directory is the Rust executable's alone, the shell site generator owns site/data/generated/ wholesale, so no directory has two writers. A canonical object added to the layer changes the fingerprint and the dataset and leaves the bootstraps alone; an exposure removed from a descriptor moves the routes and the coverage and nothing else; the test follows each mutation to exactly the projection it concerns. |
docs/CAPABILITIES.md | apps/majordomus-cli/src/site.rs | apps/majordomus-cli/tests/projections.rs |
| A change runs the gates its paths can affect, decided by one model of what affects what, and a change to the pipeline itself or a path the model does not know runs every gate The gates that always run are in every plan; a changed path selects the gates of every class it matches, as a union; a class may escalate; a gate brings what it implies and what it requires; the plan is deterministic and says for every gate why it ran or did not. The workflow reads the plan's outputs and carries no path list of its own; test/cases/26_ci_wiring.sh proves every gate's job exists, is gated on the plan and is needed by the verdict. |
.ai/repo/ci/gates.yaml | scripts/ci-plan | test/cases/94_ci_plan.sh |
| The one status a branch rule requires is green only when planning succeeded and every gate the plan selected ran in a job that succeeded; a failed or cancelled job, a selected gate whose job was skipped, or a plan that selected nothing is red The verdict job always runs, so a required check is never left pending by a job the plan skipped, and it writes a table of every gate with its job's result and the reason it was or was not planned. The case drives it with fixture plans and needs contexts through every red condition. |
docs/CI.md | scripts/ci/verdict | test/cases/94_ci_plan.sh |
| The behavioural suite runs a bounded number of cases at a time with the serial runner's semantics, a case that writes into the checkout declares itself exclusive and runs alone, and the parallel phase fails naming the paths when the checkout changed under it MJ_TEST_JOBS bounds the pool (xargs -P, never a bare ampersand); each case has its own log; the verdicts are rendered in name order with a failing case's whole log before its line; a filter that matches nothing and an empty case directory are usage errors in both modes; MJ_TEST_REPORT writes one row per case for the summary. test/cases/26_ci_wiring.sh proves the failure semantics through both modes on a private harness. |
docs/CI.md | test/run.sh | test/cases/94_ci_plan.sh |
| The Rust executable the rust gate built is published as an artifact with its provenance, and every Rust case drives the executable MAJORDOMUS_BIN names instead of building one scripts/rust-check --artifact DIR copies the debug executable beside a majordomus-cli.json naming the commit, the target triple, the toolchain and the Cargo.lock digest; the workflow uploads it as majordomus-cli-<target>. rust_bin in test/lib.sh hands a case MAJORDOMUS_BIN when set, refuses one that is not executable, builds once with cargo when unset, and asks the case to skip when there is neither; the launcher bin/majordomus-mcp honours the same variable. |
docs/CI.md | scripts/rust-check | test/cases/94_ci_plan.sh |
| Publication is gated on everything that can make the published site wrong and waits for nothing else; the checks that decide whether a change may merge run beside it on the same commit, not in front of it The publication path proves the committed derived data current by its input hash, builds the site from it, runs every static check over the output and pushes gh-pages, in one job triggered directly by a master push on paths derived from the gate model; superseded runs are cancelled; the suite, the crate gates, coverage, the macOS suite, the benchmark check and the browser probe run in validate.yml on the same commit and gate merging. The case proves the trigger, the derived paths, the cancellation, the permissions, that no heavy gate is on the path and none left validate.yml, and that a build which skips the generation is byte for byte one that does not. |
docs/GITHUB_PAGES_PERFORMANCE.md | .github/workflows/pages.yml | test/cases/97_pages_fast_path.sh |
| The OpenAPI document is inferred, the tags are the modules, the examples are the benchmark cases, the responses are the router's statuses by kind, the prose is the one text every projection shares, and the site's API reference is rendered from the committed document Tags from registry.modules(), examples from the capabilities' BenchmarkCases against the repository's index, responses from one status table keyed by kind, info from about.rs and the crate manifest; benches/routes.rs times every route with the same cases; the site projects the committed document, renders /docs/api/ and serves /openapi.json raw, and site-check refuses a build without them. |
docs/CAPABILITIES.md | apps/majordomus-cli/src/http/openapi.rs | test/cases/92_openapi_reference.sh |
| What a worker reads of the repository is declared once in .ai/repo/scope.yaml, out wins over in, and the Rust executable discovers, indexes and serves nothing outside it The declaration is typed and schema-validated (share/schemas/majordomus/scope/scope.v1.schema.json; share/allow/scope.txt is generated from it for the shell tool); a repository declaring none is read under the distribution's default and says so; a discovered file outside the scope is an out_of_scope diagnostic naming the rule; a tracked secret is a tracked_secret warning; repository.scope and repository.scope_classify are projected to MCP, HTTP and `majordomus scope`. The crate's tests/scope.rs covers every branch at the library, socket and process level. The doctrine is project.scope-is-declared. |
docs/SCOPE.md | apps/majordomus-cli/src/scope.rs | test/cases/93_scope_policy.sh |
| Every public command is named and run by an active use case, a guaranteed claim or an MCP tool without one is a named gap, and the policy says which gaps fail doctor, check and finish The tally is computed from the command registry, docs/CLAIMS.yaml, the executable's registry and the use cases under .ai/repo/use-cases/; a draft never counts. The doctrine is majordomus.use-case-coverage, gated by policy use_cases.coverage and the finish key use_cases_covered. |
docs/USE_CASES.md | lib/usecase.sh | test/cases/94_use_cases.sh |
| A use case's scenario executes against the real tool in a disposable repository, every step's exit code and output are asserted, and the normalised evidence is what the site shows majordomus usecase run writes .ai/local/evidence/use-cases/<id>.json; scripts/generate-site-data runs the same scenarios and embeds the evidence into site/data/generated/catalogue.json, so --check proves the pages show what the tool did. Paths, timestamps, ids, hashes and durations are normalised; nothing behavioural is. |
docs/USE_CASES.md | lib/usecase.sh | test/cases/94_use_cases.sh |
| From the files a change touched, the tool names the commands, rules, use cases, scenarios and behavioural cases affected, and scaffolds a draft for a capability no use case covers usecase impact maps paths through docs/RESPONSIBILITIES.yaml, the dispatch table, the rules' identities and the scenarios' fixtures; usecase scaffold writes drafts from the registry, the command fixture and the claims, and never a guarantee. |
docs/USE_CASES.md | lib/usecase.sh | test/cases/94_use_cases.sh |
| The site is deployed by one script, scripts/site-deploy, from a terminal or from the publication workflow; it refuses a dirty tree, a commit master does not contain and a build that is not HEAD's, pushes site/public to gh-pages with the source commit named, and pushes nothing when the output is unchanged The case runs the whole path against a local bare remote, no network; the footer of every page names the commit, so a deploy is verifiable with curl. The workflow calls the same script with --skip-build after its gate. Pointing GitHub Pages at the branch is a one-time --configure-pages. The hand procedure is .ai/repo/skills/deploy-site/SKILL.md. |
docs/GITHUB_PAGES_ARCHITECTURE.md | scripts/site-deploy | test/cases/96_site_deploy.sh |
| A commit whose derived data is behind its canonical inputs is refused before it exists, and a repository that unwires that gate is a doctor failure The generator records the input hash in site/data/generated/source.json, so `pages current` answers "must this be regenerated?" in a fifth of a second without generating anything, which is what lets it run on every commit rather than only in CI. It is declared in the policy's enforcement list as derived-current, wired by git-hook:pre-commit, and doctor verifies that wiring the way it verifies the others - so the gate refuses the stale commit and the doctrine refuses the repository that removes the gate. Before this, staleness surfaced only after a merge, and the person it stopped was never the person who caused it. |
docs/GITHUB_PAGES_ARCHITECTURE.md | scripts/pages | test/cases/56_derived_current_gate.sh |
| The site's pages about the Rust executable (one per module, one per capability, the command line, the MCP surface, the benchmarks, the executable's own narrative) are derived from the registry manifest and the registry dataset the executable generates, nothing names a capability by hand, and a capability that joins or leaves the registry gains or loses its page, its index entries and its links from the generators alone scripts/generate-site-data reads docs/generated/registry.json — a code-only projection, so no cycle runs through the index — for the ids it turns into routes, and writes site/data/generated/executable.json (routes, module and API links, source on GitHub, the claims attached to each surface by the path of their implementation) and the content stubs under site/content/registry/; the templates read every other fact from site/data/registry/registry.json. A manifest naming a module the registry does not hold, a source file the tree does not have, or another schema is refused and nothing is published. scripts/site-check proves the rendered side, every module and capability a page and no page without its entry, the CLI, MCP and benchmark pages rendering every command, tool, target and baseline, every capability page linking its module and its operation, and no template, generator or navigation file naming a capability. The manifest is among the site's inputs, so its hash moves with the executable's code. |
docs/GITHUB_PAGES_ARCHITECTURE.md | scripts/lib/executable-site.jq | test/cases/95_executable_reference.sh |
| Every committed derived artifact of the repository is regenerated by one command in dependency order and checked by one read-only command that names every stale artifact, a second regeneration on a clean tree changes nothing, and no derived file records the commit it lands in scripts/derive runs majordomus generate (the code-only projections), scripts/generate-site-data (the site's data and content and the derived documents, reading the registry manifest and the OpenAPI document), and majordomus generate again (the site dataset and the benchmark matrix over the index the documents joined); no stage reads its own output. scripts/derive-check composes generate --check and generate-site-data --check and exits 10 with both reports. The commit a page was built from is a fact of the build (site/data/build.json, served at /build.json, never committed), so source.json no longer carries it and the generator is idempotent on a clean tree. just derive, just derive-check and the Pages workflow call the scripts; just test runs the check. |
docs/GITHUB_PAGES_ARCHITECTURE.md | scripts/derive | test/cases/51_derived_artifacts_committed.sh |
| A skill is one directory under the layer's skills section holding SKILL.md, discovered by the source class `skill` the shell tool and the Rust executable both read, so that adding the file is the whole registration and the catalogue, the command, doctor, MCP and the site all derive from it share/kinds.yaml declares the kind, share/schemas/majordomus/skill/skill.v1.schema.json its contract, and the source classes skill and skill_example in .ai/repo/knowledge/sources.yaml which files carry it. Nothing else names a skill. lib/skills.sh derives one catalogue from that discovery for `skills list|show|check`, the doctrine validator and scripts/generate-site-data; the Rust index serves the same files as majordomus://skill/<id> resources, through majordomus_list and majordomus_get, with no code that knows the kind. The case adds, breaks, renames and removes a skill and watches every surface follow. |
docs/SCHEMAS.md | lib/skills.sh | test/cases/95_skills.sh |
| A task that declares obligations reaches the outcome completed only when each one has evidence recorded against it, and evidence taken over files that have since changed, or at a commit the branch has since left, no longer discharges anything The obligation vocabulary is share/obligations.yaml, so a token is added there and not in shell. Evidence is a task.evidence ledger line carrying the hash of the tracked files the obligation names; the validator recomputes that hash and reports both values when they differ, the way the site's own currency check already does. An obligation whose fact is remote is bound to the commit it was taken at instead, and judged with mj_git_label rather than a fifth staleness vocabulary. An outcome other than completed is never refused for owing something. |
docs/CLI.md | lib/evidence.sh | test/cases/103_obligations.sh |
| An obligation whose fact the tool can hold is established live rather than recorded — a clean tree, a remote-tracking ref that reaches the head, a trunk that reaches it, a published site that serves it — and a hand-recorded line neither discharges it nor rescues it share/obligations.yaml gives every token an established_by, and one of `none` obliges it to say in `unestablished` why nothing here can settle it. commit, push and target are settled from git without fetching, so a stale tracking ref can only say "not yet" and never "yes"; pages is settled by scripts/pages verify with --timeout 0, the single probe of the publication check the Pages workflow already runs, which now exits 12 rather than 10 when the site never answered — unreachable and unpublished are different findings. A fact established this way is taken at HEAD, which is mj_git_label's `exact`; the other three labels keep their meaning on the fallback path, and deploy and verify stay hand-recorded because a deployment is a fact about a machine this repository never contacts. |
docs/CLI.md | lib/evidence.sh | test/cases/103_obligations.sh |
| Every skill is validated against the allow-list generated from its schema, its directory, its sections and its references, a violation names the file and every reason, and a repository with no skills is reported rather than passed `skills check` and the doctrine majordomus.skill-integrity (dispatched by doctor and watch) run one examination — unknown keys, schema version, integer version, closed status set, id equal to the directory, non-empty Purpose, Procedure and Output sections, duplicate ids, descriptions that do not tell two skills apart, related ids that resolve, examples with a heading — and print the counts of what they examined. The site generator refuses to build from a skill that does not validate. |
docs/CLI.md | lib/skills.sh | test/cases/95_skills.sh |
| The website's skills section is rendered from the skills catalogue, one page per skill carrying the skill's own body and examples, and a removed or renamed skill leaves no page, entry or link behind site/data/generated/skills.json and site/content/skills/ are written by the generator from lib/skills.sh's catalogue, the content directory replaced whole on every run; generate-site-data --check exits 10 when a skill changed or the projection was edited by hand, and CI runs that check before the site builds. |
docs/GITHUB_PAGES_ARCHITECTURE.md | scripts/generate-site-data | test/cases/95_skills.sh |
| An operational moment is one file under the layer, discovered rather than registered, and answered by the command line, the HTTP API, the OpenAPI document, MCP, the derived graph and the website with nothing else changed Removing the file removes it from all of them, which is what a hidden registry would fail. |
docs/WHY.md | apps/majordomus-cli/src/why.rs | test/cases/98_why_catalogue.sh |
| Every reference a moment makes resolves against the thing it names, and one that does not is an error carrying the nearest candidate Audiences, areas and related moments against the catalogue; commands, capabilities, claims, rules and use cases against their own registries. |
docs/WHY.md | apps/majordomus-cli/src/why.rs | apps/majordomus-cli/tests/why.rs |
| A diagnosis of a reader's own symptoms is deterministic counting over the catalogue's metadata, and every recommendation names the moments that produced it No weighting and no percentage; the questionnaire on the website and `majordomus why diagnose` run the same arithmetic over the same data. |
docs/WHY.md | apps/majordomus-cli/src/why.rs | apps/majordomus-cli/tests/why.rs |
| A closed execution episode is a shared object of the layer, written by the tool from git and the ledger, valid against a contract that admits no conversation and no absolute path, and discovered rather than registered The record lives under the manifest's sessions section. Every field is derived - times from the clock, heads and commits from git, the reference lists from the ledger's events for the episode - and the body is the only authored part. The repository is named by its remote and the working copy by a hash, so the resolver can tell one checkout's records from another's without disclosing a path. One source class discovers them; the index, MCP, the object routes, the graph and the site follow. |
docs/SCHEMAS.md | lib/session.sh | test/cases/63_session_records.sh |
| A provider hook opens the episode below the model and hands the worker what the last one left, records what a compaction is about to discard, and closes the episode with a continuation record beside its envelope The provider's own SessionStart, PreCompact and SessionEnd events draw the boundary and drive the lifecycle; each is idempotent because the events are, and doctor proves the wiring by driving a payload through the end shim with the mutation disabled. The start event writes the policy's briefing to stdout, which is how a continuation record reaches a worker who did not ask for one; the other two write none, because they fire inside a turn already under way. The working context each open freezes is local, appended to rather than rewritten, and refused if it carries a conversation. |
docs/DOCTRINE.md | lib/session_context.sh | test/cases/54_session_lifecycle_hooks.sh |
| An architecture decision is one file under the layer's adrs section, validated against the decision contract, with an identity nothing else claims, reciprocal supersession, and every reference it makes resolving share/kinds.yaml declares the kind, share/schemas/majordomus/adr/adr.v1.schema.json its contract (share/allow/adr.txt is generated from it), and the source class adr in .ai/repo/knowledge/sources.yaml says which files carry it; nothing else registers a decision. lib/adr.sh derives one catalogue from that discovery for `adr list|show|check` and for mj_validate_adr, the validator of majordomus.adr-integrity dispatched from doctor and watch. The case exercises duplicate identities, an unknown status, a schema version nothing reads, one-sided supersession, a superseded record with no replacement and a missing body section. |
docs/SCHEMAS.md | lib/adr.sh | test/cases/99_adr.sh |
| A decision states what it put in force as typed references, each one validated, and the reverse direction — what a rule, a document, an implementation or a case was decided by — is read from the knowledge graph rather than written down a second time The forward edge is `related` in the decision's front matter - rule:<id>, claim:<id>, file:<path>, test:<path>. Validation refuses a rule the effective set does not have, a claim the matrix does not have and a path the repository does not contain. The extractor turns each into a graph edge (declares, supports, references, tested_by) whose provenance is the front-matter key that stated it, so `knowledge edges` answers both directions from one authored fact. |
docs/SCHEMAS.md | lib/adr.sh | test/cases/99_adr.sh |
| majordomus adr propose writes a decision with status proposed and refuses to write any other status, allocates its identity under a lock so concurrent workers never collide, and records what the decision was derived from Acceptance is the person's act — a tool that can write `accepted` turns its own inference into repository truth and a later reader cannot tell which, so propose has no flag for it and an extracted record claiming it is a finding. An extracted record must name its evidence in provenance.derived_from, whose references are typed and whose file and test references must resolve; it may still be superseded, because that is something a later record did to it. The identity is allocated under a lock over the section directory, and the case proves it with real concurrent processes, because this repository shipped two 0005s and two 0007s before anything checked. |
docs/SCHEMAS.md | lib/adr.sh | test/cases/99_adr.sh |
| Every command of the Rust executable's command line carries a summary, a long description, help on every argument and at least one example, and every example shown to a reader is executed against the built executable The documentation and the examples are declared beside the clap declaration, as typed Rust; an example is an argument vector and the command line a reader sees is composed from it. capabilities validate exits 10 naming every violation with the command, the file and the rule; the suite parses every documented vector against the canonical parser and runs every example against the compiled binary in a disposable repository, the two long-running commands as bounded probes. An example the suite deliberately does not run carries its reason and shows it wherever the example is shown. |
.ai/repo/rules/project/rust-cli-evidence.v1.md | apps/majordomus-cli/src/cli.rs | apps/majordomus-cli/tests/cli_docs.rs |
| Every web surface is declared once at its producer and resolved into one topology, from which the router, the landing page and the machine-readable index are each projected A surface is a mount, a producer, a kind, a category, a visibility and the provenance of each; the routes the executable answers come from the capability declarations that already exist, a generated directory declares itself beside its output, and the application's site is read from the site generator's configuration. There is no central list. The case asks the page and the index independently and requires the same set of public mounts, so a link written into the page by hand fails it. |
.ai/repo/adrs/0013-every-web-surface-is-discovered-from-its-producer-resolved-o.md | apps/majordomus-cli/src/web/model.rs | test/cases/89_web_surface.sh |
| /docs serves this repository's documentation and /swagger serves the Swagger UI, and neither may take the other's mount The Swagger UI held /docs until documentation arrived and wanted the name it had already been given. The case asserts the mounts as data before asserting them as routes, refuses a Swagger producer at /docs, and proves the documentation mount does not swallow /swagger or /openapi.json once it is built. Unbuilt documentation answers 503 naming the command that builds it rather than the viewer that used to answer there. |
.ai/repo/rules/project/web-surface-declared-once.v1.md | apps/majordomus-cli/src/web/discover.rs | test/cases/89_web_surface.sh |
| A branch's worktree path is derived from git identity and the branch name alone — the primary checkout's sibling named with -wt, then the branch with its hierarchy kept — and is the same answer from every directory of every worktree, with nothing registered or configured The identity is the common git directory and the main worktree git lists first, never the current directory, so a linked worktree does not derive a container inside itself. The branch name is validated by git's reference rules and the derived path is proved on every call to stay below the container. Every registered worktree gets a standing and every condition a stable code with a remedy; the four worktree.* capabilities project the same typed topology to MCP, HTTP, OpenAPI, the Swagger UI and the Cockpit, and the pre-commit hook asks the guard. |
docs/WORKTREES.md | apps/majordomus-cli/src/worktree/path.rs | test/cases/96_worktree_topology.sh |
| A misplaced worktree is brought to its canonical path with its modified, staged, unstaged and untracked work intact, verified by a fingerprint taken before and after the move, and a step is reported as moved only when the two are equal The plan changes nothing and is recomputed under the repository lock before it is applied. Git moves the directory in one rename; the container's occupant goes out to a staging path and then in; a move across filesystems is refused unless a copy is permitted, and then verified against a manifest of every entry before the original is removed. Nothing is reset, stashed, cleaned, checked out or overwritten: a destination that exists is a named conflict, a locked worktree waits, a detached one is never moved, and a session's scratch checkout is reported and moved only on request. |
docs/WORKTREES.md | apps/majordomus-cli/src/worktree/migrate.rs | test/cases/96_worktree_topology.sh |
| A product feature is one file under the layer, discovered rather than registered, and answered by the command line, the HTTP API, the OpenAPI document, MCP, the derived graph, the capability matrix and the website's homepage and feature pages with nothing else changed Removing the file removes it from all of them, which is what a hidden registry would fail. The kind, its schema and its source class are declarations, so discovery, validation, identity, the index and the MCP resource needed no code of their own. |
docs/PRODUCT.md | apps/majordomus-cli/src/product.rs | test/cases/97_product_features.sh |
| Which interfaces a feature is exposed through, what stands behind it and what it answers are derived from the registries that own those facts, never authored, and a source file that writes one down is refused by its schema A feature file holds references and editorial decisions only. The surfaces come from what its modules, commands and kinds are projected onto, the counts from the registry and the index, the moments from the why catalogue, the route from the id. The schema sets additionalProperties false, so `surfaces:` or `route:` in a feature is a validation error rather than a claim a page believes. |
docs/PRODUCT.md | apps/majordomus-cli/src/product.rs | apps/majordomus-cli/tests/product.rs |
| Every reference a feature makes resolves against the registry that owns it, and one that does not is an error naming the file, the key and the nearest candidate rather than a page linking to nothing A capability module, a public command or an object kind that no stable feature names is reported as a gap and shown on the matrix rather than refused: a thing the product does that the product page does not mention is what the model exists to make visible. |
docs/PRODUCT.md | apps/majordomus-cli/src/product.rs | apps/majordomus-cli/tests/product.rs |
| The dataset the website is rendered from carries only fields an allow-list names, so a feature's prose, a machine path or a field nobody allowed cannot reach a published page The boundary is a field-by-field copy through PUBLIC_FEATURE_FIELDS, not an exclusion that could be forgotten: a field added to the model is absent from the site until somebody names it. The test asserts no un-allow-listed key, no body, no fixture root and no temporary path; scripts/generate-site-data refuses the artifact again before rendering anything from it. |
docs/PRODUCT.md | apps/majordomus-cli/src/site.rs | apps/majordomus-cli/tests/product.rs |
| The website's homepage and feature pages name no feature, module, command, provider or count of their own, and a stale product dataset fails the build before it can be deployed scripts/site-check refuses a template, a generator, a jq program or a navigation file that names a feature route or a provider's own file, proves every public feature has its page and every page a feature, and compares the marks the matrix shows with the marks the model derived. `majordomus generate --check` refuses a tree whose dataset does not match the repository. |
docs/GITHUB_PAGES_ARCHITECTURE.md | site/templates/index.html | test/cases/12_site_build.sh |
advisory advisory
planned planned
| claim | source | implementation | test |
|---|---|---|---|
| A task will know which commits it produced, rather than inferring them from a range Today scope is checked with git diff base..HEAD, and a commit range has no author, so another session committing to the same branch in the same checkout has its files counted as this task's work. Recording each commit as it happens turns the question from a heuristic about ranges into an enumeration. The list would be a lookup that git overrules, never authority, and a rewritten commit would be reported as unrecognised rather than resolved to a guess. |
docs/CONTINUITY.md | — | — |
| Opt-in runtime adapters will clamp read size, output size and fan-out from profile-derived limits The one implementation studied was rolled back within days because its limits were global constants rather than task-derived. |
docs/DESIGN.md | — | — |
| Execution telemetry will be recorded once a provider exposes it honestly Nothing measures token spend today. Any estimated_ field is labelled and excluded from enforcement. |
docs/DESIGN.md | — | — |
| Cost per accepted outcome will be reported from measured data only | docs/DESIGN.md | — | — |
| Routing recommendations will be derived from measured cost and outcome data Recommendations only. Majordomus does not invoke a model. |
docs/DESIGN.md | — | — |
| One policy will be shared across repositories and workers | docs/DESIGN.md | — | — |
rejected rejected
| claim | source | implementation | test |
|---|---|---|---|
| Ranked or semantic retrieval over durable records is deliberately not implemented The corpus is a handful of Markdown files and one JSONL. An index or an embedding store would be a second source of truth that has to be kept in step with the first, and a scan is faster than that problem. Revisited only if a repository appears where the scan is measurably too slow. |
docs/CONTINUITY.md | — | — |
| A task record has no dependencies; sequencing between sessions is not represented Refused for task records, and only for them. Issues and milestones do carry dependencies, validated as two graphs — see docs/ROADMAP.md and docs/PLANNING.md. What stays refused is a task, the record of one session's work, waiting on another; one task is active per checkout and coordination between checkouts is the scope overlap report. The original wording said no dependency graph existed anywhere, which stopped being true when the plan model landed. |
docs/DESIGN.md | — | — |