release.analysis
The public capability surface of this tree against the one the last release published, every movement of it named with the reason it counts for what it does, and the smallest version this tree may therefore declare. The compatibility level is measured from the contract rather than read off the commit subjects: a capability, route, MCP tool, command, or a required field of either schema that a caller could hold and cannot any more is breaking whatever the commit that removed it called itself. What the commits said about themselves is carried beside the verdict as evidence, and the answer says when the two disagree.
The public capability surface of this tree against the one the last release published, every movement of it named with the reason it counts for what it does, and the smallest version this tree may therefore declare. The compatibility level is measured from the contract rather than read off the commit subjects: a capability, route, MCP tool, command, or a required field of either schema that a caller could hold and cannot any more is breaking whatever the commit that removed it called itself. What the commits said about themselves is carried beside the verdict as evidence, and the answer says when the two disagree.
- query
- implemented
- module release
- #release
- #version
- #compatibility
Exposure
Where this one definition is reachable. Absence is explicit: a surface not listed does not carry it.
| surface | as |
|---|---|
| MCP | tool majordomus_release_analysis |
| HTTP | GET /api/v1/release/analysis operationId release.analysis |
| 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 · ReleaseAnalysisInput
use majordomus_cli::capability::builtin::release::AnalysisInput;
// Absent means the last release the layer records, which is the canonical baseline.
let input = AnalysisInput::default();
assert!(input.since.is_none());
let explicit = AnalysisInput { since: Some("v0.4.0".into()) };
assert_eq!(explicit.since.as_deref(), Some("v0.4.0"));| property | type | required | description |
|---|---|---|---|
| since | string | null | no | A ref to compare with instead of the last release: v0.4.0, or any commit that
carries a committed registry. |
JSON Schema
{
"additionalProperties": false,
"description": "Which release to measure this tree against.\n\n```\nuse majordomus_cli::capability::builtin::release::AnalysisInput;\n// Absent means the last release the layer records, which is the canonical baseline.\nlet input = AnalysisInput::default();\nassert!(input.since.is_none());\nlet explicit = AnalysisInput { since: Some(\"v0.4.0\".into()) };\nassert_eq!(explicit.since.as_deref(), Some(\"v0.4.0\"));\n```",
"properties": {
"since": {
"description": "A ref to compare with instead of the last release: `v0.4.0`, or any commit that\ncarries a committed registry.",
"type": [
"string",
"null"
]
}
},
"title": "ReleaseAnalysisInput",
"type": "object"
}output · ReleaseVersionPlan
One value, and every surface renders it — the command line, /api/v1/release/analysis,
the MCP tool, the Cockpit panel, the generated document and the CI gate. There is no
second computation anywhere, which is the property that makes the number mean something.
use majordomus_cli::release::compat::{Status, VersionPlan};
#### use majordomus_cli::release::compat::{Baseline, CommitEvidence, Impact, Policy};
#### use majordomus_cli::release::version::Version;
let plan = VersionPlan {
policy: Policy::for_version(Version::parse("0.5.0").unwrap()),
baseline: Baseline {
version: "0.5.0".into(), reference: "v0.5.0".into(), read_at: "3a032c1e".into(),
commit: "3a032c1e".into(), recorded: true, atoms: 300, fingerprint: "sha256:a".into(),
},
declared_version: "0.5.0".into(),
tool_version: "0.5.0".into(),
writers_agree: true,
atoms: 333,
fingerprint: "sha256:b".into(),
implied: Impact::Minor,
required: Impact::Minor,
declared: Impact::None,
required_version: "0.6.0".into(),
status: Status::Blocked,
breaking: false,
changes: Vec::new(),
commits: CommitEvidence { commits: 24, implied: Impact::Patch, breaking: Vec::new() },
understated: true,
diagnostics: Vec::new(),
};
// The contract requires a minor and the version declares none: this tree may not land.
assert_eq!(plan.status, Status::Blocked);
assert!(plan.declared < plan.required);
assert!(plan.understated, "the commit subjects claim less than the contract did");| property | type | required | description |
|---|---|---|---|
| atoms | integer | yes | How many public atoms this tree carries. |
| baseline | reference | yes | The release this was measured against. |
| breaking | boolean | yes | Whether anything a caller could hold is gone. |
| changes | array | yes | Every movement of the contract, worst first. |
| commits | reference | yes | What the commits said about themselves. |
| declared | reference | yes | What the two version numbers already represent. |
| declared_version | string | yes | The version the crate manifest declares — the one place it is authored. |
| diagnostics | array | no | Anything incoherent about the release state, whatever the verdict. |
| fingerprint | string | yes | The fingerprint of this tree's surface. |
| implied | reference | yes | What the surface movement amounts to on its own. |
| policy | reference | yes | The policy the verdict was reached under. |
| required | reference | yes | What the policy requires of it. |
| required_version | string | yes | The smallest version this tree may declare. |
| status | reference | yes | Whether it does. |
| tool_version | string | yes | The version bin/majordomus prints: what its projection, share/version.txt, states. |
| understated | boolean | yes | Whether the commit subjects understate what the contract actually did. |
| writers_agree | boolean | yes | Whether that projection is current — states what the manifest declares. The name is kept from when the version had two writers; the manifest is the only one now. |
JSON Schema
{
"$defs": {
"ReleaseBaseline": {
"description": "The release this tree is measured against, and how it was found.\n\n```\nuse majordomus_cli::release::compat::Baseline;\nlet baseline = Baseline {\n version: \"0.5.0\".into(),\n reference: \"v0.5.0\".into(),\n read_at: \"3a032c1e033b\".into(),\n commit: \"3a032c1e033b\".into(),\n recorded: true,\n atoms: 300,\n fingerprint: \"sha256:…\".into(),\n};\n// `recorded` is the half that matters: a baseline taken from a tag alone is reported.\nassert!(baseline.recorded);\nassert_eq!(baseline.reference, \"v0.5.0\");\n```",
"properties": {
"atoms": {
"description": "How many public atoms it published.",
"format": "uint",
"minimum": 0,
"type": "integer"
},
"commit": {
"description": "The commit that ref resolved to.",
"type": "string"
},
"fingerprint": {
"description": "The fingerprint of the surface it published.",
"type": "string"
},
"read_at": {
"description": "The ref the surface was actually read from — the commit the release record names,\nwhich a clone always has, rather than a tag it may not.",
"type": "string"
},
"recorded": {
"description": "Whether the layer holds a release record for it, which is what makes it canonical\nrather than the newest thing that looked like a tag.",
"type": "boolean"
},
"reference": {
"description": "`v0.5.0`: the release, as a reader recognises it.",
"type": "string"
},
"version": {
"description": "`0.5.0`.",
"type": "string"
}
},
"required": [
"version",
"reference",
"read_at",
"commit",
"recorded",
"atoms",
"fingerprint"
],
"type": "object"
},
"ReleaseChangeDirection": {
"description": "Whether a change added, removed or altered part of the contract.\n\n```\nuse majordomus_cli::release::compat::Direction;\nassert_eq!(Direction::Removed.sign(), '-');\n```",
"oneOf": [
{
"const": "added",
"description": "It was not there before.",
"type": "string"
},
{
"const": "removed",
"description": "It was there before and is not now.",
"type": "string"
},
{
"const": "changed",
"description": "It is there in both, and differs.",
"type": "string"
}
]
},
"ReleaseCommitEvidence": {
"description": "What the commit subjects since the baseline said about themselves.\n\nEvidence, never authority. It is here so that a person can see the human account and the\nmeasured one side by side, and so that `VersionPlan::understated` can say when the two\ndisagree — which is the case this whole subsystem exists to catch.\n\n```\nuse majordomus_cli::release::compat::{CommitEvidence, Impact};\nlet commits = CommitEvidence {\n commits: 24,\n implied: Impact::Patch,\n breaking: Vec::new(),\n};\n// The contract moved by a minor and the subjects claim a patch: the plan reports that\n// disagreement rather than deferring to the words.\nassert!(commits.implied < Impact::Minor);\n```",
"properties": {
"breaking": {
"description": "The subjects of the ones that claimed to be breaking.",
"items": {
"type": "string"
},
"type": "array"
},
"commits": {
"description": "How many commits since the baseline.",
"format": "uint",
"minimum": 0,
"type": "integer"
},
"implied": {
"$ref": "#/$defs/ReleaseImpact",
"description": "What their conventional-commit types alone would have implied."
}
},
"required": [
"commits",
"implied"
],
"type": "object"
},
"ReleaseDiagnostic": {
"description": "Something about the release state that a person should be told, whatever the verdict.\n\n```\nuse majordomus_cli::release::compat::{Diagnostic, Severity};\nlet d = Diagnostic {\n id: \"tag-without-record\".into(),\n severity: Severity::Warning,\n message: \"v0.1.0 is tagged and the layer holds no release record for it\".into(),\n};\nassert_eq!(d.severity, Severity::Warning, \"a warning states the plan still stands\");\n```",
"properties": {
"id": {
"description": "`tag-without-record`, `tag-predates-records`, `record-without-tag`,\n`tag-commit-mismatch`.",
"type": "string"
},
"message": {
"description": "What is wrong, in one sentence.",
"type": "string"
},
"severity": {
"$ref": "#/$defs/ReleaseSeverity",
"description": "Whether it stops a release, only warns, or only informs."
}
},
"required": [
"id",
"severity",
"message"
],
"type": "object"
},
"ReleaseImpact": {
"description": "How much a change moves the contract.\n\nOrdered, so that \"the declared bump covers what is required\" is a comparison rather than\na table:\n\n```\nuse majordomus_cli::release::compat::Impact;\nassert!(Impact::Major > Impact::Minor);\nassert!(Impact::Minor > Impact::Patch);\nassert!(Impact::Patch > Impact::None);\n```",
"oneOf": [
{
"const": "none",
"description": "The contract did not move.",
"type": "string"
},
{
"const": "patch",
"description": "It moved behind the boundary: a caller cannot tell.",
"type": "string"
},
{
"const": "minor",
"description": "Something arrived; every existing caller still works.",
"type": "string"
},
{
"const": "major",
"description": "Something a caller could hold is gone or changed under it.",
"type": "string"
}
]
},
"ReleasePolicy": {
"description": "The compatibility policy, named and versioned.\n\n```\nuse majordomus_cli::release::compat::{Impact, Policy, POLICY_SCHEMA};\nuse majordomus_cli::release::version::Version;\nlet policy = Policy::for_version(Version::parse(\"0.5.0\").unwrap());\nassert_eq!(policy.schema, POLICY_SCHEMA, \"a verdict carries the rules it was reached under\");\nassert_eq!(policy.required_of(Impact::Major), Impact::Minor);\n```",
"properties": {
"mode": {
"$ref": "#/$defs/ReleasePolicyMode",
"description": "Which rule is in force, decided by the baseline version alone."
},
"schema": {
"description": "`majordomus/version-policy/v1`.",
"type": "string"
}
},
"required": [
"schema",
"mode"
],
"type": "object"
},
"ReleasePolicyMode": {
"description": "How this project turns a measured impact into the version it owes.\n\nSemantic versioning hands `0.y.z` a blanket exemption — \"anything MAY change at any\ntime\" — and Elm refuses the exemption by starting every package at `1.0.0`, because a\nrule with an escape hatch is a rule about when to use the hatch. This repository is `0.x`\nand has not earned `1.0.0`, so neither answer is available: demanding `major` would\ndemand `1.0.0` for a single removal, and granting the exemption would let a breaking\nchange ship as a patch.\n\nSo below `1.0.0` the floor is a minor. Any movement of the surface, gone or new, is at\nleast a minor release; only an unchanged surface owes nothing. That is the strongest\nsignal `0.x` has, and a removal is still *named* as breaking whatever the verdict, so the\nevidence does not quietly become a minor along with the number.\n\nAt `1.0.0` the shift ends: `Mode::Semver` is the ordinary rule, and the transition is\none line of `Policy::required_of`.\n\n```\nuse majordomus_cli::release::compat::{Mode, Policy};\nuse majordomus_cli::release::version::Version;\nassert_eq!(Policy::for_version(Version::parse(\"0.9.0\").unwrap()).mode, Mode::Pre1_0Strict);\nassert_eq!(Policy::for_version(Version::parse(\"1.0.0\").unwrap()).mode, Mode::Semver);\n```",
"oneOf": [
{
"const": "pre1-0-strict",
"description": "Below `1.0.0`: breaking changes are allowed, and cost a minor rather than nothing.",
"type": "string"
},
{
"const": "semver",
"description": "At or above `1.0.0`: the impact is the requirement.",
"type": "string"
}
]
},
"ReleaseSeverity": {
"description": "How much a diagnostic matters.\n\nAn error says the plan itself cannot be trusted and blocks the verdict; a warning says\nsomething about the release state is wrong and the verdict stands anyway; a note says\nsomething true and not wrong, so that the warnings keep meaning what they say.\n\n```\nuse majordomus_cli::release::compat::Severity;\nassert_ne!(Severity::Error, Severity::Warning);\nassert_ne!(Severity::Warning, Severity::Note);\n```",
"oneOf": [
{
"const": "error",
"description": "The plan cannot be trusted; the verdict is blocked.",
"type": "string"
},
{
"const": "warning",
"description": "The plan stands, and something is still wrong.",
"type": "string"
},
{
"const": "note",
"description": "Nothing is wrong; the reader is told something about the release state anyway.",
"type": "string"
}
]
},
"ReleaseStatus": {
"description": "Whether the tree may land or ship as it stands.\n\n```\nuse majordomus_cli::release::compat::Status;\nassert_ne!(Status::Ok, Status::Blocked);\n```",
"oneOf": [
{
"const": "ok",
"description": "The declared version covers what the contract did.",
"type": "string"
},
{
"const": "blocked",
"description": "It does not, and the plan says by how much.",
"type": "string"
}
]
},
"ReleaseSurfaceChange": {
"description": "One movement of the public contract, with the reason it counts for what it does.\n\n```\nuse majordomus_cli::release::compat::{Direction, Impact, SurfaceChange, SurfaceKind};\nlet change = SurfaceChange {\n impact: Impact::Major,\n direction: Direction::Removed,\n surface: SurfaceKind::Http,\n capability: \"alpha.list\".into(),\n id: \"GET /api/v1/alpha\".into(),\n detail: \"the capability survives; this way of reaching it does not\".into(),\n};\nassert_eq!(change.to_string(), \"- route GET /api/v1/alpha\");\n```",
"properties": {
"capability": {
"description": "The capability it belongs to.",
"type": "string"
},
"detail": {
"description": "Why this impact and not a smaller one, in one clause.",
"type": "string"
},
"direction": {
"$ref": "#/$defs/ReleaseChangeDirection",
"description": "Added, removed, or altered."
},
"id": {
"description": "The thing itself: `GET /api/v1/foo`, `majordomus_release_analysis`, `release.analysis`.",
"type": "string"
},
"impact": {
"$ref": "#/$defs/ReleaseImpact",
"description": "How much it moves the contract on its own."
},
"surface": {
"$ref": "#/$defs/ReleaseSurfaceKind",
"description": "Which promise it is."
}
},
"required": [
"impact",
"direction",
"surface",
"capability",
"id",
"detail"
],
"type": "object"
},
"ReleaseSurfaceKind": {
"description": "Which projection of the contract a change is in.\n\nNot where it was *found* — everything here is found in the registry — but which promise\nit is, so that a reader sees \"a route is gone\" rather than a JSON pointer.\n\n```\nuse majordomus_cli::release::compat::SurfaceKind;\nassert_eq!(SurfaceKind::Http.as_str(), \"route\");\n```",
"oneOf": [
{
"const": "capability",
"description": "The capability itself.",
"type": "string"
},
{
"const": "kind",
"description": "Whether calling it changes anything.",
"type": "string"
},
{
"const": "mcp_tool",
"description": "An MCP tool name.",
"type": "string"
},
{
"const": "mcp_resource",
"description": "An MCP resource URI.",
"type": "string"
},
{
"const": "http",
"description": "An HTTP method and path.",
"type": "string"
},
{
"const": "cli",
"description": "A command-line path.",
"type": "string"
},
{
"const": "input",
"description": "What a caller may send.",
"type": "string"
},
{
"const": "output",
"description": "What a caller is promised back.",
"type": "string"
}
]
}
},
"description": "The whole answer: what moved, what that requires, and what the tree declares.\n\nOne value, and every surface renders it — the command line, `/api/v1/release/analysis`,\nthe MCP tool, the Cockpit panel, the generated document and the CI gate. There is no\nsecond computation anywhere, which is the property that makes the number mean something.\n\n```\nuse majordomus_cli::release::compat::{Status, VersionPlan};\n# use majordomus_cli::release::compat::{Baseline, CommitEvidence, Impact, Policy};\n# use majordomus_cli::release::version::Version;\nlet plan = VersionPlan {\n policy: Policy::for_version(Version::parse(\"0.5.0\").unwrap()),\n baseline: Baseline {\n version: \"0.5.0\".into(), reference: \"v0.5.0\".into(), read_at: \"3a032c1e\".into(),\n commit: \"3a032c1e\".into(), recorded: true, atoms: 300, fingerprint: \"sha256:a\".into(),\n },\n declared_version: \"0.5.0\".into(),\n tool_version: \"0.5.0\".into(),\n writers_agree: true,\n atoms: 333,\n fingerprint: \"sha256:b\".into(),\n implied: Impact::Minor,\n required: Impact::Minor,\n declared: Impact::None,\n required_version: \"0.6.0\".into(),\n status: Status::Blocked,\n breaking: false,\n changes: Vec::new(),\n commits: CommitEvidence { commits: 24, implied: Impact::Patch, breaking: Vec::new() },\n understated: true,\n diagnostics: Vec::new(),\n};\n// The contract requires a minor and the version declares none: this tree may not land.\nassert_eq!(plan.status, Status::Blocked);\nassert!(plan.declared < plan.required);\nassert!(plan.understated, \"the commit subjects claim less than the contract did\");\n```",
"properties": {
"atoms": {
"description": "How many public atoms this tree carries.",
"format": "uint",
"minimum": 0,
"type": "integer"
},
"baseline": {
"$ref": "#/$defs/ReleaseBaseline",
"description": "The release this was measured against."
},
"breaking": {
"description": "Whether anything a caller could hold is gone.",
"type": "boolean"
},
"changes": {
"description": "Every movement of the contract, worst first.",
"items": {
"$ref": "#/$defs/ReleaseSurfaceChange"
},
"type": "array"
},
"commits": {
"$ref": "#/$defs/ReleaseCommitEvidence",
"description": "What the commits said about themselves."
},
"declared": {
"$ref": "#/$defs/ReleaseImpact",
"description": "What the two version numbers already represent."
},
"declared_version": {
"description": "The version the crate manifest declares — the one place it is authored.",
"type": "string"
},
"diagnostics": {
"description": "Anything incoherent about the release state, whatever the verdict.",
"items": {
"$ref": "#/$defs/ReleaseDiagnostic"
},
"type": "array"
},
"fingerprint": {
"description": "The fingerprint of this tree's surface.",
"type": "string"
},
"implied": {
"$ref": "#/$defs/ReleaseImpact",
"description": "What the surface movement amounts to on its own."
},
"policy": {
"$ref": "#/$defs/ReleasePolicy",
"description": "The policy the verdict was reached under."
},
"required": {
"$ref": "#/$defs/ReleaseImpact",
"description": "What the policy requires of it."
},
"required_version": {
"description": "The smallest version this tree may declare.",
"type": "string"
},
"status": {
"$ref": "#/$defs/ReleaseStatus",
"description": "Whether it does."
},
"tool_version": {
"description": "The version `bin/majordomus` prints: what its projection, `share/version.txt`, states.",
"type": "string"
},
"understated": {
"description": "Whether the commit subjects understate what the contract actually did.",
"type": "boolean"
},
"writers_agree": {
"description": "Whether that projection is current — states what the manifest declares. The name is\nkept from when the version had two writers; the manifest is the only one now.",
"type": "boolean"
}
},
"required": [
"policy",
"baseline",
"declared_version",
"tool_version",
"writers_agree",
"atoms",
"fingerprint",
"implied",
"required",
"declared",
"required_version",
"status",
"breaking",
"changes",
"commits",
"understated"
],
"title": "ReleaseVersionPlan",
"type": "object"
}Policies
- benchmark
- waived — waived: published_history
- cache
- disabled — every call runs the handler
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.
| transport | state | cases | targets |
|---|---|---|---|
| direct | waived (published_history) | 0 | |
| mcp | waived (published_history) | 0 | |
| http | waived (published_history) | 0 |