Skip to content

refactor(release): collapse to 2-PR shape + delete dead rotation slice#835

Merged
bradymiller merged 4 commits into
openemr:masterfrom
bradymiller:devops-pr1a-rotation-deletion
Jun 28, 2026
Merged

refactor(release): collapse to 2-PR shape + delete dead rotation slice#835
bradymiller merged 4 commits into
openemr:masterfrom
bradymiller:devops-pr1a-rotation-deletion

Conversation

@bradymiller

@bradymiller bradymiller commented Jun 25, 2026

Copy link
Copy Markdown
Member

Summary

Atomic deletion of the rotation slice and collapse of ship-release.yml
from 3 PRs (Infra + Conductor + Docs) to 2 PRs (Conductor + Docs).

This PR unblocks the 8.1.1 ship. Today, ship-release.yml's preflight
either blocks on PR #760's unmergeable state or, if forced, would merge
patches that resurrect the docker/openemr/* tree deleted by the docker
migration. Collapsing to 2-PR removes the Infra slot entirely; PR #760
becomes a dangling OPEN PR (close manually as cosmetic cleanup after
this lands).

Why now

The docker-pipeline migration to openemr/openemr (#790, completed
2026-06-20) moved all of rotation's live targets out of this repo:

  • versions.yml's 13 entries all point at paths that no longer exist
    (docker/openemr/**, utilities/container_benchmarking/**, two
    deleted workflows)
  • SlotRotator's last run on 2026-06-10 left
    release-rotation/auto (Release rotation (auto) #760) frozen with patches that edit files
    which now return 404 on master
  • Subsequent release-rotation.yml runs no-op because the diff against
    the existing branch is empty

The Infra PR slot exists in PullRequestTarget::forRelease() + the
ship-release orchestrator but the underlying machinery is dead.

Coordinated companion

openemr/openemr#12631 updates the cross-repo docs
(RELEASE_PROCESS.md + release-automation-plan.md) to the 2-PR shape.
Recommended landing order: #12631 first (docs match the imminent
code state), then this PR (code catches up to the new docs).

Deleted (16 files, ~2625 LOC)

  • tools/release/versions.yml (registry pointing at deleted paths)
  • tools/release/src/SlotRotator.php, SlotAssignmentParser.php,
    SlotRotationResult.php, RotationPrPublisher.php,
    VersionsRegistryLinter.php, LintIssue.php
  • tools/release/bin/rotate.php, lint-versions.php,
    open-rotation-pr.php, derive-slots-from-dispatch.php
  • tools/release/tests/SlotRotatorTest.php,
    SlotAssignmentParserTest.php, VersionsRegistryLinterTest.php,
    RotationPrPublisherTest.php
  • .github/workflows/release-rotation.yml
  • docs/release-automation-plan.md (devops-side rotation slice pre-
    implementation design doc — misleading once the rotation code is
    gone)

Modified (13 files)

  • PullRequestTarget.phpforRelease() drops the Infra row;
    Conductor mergeOrder: 2 → 1, Docs mergeOrder: 3 → 2; docblock
    updated
  • RoleLabel.php — drop Infra enum case; docblock "three" → "two"
  • ShipReleaseOrchestrator.php — 3 docblocks updated; no
    runtime code changes
    (orchestrator already used only
    RoleLabel::Conductor and RoleLabel::Docs in its logic)
  • PullRequestTargetTest.php — test renamed + rewritten for
    2-target shape
  • ShipReleaseOrchestratorTest.php — full rewrite: dropped
    INFRA_REPO/INFRA_BRANCH constants, removed all infra fixture
    setup, renumbered step assertions, renamed test methods, dropped
    testInfraAlreadyMergedSkipsThenContinues (no 2-PR analog;
    SKIPPED_ALREADY_MERGED coverage preserved by
    testConductorAlreadyMergedRefetchesDocsBeforeMerging)
  • ShipReleaseSummaryRendererTest.php — dropped Infra fixture
    entries; first two tests now exercise Conductor rows
  • Taskfile.yml — dropped 5 tasks (release:rotate,
    release:lint-versions, release:open-rotation-pr,
    release:derive-slots-from-dispatch, release:push-rotation-branch);
    release:ship desc updated
  • bin/ship-release.php — header docblock + Symfony
    setDescription updated
  • README.md — dropped versions.yml row from the directory-tree
  • AppPermissionProbe.php — comment updated to drop the
    rotation-specific motivation (workflows:write probe still needed for
    build-release / release-announcements / ship-release)
  • release-permissions-check.yml — header comment dropped the
    "only what rotation needs" framing
  • ship-release.yml — header comment updated for 2-PR shape

What's NOT touched (intentional)

  • The cross-repo docs in openemr/openemr (covered by #12631)
  • Anything outside the release-mechanism + permissions-check surfaces
    (kubernetes, packages, raspberrypi, mariadb-backup-manager, etc.)

Test plan

  • CI passes (PHPUnit will exercise the test rewrites)
  • After both #12631 and this PR land:
    • Close PR Release rotation (auto) #760 manually as cosmetic cleanup
    • gh workflow run ship-release.yml --repo openemr/openemr-devops -f version=8.1.1 -f rel_branch=rel-810 proceeds through preflight (no Infra blocker)
    • Conductor PR (chore(release): prep 8.1.1 openemr#12377) merges first, fires the tag + cascade
    • Docs PR on website-openemr merges next

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Release shipping now follows a simpler two-PR flow (Conductor → Docs) with updated merge-order guidance.
  • Bug Fixes
    • Updated release workflow permission/probing descriptions and narrowed the PR write scope to the two involved PRs.
  • Documentation
    • Removed outdated release automation plan content; updated release tooling docs to reflect the new flow.
  • Chores
    • Retired release-rotation/version-linting workflows and scripts, removed related tooling/tests, and cleared the release tooling registry configuration.

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 384abbbd-4ce6-4cb0-b390-b52e494b516c

📥 Commits

Reviewing files that changed from the base of the PR and between 509afa3 and a70e079.

📒 Files selected for processing (2)
  • tools/release/src/ShipReleaseOrchestrator.php
  • tools/release/tests/ShipReleaseOrchestratorTest.php
🚧 Files skipped from review as they are similar to previous changes (2)
  • tools/release/src/ShipReleaseOrchestrator.php
  • tools/release/tests/ShipReleaseOrchestratorTest.php

📝 Walkthrough

Walkthrough

The PR updates release automation for a two-PR conductor → docs flow, removes rotation-related tooling, and revises target ordering, command text, workflows, and test coverage to match the reduced sequence.

Changes

Release automation simplification

Layer / File(s) Summary
Target contract and rendering
tools/release/src/RoleLabel.php, tools/release/src/PullRequestTarget.php, tools/release/tests/PullRequestTargetTest.php, tools/release/tests/ShipReleaseSummaryRendererTest.php
RoleLabel drops Infra, PullRequestTarget::forRelease() returns Conductor then Docs, and the target/rendering tests assert the updated order and labels.
Ship-release orchestration
tools/release/src/ShipReleaseOrchestrator.php, tools/release/tests/ShipReleaseOrchestratorTest.php
ShipReleaseOrchestrator now validates the two-target Conductor/Docs contract, and the orchestration tests are rewritten around the reduced merge order and failure cases.
Release docs and wiring
tools/release/bin/ship-release.php, .github/workflows/ship-release.yml, .github/workflows/release-permissions-check.yml, tools/release/src/AppPermissionProbe.php, tools/release/Taskfile.yml, tools/release/README.md
Ship-release comments, workflow text, probe comments, README layout text, and Taskfile release descriptions are updated for the two-PR flow, and rotation-related Taskfile entries are removed.
Rotation tooling removal
tools/release/bin/rotate.php, tools/release/bin/derive-slots-from-dispatch.php, tools/release/bin/lint-versions.php, tools/release/bin/open-rotation-pr.php, tools/release/src/SlotAssignmentParser.php, tools/release/src/SlotRotator.php, tools/release/src/SlotRotationResult.php, tools/release/src/VersionsRegistryLinter.php, tools/release/src/RotationPrPublisher.php, tools/release/src/LintIssue.php, tools/release/tests/RotationPrPublisherTest.php, tools/release/tests/SlotAssignmentParserTest.php, tools/release/tests/SlotRotatorTest.php, tools/release/tests/VersionsRegistryLinterTest.php, tools/release/versions.yml, docs/release-automation-plan.md
The rotation-related CLI entrypoints, supporting release classes, their tests, the versions registry, and the release automation plan document are removed.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

Hop hop, I nibble the old path away,
Two release PRs now lead the play.
Conductor and Docs, in moonlit a-row,
Guide the ship where the carrots grow.
🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.81% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: moving release flow to 2 PRs and removing the obsolete rotation slice.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@bradymiller bradymiller marked this pull request as ready for review June 25, 2026 08:16

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tools/release/src/ShipReleaseOrchestrator.php (1)

76-86: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Enforce the conductor/docs target set here.

sortByMergeOrder() only rejects duplicate mergeOrder values. A stale caller can still pass two arbitrary PRs (or omit Docs entirely), and the orchestrator will proceed until much later. The new 2-PR flow should fail fast on the exact {Conductor, Docs} contract.

🔧 Suggested fix
 private function sortByMergeOrder(array $targets): array
 {
+    if (count($targets) !== 2) {
+        throw new \LogicException('ship-release expects exactly two targets: conductor and docs');
+    }
+    $roles = array_map(static fn (PullRequestTarget $t): string => $t->roleLabel->value, $targets);
+    sort($roles);
+    if ($roles !== [RoleLabel::Conductor->value, RoleLabel::Docs->value]) {
+        throw new \LogicException('ship-release targets must be Conductor and Docs');
+    }
     $orders = array_map(static fn (PullRequestTarget $t): int => $t->mergeOrder, $targets);
     if (count(array_unique($orders)) !== count($orders)) {
         throw new \LogicException('ship-release targets have duplicate mergeOrder values');
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/release/src/ShipReleaseOrchestrator.php` around lines 76 - 86,
sortByMergeOrder() currently only validates duplicate mergeOrder values, but it
also needs to enforce the exact two-target contract for the new release flow.
Update ShipReleaseOrchestrator::sortByMergeOrder to reject any target set that
is not precisely the expected Conductor and Docs pair before sorting, using the
PullRequestTarget objects to validate both presence and identity. Keep the
existing duplicate mergeOrder check, but add a fast-fail check for missing,
extra, or wrong targets so stale callers cannot proceed with an invalid target
list.
🧹 Nitpick comments (1)
tools/release/tests/ShipReleaseSummaryRendererTest.php (1)

27-43: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Also assert the docs row in the success render test.

This fixture now exercises the full two-PR success case, but the test only checks the conductor row. A renderer regression that drops or misformats the docs row would still pass.

Suggested assertion
         self::assertStringContainsString(
             '| conductor | `openemr/openemr` '
             . '| [`#22`](https://github.com/openemr/openemr/pull/22) | ✅ merged | `def5678` |',
             $md,
         );
+        self::assertStringContainsString(
+            '| docs | `openemr/website-openemr` '
+            . '| [`#33`](https://github.com/openemr/website-openemr/pull/33) | ✅ merged | `aaa9999` |',
+            $md,
+        );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/release/tests/ShipReleaseSummaryRendererTest.php` around lines 27 - 43,
The success render test for ShipReleaseSummaryRenderer only verifies the
conductor row, so it can miss regressions in the docs entry. Update
ShipReleaseSummaryRendererTest::testRenderSuccess to also assert the rendered
markdown contains the docs row for the second ShipReleaseResult step, using the
existing fixtures and row format alongside the current conductor assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@tools/release/src/ShipReleaseOrchestrator.php`:
- Around line 76-86: sortByMergeOrder() currently only validates duplicate
mergeOrder values, but it also needs to enforce the exact two-target contract
for the new release flow. Update ShipReleaseOrchestrator::sortByMergeOrder to
reject any target set that is not precisely the expected Conductor and Docs pair
before sorting, using the PullRequestTarget objects to validate both presence
and identity. Keep the existing duplicate mergeOrder check, but add a fast-fail
check for missing, extra, or wrong targets so stale callers cannot proceed with
an invalid target list.

---

Nitpick comments:
In `@tools/release/tests/ShipReleaseSummaryRendererTest.php`:
- Around line 27-43: The success render test for ShipReleaseSummaryRenderer only
verifies the conductor row, so it can miss regressions in the docs entry. Update
ShipReleaseSummaryRendererTest::testRenderSuccess to also assert the rendered
markdown contains the docs row for the second ShipReleaseResult step, using the
existing fixtures and row format alongside the current conductor assertion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c439bebe-8920-416a-baaa-67aa1b25c1da

📥 Commits

Reviewing files that changed from the base of the PR and between ca2e4df and 47ff5e4.

📒 Files selected for processing (29)
  • .github/workflows/release-permissions-check.yml
  • .github/workflows/release-rotation.yml
  • .github/workflows/ship-release.yml
  • docs/release-automation-plan.md
  • tools/release/README.md
  • tools/release/Taskfile.yml
  • tools/release/bin/derive-slots-from-dispatch.php
  • tools/release/bin/lint-versions.php
  • tools/release/bin/open-rotation-pr.php
  • tools/release/bin/rotate.php
  • tools/release/bin/ship-release.php
  • tools/release/src/AppPermissionProbe.php
  • tools/release/src/LintIssue.php
  • tools/release/src/PullRequestTarget.php
  • tools/release/src/RoleLabel.php
  • tools/release/src/RotationPrPublisher.php
  • tools/release/src/ShipReleaseOrchestrator.php
  • tools/release/src/SlotAssignmentParser.php
  • tools/release/src/SlotRotationResult.php
  • tools/release/src/SlotRotator.php
  • tools/release/src/VersionsRegistryLinter.php
  • tools/release/tests/PullRequestTargetTest.php
  • tools/release/tests/RotationPrPublisherTest.php
  • tools/release/tests/ShipReleaseOrchestratorTest.php
  • tools/release/tests/ShipReleaseSummaryRendererTest.php
  • tools/release/tests/SlotAssignmentParserTest.php
  • tools/release/tests/SlotRotatorTest.php
  • tools/release/tests/VersionsRegistryLinterTest.php
  • tools/release/versions.yml
💤 Files with no reviewable changes (17)
  • tools/release/versions.yml
  • docs/release-automation-plan.md
  • tools/release/bin/rotate.php
  • .github/workflows/release-rotation.yml
  • tools/release/src/LintIssue.php
  • tools/release/src/RotationPrPublisher.php
  • tools/release/tests/SlotRotatorTest.php
  • tools/release/tests/RotationPrPublisherTest.php
  • tools/release/bin/open-rotation-pr.php
  • tools/release/src/VersionsRegistryLinter.php
  • tools/release/tests/SlotAssignmentParserTest.php
  • tools/release/src/SlotRotationResult.php
  • tools/release/bin/derive-slots-from-dispatch.php
  • tools/release/tests/VersionsRegistryLinterTest.php
  • tools/release/src/SlotAssignmentParser.php
  • tools/release/bin/lint-versions.php
  • tools/release/src/SlotRotator.php

@bradymiller bradymiller force-pushed the devops-pr1a-rotation-deletion branch from 47ff5e4 to f965589 Compare June 25, 2026 08:25
Atomic deletion of the rotation slice and collapse of ship-release.yml
from 3 PRs (Infra + Conductor + Docs) to 2 PRs (Conductor + Docs).

The docker-pipeline migration to openemr/openemr (openemr#790, completed
2026-06-20) moved all of rotation's live targets out of this repo.
versions.yml's 13 entries all point at paths that no longer exist
(docker/openemr/**, utilities/container_benchmarking/**, two deleted
workflows); SlotRotator's last run on 2026-06-10 left the
release-rotation/auto PR (openemr-devops#760) frozen with patches that
edit files which now return 404 on master. Subsequent
release-rotation.yml runs no-op because the diff against the existing
branch is empty.

This makes shipping today's 8.1.1 release impossible through
ship-release.yml: preflight blocks on PR openemr#760's unmergeable state, or
merging it would silently resurrect the deleted docker/openemr/* tree.
Collapsing to 2-PR removes the Infra slot from PullRequestTarget +
ShipReleaseOrchestrator entirely; PR openemr#760 becomes a dangling OPEN PR
(close manually as cosmetic cleanup after this PR merges).

Coordinated companion: openemr/openemr#12631 updates the cross-repo
docs (RELEASE_PROCESS.md + release-automation-plan.md) to the 2-PR
shape. Order: land #12631 first (docs match imminent code), then this
PR (code catches up).

DELETED (16 files, ~2625 LOC):

- tools/release/versions.yml (registry pointing at deleted paths)
- tools/release/src/SlotRotator.php, SlotAssignmentParser.php,
  SlotRotationResult.php, RotationPrPublisher.php,
  VersionsRegistryLinter.php, LintIssue.php
- tools/release/bin/rotate.php, lint-versions.php,
  open-rotation-pr.php, derive-slots-from-dispatch.php
- tools/release/tests/SlotRotatorTest.php,
  SlotAssignmentParserTest.php, VersionsRegistryLinterTest.php,
  RotationPrPublisherTest.php
- .github/workflows/release-rotation.yml
- docs/release-automation-plan.md (devops-side rotation slice
  pre-implementation design doc — misleading once the rotation code
  is gone)

MODIFIED (13 files):

- tools/release/src/PullRequestTarget.php: forRelease() drops the
  Infra row; Conductor mergeOrder 2->1, Docs mergeOrder 3->2;
  docblock updated
- tools/release/src/RoleLabel.php: drop Infra enum case; docblock
  three->two
- tools/release/src/ShipReleaseOrchestrator.php: three docblocks
  updated (class header, ship() targets list, sortByMergeOrder
  comment); no runtime code changes (orchestrator already used only
  RoleLabel::Conductor and RoleLabel::Docs in its logic)
- tools/release/tests/PullRequestTargetTest.php: test renamed +
  rewritten for 2-target shape
- tools/release/tests/ShipReleaseOrchestratorTest.php: full rewrite -
  dropped INFRA_REPO/INFRA_BRANCH constants, removed all infra
  fixture setup, renumbered step assertions, renamed test methods,
  dropped testInfraAlreadyMergedSkipsThenContinues (no 2-PR analog;
  SKIPPED_ALREADY_MERGED coverage preserved by
  testConductorAlreadyMergedRefetchesDocsBeforeMerging)
- tools/release/tests/ShipReleaseSummaryRendererTest.php: dropped
  Infra fixture entries; first two tests now exercise Conductor
  rows; helper updated
- tools/release/Taskfile.yml: dropped release:rotate,
  release:lint-versions, release:open-rotation-pr,
  release:derive-slots-from-dispatch, release:push-rotation-branch
  tasks (~80 lines); release:ship desc updated three->two
- tools/release/bin/ship-release.php: header docblock + Symfony
  setDescription updated three->two
- tools/release/README.md: dropped versions.yml row from the
  directory-tree diagram
- tools/release/src/AppPermissionProbe.php: comment updated to drop
  the rotation-specific motivation (workflows:write probe still
  needed for other release-mechanism workflows)
- .github/workflows/release-permissions-check.yml: header comment
  updated to drop the 'only what rotation needs' framing
- .github/workflows/ship-release.yml: header comment updated for
  2-PR shape

NOT TOUCHED (intentional):

- The cross-repo docs in openemr/openemr (covered by #12631)
- Anything outside the release-mechanism + permissions-check
  surfaces (kubernetes, packages, raspberrypi, etc.)

Tests not run locally (devops repo has no docker dev-stack); CI will
exercise the test rewrites.

Workstream 1 PR 1a in the release-mechanism migration plan.

Assisted-by: Claude Code
…tratorTest

phpcs Generic.Files.LineLength caught a 131-char line at
ShipReleaseOrchestratorTest.php:323 (limit 120) introduced when
rewriting the test from the 3-PR shape to 2-PR shape. Wraps the
setSnapshot call across multiple lines to fit the 120-char limit.

Assisted-by: Claude Code
Addresses coderabbitai outside-diff review on PR 835.
sortByMergeOrder() previously only rejected duplicate mergeOrder
values. A stale caller could still pass 3 targets (the
pre-collapse Infra+Conductor+Docs list), 1 target, or 2 targets
of the wrong role set, and the orchestrator would proceed until
much later — leading to a silent half-failure deep in the
downstream merge logic.

The 2-PR ship contract is exactly {Conductor, Docs} in any order.
sortByMergeOrder() now fails fast on:

- count(targets) != 2 — catches both pre-collapse 3-PR callers and
  single-target accidents
- role set != {Conductor, Docs} — catches future role enum drift
  (e.g., a new Infra-like role added without updating
  PullRequestTarget::forRelease)
- duplicate mergeOrder — existing check, retained

Two new tests:
- testWrongTargetCountThrowsLogicException — covers count != 2
- testWrongTargetRoleSetThrowsLogicException — covers wrong roles
  with correct count (two Conductors)

Original duplicate-mergeOrder test renamed nothing — same fixture
+ assertion, both targets are still Conductor+Docs with mergeOrder
1+1.

Assisted-by: Claude Code
@bradymiller bradymiller force-pushed the devops-pr1a-rotation-deletion branch from 5ae0442 to 509afa3 Compare June 27, 2026 08:03

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tools/release/src/ShipReleaseOrchestrator.php (1)

87-103: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Enforce Conductor’s merge order before Docs.

The new contract verifies the role set, but usort() still trusts caller-provided mergeOrder. A stale caller can pass {Conductor, Docs} with Docs order 1 and Conductor order 2, which passes validation and merges Docs first.

🐛 Proposed fix
         $orders = array_map(static fn (PullRequestTarget $t): int => $t->mergeOrder, $targets);
         if (count(array_unique($orders)) !== count($orders)) {
             throw new \LogicException('ship-release targets have duplicate mergeOrder values');
         }
+        $conductor = $this->findRequired($targets, RoleLabel::Conductor);
+        $docs = $this->findRequired($targets, RoleLabel::Docs);
+        if ($conductor->mergeOrder >= $docs->mergeOrder) {
+            throw new \LogicException('ship-release mergeOrder must put Conductor before Docs');
+        }
         usort(
             $targets,
             static fn (PullRequestTarget $a, PullRequestTarget $b): int => $a->mergeOrder <=> $b->mergeOrder,
         );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/release/src/ShipReleaseOrchestrator.php` around lines 87 - 103, The
ShipReleaseOrchestrator validation only checks that the targets are Conductor
and Docs, but still sorts by caller-provided mergeOrder, so Conductor may merge
after Docs. Update ShipReleaseOrchestrator to enforce the intended order
explicitly when validating/sorting $targets: ensure PullRequestTarget entries
with RoleLabel::Conductor always come before RoleLabel::Docs, and do not rely
solely on mergeOrder for the final sequence. Keep the role-set and duplicate
mergeOrder checks, but make the merge ordering deterministic based on the role
labels in ShipReleaseOrchestrator.
🧹 Nitpick comments (1)
tools/release/tests/ShipReleaseOrchestratorTest.php (1)

340-381: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add coverage for reversed Conductor/Docs mergeOrder.

The validation tests cover duplicate order, wrong count, and wrong role set, but not {Conductor, Docs} with Docs ordered before Conductor. That case should fail fast to protect the strict merge sequence.

🧪 Proposed regression test
+    public function testDocsBeforeConductorMergeOrderThrowsLogicException(): void
+    {
+        $api = new FakePullRequestApi();
+        $targets = [
+            new PullRequestTarget('a/x', 'b1', 'master', RoleLabel::Conductor, 2),
+            new PullRequestTarget('a/y', 'b2', 'master', RoleLabel::Docs, 1),
+        ];
+
+        $this->expectException(\LogicException::class);
+        $this->expectExceptionMessage('Conductor before Docs');
+        (new ShipReleaseOrchestrator($api, new FakeClock()))->ship($targets);
+    }
+
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/release/tests/ShipReleaseOrchestratorTest.php` around lines 340 - 381,
Add a regression test in ShipReleaseOrchestratorTest for the reversed merge
order case where the targets are the correct {Conductor, Docs} roles but Docs
has the lower mergeOrder than Conductor. Use ShipReleaseOrchestrator::ship with
a FakePullRequestApi and FakeClock, and assert it throws LogicException with a
message indicating the merge order/sequence is invalid. This should complement
the existing testDuplicateMergeOrderThrowsLogicException,
testWrongTargetCountThrowsLogicException, and
testWrongTargetRoleSetThrowsLogicException coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@tools/release/src/ShipReleaseOrchestrator.php`:
- Around line 87-103: The ShipReleaseOrchestrator validation only checks that
the targets are Conductor and Docs, but still sorts by caller-provided
mergeOrder, so Conductor may merge after Docs. Update ShipReleaseOrchestrator to
enforce the intended order explicitly when validating/sorting $targets: ensure
PullRequestTarget entries with RoleLabel::Conductor always come before
RoleLabel::Docs, and do not rely solely on mergeOrder for the final sequence.
Keep the role-set and duplicate mergeOrder checks, but make the merge ordering
deterministic based on the role labels in ShipReleaseOrchestrator.

---

Nitpick comments:
In `@tools/release/tests/ShipReleaseOrchestratorTest.php`:
- Around line 340-381: Add a regression test in ShipReleaseOrchestratorTest for
the reversed merge order case where the targets are the correct {Conductor,
Docs} roles but Docs has the lower mergeOrder than Conductor. Use
ShipReleaseOrchestrator::ship with a FakePullRequestApi and FakeClock, and
assert it throws LogicException with a message indicating the merge
order/sequence is invalid. This should complement the existing
testDuplicateMergeOrderThrowsLogicException,
testWrongTargetCountThrowsLogicException, and
testWrongTargetRoleSetThrowsLogicException coverage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8f17c348-f0b4-4695-8325-6a8b9891293b

📥 Commits

Reviewing files that changed from the base of the PR and between 5ae0442 and 509afa3.

📒 Files selected for processing (29)
  • .github/workflows/release-permissions-check.yml
  • .github/workflows/release-rotation.yml
  • .github/workflows/ship-release.yml
  • docs/release-automation-plan.md
  • tools/release/README.md
  • tools/release/Taskfile.yml
  • tools/release/bin/derive-slots-from-dispatch.php
  • tools/release/bin/lint-versions.php
  • tools/release/bin/open-rotation-pr.php
  • tools/release/bin/rotate.php
  • tools/release/bin/ship-release.php
  • tools/release/src/AppPermissionProbe.php
  • tools/release/src/LintIssue.php
  • tools/release/src/PullRequestTarget.php
  • tools/release/src/RoleLabel.php
  • tools/release/src/RotationPrPublisher.php
  • tools/release/src/ShipReleaseOrchestrator.php
  • tools/release/src/SlotAssignmentParser.php
  • tools/release/src/SlotRotationResult.php
  • tools/release/src/SlotRotator.php
  • tools/release/src/VersionsRegistryLinter.php
  • tools/release/tests/PullRequestTargetTest.php
  • tools/release/tests/RotationPrPublisherTest.php
  • tools/release/tests/ShipReleaseOrchestratorTest.php
  • tools/release/tests/ShipReleaseSummaryRendererTest.php
  • tools/release/tests/SlotAssignmentParserTest.php
  • tools/release/tests/SlotRotatorTest.php
  • tools/release/tests/VersionsRegistryLinterTest.php
  • tools/release/versions.yml
💤 Files with no reviewable changes (17)
  • tools/release/src/SlotRotationResult.php
  • docs/release-automation-plan.md
  • tools/release/tests/RotationPrPublisherTest.php
  • tools/release/src/LintIssue.php
  • tools/release/versions.yml
  • tools/release/bin/lint-versions.php
  • tools/release/bin/derive-slots-from-dispatch.php
  • .github/workflows/release-rotation.yml
  • tools/release/tests/VersionsRegistryLinterTest.php
  • tools/release/bin/rotate.php
  • tools/release/tests/SlotRotatorTest.php
  • tools/release/tests/SlotAssignmentParserTest.php
  • tools/release/src/RotationPrPublisher.php
  • tools/release/src/SlotRotator.php
  • tools/release/src/VersionsRegistryLinter.php
  • tools/release/bin/open-rotation-pr.php
  • tools/release/src/SlotAssignmentParser.php
✅ Files skipped from review due to trivial changes (5)
  • tools/release/README.md
  • .github/workflows/ship-release.yml
  • tools/release/src/AppPermissionProbe.php
  • .github/workflows/release-permissions-check.yml
  • tools/release/Taskfile.yml
🚧 Files skipped from review as they are similar to previous changes (5)
  • tools/release/tests/PullRequestTargetTest.php
  • tools/release/bin/ship-release.php
  • tools/release/src/PullRequestTarget.php
  • tools/release/tests/ShipReleaseSummaryRendererTest.php
  • tools/release/src/RoleLabel.php

…Order

Addresses coderabbitai outside-diff finding on PR 835 (rabbit round
2). The prior role-set + dedup-mergeOrder checks miss a case: a
stale caller could pass {Conductor: mergeOrder=2, Docs: mergeOrder=1}.
That passes the new {Conductor, Docs} contract check + the
duplicate-mergeOrder check, but usort then puts Docs first —
silently violating the strict conductor → docs merge sequence
ship-release.yml depends on.

Adds an explicit check after the dedup-mergeOrder validation:
Conductor.mergeOrder must be strictly less than Docs.mergeOrder.
Throws a LogicException with both values in the error message when
violated.

New test: testReversedMergeOrderThrowsLogicException. Verifies the
exception fires with the canonical {Conductor, Docs} role set but
reversed mergeOrder values (Conductor=2, Docs=1).

Assisted-by: Claude Code
@bradymiller bradymiller merged commit c8658ce into openemr:master Jun 28, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant