Record evidence against the active task: one obligation it declared, discharged, or one validation gate of the CI model that has reported.
majordomus evidence --covers <token> [--type <kind>] (--command <cmd> | --artifact <ref>) [--result <r>] [--json]
majordomus evidence --gate <id> --exit <status> [--command <cmd>] [--result <r>] [--json]
majordomus evidence <show|claim|proves|record|stamp> [options]
The third form is the other program's. Every argument this command takes is an option, so a
word in the first position is never its own: show, claim, proves, record and stamp
are subcommands of the Rust executable, which holds the ledger of what ran
(EVIDENCE.md), and the launcher hands them to it with
every argument unchanged — --repo on either side of the command included. Their exit
codes are the executable's, and they need no active task. Which words those are is read
from the executable's own declaration (docs/generated/cli.yaml in a checkout, the shipped
executable's help evidence <word> in an installed tree), so majordomus evidence --help
lists exactly the subcommands this installation reaches. A word neither program has is
refused here, exit 2. The rest of this section is about the first two forms.
The two forms record different kinds of fact and are never one invocation. An obligation is
a promise the task made; a gate is what the validation pipeline said about the tree. A line
claiming to be both would be readable as neither, so passing --covers and --gate
together exits 2.
A task's scope says where a worker may write; its requires says what the worker owes
before the outcome completed is available. The tokens are declared in
share/obligations.yaml — implementation, tests, docs, generated, rules, commit, push,
target, pages, deploy, verify — each naming the command that discharges it and, where the
fact is local, the pathspecs its evidence is taken over.
Four of them are never recorded here. commit, push, target and pages name facts the
tool can establish — a clean tree, a remote-tracking ref that reaches the head, a trunk that
reaches it, a published site that serves it — and check and finish settle those live at
HEAD instead of asking a worker to transcribe them. Recording one has no effect: an
established obligation discharges by being true and refuses by being false. Where the
checkout cannot settle it (no remote, no default branch recorded, a site that never
answered) the token falls back to the recorded line, and the finding says which it was.
deploy and verify stay hand-recorded, and share/obligations.yaml says in one line why:
a deployment is a fact about a machine this repository never contacts.
--command or --artifact is required: narrative is not evidence. A token the vocabulary
does not declare exits 2; a token the active task never promised exits 15, because
recording evidence for something nobody asked for is how a checklist grows entries nobody
wanted.
Writes: a task.evidence line in the ledger, carrying the obligation, how it was taken,
the command or artifact, and the hash of the tracked files the obligation names. Nothing
else is written; the ledger is already append-only, ordered and integrity-checked, and its
envelope already carries the head, the branch and the session.
That hash is the point. Evidence discharges an obligation only while the recomputed hash of
its inputs equals the recorded one, so a change to any file the obligation names takes the
proof away rather than leaving it behind — the same currency question the site's own
source_hash asks, asked of a test result. An obligation whose fact is remote (a push, an
integration, a publication, a deployment) has no inputs and is bound instead to the commit
it was taken at, judged exact | advanced | diverged | different_context like every other
record here.
check and finish evaluate every obligation on every run through the doctrine
majordomus.obligation-closure, so a stale evidence is visible before someone builds on
it; only an outcome of completed is refused. A worker reporting blocked is being honest,
and refusing that would teach them to claim completed instead.
Recording a gate
--gate names a gate of .ai/repo/ci/gates.yaml, this repository's CI model, and --exit
the status it reported; 0 is a pass. A gate the model does not declare exits 2, as does
an --exit that is not a number.
majordomus evidence --gate rust-check --exit 0 --command 'scripts/rust-check --ci'
Writes: a task.gate line in the ledger, carrying the gate, its exit status and the hash
of the files that select it. Those files are derived from the model rather than declared:
the union of the paths of every class that names the gate, and everything for a gate every
plan selects, because a gate every plan selects is a gate any change can invalidate. Change
one of them and the run stops discharging the gate — committed or not, since the change set
a task is judged over includes its working tree.
check and finish evaluate every gate the task's own change set selects, through the
doctrine majordomus.completion-gates, and report each in one vocabulary:
| status | meaning |
|---|---|
pass | it ran over these inputs and exited 0 |
fail | it ran over these inputs and did not |
stale | it ran, and the files that select it have changed since |
blocked | something it cannot run without has not passed |
queued | the plan selects it and no run has ever reported |
exempt | nothing this change did can make it true or false |
unknown | it cannot be judged here at all — a model that does not parse, a reader that is not built, errors or answers nothing, no jq |
fail, stale, blocked and unknown refuse the outcome completed: completed is a
claim that the verdict is known, and a verdict that could not be read cannot back it. Every
other outcome is still accepted over any of them, named "not refused", and check reports
without refusing. A repository that declares no CI model at all has no gate to be unknown
about and refuses nothing. queued is reported by
name, never accepted as a pass and never refused: a verdict that never arrived and a verdict
that said pass are different facts, and on 2026-09-10 this repository's trunk carried three
branch-breaking defects overnight because they looked identical
(project.never-reported-is-not-green).
The whole judgement — every gate, the plan that selected it, which obligations the change
implies, and the questions of the done invariant with the source that answered each — is
one document, gates.completion, read the same way by the command line, the HTTP API, MCP
and the Cockpit:
majordomus-cli run gates.completion --input '{}' --format json | jq '.output.questions'
The questions are declared once, in share/completion.yaml, which the tool ships: the
stages of the work in order (implementation, validation, documentation, commit, gates,
version, integration, deployment, verification, closing, provenance), every question in the
stage it belongs to, and the source its answer is taken from — an obligation, a gate, the
change set, the repository's convergence or a closing reading. gates.policy
(majordomus_completion_policy, GET /api/v1/gates/policy) serves the file with what it
resolves to here: a token the vocabulary lacks is a problem, and a gate question this
repository's CI model never answers is listed under unanswered_gates. The report asks
exactly those questions in that order, and folds the answers into stage — the first stage
blocked, else the first with a question still owed, else the last — which is derived on
every reading and written down by nobody. complete is true only when a task is present
and every question passes or is exempt; finishable and complete are two different
words. A distribution whose policy cannot be read is asked one question, policy, answered
unknown, and nothing is complete.
The closing questions
Three of those questions are the ones a person asks when a session ends, and a worker is
not their source (project.a-closing-question-is-answered-by-measurement). Each is read
from the record that holds the fact, measured from where the task started:
| question | asks | read from | when it is not pass |
|---|---|---|---|
no-new-debt | is the recorded debt no larger than when the task started? | the entries of every .ai/repo/*-baseline.txt at the commit the task started at and in the working tree | fail, naming each baseline that grew and by how much |
nothing-accumulated | has every branch and worktree created since gone again? | git's reflogs: when each local branch and each linked worktree began | queued, naming what remains, with the totals now |
backlog-not-grown | has every pull request opened since been closed? | the recorded forge observation (majordomus prs refresh) | queued, numbering them; unknown when the forge was not observed since the task started |
They are the repository's readings, not one worker's: git does not say who created a
branch, so a branch another session created since the task started is counted. A reading
that could not be taken is unknown and says why; it is never a pass. majordomus check
names each question still owed as id=status on its done line, so the answer to "is
everything done, and did this leave the repository bigger than it found it?" is one command
and not a recollection; the evidence behind each word is in the questions of the document
above.