Skip to content
Cockpit0.29.0
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

project.integration-follows-the-current-master@3

Pull requests are integrated one at a time, each decided against the current master, never around the branch protection

What it is

URI
majordomus://rule/project.integration-follows-the-current-master@3
Kind
rule
Identity
project.integration-follows-the-current-master@3
Path
.ai/repo/rules/project/integration-follows-the-current-master.v3.md
Provenance
declared
Media type
text/markdown
As a capability
rule.project.integration-follows-the-current-master@3

Front matter

{
  "id": "project.integration-follows-the-current-master",
  "version": 3,
  "kind": "rule",
  "title": "Pull requests are integrated one at a time, each decided against the current master, never around the branch protection",
  "description": "An integration decision names the master and head it was taken against and is acted on only while a fresh observation still says the same; a merge invalidates every earlier plan; required checks, reviews and branch protection are never bypassed; a pull request is closed only on git's proof that its work is on master or on an authorised declaration whose successor landed; one executor mutates a base branch at a time; and every act is recorded.",
  "statement": "Decide integration from a fresh observation of the forge and git, name the master and head each decision was taken against, and act only while they still hold; re-plan from scratch after every merge; never merge with --admin, over a required check that has not passed on the current head, or a head that does not contain master; close a pull request only when git proves its work is on master, or when someone the repository lets declare a replacement declared one and git finds that replacement in master; hold the base branch's integration lease while mutating it; and record every act.",
  "status": "active",
  "class": "blocking",
  "depends_on": [
    "project.land-and-publish@1",
    "project.accumulation-is-measured@2"
  ],
  "tags": [
    "integration",
    "git",
    "github",
    "safety",
    "governance",
    "evidence"
  ],
  "x-majordomus": {
    "tests": [
      "test/cases/720_integration_follows_the_current_master.sh",
      "test/cases/740_integration_is_visible_where_a_person_looks.sh",
      "test/cases/741_a_continuous_drain_stops_cleanly_and_alone.sh",
      "test/cases/742_only_an_outage_is_asked_again.sh",
      "test/cases/850_integration_drains_cycle_by_cycle.sh",
      "test/cases/852_a_dry_run_moves_nothing.sh",
      "test/cases/855_racing_executors_merge_once.sh",
      "test/cases/856_racing_worktrees_share_one_lease.sh",
      "test/cases/857_required_checks_are_authoritative.sh",
      "test/cases/858_reviews_are_authoritative.sh",
      "test/cases/861_a_successor_that_landed_supersedes.sh",
      "apps/majordomus-cli/tests/integration_trail.rs",
      "apps/majordomus-cli/tests/integration_rollout.rs",
      "test/cases/925_a_bound_check_is_only_its_apps_run.sh",
      "test/cases/926_an_unread_writer_is_never_a_pass.sh",
      "test/cases/927_an_unbound_base_asks_for_no_writer.sh",
      "test/cases/928_an_authorised_supersedes_holds_then_closes.sh",
      "test/cases/929_a_forks_declaration_decides_nothing.sh",
      "test/cases/930_an_own_body_is_tested_on_its_author.sh",
      "test/cases/931_a_closed_successor_releases_the_hold.sh",
      "test/cases/932_a_squashed_successor_lands_by_its_merge_commit.sh",
      "test/cases/933_every_page_of_cross_references_is_read.sh",
      "test/cases/934_a_failed_declarations_read_decides_nothing.sh",
      "test/cases/935_an_older_observation_is_refused.sh",
      "test/cases/936_the_rule_names_every_closure_ground.sh",
      "test/cases/937_an_unauthorised_declaration_neither_holds_nor_closes.sh",
      "test/cases/938_a_truncated_cross_reference_read_holds.sh",
      "test/cases/939_a_batch_landed_successor_supersedes.sh"
    ]
  }
}

Content

---
id: project.integration-follows-the-current-master
version: 3
kind: rule
title: Pull requests are integrated one at a time, each decided against the current master, never around the branch protection
description: An integration decision names the master and head it was taken against and is acted on only while a fresh observation still says the same; a merge invalidates every earlier plan; required checks, reviews and branch protection are never bypassed; a pull request is closed only on git's proof that its work is on master or on an authorised declaration whose successor landed; one executor mutates a base branch at a time; and every act is recorded.
statement: Decide integration from a fresh observation of the forge and git, name the master and head each decision was taken against, and act only while they still hold; re-plan from scratch after every merge; never merge with --admin, over a required check that has not passed on the current head, or a head that does not contain master; close a pull request only when git proves its work is on master, or when someone the repository lets declare a replacement declared one and git finds that replacement in master; hold the base branch's integration lease while mutating it; and record every act.
status: active
class: blocking
depends_on: [project.land-and-publish@1, project.accumulation-is-measured@2]
tags: [integration, git, github, safety, governance, evidence]
x-majordomus:
  tests: [test/cases/720_integration_follows_the_current_master.sh, test/cases/740_integration_is_visible_where_a_person_looks.sh, test/cases/741_a_continuous_drain_stops_cleanly_and_alone.sh, test/cases/742_only_an_outage_is_asked_again.sh, test/cases/850_integration_drains_cycle_by_cycle.sh, test/cases/852_a_dry_run_moves_nothing.sh, test/cases/855_racing_executors_merge_once.sh, test/cases/856_racing_worktrees_share_one_lease.sh, test/cases/857_required_checks_are_authoritative.sh, test/cases/858_reviews_are_authoritative.sh, test/cases/861_a_successor_that_landed_supersedes.sh, apps/majordomus-cli/tests/integration_trail.rs, apps/majordomus-cli/tests/integration_rollout.rs, test/cases/925_a_bound_check_is_only_its_apps_run.sh, test/cases/926_an_unread_writer_is_never_a_pass.sh, test/cases/927_an_unbound_base_asks_for_no_writer.sh, test/cases/928_an_authorised_supersedes_holds_then_closes.sh, test/cases/929_a_forks_declaration_decides_nothing.sh, test/cases/930_an_own_body_is_tested_on_its_author.sh, test/cases/931_a_closed_successor_releases_the_hold.sh, test/cases/932_a_squashed_successor_lands_by_its_merge_commit.sh, test/cases/933_every_page_of_cross_references_is_read.sh, test/cases/934_a_failed_declarations_read_decides_nothing.sh, test/cases/935_an_older_observation_is_refused.sh, test/cases/936_the_rule_names_every_closure_ground.sh, test/cases/937_an_unauthorised_declaration_neither_holds_nor_closes.sh, test/cases/938_a_truncated_cross_reference_read_holds.sh, test/cases/939_a_batch_landed_successor_supersedes.sh]
---
# Rationale

Seventy pull requests were open on 2026-09-30, and the repository had three competing answers
to "what can land": the forge's `mergeable` flag, which cannot run the derived-file driver and
is wrong in both directions; `scripts/land`, which merged every clean pull request onto one
branch from a list its own first merge made stale; and hand-built batches whose regressions
nobody could attribute. ADR 0101 replaces all three with one typed subsystem. This rule is
what keeps it from drifting back.

# Required behaviour

- A pull request is `ready` only when it targets the base, is not a draft, carries no blocking
  label, contains the current master, has its declared dependencies landed, satisfies the
  review policy, and has every required check passed on its current head. Pending, missing,
  skipped and unreadable are not passed, and a check the base binds to an app is passed only
  by that app's own run: another writer's run of the name is not it, and a run whose writer
  was not read is unknown.
- The executor observes the forge before deciding and again before acting, and merges only
  when both decisions name the same master and head. It holds no plan across a merge.
- No integration code passes `--admin`, force-pushes, or rewrites a branch. Bringing master
  into a branch is a merge commit pushed as a plain fast-forward.
- Closure needs an explicit `--apply` and one of four grounds: the head is an ancestor of
  master; the merge changes no file; every commit the head has and master lacks is on master
  as an equal patch; or a successor was declared for it by an owner, member or collaborator
  in a pull request of this repository — in its own body or in the successor's — and git
  finds that successor's head or merge commit in master. The forge's word that a pull
  request merged proves nothing here. A successor the forge does not call merged counts
  only when its head lives in this repository: a fork's head, closed unmerged, proves
  nothing wherever it points, and neither does a successor that changes no file.
- An authorised declaration holds the pull request it replaces while the successor is open,
  and releases it when the successor is closed without landing. A declaration from anyone
  else, or from a fork, is reported as `possible_supersession` and neither holds nor closes.
- A pull request whose cross-references were not read whole is held until they are: an
  unread declaration is never taken for none, and the rest of the queue goes on. A
  declarations read the forge will not answer is not a hold: the refresh fails and the
  observation recorded before it is not replaced. A pull request that differs only in
  derived output is left for a person.
- Only what a body's author states is a declaration of replacement: a marker on a quoted
  line, in a fenced code block or in an HTML comment declares nothing.
- Mutations hold the base branch's integration lease. Observers do not.
- Every selection, stale decision, merge, refusal, refresh and closure is appended to the
  audit trail before it is taken, to one trail per repository under the common git
  directory; an act the trail cannot record is not taken.
- The lease is an exclusive `flock` held for the executor's life and taken for the base it
  observed. A live holder is never taken over; a holder that ends, even by a crash, releases
  it at once. A lease that cannot be renewed ends the run rather than acting without it.
- A merge is complete only when it is proved where it landed: master contains the decided
  master, and the merge commit's parents are that master and the decided head on master's
  first-parent line. A merge that cannot be proved stops every drain until a person runs
  `prs drain --resume-after-failure`.
- A failure is recorded with its class. A candidate's own failure (stale, conflict, a new
  failing check, a revoked review, a policy refusal) holds that candidate back and the drain
  goes on; an unverified merge or an unreadable forge stops it. Only an outage is asked
  again, a bounded number of times; a merge is never retried.
- A continuous drain runs alone, refreshes before every action, waits a bounded interval
  between cycles and stops cleanly on a signal. It is the last stage of the rollout and is
  refused until the trail holds `ROLLOUT_MERGES_BEFORE_CONTINUOUS` verified merges since
  the last merge that could not be verified (ADR 0101 §13).

# Failure behaviour

`test/cases/720_integration_follows_the_current_master.sh` fails when the integration code
names `--admin` or a force push, when a read-only `prs` command reaches the network or
writes the audit trail, when the relation to master is taken from the forge's `mergeable`,
or when the executor loses its re-observation before acting. Case 850 fails when a drain
does not re-plan after a merge, 852 when a dry run moves anything, 855 and 856 when two
executors both merge, 857 and 858 when a required check or review is taken from what is
visible rather than what is required, 925 when another writer's run passes a check bound to
an app, 926 when a run whose writer was not read passes one, 927 when a base that binds
nothing has its writers asked for, 742 when anything but an outage is asked again, and 741
when a continuous drain runs without its record, beside another executor, or past a signal.

Supersession and closure: 861 and 928 fail when an authorised successor's landing is not
seen or its hold is lost, 929, 930 and 937 when a declaration nobody entitled made holds or
closes anything, 931 when a closed successor still holds, 932 and 939 when a landing git can
see is not counted, 933, 934 and 938 when a declarations read that was partial or failed
releases or closes, 935 when an older observation is read as this one, and 936 when this
rule stops naming a ground the code closes on.

`tests/integration_trail.rs` fails when an act is taken without being recorded first, and
`tests/integration_rollout.rs` when continuous mode is reachable without the record. The
module's unit tests (`cargo test --lib integration`) hold the dispositions, who may declare
a supersession, the stale-decision refusal, the re-plan after every merge and the cleanup
threshold.

# Verification

`bash test/run.sh 720_integration_follows_the_current_master`, the same for each case named
above,
`cargo test --test integration_trail --test integration_rollout` and
`cargo test --lib integration`.