diff --git a/.agents/playbook.md b/.agents/playbook.md index 5784cf2ca3..aaecbd140e 100644 --- a/.agents/playbook.md +++ b/.agents/playbook.md @@ -269,7 +269,35 @@ Default flow when a fix is needed in both lines: git cherry-pick git push origin 3.0.x ``` -3. The forward-merge workflow (`.github/workflows/forward-merge-3.0.yml`) opens a PR back to `main` whenever `3.0.x` updates. Merging it is a near-no-op (the cherry-pick is already in `main`) but keeps the lines provably in sync. The workflow auto-resolves conflicts on always-divergent paths (`package.json` / `package-lock.json` → preserve main's; `.changeset/*.md` → preserve main's; `dist/*`, `CHANGELOG.md`, `static/schemas/source/index.json` → take 3.0.x's) and post-merge skips the PR if the result has no tree change vs `main` (which happens after a squash-merge of an earlier forward-merge). Conflicts on any path outside that allowlist fail the workflow loud and need manual resolution — they indicate a playbook violation (a change on 3.0.x that wasn't first cherry-picked from main). +3. The forward-merge workflow (`.github/workflows/forward-merge-3.0.yml`) opens a PR back to `main` whenever `3.0.x` updates. Merging it keeps the lines provably in sync. Auto-resolution is **metadata-only** — anything else fails the workflow loud and requires human review. + + **Auto-resolved (metadata that legitimately diverges by-design):** + - `package.json` / `package-lock.json` → preserve main's (main may carry structural changes — package renames, new deps — that 3.0.x doesn't) + - `.changeset/*.md` / `.changeset/pre.json` → preserve main's (independent pre-mode pool) + - `static/schemas/source/index.json` / `static/schemas/source/registry/index.yaml` → take 3.0.x's (regenerated on next release build) + - `CHANGELOG.md` → take 3.0.x's (main's next Version Packages cut prepends its own entries above) + - `dist/{schemas,compliance,protocol,docs}/*` → take 3.0.x's (immutable per release; main only ever ADDS) + + **Fails loud (everything else):** every other conflict is a real backport decision and surfaces for human review. This includes the known cross-line divergences in `static/compliance/source/universal/{storyboard-schema,runner-output-contract}.yaml`, `static/schemas/source/{core/error.json,enums/error-code.json,protocol/get-adcp-capabilities-response.json}`, `.github/workflows/training-agent-storyboards.yml`, and `docs/building/implementation/error-handling.mdx` — main carries 3.1-track additions (new enum codes, schema discriminators, the CANONICAL CHECK ENUM block) that can't ship to 3.0.x without breaking the patch contract. Each push to 3.0.x that touches one of these regions will require a manual forward-merge PR until 3.1.0 cuts and replaces both lines. + + **Why metadata-only:** an earlier version of the workflow auto-resolved the cross-line content files via whole-file `git checkout --ours`, which silently dropped 3.0.x's non-conflicting changes alongside the divergent regions. Five days of patches (v3.0.5 → v3.0.9, ~30 commits) silently failed to forward-merge before the bug was found. Loud failure on every content conflict is the right primitive: it's annoying for the divergent files but it surfaces real decisions instead of dropping work. See #4306 / #4308 / #4310 for context. + + **Manual resolution recipe** (when the workflow fails): + ```bash + git fetch origin + git checkout -b forward-merge/3.0.x- origin/main + git merge origin/3.0.x # resolve conflicts in editor — for the 7 known-divergent files, + # take main's side (it carries 3.1-track shape) + git push origin forward-merge/3.0.x- + gh pr create --base main --head forward-merge/3.0.x- + ``` + Branch name **must** start with `forward-merge/` — the changeset-check workflow skips on this prefix (the merge brings package-changing commits whose changesets were already consumed by 3.0.x's Version Packages cut). + +4. **Skip rules for release-machinery PRs.** The changeset-check workflow (`.github/workflows/changeset-check.yml`) skips PRs whose `head_ref` starts with: + - `changeset-release/` — Version Packages PRs (changesets already consumed) + - `forward-merge/` — forward-merge PRs (changesets explained by source branch's CHANGELOG, not by a new file on main) + + Any other PR head triggers the check normally. Don't bypass via empty changeset padding — the skip is the right primitive. #### Patch eligibility @@ -312,6 +340,35 @@ git add -A && git commit -m "chore(release): exit pre mode for 3.1.0 stable cut" Next Version Packages cut after the exit PR merges produces `3.1.0` stable. +#### When 3.1.0 cuts — release-line transition + +Cutting 3.1.0 ends the 3.0.x ↔ main divergence and starts a new 3.0.x → 3.1.x lifecycle. Steps to take **at the same time as the 3.1.0 stable release**: + +1. **Cut the `3.1.x` branch.** Branch off `main` immediately after the 3.1.0 tag is published: + ```bash + git checkout main && git pull + git checkout -b 3.1.x origin/main + git push -u origin 3.1.x + ``` + `main` then advances toward `3.2.0-beta.N` (re-enter pre mode with `npx changeset pre enter beta` and a new `.changeset/pre.json`). + +2. **Update `forward-merge-3.0.yml` → `forward-merge-3.1.yml`.** The workflow targets `3.1.x` now; rename the file and update `branches:` and the merge target. Keep `3.0.x` only if 3.0.x stays in maintenance for security fixes (see step 4). + +3. **Reset the auto-resolve allowlist if needed.** When 3.1.0 absorbs the 3.1-track additions on main, the cross-line divergences in `error-code.json`, `error.json`, `runner-output-contract.yaml`, `storyboard-schema.yaml`, `get-adcp-capabilities-response.json`, `training-agent-storyboards.yml`, and `error-handling.mdx` collapse — `3.1.x` and `main` start identical. The allowlist stays metadata-only (the same list as for 3.0.x is correct for 3.1.x), but the **known-divergent file list documented in the cherry-pick section above no longer applies**. Update that list in this playbook to reflect any new 3.2-track-only divergences that emerge. + +4. **Decide 3.0.x's fate.** Options: + - **Sunset immediately**: archive the `3.0.x` branch, stop publishing patches. Document the EOL in `docs/reference/version-support.mdx` and announce in the release notes. + - **Maintenance window**: keep `3.0.x` for security-only fixes for N months. If you do this, **also keep `forward-merge-3.0.yml`** so security patches still flow forward — but expect every push to fail loud (3.1.x has structurally diverged from 3.0.x at this point) and require manual resolution. Maintenance windows are heavy ops; only do this if there's an active security commitment. + +5. **Update `RELEASING.md` and this playbook** with the new release-line topology. The stale 3.0.x examples in this section will need to be rewritten in terms of 3.1.x. + +**Lessons captured from the 3.0.x line** (apply to every future maintenance line): +- **Never auto-resolve content files with `git checkout --ours` on whole files.** Whole-file resolution silently drops non-conflict changes. Allowlist metadata only; let content fail loud. +- **The forward-merge workflow's exit code must reflect actual merge state.** A "success" run that produced no PR for a non-empty divergence is a silent failure mode. Always log the merge tree-state explicitly (the workflow now does: `git diff --quiet origin/main HEAD` gates the PR-creation step). +- **Permanent cross-line divergences accumulate.** Once a file has any divergent region, every subsequent 3.0.x patch that lands near that region produces a real conflict. Manual forward-merges are the steady state, not the exception. Plan ops capacity accordingly. +- **Forward-merge PRs can't carry their own changesets** — the source branch's changesets are already consumed. The changeset-check workflow needs an explicit skip rule keyed on `head_ref` prefix (`forward-merge/*`). Carry this skip rule forward when you create the 3.1.x → main pipeline. +- **Reference implementations should land on `main` first**, then cherry-pick to maintenance lines only when truly needed (security, severe bugs). Don't dual-track features — that's how you grow the divergent-file list. + #### App-token convention `release.yml`, `release-docs.yml`, and `forward-merge-3.0.yml` mint a GitHub App installation token via `actions/create-github-app-token@v3` (secrets `RELEASE_APP_ID` / `RELEASE_APP_PRIVATE_KEY`) instead of using the default `GITHUB_TOKEN`. App-token-triggered events (push, PR open, release publish) DO fire downstream workflows; `GITHUB_TOKEN`-triggered events don't (GitHub's recursion-blocking rule). Without this swap, the Version Packages PR's required CI never fires, the release-docs snapshot is never created on `release: published`, and the auto-snapshot PR's required CI never fires either. diff --git a/.changeset/4306-release-line-forward-merge-lessons.md b/.changeset/4306-release-line-forward-merge-lessons.md new file mode 100644 index 0000000000..b85b83fa6d --- /dev/null +++ b/.changeset/4306-release-line-forward-merge-lessons.md @@ -0,0 +1,4 @@ +--- +--- + +docs(playbook,RELEASING): document forward-merge auto-resolve allowlist (metadata-only after #4308), known cross-line divergent files, manual resolution recipe, changeset-check skip rule for `forward-merge/*` PRs, and the 3.1.0 release-line transition checklist. Captures lessons from the #4306 / #4310 forward-merge silent-failure incident so they don't recur on the 3.1.x line. diff --git a/RELEASING.md b/RELEASING.md index 6756461911..0e032a128e 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,6 +1,6 @@ # Release Process -This document describes how to manage releases of the AdCP specification using Changesets. +This document describes how to manage releases of the AdCP specification using Changesets. **For deeper operational guidance** — release lines, cherry-pick conventions, the forward-merge workflow, manual resolution recipes, and the 3.1.0 transition plan — see [`.agents/playbook.md` § Release lines](.agents/playbook.md#release-lines). The playbook is canonical; this file is the introductory walkthrough. ## Overview @@ -12,6 +12,8 @@ We use [Changesets](https://github.com/changesets/changesets) to manage versions - Generates CHANGELOG entries - Creates git tags +AdCP runs **two release lines simultaneously**: `main` (next minor, currently `3.1.0-beta.N`) and `3.0.x` (patches to the current stable). Most contributors only touch `main` — the cherry-pick + forward-merge dance is documented in the playbook for cases where a fix needs to ship in both lines. + ## Workflow ### 1. Making Changes @@ -189,6 +191,20 @@ The `scripts/update-schema-versions.js` script updates the schema registry versi ## Troubleshooting +### Forward-merge PR fails on `Check for changeset` + +PRs whose head branch starts with `forward-merge/` skip the changeset check (the merge brings package-changing commits whose changesets were already consumed by 3.0.x's Version Packages cut). If the check is firing on a forward-merge PR, the head branch isn't using the `forward-merge/` prefix — rename it: + +```bash +git push origin HEAD:refs/heads/forward-merge/3.0.x- +``` + +See `.agents/playbook.md` for the full manual forward-merge runbook. + +### Forward-merge workflow fails loud on content conflicts + +Expected behavior when 3.0.x has changes in the cross-line-divergent files (`error-code.json`, `storyboard-schema.yaml`, `runner-output-contract.yaml`, `error.json`, `get-adcp-capabilities-response.json`, `training-agent-storyboards.yml`, `error-handling.mdx`). The workflow's error message includes the manual resolution recipe. See [playbook § Cherry-pick convention](.agents/playbook.md#cherry-pick-convention) for the full procedure and rationale. + ### Changesets not found Install dependencies: