release.version
The version, and the one the contract requires next
What it is
The version the crate manifest declares — the one place it is authored — the version the shell tool prints from its projection `share/version.txt`, and whether that projection is current — the question `generate --check` refuses and `scripts/release-version --check` gates on. Then the next version, which is the public contract's answer: the version `release analyze` requires against the last release — the declared version when it already satisfies the contract, otherwise the smallest one it allows, and the smallest release above the last when the contract requires none over it — with `decided_by` naming who answered. It is the one release selection `release bump` also raises to when no target is named, so the report and the writer cannot disagree. When the contract cannot be measured — no published baseline, a version that is not three numbers, or any other error that makes the plan unsound, the plan `release bump` refuses to write from — the baseline is refused, not guessed: `next` is absent, `decided_by` is `undecided` and `contract_unreadable` carries every error, one per line. The bump the conventional commits since the last release imply, the version it would produce and the commits themselves are carried beside it as evidence, and never answer `next` in the contract's place.
Descriptor
- Canonical id
release.version- Module
release- Kind
- query
- Stability
- implemented
- Provenance
- declaredcached
- Source
apps/majordomus-cli/src/capability/builtin/release.rs- Cache
process, 2 entries, per checkout state- Benchmark
required- Tags
- releaseversion
Run
queryProjections
| Interface | As | Where |
|---|---|---|
| MCP tool | majordomus_release_version | - |
| MCP resource | not exposed | - |
| HTTP | GET /api/v1/release/version | Swagger |
| Command line | not exposed | - |
Schemas
Input · Empty
{
"type": "object",
"additionalProperties": false,
"description": "No input.",
"title": "Empty"
}Output · ReleaseVersionReport
{
"type": "object",
"properties": {
"declared": {
"type": "string",
"description": "The version the crate manifest declares — the one place it is authored."
},
"tool": {
"type": "string",
"description": "The version `bin/majordomus` prints: what its projection, `share/version.txt`, states."
},
"agree": {
"type": "boolean",
"description": "Whether that projection is current. `generate --check` refuses a stale one and\n`scripts/release-version --check` proves the tool prints it; this is the same question\nasked by the executable, so every surface can show the answer."
},
"last_release": {
"type": [
"string",
"null"
],
"description": "The last release: the highest version the layer records (the newest version tag when\nit records none) — the baseline `release analyze` measures the contract from, so the\ncontract and the commit evidence describe one window."
},
"bump": {
"type": "string",
"description": "What the commit subjects since it imply: `major`, `minor`, `patch`, or `none`.\nEvidence only — the contract decides `next`; `release analyze` reports where the two\ndisagree."
},
"commits_imply": {
"type": [
"string",
"null"
],
"description": "The version that bump would produce from the last release (from the declared version\nwhen nothing was released yet). Evidence only, even when the contract cannot answer."
},
"next": {
"type": [
"string",
"null"
],
"description": "The next version. When the contract could be measured, the version it requires: the\ndeclared version when that already satisfies it, otherwise the smallest one it allows\n— and, when that is the last release itself, the smallest release above it. Absent\nwhen nothing would be released, and when the contract could not be measured\n(`decided_by: undecided`): an unmeasurable baseline is refused, not guessed."
},
"decided_by": {
"$ref": "#/$defs/ReleaseVersionDecidedBy",
"description": "Who answered `next`.",
"default": "undecided"
},
"contract_unreadable": {
"type": [
"string",
"null"
],
"description": "Why the contract could not answer, when `decided_by` is `undecided`."
},
"changes": {
"type": "array",
"items": {
"$ref": "#/$defs/ReleaseChange"
},
"description": "How many commits since the last release, and of what kind — the evidence for the\nbump, so that a surprising answer can be checked rather than believed."
}
},
"required": [
"declared",
"tool",
"agree",
"bump",
"changes"
],
"description": "What the version is, what the public contract requires it to become, and what the\ncommits since the last release say about themselves.\n\n`next` is the contract's answer (ADR 0051): the analysis' `required_version`, or the\nsmallest release above the last when that requirement is the last release itself. The\ncommit inference is evidence — `bump` and `commits_imply` — and never the answer: when the\ncontract cannot be measured `next` is absent, `decided_by` is `undecided` and\n`contract_unreadable` says why. The selection is `crate::release::version::select`, the\none function `release bump` also takes its default target from.",
"title": "ReleaseVersionReport",
"$defs": {
"ReleaseVersionDecidedBy": {
"oneOf": [
{
"type": "string",
"const": "contract",
"description": "`release analyze` measured the public contract against the last release, and `next`\nis the version it requires: the declared version when that already satisfies the\ncontract, otherwise the smallest one it allows."
},
{
"type": "string",
"const": "contract_and_commits",
"description": "The contract was measured and requires no release over the last one — only behaviour\nbehind the public boundary changed — while the commits carry changes: `next` is the\nsmallest release above the last, a patch, however much the commit subjects claim."
},
{
"type": "string",
"const": "undecided",
"description": "The contract could not be measured — no published baseline carries a registry, or a\nversion is not three numbers — so nothing decides `next`: it is absent, the reason is\nin `contract_unreadable`, and `release bump` without an explicit target refuses."
}
],
"description": "Who answered a version report's `next`: the public contract, the contract with the\ncommits — or no one, because the contract could not be measured.\n\nADR 0051 makes the contract the authority and demotes conventional commits to evidence,\nso `DecidedBy::Contract` is the normal answer. An unmeasurable contract is refused, not\nguessed: `DecidedBy::Undecided` leaves `next` absent and carries the reason, and the\ncommit inference stays what it always is, evidence — never the answer in its place.\n\n```\nuse majordomus_cli::release::model::DecidedBy;\nassert_eq!(serde_json::to_value(DecidedBy::Contract).unwrap(), \"contract\");\nassert_eq!(DecidedBy::Undecided.as_str(), \"undecided\");\nassert_eq!(DecidedBy::default(), DecidedBy::Undecided);\n```"
},
"ReleaseChange": {
"type": "object",
"properties": {
"kind": {
"$ref": "#/$defs/ReleaseChangeKind",
"description": "What it did."
},
"scope": {
"type": [
"string",
"null"
],
"description": "The area it did it in, when the subject named one: `commands`, `ci`, `site`."
},
"subject": {
"type": "string",
"description": "The subject, without the type and scope that prefixed it."
},
"breaking": {
"type": "boolean",
"description": "Whether the commit marked itself breaking, with `!` or a `BREAKING CHANGE:` trailer."
},
"commit": {
"type": "string",
"description": "The abbreviated commit."
},
"url": {
"type": [
"string",
"null"
],
"description": "Where that commit can be read, when the repository's own URL is known. Derived from\n`about::REPOSITORY`, never written beside each entry."
},
"references": {
"type": "array",
"items": {
"$ref": "#/$defs/ReleaseReference"
},
"description": "The records of the layer this commit names — issues, milestones — resolved against\nwhat the layer actually holds."
}
},
"required": [
"kind",
"subject",
"breaking",
"commit"
],
"description": "One change, from one commit."
},
"ReleaseChangeKind": {
"oneOf": [
{
"type": "string",
"const": "feat",
"description": "A capability a person did not have before."
},
{
"type": "string",
"const": "fix",
"description": "Behaviour that was wrong and is not any more."
},
{
"type": "string",
"const": "perf",
"description": "Performance, with the behaviour unchanged."
},
{
"type": "string",
"const": "refactor",
"description": "Structure, with the behaviour unchanged."
},
{
"type": "string",
"const": "docs",
"description": "Documentation."
},
{
"type": "string",
"const": "test",
"description": "Tests."
},
{
"type": "string",
"const": "chore",
"description": "The build, the pipeline, the tooling."
},
{
"type": "string",
"const": "ci",
"description": "Continuous integration."
},
{
"type": "string",
"const": "style",
"description": "Formatting, with the behaviour unchanged."
},
{
"type": "string",
"const": "build",
"description": "The build system or its dependencies."
},
{
"type": "string",
"const": "revert",
"description": "A change that undoes another."
},
{
"type": "string",
"const": "merge",
"description": "An integration: one line of work brought into another.\n\nNot in the conventional-commit specification, and in this repository's history 38\ntimes and counting, because integration here is a deliberate act with a subject\nsomebody writes — `merge: bring origin/master into feature/x` — and not the sentence\n`git merge` composes. Declaring it is what lets the validator hold those subjects to\nthe same width and the same references as every other commit, instead of either\nrefusing a convention this project practises or exempting it from being checked at\nall. The changelog never renders it: `commits::in_range` reads the log with\n`--no-merges`, so a merge commit is not a release entry however its subject is\nspelled."
},
{
"type": "string",
"const": "other",
"description": "A commit whose subject does not parse as a conventional commit."
}
],
"description": "What a conventional commit says it did.\n\nThe set is the one the repository's own commit convention uses; a commit whose subject\ndoes not parse is `ChangeKind::Other` and still appears, because a changelog that\nsilently drops what it cannot classify is a changelog that lies by omission."
},
"ReleaseReference": {
"type": "object",
"properties": {
"kind": {
"type": "string",
"description": "`issue` or `milestone`."
},
"id": {
"type": "string",
"description": "`I1305`, `M000`."
},
"title": {
"type": "string",
"description": "What it is, from the record itself."
},
"route": {
"type": [
"string",
"null"
],
"description": "Where it is published on this site, when it has a page."
}
},
"required": [
"kind",
"id",
"title"
],
"description": "A record of the layer that a commit names in its own text.\n\nInferred, never declared beside the commit: an issue id or a milestone id appearing in a\nsubject or a body is a reference, and the layer already holds the object it refers to. A\nreference to something the layer does not have is not carried — a link to a record that\ndoes not exist is worse than no link, because the reader cannot tell until they follow it.\n\n```\nuse majordomus_cli::release::model::Reference;\nlet r = Reference {\n kind: \"issue\".into(),\n id: \"I1305\".into(),\n title: \"An observed contract has a fingerprint\".into(),\n route: Some(\"/plan/i1305/\".into()),\n};\nlet json = serde_json::to_value(&r).unwrap();\nassert_eq!(json[\"id\"], \"I1305\");\nassert_eq!(json[\"route\"], \"/plan/i1305/\");\n// a reference the site has no page for carries no route rather than an invented one\nlet bare = Reference { route: None, ..r };\nassert!(serde_json::to_value(&bare).unwrap().get(\"route\").is_none());\n```"
}
}
}Examples
These are the capability's own benchmark cases: the same inputs the benchmark runs and the OpenAPI document shows.
default
{}