Skip to content
Cockpit0.29.0
A snapshot: read at commit 707a04c7ce8 on 2026-10-18T00:06:01Z by majordomus 0.29.0. Nothing on this page is read again. See it live

gates.model

The CI model

What it is

Every validation gate this repository declares — the job that runs it, the command it runs, what it proves, whether every plan selects it and whether its runner can be had on demand at all — with the path classes that decide which change selects which gate, and, derived from those classes, the pathspecs each gate's evidence is taken over.

Descriptor

Canonical id
gates.model
Module
gates
Kind
query
Stability
behaviorallyverified
Provenance
declaredcached
Source
apps/majordomus-cli/src/capability/builtin/gates.rs
Cache
process, 4 entries, 5s
Benchmark
required
Tags
gatescicompletionintrospection

Run

query
A query. It reads and changes nothing.

This capability takes no input.

GET /api/v1/gates

Projections

InterfaceAsWhere
MCP toolmajordomus_gates-
MCP resourcemajordomus://gates-
HTTPGET /api/v1/gatesSwagger
Command linenot exposed-

Schemas

Input · Empty
{
  "type": "object",
  "additionalProperties": false,
  "description": "No input.",
  "title": "Empty"
}
Output · GateModelReport
{
  "type": "object",
  "properties": {
    "version": {
      "type": "integer",
      "format": "uint32",
      "minimum": 0,
      "description": "The schema version the file states."
    },
    "source": {
      "type": "string",
      "description": "Where it was read from, repository-relative."
    },
    "count": {
      "type": "integer",
      "format": "uint",
      "minimum": 0,
      "description": "How many gates it declares; measured, not written down."
    },
    "on_demand": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "The gates whose runner cannot be had on demand, so no routine plan selects them.\nA gate that never runs protects nothing, and this is where that is said out loud."
    },
    "gates": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/GateModelEntry"
      },
      "description": "Every gate, in declaration order, with the pathspecs its evidence is taken over."
    },
    "classes": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/GateClass"
      },
      "description": "Every path class, in declaration order."
    },
    "unreadable": {
      "type": [
        "string",
        "null"
      ],
      "description": "Why there is no model to report, when there is none. A repository of the layer does\nnot have to declare a CI model, and a reader is entitled to the difference between\n\"this repository declares no gates\" and \"this call failed\"."
    }
  },
  "required": [
    "version",
    "source",
    "count",
    "on_demand",
    "gates",
    "classes"
  ],
  "description": "The CI model this repository declares: what may refuse a change, and what selects each.\n\nTwo things about this report are worth knowing before reading one. `count` is measured\nfrom `gates` rather than written down, so the two can never disagree the way a\nhand-maintained total does. And `unreadable` is how a reader tells \"this repository\ndeclares no CI model\" from \"the call failed\": a repository of the layer is not obliged\nto declare gates, so the empty model is an answer, and it says why it is empty.\n\n```\nuse majordomus_cli::capability::builtin::gates::GateModelReport;\nuse serde_json::json;\n\n// the shape every surface serves: `unreadable` is skipped when the model was read\nlet r: GateModelReport = serde_json::from_value(json!({\n    \"version\": 1,\n    \"source\": \".ai/repo/ci/gates.yaml\",\n    \"count\": 0,\n    \"on_demand\": [],\n    \"gates\": [],\n    \"classes\": [],\n})).unwrap();\nassert_eq!(r.count, r.gates.len(), \"the total is measured, never declared\");\nassert!(r.unreadable.is_none(), \"read, and it declares nothing\");\n\n// and the other empty answer, which a reader must not confuse with that one\nlet absent: GateModelReport = serde_json::from_value(json!({\n    \"version\": 0,\n    \"source\": \".ai/repo/ci/gates.yaml\",\n    \"count\": 0,\n    \"on_demand\": [],\n    \"gates\": [],\n    \"classes\": [],\n    \"unreadable\": \"no such file\",\n})).unwrap();\nassert_eq!(absent.unreadable.as_deref(), Some(\"no such file\"));\n```",
  "title": "GateModelReport",
  "$defs": {
    "GateModelEntry": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "description": "The gate's identity, unique in the model."
        },
        "job": {
          "type": "string",
          "description": "The CI job that runs it."
        },
        "runs": {
          "type": "string",
          "description": "The command it runs."
        },
        "summary": {
          "type": "string",
          "description": "One line saying what it proves.",
          "default": ""
        },
        "always": {
          "type": "boolean",
          "description": "True when every plan selects it, whatever changed.",
          "default": false
        },
        "on-demand": {
          "type": "boolean",
          "description": "True when only a plan that asks for it selects it: its runner cannot be had on\ndemand, so planning it on every push is planning a verdict that never arrives.",
          "default": false
        },
        "at-finish": {
          "type": "boolean",
          "description": "True when the gate measures the deployment rather than the tree, so no change to\nany file can select it and the path classes can never reach it. `finish` asks it\ninstead: the question \"is what the public is being served still a projection of\nthe trunk\" has no answer a commit can give, and its moment is when a session\nclaims to be done.",
          "default": false
        },
        "implies": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Gates that are selected whenever this one is.",
          "default": []
        },
        "requires": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Gates this one cannot run without.",
          "default": []
        },
        "inputs": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "The pathspecs a run of this gate is evidence over, derived from the classes that\nselect it. A change to any of them makes a recorded run stale."
        }
      },
      "required": [
        "id",
        "job",
        "runs",
        "inputs"
      ],
      "description": "```\nuse majordomus_cli::capability::builtin::gates::GateModelEntry;\nuse majordomus_cli::gates::GateDecl;\n\nlet decl: GateDecl = serde_json::from_value(serde_json::json!({\n    \"id\": \"site-check\",\n    \"job\": \"site\",\n    \"runs\": \"scripts/site-check\",\n    \"summary\": \"the site is whole\"\n}))\n.unwrap();\nlet entry = GateModelEntry { declared: decl, inputs: vec![\"site/**\".into()] };\n// the declaration as the file carries it, plus the paths its evidence is taken over —\n// derived from the classes that select it, never written down a second time\nassert_eq!(entry.declared.id, \"site-check\");\nassert_eq!(entry.inputs, [\"site/**\"]);\n```\nOne gate of the model, with what the model implies about it."
    },
    "GateClass": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "description": "The class's identity."
        },
        "paths": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "The pathspecs it covers, in the subset the model's header defines.",
          "default": []
        },
        "except": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Single paths its patterns cover and the class does not: a data file under an\nescalating directory, read by named gates only. Each one is a path, never a\npattern, so a file that appears under the directory tomorrow still falls in it.",
          "default": []
        },
        "gates": {
          "anyOf": [
            {
              "$ref": "#/$defs/GateClassSelects"
            },
            {
              "type": "null"
            }
          ],
          "description": "`full`, or the gates it names.",
          "default": null
        }
      },
      "required": [
        "id"
      ],
      "description": "```\nuse majordomus_cli::gates::GateClass;\n\n// one path class as the CI model declares it: which paths select which gates\nlet class: GateClass = serde_json::from_value(serde_json::json!({\n    \"id\": \"docs\",\n    \"paths\": [\"docs/**\"],\n    \"gates\": [\"reference-check\"]\n}))\n.unwrap();\nassert_eq!(class.id, \"docs\");\nassert_eq!(class.paths, [\"docs/**\"]);\n```\nOne path class: the paths it covers and the gates a change to them selects."
    },
    "GateClassSelects": {
      "anyOf": [
        {
          "type": "string",
          "description": "`gates: full` — any change in this class escalates the whole plan."
        },
        {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "`gates: [a, b]` — exactly these, and `gates: []` — nothing."
        }
      ],
      "description": "What a class selects: the word `full`, or the gates it names."
    }
  }
}

Examples

These are the capability's own benchmark cases: the same inputs the benchmark runs and the OpenAPI document shows.

default
{}