Skip to content

docs(release): add release-mechanism gaps + open questions doc [DRAFT]#12599

Draft
bradymiller wants to merge 36 commits into
openemr:masterfrom
bradymiller:release-mechanism-gaps-doc
Draft

docs(release): add release-mechanism gaps + open questions doc [DRAFT]#12599
bradymiller wants to merge 36 commits into
openemr:masterfrom
bradymiller:release-mechanism-gaps-doc

Conversation

@bradymiller

Copy link
Copy Markdown
Member

Summary

Draft companion to PR #12598 (the release-mechanism migration
plan). Adds a working-notes doc tracking gaps + manual procedures
in the current release-mechanism — the canonical place to capture
surprises, manual workarounds, timing nuances, and the "this is how
we actually do X right now" procedures that emerge during release
work.

What's in here

File Lines
docs/release-mechanism-gaps.md 781

The doc covers:

  • Quick context — current branch under release work, conductor
    location, patch-flow stance.
  • 5 identified gaps:
    • G1--skip-globals flag on the conductor (why is
      GlobalsIncMutator disabled in production runs?)
    • G2 — post-release version bump to next-dev on rel branch is
      unwired (no auto-bump after a tag ships)
    • G3SqlUpgradeSkeletonMutator is master-only AND
      currently has no workflow path
    • G4 — no workflow invocation path for --scope=master
      (cross-tracked in migration plan's deferred-debt)
    • G5 — no auto-trigger on rel-* branch creation (cross-tracked
      in migration plan's deferred-debt)
  • Master-side branch-cut procedure — full 7-step manual
    checklist for cutting a new rel branch from master, capturing the
    easy-to-forget Dockerfile two-block footgun
  • Per-release-on-rel-branch docker upgrade procedure — mandatory
    per release (this IS the consumer auto-upgrade feature),
    cross-branch propagation requirement, ~5-substitution template
    pattern for new fsupgrade-N.sh
  • SQL upgrade script procedure — master file-rename dance for
    intermediate-minor cases
  • Docker Hub tag modeldev / next / latest are
    version-names (not floating pointers), with the slot-promotion
    pattern that fires when a release ships
  • openemr_version_ref branch-tip-vs-tag-pin lifecycle across a
    release
  • Consolidated timing picture — who does what, when (conductor
    vs manual × release time vs cut time)
  • Canonical 8.1.1 release sequence (P1-P8) — the full
    end-to-end PR flow
  • Things-to-verify checklist for the upcoming 8.1.1 manual prep
  • Decisions-made (don't re-litigate) list
  • Update log

Why a draft PR

Visibility for the community + other maintainers' AI assistants who
might be examining release-mechanism work. The gap-tracking content
is substantive enough to warrant the formal-review surface that a PR
provides. Expected to be a living doc — gap discoveries land via
normal PRs against master as they emerge.

Test plan

🤖 Generated with Claude Code

Working notes tracking open gaps + manual procedures in the current
release-mechanism. Pairs with PR openemr#12598 (the migration plan).
Pre-publication draft.

Assisted-by: Claude Code
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f0c043cf-a687-4cb6-947e-ddf19212c711

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@codecov

codecov Bot commented Jun 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 25.52%. Comparing base (30226e9) to head (5c18975).
⚠️ Report is 222 commits behind head on master.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@             Coverage Diff              @@
##             master   #12599      +/-   ##
============================================
+ Coverage     24.87%   25.52%   +0.64%     
- Complexity    84366    85513    +1147     
============================================
  Files          3887     3938      +51     
  Lines        417119   421866    +4747     
============================================
+ Hits         103759   107661    +3902     
- Misses       313360   314205     +845     
Flag Coverage Δ
apache 15.32% <ø> (-0.18%) ⬇️
api 3.83% <ø> (+0.02%) ⬆️
api-tests 3.83% <ø> (+0.05%) ⬆️
e2e 8.77% <ø> (-0.48%) ⬇️
e2e-tests 8.77% <ø> (-0.48%) ⬇️
email 0.18% <ø> (+<0.01%) ⬆️
isolated-php8.2 9.53% <ø> (+1.02%) ⬆️
isolated-php8.3 9.53% <ø> (+1.02%) ⬆️
isolated-php8.4 9.53% <ø> (+1.02%) ⬆️
isolated-php8.5 9.53% <ø> (+1.02%) ⬆️
isolated-php8.6 9.53% <ø> (+1.02%) ⬆️
mariadb10.11.18 ?
mariadb10.6.27 ?
mariadb11.4.12 ?
mariadb11.8.8 15.32% <ø> (-0.40%) ⬇️
mariadb12.2.2 ?
mysql5.7.44 ?
mysql8.0.46 ?
mysql9.3.0 ?
nginx ?
php8.2 15.32% <ø> (-0.14%) ⬇️
php8.3 ?
php8.4 ?
php8.5 ?
services 5.33% <ø> (+0.03%) ⬆️
upgrade ?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Brings PR openemr#12599's working copy in sync with the standalone
working notes (which had been kept ahead during 8.1.1 release
prep + recovery work on 2026-06-23).

Adds four new gaps surfaced during 8.1.1 prep:

- **G7 — Conductor tooling drift on pre-820 rel branches.**
  rel-810/800/704 carry per-branch copies of the conductor's PHP
  + workflows that silently diverge from master. Discovered live
  when rel-810's old static BranchVersionResolver::branchToVersion()
  returned wrong version for conductor dispatch (fixed in openemr#12611).
  Full conductor sync from master to all pre-820 rel branches
  is the structural fix; recommended after 8.1.1 ships.

- **G8 — No automated regression test for conductor resolvers.**
  BranchVersionResolver + DispatchDataBuilder + others have no
  isolated PHPUnit coverage. The G7 bug shipped silently because
  nothing caught 'returns wrong version for rel-810' before live
  fire. Concrete test surface enumerated; complements G7 — running
  the suite on each branch's CI catches future drift automatically.

- **G9 — release-docs/<version> PRs on website-openemr don't
  supersede across version changes.** Each conductor re-resolution
  to a new version opens a fresh PR at the new branch and leaves
  the prior orphaned. Two fix options sketched; per-rel-branch
  head naming (matching openemr/openemr's release-prep/rel-810
  pattern) is the structurally cleaner answer.

- **G10 — Reusable workflows as a replacement for the
  byte-identical canary on the docker pipeline.** Captured as a
  future consideration. If starting today, reusable workflows
  would be the structurally cleaner answer (master owns impl, rel
  branches carry thin caller stubs, drift impossible by
  construction). Canary works and is production-validated, so
  not urgent; natural time to revisit is when a 'this would have
  been easier with single-source-of-truth' moment hits the docker
  side.

Assisted-by: Claude Code
…g out)

Per maintainer (2026-06-23): rel-800 and rel-704 will not get
BranchVersionResolver / conductor tooling backports because both
branches will rotate out — no future releases planned on either.
Latent drift bugs on those branches will never fire.

G7 (drift) scope shrinks to rel-810 only. Full conductor sync
recommended after 8.1.1 ships and before the 8.1.2 cycle. If a
need ever surfaces to ship from rel-800/rel-704 before retirement,
that triggers a per-branch surgical backport at that time, not
pre-emptive sync now.

G8 (regression tests) scope correspondingly narrows: tests added
on master + carried to rel-810 via the G7 sync, rather than to
all pre-820 rel branches.

Assisted-by: Claude Code
…l paths

Adds a pre-ship blocker note to the 'P5. Merge the release-prep PR'
section of the canonical 8.1.1 release sequence:

The current Infra PR slot (openemr-devops PR openemr#760,
release-rotation/auto) is frozen with pre-docker-migration content
from 2026-06-10. Its diff edits docker/openemr/8.0.0/Dockerfile,
docker/openemr/current, and dependabot.yml references to
docker/openemr/* -- all paths now deleted on devops master (404).

release-rotation.yml runs successfully on every dispatch but skips
force-push + PR-update because the diff against the already-rotated
branch is empty. PR openemr#760 stays frozen indefinitely.

Shipping 8.1.1 via ship-release.yml today would either:
1. Block on PR openemr#760's unmergeable state (preflight refuses to merge)
2. Or silently undo the docker migration by resurrecting deleted paths

Workstream 1 of the migration doc (delete rotation slice + collapse
ship-release to 2-PR shape) unblocks this path. Discovered 2026-06-24
while tracing the ship flow end-to-end.

Also adds:

- 'Recommended path' (ship-release.yml) vs 'Direct path' (merge
  conductor PR directly) options for P5, with tradeoffs (direct path
  sidesteps the blocker but loses multi-PR-merge ordering safety net)
- Ordering constraints summary updated to note P5 blocked on
  workstream 1

Assisted-by: Claude Code
…2656

PR openemr#12656 reverses the 2026-06-23 decision that
release-targets.yml allows only one row per branch. Multi-row IS
needed for the in-dev patch release case: when a rel branch hits
a new patch in dev mode (e.g., rel-810 -> 8.1.2-dev), the daily
orchestrator still needs to publish the prior stable (8.1.1) until
the new dev ships. One row per branch can't express that.

PR drops the validator's branch-uniqueness check (the
duplicate-docker_tag check stays — push race on Docker Hub still
matters). Adds optional 'unreleased: true' row marker for
placeholders; orchestrator skip-on-unreleased wired in same PR;
dockerhub readme push + demo_farm bumper to follow.

Example row added in openemr#12656: second rel-810 entry with
docker_tags: 8.1.0 + openemr_version_ref: v8_1_0 +
unreleased: true. Wires up the multi-row mechanism without
immediately publishing 8.1.0 daily images.

Decisions-made section + update log both touched.

Assisted-by: Claude Code
Three updates capturing the design discussion of 2026-06-27 around
the branch-cut + release-time extensions:

G4 — folds into G5's branch-cut automation design instead of
exposing scope as a release-prep.yml input. Keeps release-prep.yml's
scope narrow (rel-branch-only); the new branch-cut-automation.yml
workflow invokes the master-scope mutator as one of its steps.

G5 — expanded with full design. on: create trigger fires once on
rel-NNN0 branch creation; opens two coordinated PRs (rel-NNN0-side
Dockerfile ARG edit + master-side version.php advance + add
release-targets row + rotate next + SQL skeleton chain adjust).
Notes the conductor's premature release-prep PR interaction and
the recommendation to accept it (vs add suppression complexity).

G11 (new) — Post-release release-targets.yml updates are manual.
Extend conductor to open a SECOND PR (release-finalize/<rel-branch>)
on master paired with the existing release-prep/<rel-branch> PR.
Carries the slot shuffle, ref pin, and unreleased-row drop. Lands
on tag dispatch consumer marking it Ready and auto-merging.

Both G5 and G11 map to workstreams 2 (branch-cut) and 3 (per-release
optimization) in the planning doc. Implementation work captured
there in workstream-specific subsections.

Assisted-by: Claude Code
Captures the 2026-06-27 design discussion that meaningfully reshapes
G6 from from-scratch render to reconciliation. Cluster identity is
sticky across runs because each cluster name maps to a subdomain
(eight -> eight.openemr.io) that's referenced from external surfaces
(wiki, social, mail). Reassigning a cluster's meaning would break
those external references, so the bot reads the CURRENT
ip_map_branch.txt as state, applies a reconciliation diff against
the desired state computed from upstream, and writes the new file
preserving cluster -> subdomain stability.

Key refinements:

- Per-input source map: release-targets.yml is master-only;
  Dockerfiles are per-branch; current demo_farm files are read for
  reconciliation state
- Section ownership matrix: Production + Up-for-grabs are fixed
  cluster identities; Master demos + Release demos are sticky from
  prior state with new ones taken from parked; Parked is the
  overflow bench; Misc is hand-curated (bot never touches)
- Up-for-grabs flex always from master's Dockerfile, regardless of
  community-claimed branch override
- Cluster count derivation: from current per-cluster state
  (preserves production=3, up-for-grabs=3, rest=2 pattern); default
  2 for newly-assigned
- Fail-loud edge cases: no latest holder, empty parked bench when
  cluster needed, Dockerfile ARG parse failure, CI matrix path
  missing
- Reconciliation algorithm sketch (6 steps from inputs to PR)
- Trigger composition: daily cron (load-bearing self-healing) +
  eager repository_dispatch (optional optimization) + workflow_dispatch
  (manual / dry-run)
- Implementation language: bash + yq + curl + awk (demo_farm self-
  contained, low contributor barrier)
- Coordination boundary with workstreams 2 + 3:
  release-targets.yml is the contract; no tight coupling
- Three-PR scaffolding plan: dry-run -> live PR -> eager dispatch
  + unreleased skip

The unreleased-skip in PR #3 is the demo_farm-side consumer of the
openemr#12656 marker that's currently tracked as the
remaining follow-up.

Assisted-by: Claude Code
…ior bumper)

Surveyed demo_farm_openemr today and found the prior automation
consists of more than just bump-tag.yml: there's a whole
tools/release/ PHP project (IpMapBumper class + CLI + tests +
toolchain scaffolding) that exists only to support bump-tag.yml.
All of it becomes dead code after the new bot's dispatch consumers
land.

PR #3 expanded to atomic flip:

Add:
- repository_dispatch consumers (openemr-tag / openemr-rel-cut /
  openemr-rel-update)
- unreleased: true skip on release-targets.yml rows

Delete:
- .github/workflows/bump-tag.yml (109 lines)
- tools/release/src/IpMapBumper.php + bin/bump-ip-map.php +
  tests/IpMapBumperTest.php
- tools/release/{Taskfile.yml, phpunit.xml.dist, phpcs.xml,
  phpstan.neon, rector.php, composer.json, composer.lock,
  .gitignore} — entire PHP toolchain scaffolding since the new
  bot is bash and there are no other PHP tools in demo_farm

Net: ~200 LOC + ~10 config files deleted; ~50 LOC added for the
new dispatch wiring.

Atomic-ness rationale:

- Retiring BEFORE PR #3 -> gap window where openemr-tag dispatches
  don't update demo_farm
- Retiring AFTER PR #3 -> race condition (both workflows fire on
  every openemr-tag dispatch)
- Atomic -> one dispatch consumer at all times, clean cutover

Future bash tools live in their own dir; future PHP tools (if
ever) re-scaffold their own project — keeping empty scaffolding is
bitrot bait.

Assisted-by: Claude Code
bradymiller added a commit that referenced this pull request Jul 1, 2026
…r (workstream 3 Phase A) (#12662)

## Context (for anyone catching up)

This is part of the broader **release-mechanism migration** project. Two
living tracking docs describe the larger picture and are required
reading for context:

- **Planning doc** (still in DRAFT PR #12598):
[`docs/release-mechanism-migration-from-devops.md`](https://github.com/bradymiller/openemr/blob/release-mechanism-migration-doc/docs/release-mechanism-migration-from-devops.md)
— 6 workstreams of release infrastructure work. See the "**Workstream 3
detail — release-time partner PR + release-cycle-bot**" section for what
this PR fits into. Workstreams 1 + 5 (devops cleanup + demo_farm
auto-derive bot) already shipped 2026-06-28; workstream 4 (8.1.1 ship
from rel-810) is the next actual release event.
- **Gaps doc** (still in DRAFT PR #12599):
[`docs/release-mechanism-gaps.md`](https://github.com/bradymiller/openemr/blob/release-mechanism-gaps-doc/docs/release-mechanism-gaps.md)
— open questions + design notes for each gap. See the "**G11 —
Post-release release-targets.yml updates are manual** *(workstream 3
design)*" section, especially the "Phase A refinement (2026-06-28)"
subsection that drove this PR's exact scope.

**Existing baseline**: the conductor (`release-prep.yml`) lives in every
branch and fires on push to `rel-*`. It opens a tracking PR titled
`release-prep/<rel-branch>` against the rel branch, force-updated on
every push, merged at ship-time. This PR extends that conductor to ALSO
open a master-side partner PR (`release-finalize/<rel-branch>`) at the
same trigger.

## Summary

Phase A of workstream 3 (G11). Automates the three coordinated edits
master's `.github/release-targets.yml` needs after a rel branch ships,
as a draft `release-finalize/<rel-branch>` partner PR opened alongside
the existing `release-prep/<rel-branch>` PR.

The three transforms (today done by hand for every release):

- Pin the rel branch row's `openemr_version_ref` to the new tag (e.g.
`rel-810` -> `v8_1_1`) so daily docker builds stop tracking the branch
tip
- Slot-shuffle Docker Hub `latest`/`next`/`dev` across rows:
just-shipped rel branch promotes `next` -> `latest`, prior `latest`
holder drops it, next-upcoming-stable owner (newer rel branch if any
else master) acquires `next`
- Drop the unreleased placeholder row from the multi-row dev pattern
(#12656) when present

## What's in this PR

- New mutator `PostReleaseTargetsMutator` (line-based surgical edits to
preserve the file's substantial human-authored comments; idempotent;
Symfony YAML parser used as post-edit sanity check)
- `MutatorContext` gains an optional `relBranch` field + `tagName()`
helper; existing mutators ignore it
- `ReleasePrepCommand` gains `--rel-branch` (required for
`--scope=master`), plumbs to context, rewires master-scope list to a
single `PostReleaseTargetsMutator`
- `VersionPhpMasterMutator` + `SqlUpgradeSkeletonMutator` stay defined
but intentionally unwired — they are branch-cut concerns workstream 2
(G4) will own
- `.github/workflows/release-prep.yml` grows steps after the existing
prep-PR step (checkout master / composer install in master-checkout /
run master-scope mutators / open `release-finalize/<rel-branch>` via
peter-evans, draft, test-mode skipped)
- `render-pr-body.php` learns `--rel-branch` for the new finalize
template

## Lifecycle

1. Conductor fires on push to rel-* -> opens BOTH
`release-prep/<rel-branch>` (rel-*) AND `release-finalize/<rel-branch>`
(master) as drafts
2. Operator marks the rel-branch PR Ready and merges, triggering the tag
3. Operator marks the finalize PR Ready and merges (auto-mark-Ready on
tag creation can land in a follow-up)

## Sequencing with Phase B

Phase B (separate PR) cherry-picks the conductor extension to `rel-810`
so the upcoming 8.1.1 release benefits — rel-810's conductor invokes its
own `src/Common/Command/...` tree, not master's, so both the workflow
YAML and the PHP changes must land there.

## References

- **Gaps doc G11** (DRAFT PR #12599): [link to
file](https://github.com/bradymiller/openemr/blob/release-mechanism-gaps-doc/docs/release-mechanism-gaps.md)
— especially the "Phase A refinement (2026-06-28)" subsection and the
"Docker Hub tag model" / slot-promotion model section
- **Planning doc Workstream 3 detail** (DRAFT PR #12598): [link to
file](https://github.com/bradymiller/openemr/blob/release-mechanism-migration-doc/docs/release-mechanism-migration-from-devops.md)
- The dual-PR partner pattern generalizes — workstream 2 (G4/G5) will
reuse the extended `MutatorContext` + dual-checkout workflow structure
for branch-cut automation

## Dry-run output

Run against the live `.github/release-targets.yml` with
`--target-version=8.1.1 --rel-branch=rel-810 --scope=master`:

```diff
@@ -99,30 +99,19 @@
   # release cycle (e.g., when version.php switches to 8.3.0-dev).
   # The docker_tag <-> version.php alignment guard in
   # docker-validate-release-targets.yml catches drift on PRs.
-  docker_tags: 8.2.0,dev
+  docker_tags: 8.2.0,dev,next
   openemr_version_ref: master
 
 - branch: rel-810
-  docker_tags: 8.1.1,next
-  openemr_version_ref: rel-810
-
-# Second rel-810 row -- placeholder for the multi-row dev pattern (see
-# `unreleased` field doc above). Wires up the second-row mechanism
-# without actually publishing 8.1.0 from this configuration. When a real
-# dev cycle on rel-810 starts (e.g., 8.1.2-dev with 8.1.1 already
-# shipped), this row gets repurposed: docker_tags -> 8.1.1,
-# openemr_version_ref -> v8_1_1, `unreleased` flag dropped.
-- branch: rel-810
-  docker_tags: 8.1.0
-  openemr_version_ref: v8_1_0
-  unreleased: true
+  docker_tags: 8.1.1,latest
+  openemr_version_ref: v8_1_1
 
 - branch: rel-800
   # 8.0.0 is the floating "current latest 8.0.0.x" pointer (always advances
   # with openemr_version_ref); 8.0.0.3 is the specific-patch pointer (only
   # advances when this row bumps to 8.0.0.4+). Both tags get an auto-appended
   # "<tag>-YYYY-MM-DD" sibling for immutable per-build pinning.
-  docker_tags: 8.0.0,8.0.0.3,latest
+  docker_tags: 8.0.0,8.0.0.3
   openemr_version_ref: v8_0_0_3
 
 - branch: rel-704
```

Second invocation is a no-op (idempotency confirmed).

## Test plan

- [x] PHPStan level 10 clean on changed files
- [x] PHPCS clean on changed files
- [x] Rector dry-run clean on changed files
- [x] phpunit-isolated: PostReleaseTargets + ReleasePrep +
MutatorContext family passes (10+ test methods covering canonical
8.1.1-from-rel-810 case, idempotency, multi-row placeholder drop,
single-rel-branch base case, comment preservation,
next-owner-prefers-newer-rel-branch-over-master,
missing-rel-branch-throws, historical-secondary-row-not-re-pinned,
inline-comments-preserved on scalars, legacy rel-704 idempotency)
- [x] Dry-run against the live `.github/release-targets.yml` for
8.1.1-from-rel-810 produces the expected diff (rel-810 pinned to v8_1_1
with next->latest, rel-800 drops latest, master acquires next,
placeholder row + its comments dropped)
- [x] Second-run idempotency confirmed via CLI
- [x] Workflow YAML parses (yaml.safe_load) and actionlint clean
- [ ] End-to-end conductor exercise — requires push to a rel-* branch;
covered by Phase B's cherry-pick to rel-810

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bradymiller added a commit that referenced this pull request Jul 1, 2026
## Context for anyone catching up

This PR builds the patch-prep automation arm of the release-mechanism
migration — sibling to the branch-cut automation in #12696. The same
reference docs lay out the plan:

- **Planning doc — workstream 6 detail**:
https://github.com/bradymiller/openemr/blob/release-mechanism-migration-doc/docs/release-mechanism-migration-from-devops.md
(search for *Workstream 6 — patch-prep automation*)
- **Gaps doc — G12** (plus G4 + G5 for the workstream 2 mutator
framework this builds on):
https://github.com/bradymiller/openemr/blob/release-mechanism-gaps-doc/docs/release-mechanism-gaps.md

> **Naming note (2026-06-30):** Originally labelled workstream 3 Phase
B; renamed to workstream 6 in the planning/gaps doc updates (PRs #12598
and #12599) to avoid collision with workstream 3's actual Phase B — the
cherry-pick of #12662's conductor extension to rel-810 for the 8.1.1
ship scenario. The COMMIT messages on this branch retain the original
"workstream 3 phase B" framing; rewriting them would require a
force-push and isn't worth it for a naming refinement. The PR title +
body here reflect the new naming.

**Based on #12696** (workstream 2 — branch-cut automation), which is in
turn based on #12662 (workstream 3 phase A — release-finalize partner
PR). HEAD of this PR's branch sits on top of #12696's HEAD
(`b9cf8d3714`). The diff against `master` therefore includes all three
PRs' commits; once #12662 and #12696 land in order, this PR rebases
cleanly and the diff shrinks to just this workstream's commits. No
overlap with #12696's mutator code — this PR's changes to
`DockerUpgradeScaffoldMutator` and `SqlUpgradeSkeletonMutator` are
strictly additive (they consult `MutatorContext::$fromVersion` when set,
fall through to existing behavior otherwise).

## Summary

When a maintainer pushes a `$v_patch` increment to `version.php` on a
`rel-*` branch (e.g., rel-810 going `8.1.0` → `8.1.1-dev`), GitHub
Actions today does nothing. This PR makes that push event fire a
workflow that opens **two coordinated ready-for-review PRs** (not drafts
— patch-prep PRs should land fast, paving the way for the new dev
cycle):

- **rel-side PR** (`patch-prep/rel-NNN0` → base `rel-NNN0`): docker
upgrade scaffold + new SQL patch upgrade skeleton.
- **master-side PR** (`patch-prep/rel-NNN0-master` → base `master`):
same docker upgrade scaffold + same new SQL patch upgrade skeleton +
master bridge file rename + `release-targets.yml` row insert +
placeholder drop.

Maintainer reviews both, marks Ready (already there — not draft), merges
rel-side first, then master-side.

## Trigger mechanics + gate logic

`on: push` to `branches: rel-*` filtered to `paths: version.php`. From
that, the resolve step:

1. **New-branch creation** (`before` SHA all zeros) → skip cleanly.
Branch-cut (#12696) owns that lifecycle event.
2. Fetch before-state + after-state `version.php` via the GitHub API;
parse `$v_major`, `$v_minor`, `$v_patch`, `$v_tag` with regex.
3. **Gate 1**: `$v_patch` must have strictly increased (`after_patch >
before_patch`). Cosmetic edits to other parts of `version.php` skip.
4. **Gate 2**: after-state `$v_tag` must be `-dev`. release-prep's
mid-flight strip of `-dev` to ship a release also bumps `$v_patch` in
some flows; that's not our event.
5. If both gates pass, derive `target_version = MAJOR.MINOR.after_patch`
and `prev_version = MAJOR.MINOR.before_patch`.

`workflow_dispatch` provides an escape hatch — maintainer supplies
`rel-branch`, `target-version`, `prev-version` explicitly for recovery
from a workflow miss.

## Mutator inventory

**Reused from #12696 without code modification (just additive context
extension):**

| Mutator | Side | Behavior |
|---------|------|----------|
| `DockerUpgradeScaffoldMutator` | both | Bumps 3 docker-version files
(N → N+1), creates `fsupgrade-(N+1).sh` stub, extends Dockerfile COPY +
chmod blocks. Cross-branch sync requirement per the `docker upgrade
actions mandatory per release` rule. |
| `SqlUpgradeSkeletonMutator` | both | Creates
`sql/<from>-to-<to>_upgrade.sql` with the comment-meta-language header.
|

**New in this PR:**

| Mutator | Side | Behavior |
|---------|------|----------|
| `MasterSqlPatchBridgeMutator` | master | Renames the long-lived bridge
file `sql/<X_Y_(P-1)>-to-<X_(Y+1)_0>_upgrade.sql` →
`sql/<X_Y_P>-to-<X_(Y+1)_0>_upgrade.sql`. Contents preserved (they
accumulate dev-cycle SQL); only the "from" anchor in the filename
advances. |
| `PatchPrepReleaseTargetsMutator` | master | Inserts a fresh `- branch:
rel-NNN0` row with `docker_tags: <target>,next`; drops any rows for the
target branch flagged `unreleased: true`. Other branches' rows
untouched. Surgical line-based edits to preserve comments; Symfony YAML
parser used as a structural sanity check on the result. |

**`MutatorContext` extension** — adds optional `?string $fromVersion`
field with `MAJOR.MINOR.PATCH` validation. The patch-prep conductor
supplies it because:
- Rel side: `version.php` has already been bumped past the value
`SqlUpgradeSkeletonMutator` needs to anchor at (post-bump `$v_patch` is
the *target*, not the *from*).
- Master side: `version.php` is for the next-minor line entirely (e.g.,
`8.2.0-dev`) and bears no relationship to the rel-branch patch.

Both consuming mutators fall through to existing behavior when
`fromVersion` is null, so the branch-cut path is unaffected.

## Mutator order (master side)

```
1. DockerUpgradeScaffoldMutator       (cross-branch sync)
2. SqlUpgradeSkeletonMutator          (creates new patch's <from>-to-<to>.sql)
3. MasterSqlPatchBridgeMutator        (renames the long-lived bridge file)
4. PatchPrepReleaseTargetsMutator     (release-targets.yml row insert + placeholder drop)
```

The skeleton mutator runs **before** the bridge mutator deliberately.
Both touch disjoint files (skeleton writes `<X_Y_P-1>-to-<X_Y_P>.sql`;
bridge renames `<X_Y_P-1>-to-<X_(Y+1)_0>.sql` →
`<X_Y_P>-to-<X_(Y+1)_0>.sql`), but in a partial-run scenario the
skeleton-first ordering means the new patch file is present before the
long-lived bridge gets disturbed. Also matches the rel-side ordering.

## Lifecycle

1. Maintainer bumps `$v_patch` on a rel branch (`rel-810`:
`$v_patch='0'` → `$v_patch='1'`, `$v_tag='-dev'`) and pushes.
2. `patch-prep-automation.yml` fires, resolves target-version = 8.1.1 +
prev-version = 8.1.0.
3. Workflow checks out the rel branch, runs `php bin/console
openemr:patch-prep --side=rel`, opens `patch-prep/rel-810`
ready-for-review PR.
4. Workflow checks out master, runs `php bin/console openemr:patch-prep
--side=master`, opens `patch-prep/rel-810-master` ready-for-review PR.
5. Maintainer reviews both, merges rel-side first, then master-side.

## Test plan

- [x] `openemr-cmd worktree exec ws-patch-prep-automation pit` — full
isolated suite passes (3572 tests)
- [x] `openemr-cmd worktree exec ws-patch-prep-automation pst` — PHPStan
level 10 clean
- [x] `openemr-cmd worktree exec ws-patch-prep-automation pr` — phpcs
clean on all new files (only finding is the pre-existing
`sites/default/sqlconf.php` dev-stack auto-config write, same as #12696)
- [x] Local dry-run on this worktree, both sides, producing the diffs in
the *Sample diffs* section below
- [x] Idempotency verified on both new mutators
(`MasterSqlPatchBridgeMutator`, `PatchPrepReleaseTargetsMutator`): each
has dedicated `testIdempotentOnRerun` /
`testIdempotentWhenNewExistsAndOldGone` tests
- [x] YAML output of `PatchPrepReleaseTargetsMutator` parsed via Symfony
YAML as structural sanity check
- [ ] CI green on this PR's checks
- [ ] (Future) End-to-end exercise at the next real patch-prep on a live
rel branch

## Note about `sites/default/docker-version` and the docker volume

Same as #12696: the easy-dev docker stack used to capture the dry-run
diffs below mounts `sites/default/` as a docker volume rather than a
host bind, so `DockerUpgradeScaffoldMutator`'s write to
`sites/default/docker-version` doesn't propagate back to the host. The
mutator DOES write it (verified by inspecting the volume contents inside
the container) — it just doesn't surface on the host. The actual CI
workflow run won't have this quirk; all 3 docker-version files
(top-level `docker-version`, `docker/release/upgrade/docker-version`,
`sites/default/docker-version`) will appear in the real patch-prep PR's
diff.

## Sample diffs — what the patch-prep PRs will look like

Captured via `php bin/console openemr:patch-prep --target-version=8.1.2
--rel-branch=rel-810 --prev-version=8.1.1 --side=<rel|master>` against
the worktree at this commit. Scenario: rel-810 has just shipped 8.1.1;
maintainer bumps to `8.1.2-dev`.

<details>
<summary>Master-side patch-prep PR diff (9 files; SQL skeleton header
truncated)</summary>

```diff
diff --git a/.github/release-targets.yml b/.github/release-targets.yml
index e070a34..61b552efe9 100644
--- a/.github/release-targets.yml
+++ b/.github/release-targets.yml
@@ -103,19 +103,12 @@
   openemr_version_ref: master
 
 - branch: rel-810
-  docker_tags: 8.1.1,next
+  docker_tags: 8.1.2,next
   openemr_version_ref: rel-810
 
-# Second rel-810 row -- placeholder for the multi-row dev pattern (see
-# `unreleased` field doc above). Wires up the second-row mechanism
-# without actually publishing 8.1.0 from this configuration. When a real
-# dev cycle on rel-810 starts (e.g., 8.1.2-dev with 8.1.1 already
-# shipped), this row gets repurposed: docker_tags -> 8.1.1,
-# openemr_version_ref -> v8_1_1, `unreleased` flag dropped.
 - branch: rel-810
-  docker_tags: 8.1.0
-  openemr_version_ref: v8_1_0
-  unreleased: true
+  docker_tags: 8.1.1,next
+  openemr_version_ref: rel-810
 
 - branch: rel-800
   # 8.0.0 is the floating "current latest 8.0.0.x" pointer (always advances

diff --git a/docker-version b/docker-version
index b4de394..48082f7 100644
--- a/docker-version
+++ b/docker-version
@@ -1 +1 @@
-11
+12

diff --git a/docker/release/upgrade/docker-version b/docker/release/upgrade/docker-version
index b4de394..48082f7 100644
--- a/docker/release/upgrade/docker-version
+++ b/docker/release/upgrade/docker-version
@@ -1 +1 @@
-11
+12

diff --git a/docker/release/Dockerfile b/docker/release/Dockerfile
index 7ba67a1..5b601c1 100644
--- a/docker/release/Dockerfile
+++ b/docker/release/Dockerfile
@@ -289,6 +289,7 @@ COPY upgrade/docker-version \
      upgrade/fsupgrade-9.sh \
      upgrade/fsupgrade-10.sh \
      upgrade/fsupgrade-11.sh \
+     upgrade/fsupgrade-12.sh \
      /root/
 
 # Set upgrade scripts as executable (read and execute for owner only)
@@ -303,7 +304,8 @@ RUN chmod 500 \
     /root/fsupgrade-8.sh \
     /root/fsupgrade-9.sh \
     /root/fsupgrade-10.sh \
-    /root/fsupgrade-11.sh
+    /root/fsupgrade-11.sh \
+    /root/fsupgrade-12.sh
```

Bridge rename (the `sql/8_1_1-to-8_2_0_upgrade.sql` file's contents are
preserved exactly; only the filename changes):

```diff
-sql/8_1_1-to-8_2_0_upgrade.sql   [deleted]
+sql/8_1_2-to-8_2_0_upgrade.sql   [created, byte-identical to deleted]
```

New files:

```
sql/8_1_1-to-8_1_2_upgrade.sql            (new — header-only SQL upgrade skeleton)
docker/release/upgrade/fsupgrade-12.sh    (new — see contents in rel-side block below)
```

</details>

<details>
<summary>Rel-side patch-prep PR diff (5 files; byte-identical docker
scaffold to master-side, plus skeleton)</summary>

```diff
diff --git a/docker-version b/docker-version
index b4de394..48082f7 100644
--- a/docker-version
+++ b/docker-version
@@ -1 +1 @@
-11
+12

diff --git a/docker/release/upgrade/docker-version b/docker/release/upgrade/docker-version
index b4de394..48082f7 100644
--- a/docker/release/upgrade/docker-version
+++ b/docker/release/upgrade/docker-version
@@ -1 +1 @@
-11
+12

diff --git a/docker/release/Dockerfile b/docker/release/Dockerfile
index 7ba67a1..5b601c1 100644
--- a/docker/release/Dockerfile
+++ b/docker/release/Dockerfile
@@ -289,6 +289,7 @@ COPY upgrade/docker-version \
      upgrade/fsupgrade-9.sh \
      upgrade/fsupgrade-10.sh \
      upgrade/fsupgrade-11.sh \
+     upgrade/fsupgrade-12.sh \
      /root/
 
@@ -303,7 +304,8 @@ RUN chmod 500 \
     /root/fsupgrade-8.sh \
     /root/fsupgrade-9.sh \
     /root/fsupgrade-10.sh \
-    /root/fsupgrade-11.sh
+    /root/fsupgrade-11.sh \
+    /root/fsupgrade-12.sh
```

New `docker/release/upgrade/fsupgrade-12.sh`:

```bash
#!/bin/bash
# Upgrade number 12 for OpenEMR docker
#  From prior version 8.1.1 (needed for the sql upgrade script).
priorOpenemrVersion="8.1.1"
echo "Start: Upgrade to docker-version 12"
# TODO: fill in upgrade logic per-release; see prior fsupgrade-*.sh for examples
echo "Completed: Upgrade to docker-version 12"
```

New `sql/8_1_1-to-8_1_2_upgrade.sql` (header-only — first 10 of ~190
lines shown):

```sql
--
--  Comment Meta Language Constructs:
--
--  #IfNotTable
--    argument: table_name
--    behavior: if the table_name does not exist,  the block will be executed

--  #IfTable
--    argument: table_name
--    behavior: if the table_name does exist, the block will be executed
... [comment-meta-language header continues; no upgrade SQL — per-release work fills the body]
```

</details>

## Design decisions worth flagging

- **Sibling command, not `--scope=patch-prep` extension.** Matches the
precedent set by #12696 (which itself follows #12662's `--scope=master`
framing for release-finalize). Each lifecycle event gets its own command
with its own mutator list.
- **`MasterSqlPatchBridgeMutator` reports BOTH old and new paths** in
its `MutatorResult::changedFiles` (since rename = delete + create). The
conductor's output shows both for visibility.
- **`PatchPrepReleaseTargetsMutator` is scoped to the target rel
branch.** Unlike `BranchCutReleaseTargetsMutator` (#12696) which drops
all `unreleased: true` rows uniformly, patch-prep only drops
placeholders for the branch being patched. Other rel branches'
placeholders are not our concern.
- **`workflow_dispatch` requires explicit `prev-version`** rather than
computing it from current version.php. Recovery scenarios may want a
different baseline than auto-detect would pick.

## Notes

- DO NOT enable auto-merge.
- BASED ON #12696 — will need rebase after that lands. Should be clean:
this PR's only overlap with #12696 is on `MutatorContext.php` (additive
`fromVersion` field), `DockerUpgradeScaffoldMutator.php` (additive
`fromVersion` consumption in one helper), and
`SqlUpgradeSkeletonMutator.php` (additive `fromVersion` consumption in
one line) — all strictly additive against #12696's versions.
- For the `peter-evans/create-pull-request@v8` action, verified
beforehand that the workflow's target branches (`patch-prep/rel-810`,
`patch-prep/rel-810-master`) do not exist on the remote (clean state for
first run).



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added patch-prep automation for release branches, including new pull
request templates and a workflow that opens coordinated rel-side and
master-side PRs.
* Added a new command to generate patch-prep changes and report the
files updated.

* **Bug Fixes**
* Improved version handling so patch-prep can use the exact previous
patch version when creating upgrade scaffolding.
* Added safer handling for master SQL bridge file renames and
release-target list updates.

* **Tests**
* Added coverage for the new patch-prep flow, version validation, and
file-update behavior.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Reflect the 2026-07-01 landings to openemr/openemr master: G4 + G5
consumed by workstream 2 branch-cut automation (PR openemr#12696,
`6513b487b6`) via new `openemr:branch-cut` command + workflow + 4
new mutators; G11 Phase A shipped as release-finalize partner PR (PR
openemr#12662, `abb1e2d940`); G12 shipped as workstream 6 patch-prep
automation (PR openemr#12697, `c1af6370a0`). Note in-flight rabbit-fix
follow-up PR openemr#12709 addressing 3 Major items missed at openemr#12696 merge.
Wire G3's forward reference to `SqlUpgradeSkeletonMutator` to its
now-shipped consumers in workstreams 2 + 6. Resolve conditional
sequencing subsections now that 8.1.1 is being pursued and all three
workstreams landed to master before the 8.1.1 ship.

Assisted-by: Claude Code
bradymiller added a commit that referenced this pull request Jul 1, 2026
…12712)

Prep for the imminent rel-820 cut. 8.1.x is being skipped entirely (no
8.1.1 ship), so both rel-810 rows should be gone by the time
\`branch-cut-automation.yml\` fires on the \`create\` event.

## Changes

- **Add \`unreleased: true\` to the rel-810 \`8.1.1,next\` row.** Pauses
daily publishing from rel-810 across the cut window.
\`BranchCutReleaseTargetsMutator.dropAllUnreleasedRows()\` (from #12696)
will then uniformly drop this row alongside the existing \`8.1.0\`
placeholder — leaving no rel-810 rows post-cut, matching the skip-line
scenario documented in \`docs/release-mechanism-gaps.md\` G5 refinement.
- **Move the \`next\` tag from rel-810 to master.** Interim posture so
master owns \`next\` temporarily during the cut. At the \`create\`
event, \`BranchCutReleaseTargetsMutator.bumpMasterRow()\` drops master's
\`next\` and bumps minor to \`8.3.0,dev\`, and the newly-inserted
rel-820 row picks up \`8.2.0,next\`.

## Post-cut end state (once branch-cut workflow runs against rel-820)

\`\`\`yaml
- branch: master
  docker_tags: 8.3.0,dev
  openemr_version_ref: master

- branch: rel-820
  docker_tags: 8.2.0,next
  openemr_version_ref: rel-820

- branch: rel-800
  docker_tags: 8.0.0,8.0.0.3,latest
  openemr_version_ref: v8_0_0_3

- branch: rel-704
  docker_tags: 7.0.4
  openemr_version_ref: v7_0_4
\`\`\`

## Test plan

- [ ] \`docker-validate-release-targets.yml\` CI check passes
(docker_tag ↔ version.php alignment: master's \`8.2.0,dev,next\` should
still align with master's current \`\$v_major.\$v_minor = 8.2\`;
rel-810's \`8.1.1\` is on an unreleased row so should be exempt from the
guard).
- [ ] After merge + rel-820 cut, verify the branch-cut PRs open with the
expected end state.

## References

Skip-line cut pattern: \`docs/release-mechanism-gaps.md\` G5 "Refinement
(2026-06-30) — Skip-line cut scenario" (already in master via #12599).
…rcise

8.1.x is being skipped entirely; no 8.1.1 ship. rel-820 cut becomes the first end-to-end exercise of branch-cut automation. Workstream 3 Phase B (cherry-pick to rel-810) is now unnecessary. Pre-cut posture in openemr#12712.

Assisted-by: Claude Code
…20 cut, 2026-07-02)

Assisted-by: Claude Code
Two new gaps discovered + closed same session (2026-07-05) while
shaking out the 8.2.0 dispatch chain end-to-end for the first time
under the workstream 2/3/6 automation:

- **G13 - derive-prev-release returned skipped-version tags.**
  Resolver walked annotated v* tags without consulting the shipped-
  versions manifest, so v8_1_0 (cut then skipped) got returned as
  prev_release for target 8.2.0. Fixed by fetching
  website-openemr/data/releases.json at derive time and treating any
  tag absent from FINAL entries as skipped. Landed via
  openemr#12769/openemr#12770 with follow-up openemr#12771/openemr#12772 for a
  secondary bug (lightweight tags like v8_0_0 dropped by the
  annotated-tag filter).

- **G14 - release-docs/<version> branches accumulated divergent
  history.** Byproduct of G13's fix. Even with prev_release correctly
  computed, PR openemr#164 stayed CONFLICTING because docs branches
  committed on top of the previous state each dispatch and history
  diverged from master indefinitely. Fixed by rebasing docs branches
  on master each dispatch: openemr/website-openemr#174 (initial reset,
  subsumed), openemr#175 (rebase-per-dispatch), openemr#176 (restore fetch for
  --force-with-lease baseline).

Update log entries added covering both gaps + workflow_dispatch
verification pattern + the website-openemr consumer parallel
workstream that landed same day (bot filters, wiki-agnostic
defaults, dropped install/upgrade page generation, etc.).

Assisted-by: Claude Code
Adds a note in the intro block that artifact-testing is being addressed
separately by the sibling artifact-acceptance-testing plan
(openemr#12811, draft). Prevents future readers from adding a
gap here for what's already tracked elsewhere.

The two docs sit at distinct layers -- release-mechanism owns the
pipeline that produces artifacts; acceptance-testing owns the
verification that they work. No structural overlap.

Also bumps the last-updated date.

Assisted-by: Claude Code
…nery

Adds a new numbered gap (G15) formalizing the "how do we manage drift on
rel-* branches for the release-machinery workflows moving into core"
question as a tracked open item.

The question was already discussed in the migration doc's Pre-Phase-1
architectural decision section (per-branch copies vs reusable workflows),
but living only there meant it wasn't tracked alongside the other
numbered gaps with status + revisit criteria.

Content covers:
- What the drift surface is (workflows + supporting PHP classes on rel-*)
- G7 as empirical precedent (BranchVersionResolver drift on rel-810)
- Docker-pipeline byte-identical mechanism as the working comparison
- Three options: ad-hoc backport / byte-identical canary / reusable workflows
- Migration doc cross-reference
- Revisit at workstream 7 entry (post-8.2.0)
- Related gaps (G7 driver, G10 future canary retirement)

Assisted-by: Claude Code
…zing

Locks G15's drift-management decision to option 2 (byte-identical canary
+ auto-sync, docker-pipeline pattern) with six reasons in weight order --
production-validated this session, lower migration risk, same team
mental model, doesn't preclude option 3 later, sync PRs stay review-
gated, and preserves per-branch customization escape hatch.

Also adds concrete file-set sizing per phase (only Phase 2 fires from
rel-* branches; ~20-25 files added; ~30 total FILES_ALL post-migration)
and recommends the enumerated-workflows plus glob-for-PHP-tree shape to
avoid per-file curation as classes churn.

Related gap G10 (future canary retirement) narrows in scope -- the
release-mechanism side of that consideration is closed by locking
option 2 here.

Assisted-by: Claude Code
…hipping

Accumulated updates from the 8.2.0 shipping cycle and follow-up
gap-fix work. Every gap has an inline status header at the top
of its section (SHIPPED / PR in flight / open) and a corresponding
update-log entry at the bottom of the doc.

- G16 SHIPPED via openemr#12868 + rel-820 backport openemr#12872 (patch-id parity
  verified). Conductor overshoot to next-patch on post-tag replay.
- G17 + G18 SHIPPED via openemr#12862. docker-build-release trigger + concurrency.
- G19 SHIPPED via openemr#12883. Anonymous raw.githubusercontent.com fetches
  switched to authenticated gh api calls.
- G20 SHIPPED via openemr#12887 + openemr-devops#855 + website-openemr#189.
  prev_release added to openemr-tag dispatch event's tagData.
- G21 partial (website-openemr#184 acknowledgements filter landed;
  changelog side stays open pending G22).
- G23 SHIPPED via website-openemr#187. workflow:upsert-pr filters
  gh pr list by --head + --state open.
- G24 SHIPPED via website-openemr#185. current-stable-version Hugo
  shortcode reading data/releases.json.
- G25 added (Copilot in acknowledgements author list).
- G26 SHIPPED via arduino/setup-task@v3 + repo-token bump PRs.
- G27 added (per-channel announcement template review, placeholder).
- G28 added + PR in flight (openemr#12889). Master-side finalize PR requires
  post-tag auto-update before merge; ordering isn't documented or
  enforced. Fix folds master-scope mutator + release-finalize PR
  update work into the finalize job, plus gh pr ready + gh pr comment
  for the "ready to merge" maintainer signal.

Also refines the timing table + docker-tags model discussion with
the 8.2.0-shipping-informed picture of what's manual vs automated
and when in the release cycle each artifact lands.
…ackport in flight (openemr#12891)

G28 header retitled from "fix in flight" to "SHIPPED 2026-07-11".
Header block calls out that the master fix (openemr#12889) is in and
the rel-820 backport (openemr#12891) is in flight with a one-line
conflict resolution around G20's --prev-release wiring.

Update-log entry replaces the earlier "fix in flight" bullet
with the SHIPPED entry. Investigation-narrative that used to
live in the update-log bullet is not repeated -- it already
lives in G28's "Actual fix" subsection above.
… G20 rel-820 drift catch + G28 fixture cleanup in flight

G28 header updated to reflect both master (openemr#12889) and rel-820
(openemr#12891) shipped, byte-identical patch-id parity now that G20
also landed on rel-820 (openemr#12892).

Update-log entries added for:
- G28 fully SHIPPED (both branches, byte-identical parity).
- G20 rel-820 drift catch + fix via openemr#12892 -- surfaced during
  G28 backport conflict resolution. Follow-up note: release-
  mechanism files aren't under byte-identical enforcement yet;
  discipline is manual cross-branch propagation until the
  migration folds them in.
- 8.1.0 fixture cleanup PR openemr#12893 in flight to sweep the
  remaining rel-cut/rel-update/docs-binaries goldens + fixture
  JSONs + DispatchDataBuilderTest cases that openemr#12887's pass
  didn't reach.
…ce early-migration slice" SHIPPED

G22 header retagged from "discovered 2026-07-09" to "fix in flight 2026-07-12". New status block at the top of the entry itemizes the six-PR slice and marks PRs 1-3 as SHIPPED with links: openemr#12896 (manifest rename), openemr#12904 (glob support), openemr#12910 (release-mechanism surface added to manifest). PRs 4-6 (ChangelogGenerator move, devops rewire, website retire) noted as pending.

Ancillary follow-ups cataloged inline: three orphan-cleanup PRs, one stale-refs cleanup, one sync-workflow hotfix, three auto-sync PRs from rel-branch propagation, plus the unrelated devops kubernetes 8.1.0 -> 8.2.0 bump done in the same window.

Update-log entry at the bottom summarizes what shipped and what's still pending.
G22 fix-in-flight tally:
  * Added openemr#12924 as a "Pre-PR-4 refactor" bullet in the PR list --
    extracts manifest parsing into shared list-manifest-paths.sh with
    BATS coverage. Motivated by two consecutive workflow yq bugs
    (openemr#12916, openemr#12920) that traced back to three-way parsing duplication
    between the sync script, validate script, and inline yq in
    sync-byte-identical.yml. Not in the original slice plan.
  * Ancillary follow-ups paragraph gets openemr#12915 (PR 3's object-form
    exclude-branches pivot -- rel-800/rel-704 couldn't carry the 60
    release-mechanism PHP files without autoload topology), openemr#12916 +
    openemr#12920 (the workflow yq bugs), and openemr#12921/openemr#12922/openemr#12923 (auto-
    sync PRs propagating PR 3's surface + object-form manifest to rel
    branches).
  * Change-log entry for 2026-07-12 rewritten to cover today's
    second-half activity end-to-end -- pivot -> workflow bugs ->
    refactor -> sync PRs.
…ordinated pair)

G22 status block updated for the 7-PR slice (was 6):
  * PR 4 SHIPPED marker + coordinated devops#857 note
  * PR 7 added (retire CreateReleaseChangelogCommand — belongs
    after PR 6, discovered post-PR-4 landing)
  * Ancillary paragraph gains openemr#12926 (auto-sync to rel-820)
    and openemr#12927 (manual rel-820 composer-require-checker.json
    whitelist port — the file isn't in the byte-identical set)
  * Deferred-follow-ups paragraph captures CompatibilityMutator,
    CompatibilityNotesRenderer.inject() idempotence, and the
    frozen-inputs fixture regression punts from PR 4

Change-log entry for 2026-07-13 covers PR 4 end-to-end: the
class moves, filter port, compare-link fix, mutator wiring
across both scopes, the three rounds of CR fixes
(preg_replace_callback, ClockInterface, Markdown escape + URL
sanitize), sync PR + follow-up whitelist port to rel-820, PR 7
addition to the slice plan, and the live gh-api smoke test.
…r + PR 9 fixture regression)

The two follow-ups deferred from PR 4 promoted to first-class
entries in the slice plan:

  * PR 8 -- CompatibilityMutator threads CompatibilityDeriver +
    CompatibilityNotesRenderer through the mutator flow, fixes
    CompatibilityNotesRenderer::inject() non-idempotence in the
    process (mutator is the first live consumer, we own it).

  * PR 9 -- fixture regression against 8.2.0's real ~100 v8_1_0...
    rel-820 PRs, locks the noise filter + categorization + section
    ordering + area sub-grouping against realistic input. Sequenced
    after PR 8 so the fixture covers the fully consolidated flow
    including compatibility.
…ndow)

Original PR 8 slot was 'non-blocking, at the end.' Tracing the
actual data flow post-PR-4 revealed a compat-gap window: without
PR 8, PR 5's section-extract would pull a CHANGELOG entry missing
the Minimum-supported-versions block that today's 8.2.0 entry
has (devops's derive-compatibility.php step injects it before
commit; ChangelogMutator doesn't touch it). Land PR 8 first so
compat is baked into openemr's CHANGELOG.md by the time PR 5
swaps devops to extract-only. Numbers stay stable to preserve
existing PR body references.
Status block bumps 4/9 -> 5/9. PR 8 gets its own bullet in the
PR list with the coordinated rel-820 config-file port PR + auto-
sync PR references. PR 5 note updated to reflect unblocked
status. Change-log entry for 2026-07-13 (later) captures:

  * PR 8 SHIPPED as openemr#12928 + rel-820 ports openemr#12933 +
    auto-sync openemr#12932
  * Two-config-file coordination shape (both
    .composer-require-checker.json and .codespell-ignore-words.txt
    needed manual ports; neither in byte-identical set)
  * CR round-1 catch on 12928: strip regex was unscoped, would
    delete older releases' compat blocks on multi-release
    CHANGELOG; landed fix scopes strip to first ## section
  * Latent ChangelogMutator relBranch bug fix (workflow now
    passes --rel-branch on rel scope too)
  * Live smoke test evidence
  * Compat-gap window closed; PR 5 unblocked

Ignore-word 'deriver' backported to this worktree's
.codespell-ignore-words.txt so the pre-commit hook doesn't flag
the mention in the change-log narrative.
… follow-ups

Mark G22 CLOSED. All 10 PRs of the changelog-surface early-migration
slice landed (through 2026-07-15). Update the PR list with SHIPPED
markers and rel-820 sync details for PR 9 + PR 10.

Add "Post-slice follow-ups" subsection documenting the amendment-
workflow work that surfaced from the design discussion: the slice
migrated the changelog surface but exposed a real timing gap
(GHSAs published post-release don't show up in shipped
CHANGELOG.md / Release body via the mutator-driven flow).
Not a regression from the migration, but the migration made it
fixable in openemr core.

Follow-ups documented:
* openemr#12993 — GHSA "Patched versions" convention docs
* openemr#12996 — release-amendment.yml workflow
* openemr#12998 — rel-820 doc sync
* openemr#12999 — GH_TOKEN fix on amendment mutator steps
* openemr#13000 — release-mechanism-smoketest.yml CI gate
* openemr#13002/openemr#13003 — first amendment PRs (8.2.0 Security section)
* openemr#13004 — extract-order fix (peter-evans working-tree-reset gotcha)

Plus end-to-end validation history: 2 dry-runs + 1 real dispatch,
each surfacing a real bug that unit tests couldn't have caught.
Update G22's post-slice follow-ups list with the final shipping
status:

* openemr#13004 SHIPPED (extract-order fix + memory saved for peter-evans
  behavior gotcha)
* openemr#13005 SHIPPED + rel-820 sync openemr#13006 (noise-filter relaxation --
  drop backport + release-scope filters, tighten chore-release-cut
  regex per CR)
* openemr#13007 SHIPPED (rel-checkout submodule leak fix)
* openemr#13002 + openemr#13003 SHIPPED (amendment PRs landed after 3 real
  dispatches / 3 iterations of fixes)

Expanded End-to-end validation history with all 3 real dispatches
including the fully-green run 29422237030 that finally landed
clean. All four surfaces (rel-branch CHANGELOG, master CHANGELOG,
GitHub Release body, changelog.md attachment) now carry the
amended 8.2.0 Security section + full PR list.
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