Skip to content

The next version is allocated at landing, by the integration executor, from the trunk's version and the change's measured impact under a typed policy floor

The next version is allocated at landing, by the integration executor, from the trunk's version and the change's measured impact under a typed policy floor

majordomus://adr/adr-0134proposed releaseversioningintegration

it references

  • put_in_force apps/majordomus-cli/src/integration/allocate.rs (file)
  • put_in_force apps/majordomus-cli/src/integration/drain.rs (file)
  • put_in_force apps/majordomus-cli/src/release/compat.rs (file)
  • put_in_force apps/majordomus-cli/src/release/landing.rs (file)
  • put_in_force adr-0029
  • put_in_force adr-0051
  • put_in_force adr-0085
  • put_in_force project.release-is-a-projection@2
  • put_in_force project.the-version-is-measured@2
  • put_in_force test/cases/1060_the_version_is_allocated_at_landing.sh (test)
  • put_in_force test/cases/1061_the_executor_allocates_the_version_at_landing.sh (test)

referenced by

Nothing in the layer names it. Backlinks are derived, so an empty list is an answer rather than an omission.

134. The next version is allocated at landing, by the integration executor, from the trunk's version and the change's measured impact under a typed policy floor

Context

The version has one writer (ADR 0029, ADR 0085) and its minimum is measured from the public contract against the last release (ADR 0051). Who writes it, and when, was left to whoever noticed first: version-surface refuses the first pull request after a release that moves the contract until that pull request raises the version itself.

On 2026-10-09 and 2026-10-10 that cost, measured in this repository's own work:

  • the next number was negotiated between sessions by message (0.19.1 for one pull request, 0.19.2 for another, 0.20.0 for "the first feature");
  • every feature branch then conflicted with the trunk on the version line of apps/majordomus-cli/Cargo.toml, an authored path the integration executor's refresh refuses, so each was repaired by hand (#807, #821, #827, #829);
  • a release in between (v0.20.0) made every carried bump stale, and each branch had to be bumped again to 0.21.0.

A pull request chose its version before it knew which master it would land on. Nothing serialised the choice except people.

Decision

  1. The version is allocated at landing. When a repository's policy says release.allocation: landing, a pull request does not raise the version. The integration executor (ADR 0101) raises it in the refresh it already performs before a pull request's last CI run, after master is merged in and before the derive, so the head the required check judges and the head that lands carry the same version.
  2. The number is computed from the trunk, never from the branch. The next version is trunk_version.raised_to(selected), where trunk_version is what master declares at the moment of the refresh. A retry, a second refresh after master moved, or a recovery after a crash recomputes from the new trunk, so no integration can raise the version twice and two pull requests cannot hold the same number: the executor refreshes one pull request at a time under the integration lease, and a landing that moves master makes every other head behind again.
  3. What is selected is typed. selected = max(required_by_compatibility, floor):
    • required_by_compatibility is ADR 0051's measurement of this pull request's own movement of the public surface, master against the candidate, translated by the version's mode (below 1.0.0 a removal costs a minor, from 1.0.0 a major);
    • floor is release.floor in the policy: none, patch, minor or major, read through the version's mode as compatibility is, so below 1.0.0 a major floor asks a minor; a change that touches no path is no landing and owes no floor;
    • the changed paths are listed without rename detection, so a file moved into an exempt tree is also judged at the path it left;
    • a change that adds .ai/repo/releases/v<trunk>.yaml, the record of the release the trunk already declares, owes no floor: the record follows its tag and may change more than the record class covers (the v0.20.0 record also changed .gitattributes and docs/INSTALL.md), and raising it would carry it past the tag it records. Adding is what counts, measured from where the head forked: a change that edits a record already written owes the floor, so no pull request escapes it by touching the record;
    • an unknown movement stays what ADR 0051 makes it, never a safe patch.
  4. An exemption lowers the floor and nothing else. release.exempt declares classes, each with an id, a description and the paths it covers. A pull request is exempt when every path it changes is covered by one class: then the floor does not apply, and the compatibility requirement still does. A release record, the executor's own refresh and a change to plan records are the classes this repository declares. There is no per-pull-request switch.
  5. Allocation is not publication. A raised version is an integrated fact; a release is published by a tag and verified by its record and smoke. Nothing in this decision tags or publishes.
  6. A repository that says nothing is unchanged. release.allocation defaults to off and release.floor to none: the gate and the bump work exactly as before.

Consequences

  • majordomus release next (capability release.next) answers, read-only, what the executor would allocate for a head against a base, and why: the trunk version, the measured movement, the floor, the exemption if any, and the next version.
  • This repository sets allocation: landing and floor: minor: every eligible integration advances at least a minor (the owner's request of 2026-10-10).
  • The executor's refresh must resolve a conflict on the version line to the trunk's before it allocates, because the branch's own number is no longer an author's statement.
  • Consistency model, stated plainly: GitHub does not merge and publish atomically. What is guaranteed is that each landed integration carries exactly one allocation computed from the master it landed on, because the head that lands is the head the executor refreshed and CI judged (--match-head-commit), and the executor is the only writer of the allocation.

Delivery

  • Slice A (this record): the typed policy block, the decision engine, release.next.
  • Slice B: allocation inside the executor's refresh (integration/allocate.rs), version-line conflict resolution in the classification and in the refresh, and the Version-Allocated: <next> (<selected> from <trunk>) trailer on the commit the refresh pushes. The policy a landing obeys is the one master commits, read from the commit and never from the executor's working tree: the relation of a head to master is cached by the pair of commit ids as a fact forever, so everything it depends on must be a fact of those commits. A retry or a refresh after master moved recomputes from the new master (case 1061).
  • Slice B2: a head that already contains master but does not carry the number its landing allocates owes a refresh all the same. Its relation stays up_to_date and says which version it owes (owes_version, read from the two commits like everything the relation cache holds), and the freshness gate refuses it as needs_refresh with version_unallocated:VERSION, never as behind_master, which says master is missing. Delivered with slice B. allocation stays off in this repository until slice C, so that a pull request is not asked to bump by hand and refreshed again for it.
  • Slice C: version-surface on a pull request under allocation: landing refuses a hand bump instead of demanding one; version.yml stops opening bump pull requests.
  • Slice D: the release lifecycle as one typed state machine over the existing workflow.
  • Slice E: the manifest path from the policy, so an installed repository uses the same engine.

Where this comes from

canonical file
.ai/repo/adrs/0134-the-version-is-allocated-at-landing.md
identity
adr-0134
also served as
MCP resource majordomus://adr/adr-0134 · majordomus entity show adr/adr-0134 · GET /api/v1/entity?uri=majordomus://adr/adr-0134
in the Cockpit
/cockpit/objects/adr/adr-0134

This page is generated from the file above by scripts/generate-site-data; editing the page changes nothing, editing the file changes every surface. The relations above are edges of docs/generated/graph.json, which the executable writes from the same index.

Searches the titles and descriptions of every page this site publishes.