Skip to content

obligations.closure

Every obligation the active task declared, joined with what the vocabulary says about it and with the evidence that does or does not discharge it: what is owed, what is discharged, and what has gone stale — with the recorded input hash and the tree's current one, or the recorded commit and its label, so a reader can see against what. The judgement is the one `finish` applies, reproduced rather than re-decided, and the staleness words are the repository's only four. A checkout with no task reports that, rather than reporting nothing owed.

Every obligation the active task declared, joined with what the vocabulary says about it and with the evidence that does or does not discharge it: what is owed, what is discharged, and what has gone stale — with the recorded input hash and the tree's current one, or the recorded commit and its label, so a reader can see against what. The judgement is the one `finish` applies, reproduced rather than re-decided, and the staleness words are the repository's only four. A checkout with no task reports that, rather than reporting nothing owed.

Exposure

Where this one definition is reachable. Absence is explicit: a surface not listed does not carry it.

surfaceas
MCPtool majordomus_obligation_closure
resource majordomus://obligations/closure
HTTPGET /api/v1/obligations/closure operationId obligations.closure
command line

Schemas

The canonical input and output, from the Rust types; the MCP tool schema and the OpenAPI parameters and responses are derived from these.

input · Empty

No input.

No properties: the capability takes no input.

JSON Schema
{
  "additionalProperties": false,
  "description": "No input.",
  "title": "Empty",
  "type": "object"
}

output · Closure

What this checkout's active task owes, and how much of it is still true. A clone that has never run the lifecycle answers this too, and answers it as absence: `present` false, no task, and a finding saying so. "Nothing owed" and "nothing to owe it" are different facts, and a served instance must not report the second as the first. ``` use majordomus_cli::capability::builtin::obligations::Closure; let fresh: Closure = serde_json::from_str( r#"{"present":false,"worktree":"/srv/clone","branch":"master","closed":false, "tallies":{}, "findings":["no active task in this checkout (.ai/local/state/current.yaml); nothing owes anything here"]}"#, ) .unwrap(); assert!(!fresh.present && !fresh.closed, "nothing that does not exist is closed"); assert!(fresh.task.is_none() && fresh.obligations.is_empty()); assert_eq!(fresh.findings.len(), 1, "absence is reported, not implied"); ```

propertytyperequireddescription
branch string yes The branch, or `DETACHED`.
closed boolean yes True when the task declares obligations and every one of them is discharged and current — that is, when `majordomus.obligation-closure` would not refuse `finish --outcome completed`. False when anything is owed, stale or undeclared, and false when there is no task: nothing that does not exist is closed.
findings array no What a reader should know before trusting any of the above: an unreadable vocabulary, a ledger that could not be read, a task that declares nothing. Empty is the ordinary case.
head string no The commit this checkout is on.
obligations array no Every obligation the task declared, in the order it declared them.
present boolean yes Whether this checkout has a task to report about at all. False in a clone that has never run the lifecycle, which is not a fault and is not "nothing owed".
tallies object yes How many obligations stand where, by state word. Absent states are absent rather than zero, so a reader never has to know the vocabulary to read the tallies.
task one of 2 no The active task, or `None`. Its `requires` is the list the entries below expand.
working_tree string no `clean` or `dirty`.
worktree string yes The worktree this answer is about. Every reading below is scoped to it, and to no other checkout of the same repository.
JSON Schema
{
  "$defs": {
    "ActiveTask": {
      "description": "The active task of this checkout, when there is one.",
      "properties": {
        "head": {
          "description": "The commit it started at.",
          "type": "string"
        },
        "id": {
          "description": "The task id.",
          "type": "string"
        },
        "outcome": {
          "description": "Its typed outcome so far: `active`, `handed_over`, or a finished one.",
          "type": "string"
        },
        "profile": {
          "description": "The execution profile it runs under.",
          "type": "string"
        },
        "requires": {
          "description": "The obligations it owes before the outcome `completed` is available.\n\nBeside `scope` and not inside it, because the two are different promises: scope is\ncontainment — where a worker may write — and this is delivery. A change can sit\nentirely inside its scope and still be uncommitted on a laptop (ADR 0030).\n[`super::obligations`] is what judges each of these against its evidence; here it\nis reported as declared.",
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "scope": {
          "description": "The paths it claims.",
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "started_at": {
          "description": "When it started.",
          "type": "string"
        },
        "task": {
          "description": "What is being worked on.",
          "type": "string"
        }
      },
      "required": [
        "id",
        "task",
        "profile",
        "outcome"
      ],
      "type": "object"
    },
    "Divergence": {
      "description": "How far a record's recorded commit is from the commit this checkout is on.\n\nThe four words are the shell tool's, deliberately. A reader that met `advanced` from one\nsurface and `stale` from another would have to learn the same four facts twice.",
      "oneOf": [
        {
          "const": "exact",
          "description": "Written at this commit. Trust it.",
          "type": "string"
        },
        {
          "const": "advanced",
          "description": "Git has moved forward since. Trust it, and expect some of it to be done.",
          "type": "string"
        },
        {
          "const": "diverged",
          "description": "The recorded commit is not an ancestor: history was rewritten. Trust git, not this.",
          "type": "string"
        },
        {
          "const": "different_context",
          "description": "Another branch or another worktree. This record is not about your work.",
          "type": "string"
        },
        {
          "const": "unknown",
          "description": "Git could not answer, so this process says so rather than guessing `exact`.",
          "type": "string"
        }
      ]
    },
    "Evidence": {
      "description": "The `task.evidence` line that discharged an obligation, as the ledger holds it.\n\nNothing older is consulted: evidence is superseded by evidence, and the ledger keeps\nthe history for a reader that wants it.\n```\nuse majordomus_cli::capability::builtin::obligations::Evidence;\nlet e: Evidence = serde_json::from_str(\n    r#\"{\"recorded_at\":\"2026-09-09T21:01:00Z\",\"head\":\"f00ba7\",\"branch\":\"master\",\n        \"kind\":\"ci\",\"command\":\"scripts/ci/reference-check\",\"inputs_hash\":\"7f68b9\"}\"#,\n)\n.unwrap();\nassert_eq!(e.head, \"f00ba7\", \"the envelope's commit is what a remote fact is judged against\");\nassert!(e.artifact.is_empty(), \"one of command and artifact; narrative is not evidence\");\n```",
      "properties": {
        "artifact": {
          "description": "The reference it points at, such as a published URL.",
          "type": "string"
        },
        "branch": {
          "description": "The branch it was recorded on.",
          "type": "string"
        },
        "command": {
          "description": "The command that produced it. Narrative is not evidence, so one of this and\n`artifact` is always present.",
          "type": "string"
        },
        "head": {
          "description": "The commit the ledger's envelope stamped on it.",
          "type": "string"
        },
        "inputs_hash": {
          "description": "The hash of the token's declared inputs at the moment it was taken. Empty for a\ntoken that declares none.",
          "type": "string"
        },
        "kind": {
          "description": "How it was taken: `test`, `build`, `ci`, `artifact` or `manual`.",
          "type": "string"
        },
        "recorded_at": {
          "description": "When the line was appended.",
          "type": "string"
        },
        "result": {
          "description": "What the command said, when its output was the point.",
          "type": "string"
        },
        "session": {
          "description": "The episode that recorded it, when one was open.",
          "type": "string"
        }
      },
      "required": [
        "recorded_at",
        "head"
      ],
      "type": "object"
    },
    "ObligationClosure": {
      "description": "One obligation the task declared, joined with what the vocabulary says about it and\nwith the evidence that does or does not discharge it.\n\nThe vocabulary's fields are repeated here on purpose: a client asking what this task\nowes gets the token's title, its summary and the command that would discharge it in the\nsame answer, and needs no second call to render a report.\n\n```\nuse majordomus_cli::capability::builtin::obligations::{ObligationClosure, ObligationState};\nlet owed: ObligationClosure = serde_json::from_str(\n    r#\"{\"id\":\"pages\",\"title\":\"The published site serves this commit\",\"remote\":true,\n        \"state\":\"owed\",\"detail\":\"owed, and no evidence was recorded\",\n        \"reproduce\":\"majordomus evidence --covers pages --command 'scripts/pages verify'\"}\"#,\n)\n.unwrap();\nassert_eq!(owed.state, ObligationState::Owed);\nassert!(owed.evidence.is_none() && owed.staleness.is_none(), \"nothing to label\");\nassert!(owed.reproduce.starts_with(\"majordomus evidence\"), \"a finding carries its repair\");\n```",
      "properties": {
        "detail": {
          "description": "One line: what this obligation's standing actually is, in the words the validator\nuses when it refuses.",
          "type": "string"
        },
        "discharged_by": {
          "description": "From the vocabulary: the command that produces the evidence.",
          "type": "string"
        },
        "evidence": {
          "anyOf": [
            {
              "$ref": "#/$defs/Evidence"
            },
            {
              "type": "null"
            }
          ],
          "description": "The line that discharged it, or `None`."
        },
        "id": {
          "description": "The token, as the task's `requires` names it.",
          "type": "string"
        },
        "inputs": {
          "description": "From the vocabulary: the pathspecs the evidence is hashed over.",
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "inputs_files": {
          "description": "How many tracked files that hash was taken over. Reported because the shell's\nselection and this one are not always the same set — see the module header — and a\ncount is the cheapest way for a reader to see it rather than be told it.",
          "format": "uint",
          "minimum": 0,
          "type": [
            "integer",
            "null"
          ]
        },
        "inputs_hash_now": {
          "description": "What the token's inputs hash to in this tree now — the other half of \"stale against\nwhat\". `None` for a token that declares no inputs, and `None` when git could not be\nasked which files they select.",
          "type": [
            "string",
            "null"
          ]
        },
        "remote": {
          "description": "From the vocabulary: whether the fact is remote.",
          "type": "boolean"
        },
        "reproduce": {
          "description": "The command that would discharge it. Every finding here carries the way to act on\nit, as `project.finding-carries-reproduce` asks.",
          "type": "string"
        },
        "staleness": {
          "anyOf": [
            {
              "$ref": "#/$defs/Divergence"
            },
            {
              "type": "null"
            }
          ],
          "description": "How far the evidence is from this checkout, in the repository's one staleness\nvocabulary. `None` when there is no evidence to label.\n\nA remote fact is labelled against the commit it was taken at, because the site that\nserves a commit goes on serving it while the tree moves underneath. A tree-bound\nfact is labelled by re-hashing: equal hashes are `exact`, and a difference takes the\ncommit's label, which is `advanced` when the working tree has merely moved on and\n`diverged` when the history it named is gone. A token that declares neither inputs\nnor a remote fact is bound to nothing and stays `exact` once recorded — that is the\nvalidator's behaviour, and it is reproduced rather than tightened here."
        },
        "state": {
          "$ref": "#/$defs/ObligationState",
          "description": "Where it stands."
        },
        "summary": {
          "description": "From the vocabulary.",
          "type": "string"
        },
        "title": {
          "description": "From the vocabulary; the token itself when it declares none.",
          "type": "string"
        }
      },
      "required": [
        "id",
        "title",
        "remote",
        "state",
        "detail",
        "reproduce"
      ],
      "type": "object"
    },
    "ObligationState": {
      "description": "Where one obligation stands.\n\nThe words are the validator's verdicts, not a severity scale: `stale` is not a worse\n`owed`, it is evidence that was true and no longer describes what it proved.\n\n```\nuse majordomus_cli::capability::builtin::obligations::ObligationState;\n// only one of the four lets a task be called completed\nlet completable = |s: ObligationState| s == ObligationState::Discharged;\nassert!(completable(ObligationState::Discharged));\nassert!(!completable(ObligationState::Stale), \"evidence that no longer describes the tree\");\nassert!(!completable(ObligationState::Owed));\nassert!(!completable(ObligationState::Undeclared));\n```",
      "oneOf": [
        {
          "const": "owed",
          "description": "Declared, and no evidence for it was ever recorded.",
          "type": "string"
        },
        {
          "const": "discharged",
          "description": "Evidence exists and still describes this tree, or this commit.",
          "type": "string"
        },
        {
          "const": "stale",
          "description": "Evidence exists and no longer describes what it proved: the inputs changed, or the\ncommit it named is not this one.",
          "type": "string"
        },
        {
          "const": "undeclared",
          "description": "The task requires a token the shipped vocabulary does not declare. Nothing can\ndischarge it, because nothing knows what would.",
          "type": "string"
        }
      ]
    }
  },
  "description": "What this checkout's active task owes, and how much of it is still true.\n\nA clone that has never run the lifecycle answers this too, and answers it as absence:\n`present` false, no task, and a finding saying so. \"Nothing owed\" and \"nothing to owe\nit\" are different facts, and a served instance must not report the second as the first.\n\n```\nuse majordomus_cli::capability::builtin::obligations::Closure;\nlet fresh: Closure = serde_json::from_str(\n    r#\"{\"present\":false,\"worktree\":\"/srv/clone\",\"branch\":\"master\",\"closed\":false,\n        \"tallies\":{},\n        \"findings\":[\"no active task in this checkout (.ai/local/state/current.yaml); nothing owes anything here\"]}\"#,\n)\n.unwrap();\nassert!(!fresh.present && !fresh.closed, \"nothing that does not exist is closed\");\nassert!(fresh.task.is_none() && fresh.obligations.is_empty());\nassert_eq!(fresh.findings.len(), 1, \"absence is reported, not implied\");\n```",
  "properties": {
    "branch": {
      "description": "The branch, or `DETACHED`.",
      "type": "string"
    },
    "closed": {
      "description": "True when the task declares obligations and every one of them is discharged and\ncurrent — that is, when `majordomus.obligation-closure` would not refuse\n`finish --outcome completed`. False when anything is owed, stale or undeclared, and\nfalse when there is no task: nothing that does not exist is closed.",
      "type": "boolean"
    },
    "findings": {
      "description": "What a reader should know before trusting any of the above: an unreadable\nvocabulary, a ledger that could not be read, a task that declares nothing. Empty is\nthe ordinary case.",
      "items": {
        "type": "string"
      },
      "type": "array"
    },
    "head": {
      "description": "The commit this checkout is on.",
      "type": "string"
    },
    "obligations": {
      "description": "Every obligation the task declared, in the order it declared them.",
      "items": {
        "$ref": "#/$defs/ObligationClosure"
      },
      "type": "array"
    },
    "present": {
      "description": "Whether this checkout has a task to report about at all. False in a clone that has\nnever run the lifecycle, which is not a fault and is not \"nothing owed\".",
      "type": "boolean"
    },
    "tallies": {
      "additionalProperties": {
        "format": "uint",
        "minimum": 0,
        "type": "integer"
      },
      "description": "How many obligations stand where, by state word. Absent states are absent rather\nthan zero, so a reader never has to know the vocabulary to read the tallies.",
      "type": "object"
    },
    "task": {
      "anyOf": [
        {
          "$ref": "#/$defs/ActiveTask"
        },
        {
          "type": "null"
        }
      ],
      "description": "The active task, or `None`. Its `requires` is the list the entries below expand."
    },
    "working_tree": {
      "description": "`clean` or `dirty`.",
      "type": "string"
    },
    "worktree": {
      "description": "The worktree this answer is about. Every reading below is scoped to it, and to no\nother checkout of the same repository.",
      "type": "string"
    }
  },
  "required": [
    "present",
    "worktree",
    "branch",
    "closed",
    "tallies"
  ],
  "title": "Closure",
  "type": "object"
}

Policies

benchmark
required — a target on every transport the exposure declares; the cases are the input type's
cache
process — up to 2 entries in the process, 2s each, scoped by the registry fingerprint

Benchmark targets

Derived from the registry for this repository: one requirement per transport, and the cases the input type provides. The whole matrix is on the benchmarks page.

transportstatecasestargets
directcovered1default cold+warm
mcpcovered1default cold+warm
httpcovered1default cold+warm