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
entity.show
Read one entity
What it is
One object of the layer as an addressable node, by URI or by the address it is served at: identity and route, provenance, front matter and content, every reference it declares and every reference that resolves to it, the surfaces that answer for it, the state of the executable artefacts it names, and its public documentation page as the repository's site/data/publication.toml derives it (null when the repository declares none).
Descriptor
- Canonical id
entity.show- Module
entity- Kind
- query
- Stability
- implemented
- Provenance
- declared
- Source
apps/majordomus-cli/src/capability/builtin/entity.rs- Cache
disabled- Benchmark
required- Tags
- objectsgovernance
Run
queryA query. It reads and changes nothing.
Projections
| Interface | As | Where |
|---|---|---|
| MCP tool | majordomus_entity | - |
| MCP resource | not exposed | - |
| HTTP | GET /api/v1/entity | Swagger |
| Command line | majordomus entity show | - |
Schemas
Input · EntityInput
{
"type": "object",
"additionalProperties": false,
"properties": {
"uri": {
"type": [
"string",
"null"
],
"description": "`majordomus://<kind>/<identity>`. Either this, or `kind` and `slug` together."
},
"kind": {
"type": [
"string",
"null"
],
"description": "The kind, when addressing by route."
},
"slug": {
"type": [
"string",
"null"
],
"description": "The last segment of the entity's route, when addressing by route."
}
},
"description": "Which entity: by URI, or by the address it is served at.\n\nTwo spellings of one question, because two kinds of caller ask it: MCP and the API hold\nthe `majordomus://` URI the index is keyed by, and the Cockpit's router has a `kind` and\na `slug` out of a path. Nothing converts one form into the other at the edge — the\ncapability resolves either, so neither caller has to know the other's spelling.\n\n```\nuse majordomus_cli::capability::builtin::entity::EntityInput;\n\nlet by_route: EntityInput =\n serde_json::from_value(serde_json::json!({ \"kind\": \"adr\", \"slug\": \"adr-0056\" }))\n .unwrap();\nassert_eq!(by_route.kind.as_deref(), Some(\"adr\"));\nassert_eq!(by_route.slug.as_deref(), Some(\"adr-0056\"));\nassert_eq!(by_route.uri, None);\n\n// absent is a shape, not an error: which fields are required is the handler's question\nassert_eq!(EntityInput::default().kind, None);\n```",
"title": "EntityInput"
}Output · EntityView
{
"type": "object",
"properties": {
"uri": {
"type": "string",
"description": "`majordomus://<kind>/<identity>`."
},
"id": {
"type": "string",
"description": "The capability id, `<kind>.<identity>`."
},
"kind": {
"type": "string",
"description": "The kind."
},
"identity": {
"type": "string",
"description": "The identity within the kind."
},
"slug": {
"type": "string",
"description": "The last segment of its route, derived from the identity."
},
"route": {
"type": "string",
"description": "Its route in the Cockpit."
},
"kind_route": {
"type": "string",
"description": "The route of its kind's index."
},
"title": {
"type": [
"string",
"null"
],
"description": "The title, when the kind's title rule found one."
},
"description": {
"type": [
"string",
"null"
],
"description": "The one-line description, when the kind holds one."
},
"provenance": {
"$ref": "#/$defs/ObjectProvenance",
"description": "Where it came from."
},
"metadata": {
"description": "The parsed front matter or YAML, keys in the file's order."
},
"media_type": {
"type": "string",
"description": "IANA media type of `content`."
},
"content": {
"type": "string",
"description": "The file as read."
},
"relations": {
"type": "array",
"items": {
"$ref": "#/$defs/EntityEdge"
},
"description": "The references it declares, and the references that resolve to it."
},
"surfaces": {
"type": "array",
"items": {
"$ref": "#/$defs/EntitySurface"
},
"description": "Every surface that answers for it."
},
"evidence": {
"$ref": "#/$defs/EntityEvidence",
"description": "What can be said about its enforcement without running anything."
},
"documentation": {
"anyOf": [
{
"$ref": "#/$defs/EntityDocumentation"
},
{
"type": "null"
}
],
"description": "Its public documentation page, derived from the repository's publication declaration\n(`site/data/publication.toml`) and the site's `base_url` by the same function the site\ngenerator writes the page with. Null when the repository declares no publication or\ndoes not declare this kind; a kind declared as not published carries the reason.",
"default": null
}
},
"required": [
"uri",
"id",
"kind",
"identity",
"slug",
"route",
"kind_route",
"provenance",
"metadata",
"media_type",
"content",
"relations",
"surfaces",
"evidence"
],
"description": "One object of the layer as an addressable node: what it is, where it is served, what it\nis joined to, and what can be said about its enforcement.\n\nThe whole answer in one value, because it is one question: every surface that shows an\nentity — the Cockpit page, `GET /api/v1/entity`, `majordomus_entity`, `majordomus entity\nshow` — renders this and composes nothing of its own. A field a reader sees on one\nsurface and not another would be a surface holding an opinion, which is what ADR 0004\nforecloses.\n\n```\nuse majordomus_cli::capability::builtin::entity::EntityView;\n\n// it round-trips through the wire shape every projection reads it as\nlet view: EntityView = serde_json::from_value(serde_json::json!({\n \"uri\": \"majordomus://adr/adr-0056\",\n \"id\": \"adr.adr-0056\",\n \"kind\": \"adr\",\n \"identity\": \"adr-0056\",\n \"slug\": \"adr-0056\",\n \"route\": \"/cockpit/objects/adr/adr-0056\",\n \"kind_route\": \"/cockpit/objects/adr\",\n \"title\": \"Every object of the layer has a derived address\",\n \"provenance\": { \"path\": \"x.md\", \"directory\": \"x\", \"source_class\": \"adr\", \"bytes\": 0 },\n \"metadata\": {},\n \"media_type\": \"text/markdown\",\n \"content\": \"\",\n \"relations\": [],\n \"surfaces\": [],\n \"evidence\": { \"state\": \"unclaimed\", \"meaning\": \"nothing named\" },\n \"documentation\": {\n \"projection\": \"entity\",\n \"route\": \"/adrs/adr-0056/\",\n \"url\": \"https://majordomus.dev/adrs/adr-0056/\",\n \"declared_in\": \"site/data/publication.toml\",\n },\n}))\n.unwrap();\n\nassert_eq!(view.route, \"/cockpit/objects/adr/adr-0056\");\n// the Cockpit route is the object's; the public page is the site's, and both are named\nlet public = view.documentation.as_ref().and_then(|d| d.url.as_deref());\nassert_eq!(public, Some(\"https://majordomus.dev/adrs/adr-0056/\"));\n// the kind's index is the entity's route with the last segment removed\nassert!(view.route.starts_with(&view.kind_route));\n```",
"title": "EntityView",
"$defs": {
"ObjectProvenance": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Repository-relative path, forward slashes, as the version-control index names it."
},
"directory": {
"type": "string",
"description": "The directory the path sits in, repository-relative; `.` for the root. This is the\nhierarchy position a client orders by; no merge semantics are implied."
},
"source_class": {
"type": "string",
"description": "The `sources.yaml` class that discovered the file (`rule`, `readme`, ...)."
},
"section": {
"type": [
"string",
"null"
],
"description": "The manifest section the path falls under (`rules`, `prompts`, ...), when it falls\nunder one; a root `README.md` falls under none."
},
"bytes": {
"type": "integer",
"format": "uint64",
"minimum": 0,
"description": "Size of the file in bytes."
},
"member": {
"type": [
"string",
"null"
],
"description": "For an object that is one member of a collection file: the member's key path in\nthe file (`claims.3`)."
}
},
"required": [
"path",
"directory",
"source_class",
"bytes"
],
"description": "Where an object came from. Every field is computed from the repository, never authored."
},
"EntityEdge": {
"type": "object",
"properties": {
"direction": {
"$ref": "#/$defs/RelationDirection",
"description": "Which way it runs from the entity that was asked for."
},
"edge": {
"type": "string",
"description": "The relationship, in the graph's vocabulary (`depends_on`, `put_in_force`, ...)."
},
"field": {
"type": "string",
"description": "The front matter key the reference was declared under."
},
"uri": {
"type": [
"string",
"null"
],
"description": "The object at the other end: its URI when the layer holds it."
},
"kind": {
"type": "string",
"description": "Its kind — of the layer, or the external vocabulary (`file`, `test`, `command`)."
},
"label": {
"type": "string",
"description": "What to show: the identity of an object, or the name of something outside."
},
"title": {
"type": [
"string",
"null"
],
"description": "Its title, when it is an object that has one."
},
"route": {
"type": [
"string",
"null"
],
"description": "Its entity route, when it has one; absent for something outside the layer."
},
"external": {
"type": "boolean",
"description": "True when the other end is not an object of this index."
}
},
"required": [
"direction",
"edge",
"field",
"kind",
"label",
"external"
],
"description": "One edge, as an entity page reads it.\n\nEverything a reader needs about the other end is here — what to show, what kind of thing\nit is, and the address to send them to — so the page renders a relation without going\nback to the index for it. `route` is `None` exactly when `external` is true: a `file`, a\n`test` or a `command` is named by the layer and is not an object of it, so there is no\nentity page to link to and the template shows plain text instead of a dead link.\n\n```\nuse majordomus_cli::entity::{Direction, Edge};\nuse majordomus_cli::order::Ordered;\n\nlet edge = Edge {\n direction: Direction::Incoming,\n edge: \"depends_on\".into(),\n field: \"depends_on\".into(),\n uri: Some(\"majordomus://rule/project.x@1\".into()),\n kind: \"rule\".into(),\n label: \"project.x@1\".into(),\n title: Some(\"A rule\".into()),\n route: Some(\"/cockpit/objects/rule/project-x-1\".into()),\n external: false,\n};\n\n// the relation is the group and the direction ranks inside it; see the `Ordered` impl\nlet key = edge.order_key();\nassert_eq!(key.group, Some(\"depends_on\"));\nassert_eq!(key.rank, Direction::Incoming as i64);\nassert_eq!(key.identity, \"majordomus://rule/project.x@1\");\n```"
},
"RelationDirection": {
"oneOf": [
{
"type": "string",
"const": "outgoing",
"description": "This entity declared the reference."
},
{
"type": "string",
"const": "incoming",
"description": "Another entity named this one. Never declared: derived by reading every other\nobject's references and keeping the ones that resolve here."
}
],
"description": "Which way a relation runs from the entity being read.\n\nThe direction is a property of the *reading*, not of the relation: one edge in\n`graph::RELATIONS` is outgoing on the page of the object that declared it and incoming\non the page of the object it names. Nothing in the layer writes `Incoming` down.\n\n```\nuse majordomus_cli::entity::Direction;\n\n// the wire spelling, which every surface shows and the site template reads\nassert_eq!(\n serde_json::to_string(&Direction::Outgoing).unwrap(),\n \"\\\"outgoing\\\"\"\n);\nassert_eq!(\n serde_json::to_string(&Direction::Incoming).unwrap(),\n \"\\\"incoming\\\"\"\n);\n// declared before derived, which is the order an entity page renders the two sections\nassert!((Direction::Outgoing as u8) < (Direction::Incoming as u8));\n```"
},
"EntitySurface": {
"type": "object",
"properties": {
"surface": {
"type": "string",
"description": "Which surface: `mcp`, `http`, `cli`, `cockpit`, `site`."
},
"address": {
"type": "string",
"description": "The address on it: a URI, a route, a command line."
},
"detail": {
"type": "string",
"description": "What a reader does with it."
}
},
"required": [
"surface",
"address",
"detail"
],
"description": "One place this entity can be reached, and how.\n\nA row of the \"where else this is answered\" table on an entity page. `surface` is the\ninterface's own name, so a reader who works on the command line and a reader who works\nover MCP each find their own line rather than a route they have to translate.\n\n```\nuse majordomus_cli::entity::Surface;\n\nlet s = Surface {\n surface: \"mcp\".into(),\n address: \"majordomus://rule/project.x@1\".into(),\n detail: \"the file as read\".into(),\n};\nassert_eq!(\n serde_json::to_value(&s).unwrap()[\"surface\"],\n serde_json::json!(\"mcp\")\n);\n```"
},
"EntityEvidence": {
"type": "object",
"properties": {
"state": {
"$ref": "#/$defs/EntityEvidenceState",
"description": "The state, which is never rounded up."
},
"meaning": {
"type": "string",
"description": "That state in one sentence, for a reader."
},
"artifacts": {
"type": "array",
"items": {
"$ref": "#/$defs/ClaimedArtifact"
},
"description": "Every artefact named, with whether the tree holds it."
},
"proof": {
"anyOf": [
{
"$ref": "#/$defs/EntityProofRef"
},
{
"type": "null"
}
],
"description": "The capability that can say more, when this kind has one."
}
},
"required": [
"state",
"meaning"
],
"description": "What can be said about an object's enforcement without running anything.\n\n`meaning` travels with `state` so that every surface shows the same sentence about it:\na badge alone invites each renderer to write its own gloss, and the gloss is where a\n\"resolved\" quietly becomes a \"passing\". A kind that names no artefact is `Unclaimed`\nwith an empty `artifacts`, which is an answer and not an omission.\n\n```\nuse majordomus_cli::capability::builtin::entity::{Evidence, EvidenceState};\n\nlet e = Evidence {\n state: EvidenceState::Unclaimed,\n meaning: \"This object names no executable artefact.\".into(),\n artifacts: Vec::new(),\n proof: None,\n};\n\n// the empty collections are omitted from the wire, so absence reads as absence\nlet wire = serde_json::to_value(&e).unwrap();\nassert_eq!(wire[\"state\"], serde_json::json!(\"unclaimed\"));\nassert!(wire.get(\"artifacts\").is_none());\nassert!(wire.get(\"proof\").is_none());\n```"
},
"EntityEvidenceState": {
"oneOf": [
{
"type": "string",
"const": "unclaimed",
"description": "The object names no executable artefact. Most kinds name none, and that is not a\ndefect: a prompt is not enforced by anything."
},
{
"type": "string",
"const": "dangling",
"description": "It names artefacts and at least one of them is not in the tree. A claim that points\nat nothing is worse than no claim, because it reads as enforcement."
},
{
"type": "string",
"const": "resolved",
"description": "Every artefact it names is in the tree. This says the claim resolves — never that it\npassed. Whether it ever ran is a different question, and `proof` is where it is\nasked."
}
],
"description": "What this repository can say about the executable artefacts an object names, and only\nthat. The word is chosen so that no reader can mistake it for a run.\n\nThe three states are ordered by how much they permit a reader to conclude, and the\nstrongest of them still concludes nothing about a run: `Resolved` means the files an\nobject points at exist. `proof` on `Evidence` is where \"did it ever pass\" is asked.\n\n```\nuse majordomus_cli::capability::builtin::entity::EvidenceState;\n\n// the wire spellings, which the Cockpit badge and the site template both read\nfor (state, word) in [\n (EvidenceState::Unclaimed, \"unclaimed\"),\n (EvidenceState::Dangling, \"dangling\"),\n (EvidenceState::Resolved, \"resolved\"),\n] {\n assert_eq!(serde_json::to_value(state).unwrap(), serde_json::json!(word));\n}\n```"
},
"ClaimedArtifact": {
"type": "object",
"properties": {
"field": {
"type": "string",
"description": "The front matter key it was named under (`x-majordomus.tests`, `test`, ...)."
},
"path": {
"type": "string",
"description": "The repository-relative path as named."
},
"present": {
"type": "boolean",
"description": "Whether the tree holds it."
}
},
"required": [
"field",
"path",
"present"
],
"description": "One executable artefact an object names, and whether the tree holds it.\n\n`field` is kept beside the path because the same object can name artefacts under several\nkeys, and a reader fixing a dangling one needs to know which line of the front matter to\nlook at rather than which file is missing.\n\n```\nuse majordomus_cli::capability::builtin::entity::ClaimedArtifact;\n\nlet a = ClaimedArtifact {\n field: \"x-majordomus.tests\".into(),\n path: \"test/cases/277_entities_are_routable.sh\".into(),\n present: true,\n};\nlet wire = serde_json::to_value(&a).unwrap();\nassert_eq!(wire[\"field\"], serde_json::json!(\"x-majordomus.tests\"));\nassert_eq!(wire[\"present\"], serde_json::json!(true));\n```"
},
"EntityProofRef": {
"type": "object",
"properties": {
"capability": {
"type": "string",
"description": "The capability that answers it."
},
"title": {
"type": "string",
"description": "Its title."
},
"address": {
"type": "string",
"description": "How to ask it here."
}
},
"required": [
"capability",
"title",
"address"
],
"description": "Where a fuller answer about this entity's enforcement lives.\n\nA pointer, never an answer. This module states what it can see in the tree and then\nnames the capability that can say more, so that an entity page does not grow a second,\nweaker implementation of proof beside the one that already exists.\n\n```\nuse majordomus_cli::capability::builtin::entity::ProofRef;\n\nlet p = ProofRef {\n capability: \"rule.proves\".into(),\n title: \"What proves a rule\".into(),\n address: \"majordomus rule proves project.entities-are-routable@1\".into(),\n};\nassert_eq!(\n serde_json::to_value(&p).unwrap()[\"capability\"],\n serde_json::json!(\"rule.proves\")\n);\n```"
},
"EntityDocumentation": {
"type": "object",
"properties": {
"projection": {
"type": "string",
"description": "The kind's projection: `entity`, `section` or `none`."
},
"route": {
"type": [
"string",
"null"
],
"description": "The public route of the page, site-relative: the entity's own page for an `entity`\nkind, the kind's section for a `section` kind, absent for `none`."
},
"url": {
"type": [
"string",
"null"
],
"description": "The route under the site's `base_url`, when the site configuration names one."
},
"reason": {
"type": [
"string",
"null"
],
"description": "Why the kind is not published, for `none`."
},
"declared_in": {
"type": "string",
"description": "The file the decision is read from."
}
},
"required": [
"projection",
"declared_in"
],
"description": "The public documentation page of one entity, as its kind's publication decision derives\nit. Every surface that shows an entity carries this value and renders it; none of them\nderives a public address of its own.\n\n```\nuse majordomus_cli::entity::Documentation;\n\nlet d = Documentation {\n projection: \"entity\".into(),\n route: Some(\"/adrs/adr-0056/\".into()),\n url: Some(\"https://majordomus.dev/adrs/adr-0056/\".into()),\n reason: None,\n declared_in: \"site/data/publication.toml\".into(),\n};\nlet wire = serde_json::to_value(&d).unwrap();\nassert_eq!(wire[\"url\"], serde_json::json!(\"https://majordomus.dev/adrs/adr-0056/\"));\n// a field that does not apply is absent, not an empty string\nassert!(wire.get(\"reason\").is_none());\n```"
}
}
}Examples
These are the capability's own benchmark cases: the same inputs the benchmark runs and the OpenAPI document shows.
first-object
{
"uri": "majordomus://adr/adr-0001"
}