You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
ci: automate promotion and sync release history back to staging - #374
Production promotions leave a merge commit on main that has to be reconciled into staging before the next release. Add a Production promotion Actions workflow: manually run promote on main to sync outstanding history and open/reuse the release PR. Every push to main automatically runs sync.
The dedicated release App creates a history-only snapshot PR into staging, waits for its full CI run, then merges it with a merge commit. The snapshot must preserve its staging parent's exact tree. Conflicts, content changes, modified PR heads, failed checks, and denied merges stop the run. Concurrent staging work, including a revert of released code, is preserved. Retrying reuses the snapshot/PR or exits if already synced. Production PR review and merge remain manual.
Security and rollout
Created the private UTMIST Misty Release App, installed only on this repository, with Contents/Pull requests/Workflows write and Actions read. No Administration permission.
Credentials are stored in the release environment, whose branch policy permits only main (no tags).
Only this App can bypass staging's PR-review requirement and merge queue (PR-only bypass). Required checks, administrator enforcement, and main protections remain intact; the configuration was read back and compared.
The credentialed job runs main's code and uses Git object operations to inspect staging. It never executes staging dependencies or pushes directly to a protected branch.
This must merge into staging and be promoted to main before automatic push-triggered sync is active. The first release handles existing history too. There is a manual sync operation for retries.
Changed-file rationale
File
Why needed
.github/workflows/promotion.yml
Manual promotion entry point, automatic main-push sync, serialized runs, main-only environment and scoped App token.
.github/scripts/promotion.cjs
Idempotent Git snapshot/PR orchestration, unchanged-tree validation, CI wait, pinned merge, and ancestry verification.
.github/scripts/promotion.test.cjs
Real disposable Git graphs and fake GitHub calls cover history preservation, concurrent writes/reverts, retries, failed CI, tampering, and execution guards.
.github/workflows/ci.yml
Run these regressions in the existing required node-test check, including merge groups.
Makefile
Include the same regressions in local fast/full test commands and provide make test-promotion.
docs/DEVELOPMENT.md
Operator usage, App/environment/bypass setup, activation, retries, rollback, and the current CI inventory.
docs/RELEASING.md
Connect the version/wiki release checklist to promotion and explain initial activation.
AGENTS.md
Point agents to the release workflow and prevent squash/rebase of ancestry syncs.
This intentionally spans .github, docs, and root for automation and its local checks/runbook. Conflict resolution preserves both the release-version guard and promotion checks, and integrates the current version/wiki release process.
Validation
After integrating the latest staging/versioning and documentation changes, make check passed using the pinned Python dependencies and Node 26.8.1; this includes lint/format checks and the repository's fast suites.
make test-release test-promotion passed on the integrated branch. Both targets remain included in make test and make test-full.
Earlier implementation validation: actionlint passed for the changed workflows; the App-token action is pinned to an inspected v3 commit.
git diff --check passed.
Fresh CI initially failed only the unchanged chunking speed assertion (test_line_heavy_text_stays_fast: 6.52 seconds against a 5-second limit). The same test passed locally in 0.67 seconds with source paths verified; the failed job was rerun without modifying code or thresholds. Fresh CI passed on commit c46c6584d0af0c74d739ac44516208263ca54f09.
The current real main → staging merge was computed locally and its tree exactly matched staging.
Verified the App can mint a short-lived installation token restricted to UTMIST/Misty, then revoked the verification token.
Live verification: the exact committed script ran with the dedicated App, created zero-file-diff sync PR chore: sync main 9f08888366c6 back into staging #375, waited for full CI, and merged it as app/utmist-misty-release at a361a97022a8e666b2847bf70427dfe34096a34a. Verified main is now an ancestor of staging and staging's tree is unchanged (3355689efe47a8b2ee3c630df55e172410c6538f). The temporary token was revoked and local private-key copy removed.
The normal production deploy remains triggered by merging the reviewed staging → main PR. A history sync is not proof of production deployment health.
assertSnapshot() checks the parent identities and tree equality, but doesn’t verify that the snapshot represents a valid, unchanged merge of those parents. A precreated or replaced automation branch can therefore bypass the merge-tree check used for new branches.
I reproduced two cases through the actual CLI with real Git repositories and simulated GitHub responses:
A snapshot using an older staging ancestor as its first parent passes validation, then rolls back staging files when merged.
A snapshot omitting a production-only hotfix passes validation and records main as merged, causing subsequent syncs to skip the missing fix.
Since the release App bypasses staging review, please recompute git merge-tree --write-tree <snapshot-first-parent> <main> when validating an existing snapshot, reject conflicts, and require the resulting tree to match the snapshot. Both cases should have regression coverage.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
size/xl>= 500 lines changedzone: docsOwned by the docs zone (docs/CODE-OWNERSHIP.md)zone: .githubOwned by the .github zone (docs/CODE-OWNERSHIP.md)zone: rootOwned by the root zone (docs/CODE-OWNERSHIP.md)
3 participants
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
Production promotions leave a merge commit on
mainthat has to be reconciled intostagingbefore the next release. Add a Production promotion Actions workflow: manually runpromoteonmainto sync outstanding history and open/reuse the release PR. Every push tomainautomatically runssync.The dedicated release App creates a history-only snapshot PR into staging, waits for its full CI run, then merges it with a merge commit. The snapshot must preserve its staging parent's exact tree. Conflicts, content changes, modified PR heads, failed checks, and denied merges stop the run. Concurrent staging work, including a revert of released code, is preserved. Retrying reuses the snapshot/PR or exits if already synced. Production PR review and merge remain manual.
Security and rollout
releaseenvironment, whose branch policy permits onlymain(no tags).syncoperation for retries.Changed-file rationale
.github/workflows/promotion.yml.github/scripts/promotion.cjs.github/scripts/promotion.test.cjs.github/workflows/ci.ymlnode-testcheck, including merge groups.Makefilemake test-promotion.docs/DEVELOPMENT.mddocs/RELEASING.mdAGENTS.mdThis intentionally spans
.github,docs, androotfor automation and its local checks/runbook. Conflict resolution preserves both the release-version guard and promotion checks, and integrates the current version/wiki release process.Validation
make checkpassed using the pinned Python dependencies and Node 26.8.1; this includes lint/format checks and the repository's fast suites.make test-release test-promotionpassed on the integrated branch. Both targets remain included inmake testandmake test-full..github/PROJECT-BOARD.mdand.github/workflows/project-board.yml); this PR introduces no new broken links. The promotion script and workflows retain their approved behavior.actionlintpassed for the changed workflows; the App-token action is pinned to an inspected v3 commit.git diff --checkpassed.test_line_heavy_text_stays_fast: 6.52 seconds against a 5-second limit). The same test passed locally in 0.67 seconds with source paths verified; the failed job was rerun without modifying code or thresholds. Fresh CI passed on commitc46c6584d0af0c74d739ac44516208263ca54f09.main → stagingmerge was computed locally and its tree exactly matched staging.UTMIST/Misty, then revoked the verification token.app/utmist-misty-releaseata361a97022a8e666b2847bf70427dfe34096a34a. Verified main is now an ancestor of staging and staging's tree is unchanged (3355689efe47a8b2ee3c630df55e172410c6538f). The temporary token was revoked and local private-key copy removed.The normal production deploy remains triggered by merging the reviewed
staging → mainPR. A history sync is not proof of production deployment health.