gates.model
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.
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.
- query
- behaviorally_verified
- module gates
- #gates
- #ci
- #completion
- #introspection
Exposure
Where this one definition is reachable. Absence is explicit: a surface not listed does not carry it.
| surface | as |
|---|---|
| MCP | tool majordomus_gates resource majordomus://gates |
| HTTP | GET /api/v1/gates operationId gates.model |
| 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 properties: the capability takes no input.
JSON Schema
{
"additionalProperties": false,
"description": "No input.",
"title": "Empty",
"type": "object"
}output · GateModelReport
Two things about this report are worth knowing before reading one. count is measured
from gates rather than written down, so the two can never disagree the way a
hand-maintained total does. And unreadable is how a reader tells "this repository
declares no CI model" from "the call failed": a repository of the layer is not obliged
to declare gates, so the empty model is an answer, and it says why it is empty.
use majordomus_cli::capability::builtin::gates::GateModelReport;
use serde_json::json;
// the shape every surface serves: `unreadable` is skipped when the model was read
let r: GateModelReport = serde_json::from_value(json!({
"version": 1,
"source": ".ai/repo/ci/gates.yaml",
"count": 0,
"on_demand": [],
"gates": [],
"classes": [],
})).unwrap();
assert_eq!(r.count, r.gates.len(), "the total is measured, never declared");
assert!(r.unreadable.is_none(), "read, and it declares nothing");
// and the other empty answer, which a reader must not confuse with that one
let absent: GateModelReport = serde_json::from_value(json!({
"version": 0,
"source": ".ai/repo/ci/gates.yaml",
"count": 0,
"on_demand": [],
"gates": [],
"classes": [],
"unreadable": "no such file",
})).unwrap();
assert_eq!(absent.unreadable.as_deref(), Some("no such file"));| property | type | required | description |
|---|---|---|---|
| classes | array | yes | Every path class, in declaration order. |
| count | integer | yes | How many gates it declares; measured, not written down. |
| gates | array | yes | Every gate, in declaration order, with the pathspecs its evidence is taken over. |
| on_demand | array | yes | The gates whose runner cannot be had on demand, so no routine plan selects them. A gate that never runs protects nothing, and this is where that is said out loud. |
| source | string | yes | Where it was read from, repository-relative. |
| unreadable | string | null | no | Why there is no model to report, when there is none. A repository of the layer does not have to declare a CI model, and a reader is entitled to the difference between "this repository declares no gates" and "this call failed". |
| version | integer | yes | The schema version the file states. |
JSON Schema
{
"$defs": {
"GateClass": {
"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.",
"properties": {
"except": {
"default": [],
"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.",
"items": {
"type": "string"
},
"type": "array"
},
"gates": {
"anyOf": [
{
"$ref": "#/$defs/GateClassSelects"
},
{
"type": "null"
}
],
"default": null,
"description": "`full`, or the gates it names."
},
"id": {
"description": "The class's identity.",
"type": "string"
},
"paths": {
"default": [],
"description": "The pathspecs it covers, in the subset the model's header defines.",
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"id"
],
"type": "object"
},
"GateClassSelects": {
"anyOf": [
{
"description": "`gates: full` — any change in this class escalates the whole plan.",
"type": "string"
},
{
"description": "`gates: [a, b]` — exactly these, and `gates: []` — nothing.",
"items": {
"type": "string"
},
"type": "array"
}
],
"description": "What a class selects: the word `full`, or the gates it names."
},
"GateModelEntry": {
"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.",
"properties": {
"always": {
"default": false,
"description": "True when every plan selects it, whatever changed.",
"type": "boolean"
},
"at-finish": {
"default": false,
"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.",
"type": "boolean"
},
"id": {
"description": "The gate's identity, unique in the model.",
"type": "string"
},
"implies": {
"default": [],
"description": "Gates that are selected whenever this one is.",
"items": {
"type": "string"
},
"type": "array"
},
"inputs": {
"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.",
"items": {
"type": "string"
},
"type": "array"
},
"job": {
"description": "The CI job that runs it.",
"type": "string"
},
"on-demand": {
"default": false,
"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.",
"type": "boolean"
},
"requires": {
"default": [],
"description": "Gates this one cannot run without.",
"items": {
"type": "string"
},
"type": "array"
},
"runs": {
"description": "The command it runs.",
"type": "string"
},
"summary": {
"default": "",
"description": "One line saying what it proves.",
"type": "string"
}
},
"required": [
"id",
"job",
"runs",
"inputs"
],
"type": "object"
}
},
"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```",
"properties": {
"classes": {
"description": "Every path class, in declaration order.",
"items": {
"$ref": "#/$defs/GateClass"
},
"type": "array"
},
"count": {
"description": "How many gates it declares; measured, not written down.",
"format": "uint",
"minimum": 0,
"type": "integer"
},
"gates": {
"description": "Every gate, in declaration order, with the pathspecs its evidence is taken over.",
"items": {
"$ref": "#/$defs/GateModelEntry"
},
"type": "array"
},
"on_demand": {
"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.",
"items": {
"type": "string"
},
"type": "array"
},
"source": {
"description": "Where it was read from, repository-relative.",
"type": "string"
},
"unreadable": {
"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\".",
"type": [
"string",
"null"
]
},
"version": {
"description": "The schema version the file states.",
"format": "uint32",
"minimum": 0,
"type": "integer"
}
},
"required": [
"version",
"source",
"count",
"on_demand",
"gates",
"classes"
],
"title": "GateModelReport",
"type": "object"
}Policies
- benchmark
- required — a target on every transport the exposure declares; the cases are the input type's
- cache
- process — up to 4 entries in the process, 5s 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.