Skip to content

majordomus evidence

Record evidence against the active task: one obligation it declared, discharged, or one validation gate of the CI model that has reported — each as a ledger line carrying the hash of the files it was taken over.

Record evidence against the active task: one obligation it declared, discharged, or one validation gate of the CI model that has reported — each as a ledger line carrying the hash of the files it was taken over.

  • state-mutating
  • task
  • active task required
  • --json

Every argument the shell 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 (docs/EVIDENCE.md), and the launcher hands them to it with every argument unchanged. Which words those are is read from the executable's declaration, never listed in the launcher. They need no active task, their exit codes are the executable's, and what each reads and writes is on its own page under /docs/cli/evidence/.

Syntax

majordomus evidence (--covers <token> [--type <kind>] (--command <cmd> | --artifact <ref>) | --gate <id> --exit <status>) [--result <r>] [--json] | majordomus evidence <show|claim|proves|record|stamp> [options]

Lifecycle

  1. Set up
  2. Begin
  3. Work
  4. Verify
  5. Conclude
  6. Inspect

Other commands at this stage:context, checkpoint, decision, question, prompt, skills, capture.

Reads

  • share/obligations.yaml
  • .ai/repo/ci/gates.yaml
  • .ai/local/state/current.yaml

Writes

  • .ai/local/state/ledger.jsonl

Exit codes

Only the codes this command can produce. The full contract is on the commands index.

  • 0 ok
  • 2 usage
  • 12 missing artifact
  • 13 internal error
  • 15 refused

What the task owed, and what proves it

A task's scope says where a worker may write. Its requires says what the worker owes. Evidence discharges one obligation, and carries the hash of the files that obligation names, so that changing any of them takes the proof away rather than leaving it behind.

Recording a discharge

active task
yes
the task owes tests
yes

Given this repository

# ... and an active task that owes an obligation, with work done inside its scope. The
# obligation is written into the record the way a caller would declare it, because what the
# scenario is about is the discharge, not how the list got there.
. "$FIXTURE_SETUP/installed.sh"
"$MJ" start "narrow the parser" --scope lib >/dev/null
echo work >> lib/a
python3 - <<'PY'
import io
p = '.ai/local/state/current.yaml'
s = io.open(p, encoding='utf-8').read()
io.open(p, 'w', encoding='utf-8').write(s.replace('outcome:', 'requires:\n  - tests\noutcome:'))
PY

Run

$ majordomus evidence --covers tests --type test --command bash test/run.sh

Output contains

  • evidence: tests recorded

exit 0

The line lands in the ledger, which is already append-only and integrity-checked, and carries the hash of the tracked files the obligation names. Nothing else is written; a second store of evidence would be a second account of the same facts.

For something the task never promised

active task
yes
the task owes tests
yes

Given this repository

# ... and an active task that owes an obligation, with work done inside its scope. The
# obligation is written into the record the way a caller would declare it, because what the
# scenario is about is the discharge, not how the list got there.
. "$FIXTURE_SETUP/installed.sh"
"$MJ" start "narrow the parser" --scope lib >/dev/null
echo work >> lib/a
python3 - <<'PY'
import io
p = '.ai/local/state/current.yaml'
s = io.open(p, encoding='utf-8').read()
io.open(p, 'w', encoding='utf-8').write(s.replace('outcome:', 'requires:\n  - tests\noutcome:'))
PY

Run

$ majordomus evidence --covers docs --command scripts/ci/reference-check

Output contains

  • does not require 'docs'

exit 15

Recording evidence against an obligation nobody declared is how a checklist grows entries nobody asked for. The refusal names what the task does require.

Without a command or an artifact

active task
yes
the task owes tests
yes

Given this repository

# ... and an active task that owes an obligation, with work done inside its scope. The
# obligation is written into the record the way a caller would declare it, because what the
# scenario is about is the discharge, not how the list got there.
. "$FIXTURE_SETUP/installed.sh"
"$MJ" start "narrow the parser" --scope lib >/dev/null
echo work >> lib/a
python3 - <<'PY'
import io
p = '.ai/local/state/current.yaml'
s = io.open(p, encoding='utf-8').read()
io.open(p, 'w', encoding='utf-8').write(s.replace('outcome:', 'requires:\n  - tests\noutcome:'))
PY

Run

$ majordomus evidence --covers tests

Output contains

  • narrative is not evidence

exit 2

An assertion that something was done is the thing this command exists to replace. Either a command produced the evidence or an artifact points at it.

With no active task

active task
no

Given this repository

# A repository with Majordomus installed and projections generated, and one commit of work.
"$MJ" init >/dev/null
"$MJ" update >/dev/null
mkdir -p lib docs
echo a > lib/a
echo d > docs/d
git add . && git commit -qm base

Run

$ majordomus evidence --covers tests --command bash test/run.sh

Output contains

  • no active task

exit 12

An obligation is owed by a task. With nothing to attach it to, the evidence would describe no promise.

Reference

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:

statusmeaning
passit ran over these inputs and exited 0
failit ran over these inputs and did not
staleit ran, and the files that select it have changed since
blockedsomething it cannot run without has not passed
queuedthe plan selects it and no run has ever reported
exemptnothing this change did can make it true or false
unknownit 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:

questionasksread fromwhen it is not pass
no-new-debtis 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 treefail, naming each baseline that grew and by how much
nothing-accumulatedhas every branch and worktree created since gone again?git's reflogs: when each local branch and each linked worktree beganqueued, naming what remains, with the totals now
backlog-not-grownhas 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.

Evidence

Every case below runs in CI on Linux and macOS. The site refuses to build if a public command has no behavioural case, no negative case, or no demonstration.

Demonstration

Tested by test/cases/34_command_fixtures.sh, which replays the fixture and compares it with what the command prints.

Guarantees this command carries

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