entity.show
Jeden objekt vrstvy jako adresovatelný uzel, podle URI nebo adresy, na které je poskytován: identita a trasa, původ, front matter a obsah, každá reference, kterou deklaruje, a každá reference, která se na něj vyřeší, povrchy, které za něj odpovídají, stav spustitelných artefaktů, které pojmenovává, a jeho veřejná dokumentační stránka, jak ji odvozuje site/data/publication.toml repozitáře (null, když repozitář žádnou nedeklaruje).
Jeden objekt vrstvy jako adresovatelný uzel, podle URI nebo adresy, na které je poskytován: identita a trasa, původ, front matter a obsah, každá reference, kterou deklaruje, a každá reference, která se na něj vyřeší, povrchy, které za něj odpovídají, stav spustitelných artefaktů, které pojmenovává, a jeho veřejná dokumentační stránka, jak ji odvozuje site/data/publication.toml repozitáře (null, když repozitář žádnou nedeklaruje).
- query
- implementováno
- module entity
- #objects
- #governance
Zpřístupnění
Kde je tato jedna definice dosažitelná. Absence je explicitní: povrch, který není uveden, ji nenese.
| povrch | jako |
|---|---|
| MCP | nástroj majordomus_entity |
| HTTP | GET /api/v1/entity operationId entity.show |
| příkazová řádka | majordomus entity show |
Schémata
Kanonický vstup a výstup z typů Rustu; schéma nástroje MCP a parametry a odpovědi OpenAPI se odvozují z nich.
vstup · EntityInput
Dva zápisy jedné otázky, protože ji kladou dva druhy volajících: MCP a API drží URI majordomus://, podle něhož je index klíčován, a router Cockpitu má z cesty kind a slug. Nic na okraji jednu formu nepřevádí na druhou — schopnost rozřeší obě, takže žádný volající nemusí znát zápis toho druhého.
use majordomus_cli::capability::builtin::entity::EntityInput;
let by_route: EntityInput =
serde_json::from_value(serde_json::json!({ "kind": "adr", "slug": "adr-0056" }))
.unwrap();
assert_eq!(by_route.kind.as_deref(), Some("adr"));
assert_eq!(by_route.slug.as_deref(), Some("adr-0056"));
assert_eq!(by_route.uri, None);
// absent is a shape, not an error: which fields are required is the handler's question
assert_eq!(EntityInput::default().kind, None);| vlastnost | typ | povinné | popis |
|---|---|---|---|
| druh | string | null | ne | Druh, když se adresuje podle trasy. |
| slug | string | null | ne | Poslední segment trasy entity při adresování podle trasy. |
| uri | string | null | ne | majordomus://<kind>/<identity>. Buď toto, nebo kind a slug dohromady. |
JSON Schema
{
"additionalProperties": false,
"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```",
"properties": {
"kind": {
"description": "The kind, when addressing by route.",
"type": [
"string",
"null"
]
},
"slug": {
"description": "The last segment of the entity's route, when addressing by route.",
"type": [
"string",
"null"
]
},
"uri": {
"description": "`majordomus://<kind>/<identity>`. Either this, or `kind` and `slug` together.",
"type": [
"string",
"null"
]
}
},
"title": "EntityInput",
"type": "object"
}výstup · EntityView
Celá odpověď v jedné hodnotě, protože je to jedna otázka: každé rozhraní, které ukazuje entitu — stránka Cockpitu, GET /api/v1/entity, majordomus_entity, majordomus entity show — vykresluje tohle a nic si nesestavuje samo. Pole, které čtenář vidí na jednom rozhraní a na jiném ne, by znamenalo, že rozhraní zastává názor, což ADR 0004 vylučuje.
use majordomus_cli::capability::builtin::entity::EntityView;
// it round-trips through the wire shape every projection reads it as
let view: EntityView = serde_json::from_value(serde_json::json!({
"uri": "majordomus://adr/adr-0056",
"id": "adr.adr-0056",
"kind": "adr",
"identity": "adr-0056",
"slug": "adr-0056",
"route": "/cockpit/objects/adr/adr-0056",
"kind_route": "/cockpit/objects/adr",
"title": "Every object of the layer has a derived address",
"provenance": { "path": "x.md", "directory": "x", "source_class": "adr", "bytes": 0 },
"metadata": {},
"media_type": "text/markdown",
"content": "",
"relations": [],
"surfaces": [],
"evidence": { "state": "unclaimed", "meaning": "nothing named" },
"documentation": {
"projection": "entity",
"route": "/adrs/adr-0056/",
"url": "https://majordomus.dev/adrs/adr-0056/",
"declared_in": "site/data/publication.toml",
},
}))
.unwrap();
assert_eq!(view.route, "/cockpit/objects/adr/adr-0056");
// the Cockpit route is the object's; the public page is the site's, and both are named
let public = view.documentation.as_ref().and_then(|d| d.url.as_deref());
assert_eq!(public, Some("https://majordomus.dev/adrs/adr-0056/"));
// the kind's index is the entity's route with the last segment removed
assert!(view.route.starts_with(&view.kind_route));| vlastnost | typ | povinné | popis |
|---|---|---|---|
| obsah | string | ano | Soubor tak, jak byl přečten. |
| popis | string | null | ne | Jednořádkový popis, pokud jej druh má. |
| dokumentace | jedno ze 2 | ne | Jeho veřejná dokumentační stránka, odvozená z publikační deklarace repozitáře (site/data/publication.toml) a base_url webu toutéž funkcí, kterou generátor webu stránku zapisuje. Null, když repozitář nedeklaruje žádnou publikaci nebo nedeklaruje tento druh; druh deklarovaný jako nepublikovaný nese důvod. výchozí null |
| evidence | reference | ano | Co lze říct o jeho vynucování, aniž by se cokoli spustilo. |
| id | string | ano | Id schopnosti, <kind>.<identity>. |
| identity | string | ano | Identita v rámci druhu. |
| druh | string | ano | Druh. |
| kind_route | string | ano | Routa indexu jeho druhu. |
| media_type | string | ano | Typ média IANA pro content. |
| metadata | object | ano | Naparsovaný front matter nebo YAML, klíče v pořadí souboru. |
| provenance | reference | ano | Odkud pochází. |
| vztahy | array | ano | Odkazy, které deklaruje, a odkazy, které se na něj vyřeší. |
| route | string | ano | Její trasa v Cockpitu. |
| slug | string | ano | Poslední segment její trasy, odvozený z identity. |
| povrchy | array | ano | Každý povrch, který za to odpovídá. |
| název | string | null | ne | Název, pokud jej pravidlo názvu druhu našlo. |
| uri | string | ano | majordomus://<kind>/<identity>. |
JSON Schema
{
"$defs": {
"ClaimedArtifact": {
"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```",
"properties": {
"field": {
"description": "The front matter key it was named under (`x-majordomus.tests`, `test`, ...).",
"type": "string"
},
"path": {
"description": "The repository-relative path as named.",
"type": "string"
},
"present": {
"description": "Whether the tree holds it.",
"type": "boolean"
}
},
"required": [
"field",
"path",
"present"
],
"type": "object"
},
"EntityDocumentation": {
"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```",
"properties": {
"declared_in": {
"description": "The file the decision is read from.",
"type": "string"
},
"projection": {
"description": "The kind's projection: `entity`, `section` or `none`.",
"type": "string"
},
"reason": {
"description": "Why the kind is not published, for `none`.",
"type": [
"string",
"null"
]
},
"route": {
"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`.",
"type": [
"string",
"null"
]
},
"url": {
"description": "The route under the site's `base_url`, when the site configuration names one.",
"type": [
"string",
"null"
]
}
},
"required": [
"projection",
"declared_in"
],
"type": "object"
},
"EntityEdge": {
"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```",
"properties": {
"direction": {
"$ref": "#/$defs/RelationDirection",
"description": "Which way it runs from the entity that was asked for."
},
"edge": {
"description": "The relationship, in the graph's vocabulary (`depends_on`, `put_in_force`, ...).",
"type": "string"
},
"external": {
"description": "True when the other end is not an object of this index.",
"type": "boolean"
},
"field": {
"description": "The front matter key the reference was declared under.",
"type": "string"
},
"kind": {
"description": "Its kind — of the layer, or the external vocabulary (`file`, `test`, `command`).",
"type": "string"
},
"label": {
"description": "What to show: the identity of an object, or the name of something outside.",
"type": "string"
},
"route": {
"description": "Its entity route, when it has one; absent for something outside the layer.",
"type": [
"string",
"null"
]
},
"title": {
"description": "Its title, when it is an object that has one.",
"type": [
"string",
"null"
]
},
"uri": {
"description": "The object at the other end: its URI when the layer holds it.",
"type": [
"string",
"null"
]
}
},
"required": [
"direction",
"edge",
"field",
"kind",
"label",
"external"
],
"type": "object"
},
"EntityEvidence": {
"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```",
"properties": {
"artifacts": {
"description": "Every artefact named, with whether the tree holds it.",
"items": {
"$ref": "#/$defs/ClaimedArtifact"
},
"type": "array"
},
"meaning": {
"description": "That state in one sentence, for a reader.",
"type": "string"
},
"proof": {
"anyOf": [
{
"$ref": "#/$defs/EntityProofRef"
},
{
"type": "null"
}
],
"description": "The capability that can say more, when this kind has one."
},
"state": {
"$ref": "#/$defs/EntityEvidenceState",
"description": "The state, which is never rounded up."
}
},
"required": [
"state",
"meaning"
],
"type": "object"
},
"EntityEvidenceState": {
"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```",
"oneOf": [
{
"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.",
"type": "string"
}
]
},
"EntityProofRef": {
"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```",
"properties": {
"address": {
"description": "How to ask it here.",
"type": "string"
},
"capability": {
"description": "The capability that answers it.",
"type": "string"
},
"title": {
"description": "Its title.",
"type": "string"
}
},
"required": [
"capability",
"title",
"address"
],
"type": "object"
},
"EntitySurface": {
"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```",
"properties": {
"address": {
"description": "The address on it: a URI, a route, a command line.",
"type": "string"
},
"detail": {
"description": "What a reader does with it.",
"type": "string"
},
"surface": {
"description": "Which surface: `mcp`, `http`, `cli`, `cockpit`, `site`.",
"type": "string"
}
},
"required": [
"surface",
"address",
"detail"
],
"type": "object"
},
"ObjectProvenance": {
"description": "Where an object came from. Every field is computed from the repository, never authored.",
"properties": {
"bytes": {
"description": "Size of the file in bytes.",
"format": "uint64",
"minimum": 0,
"type": "integer"
},
"directory": {
"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.",
"type": "string"
},
"member": {
"description": "For an object that is one member of a collection file: the member's key path in\nthe file (`claims.3`).",
"type": [
"string",
"null"
]
},
"path": {
"description": "Repository-relative path, forward slashes, as the version-control index names it.",
"type": "string"
},
"section": {
"description": "The manifest section the path falls under (`rules`, `prompts`, ...), when it falls\nunder one; a root `README.md` falls under none.",
"type": [
"string",
"null"
]
},
"source_class": {
"description": "The `sources.yaml` class that discovered the file (`rule`, `readme`, ...).",
"type": "string"
}
},
"required": [
"path",
"directory",
"source_class",
"bytes"
],
"type": "object"
},
"RelationDirection": {
"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```",
"oneOf": [
{
"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.",
"type": "string"
}
]
}
},
"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```",
"properties": {
"content": {
"description": "The file as read.",
"type": "string"
},
"description": {
"description": "The one-line description, when the kind holds one.",
"type": [
"string",
"null"
]
},
"documentation": {
"anyOf": [
{
"$ref": "#/$defs/EntityDocumentation"
},
{
"type": "null"
}
],
"default": 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."
},
"evidence": {
"$ref": "#/$defs/EntityEvidence",
"description": "What can be said about its enforcement without running anything."
},
"id": {
"description": "The capability id, `<kind>.<identity>`.",
"type": "string"
},
"identity": {
"description": "The identity within the kind.",
"type": "string"
},
"kind": {
"description": "The kind.",
"type": "string"
},
"kind_route": {
"description": "The route of its kind's index.",
"type": "string"
},
"media_type": {
"description": "IANA media type of `content`.",
"type": "string"
},
"metadata": {
"description": "The parsed front matter or YAML, keys in the file's order."
},
"provenance": {
"$ref": "#/$defs/ObjectProvenance",
"description": "Where it came from."
},
"relations": {
"description": "The references it declares, and the references that resolve to it.",
"items": {
"$ref": "#/$defs/EntityEdge"
},
"type": "array"
},
"route": {
"description": "Its route in the Cockpit.",
"type": "string"
},
"slug": {
"description": "The last segment of its route, derived from the identity.",
"type": "string"
},
"surfaces": {
"description": "Every surface that answers for it.",
"items": {
"$ref": "#/$defs/EntitySurface"
},
"type": "array"
},
"title": {
"description": "The title, when the kind's title rule found one.",
"type": [
"string",
"null"
]
},
"uri": {
"description": "`majordomus://<kind>/<identity>`.",
"type": "string"
}
},
"required": [
"uri",
"id",
"kind",
"identity",
"slug",
"route",
"kind_route",
"provenance",
"metadata",
"media_type",
"content",
"relations",
"surfaces",
"evidence"
],
"title": "EntityView",
"type": "object"
}Politiky
- benchmark
- povinné — cíl na každém transportu, který expozice deklaruje; případy jsou ty ze vstupního typu
- cache
- vypnuto — každé volání spustí handler
Cíle benchmarku
Odvozeno z registru pro tento repozitář: jeden požadavek na transport a případy, které poskytuje typ vstupu. Celá matice je na stránce měření výkonu.
| transport | state | případy | targets |
|---|---|---|---|
| direct | pokryto | 1 | first-object |
| mcp | pokryto | 1 | first-object |
| http | pokryto | 1 | first-object |