| title | Release Process | |||
|---|---|---|---|---|
| description | Authoritative release process for lightspeedwp/.github: develop-first stacked PR flow with authorization gating, changelog validation, and automated post-release sync. | |||
| file_type | documentation | |||
| version | v3.0.1 | |||
| last_updated | 2026-08-08 | |||
| author | LightSpeed Team | |||
| maintainer | LightSpeed Team | |||
| owners |
|
|||
| tags |
|
Goal: ship reliable releases using a develop-first stacked PR model: feature work integrates to develop first, then a release PR merges to main, with automatic post-release sync back to develop.
feature branch
β
develop (PR, integrate feature work)
β
release/vX.Y.Z branch (created by agent)
β
[STACKED] PR #1: release/vX.Y.Z β develop (changelog + version bump)
β
[STACKED] PR #2: release/vX.Y.Z β main (after develop PR merges)
β
main (release published)
β
post-release-sync (chore: main β develop)
Flow sequence:
- Feature work integrates to
developvia normal PR workflow. - When ready for release, trigger
release.ymlworkflow ondevelop. - Agent creates
release/vX.Y.Zbranch, bumpsVERSION, updatesCHANGELOG.md. - Agent creates PR #1:
release/vX.Y.Zβdevelop(changelog + version changes). - Developer merges PR #1 to
develop. - Agent creates PR #2:
release/vX.Y.Zβmain(stacked on PR #1). - Developer merges PR #2 to
main. - GitHub Release published with compiled notes (sections, highlights, contributors).
post-release-syncworkflow automatically creates PR:mainβdevelopto keep branches in sync.
flowchart TD
accTitle: Release workflow full flow
accDescr: Developer triggers release, authorization checks, CI gates, version bump, stacked PRs to develop and main, tag and release, post-release sync
A["π€ Developer (on develop)<br/>Trigger release workflow"] -->|"gh workflow run release.yml"| B["π Trigger Telemetry<br/>Validate authorization"]
B -->|Authorized| C["β
Lint & Test Gates<br/>Run checks"]
B -->|Unauthorized| Z1["β Workflow Fails<br/>Log attempt"]
C -->|Checks pass| D["π Release Agent<br/>Create release/vX.Y.Z"]
C -->|Checks fail| Z2["β Workflow Fails<br/>Fix issues & retry"]
D -->|"Version + CHANGELOG"| E["π PR #1<br/>release/vX.Y.Z β develop<br/>For review"]
E -->|Developer merges| F["β
develop updated<br/>Version + changelog rolled"]
F -->|Auto-trigger| G["π PR #2<br/>release/vX.Y.Z β main<br/>For final review"]
G -->|Developer merges| H["π·οΈ Tag created<br/>Release published<br/>GitHub Release live"]
H -->|Post-release| I["π Post-Release Sync<br/>Merge main β develop<br/>PR for review"]
I -->|Developer merges| J["β
Release Complete<br/>Branches in sync"]
style A fill:#01579b,color:#fff,stroke:#000,stroke-width:2px
style B fill:#bf360c,color:#fff,stroke:#000,stroke-width:2px
style C fill:#1b5e20,color:#fff,stroke:#000,stroke-width:2px
style D fill:#4a148c,color:#fff,stroke:#000,stroke-width:2px
style E fill:#880e4f,color:#fff,stroke:#000,stroke-width:2px
style F fill:#00695c,color:#fff,stroke:#000,stroke-width:2px
style G fill:#880e4f,color:#fff,stroke:#000,stroke-width:2px
style H fill:#f57f17,color:#000,stroke:#000,stroke-width:2px
style I fill:#00695c,color:#fff,stroke:#000,stroke-width:2px
style J fill:#2e7d32,color:#fff,stroke:#000,stroke-width:2px
style Z1 fill:#b71c1c,color:#fff,stroke:#000,stroke-width:2px
style Z2 fill:#b71c1c,color:#fff,stroke:#000,stroke-width:2px
New in v3.0: Authorization validation gates all release workflow triggers.
- Trigger validation: Only
workflow_dispatchandworkflow_callevents allowed (blocks accidental or unauthorized triggers). - Actor validation: Trigger actor must be an active member of the
maintainersteam in the lightspeedwp organisation. - Audit logging: All authorization attempts logged in
trigger-telemetry.jsonwith timestamp, actor, event, and failure reason. - Blocking: Unauthorized attempts cause the workflow to fail immediately (no workaround;
continue-on-error: false).
Example audit log (unauthorized):
{
"event": "push",
"actor": "unknown-user",
"is_authorized": false,
"unauthorized_attempts": 1,
"failure_reason": "Invalid trigger event: push",
"timestamp": "2026-08-05T19:00:00Z"
}flowchart TD
accTitle: Authorization validation flow
accDescr: Event type validation, actor team membership check, authorization decision with audit logging
A["Workflow triggered<br/>workflow_dispatch or<br/>other event"] -->|Check event| B{Valid event type?}
B -->|No| C["β FAIL<br/>Invalid trigger event<br/>Log: Invalid event type"]
B -->|Yes| D{Actor in<br/>maintainers team?}
D -->|No| E["β FAIL<br/>Unauthorized actor<br/>Log: Actor not in team"]
D -->|Yes| F["β
PASS<br/>Release authorized<br/>Log: Success"]
C -->|Artifact| G["π trigger-telemetry.json<br/>Timestamp, actor, reason,<br/>is_authorized: false"]
E -->|Artifact| G
F -->|Artifact| H["π trigger-telemetry.json<br/>Timestamp, actor, reason,<br/>is_authorized: true"]
G --> I["π Review audit logs<br/>Identify unauthorized attempts"]
H --> J["βΆοΈ Continue workflow<br/>Proceed to lint & test"]
style A fill:#01579b,color:#fff,stroke:#000,stroke-width:2px
style B fill:#f57f17,color:#000,stroke:#000,stroke-width:2px
style C fill:#b71c1c,color:#fff,stroke:#000,stroke-width:2px
style D fill:#f57f17,color:#000,stroke:#000,stroke-width:2px
style E fill:#b71c1c,color:#fff,stroke:#000,stroke-width:2px
style F fill:#2e7d32,color:#fff,stroke:#000,stroke-width:2px
style G fill:#bf360c,color:#fff,stroke:#000,stroke-width:2px
style H fill:#2e7d32,color:#fff,stroke:#000,stroke-width:2px
style I fill:#bf360c,color:#fff,stroke:#000,stroke-width:2px
style J fill:#2e7d32,color:#fff,stroke:#000,stroke-width:2px
See ADR-002: Authorization Gating Strategy for detailed rationale.
- Changelog validation (
.github/workflows/changelog.yml)- Runs on every PR (all branches) and on
developpushes to ensure:CHANGELOG.mdconforms tochangelog.schema.json.- Unreleased section exists and is populated.
- Runs on every PR (all branches) and on
- Release workflow (
.github/workflows/release.yml)- Manual
workflow_dispatchand reusableworkflow_call. - Typed inputs:
version,notes_from,scope,provider,dry_run. - Authorization gating: trigger-telemetry job validates actor + event (blocks unauthorized).
- Hard gate on lint (
checks.ymlunified linting workflow). - Runs schema + unreleased validation before invoking
release.agent.js. - Uses
release.agent.js(ESM) to:- Create
release/vX.Y.Zbranch fromdevelop - Bump
VERSIONand updateCHANGELOG.md - Create PR #1:
release/vX.Y.Zβdevelop - After PR #1 merges, create PR #2:
release/vX.Y.Zβmain - Tag and publish GitHub Release with compiled notes
- Create
- Provider mode:
shell(default): gh/git-backed publication.mcp: GitHub API-backed publication for tag ref, PR, and release.
- Dry-run mode publishes review artefacts (
release-agent.log,release-notes-preview.md) without creating commits/tags/releases. - Trigger telemetry records authorisation attempts (expected
0unauthorized).
- Manual
- Post-release sync (
.github/workflows/release.ymlβ new job)- Runs after release if not dry-run.
- Creates
chore/post-release-sync-main-to-developbranch. - Merges
mainintodevelopto keep branches in sync. - Creates PR
mainβdevelopfor developer review/merge.
- Required checks before merging release PRs
- Lint/test green.
- Changelog validation green.
- Version bump and dated changelog entry present.
- Single source of truth:
VERSIONfile. - Scope values:
patch(default),minor,major. - Workflow dispatch examples:
Via GitHub UI:
- Go to Actions β release workflow
- Click Run workflow
- Select inputs:
- version: (leave blank to use scope)
- scope:
patch(default),minor, ormajor - provider:
shell(default) ormcp - dry_run:
true(default, safe mode) orfalse(live release)
- Click Run workflow
Via CLI:
gh workflow run release.yml \
--ref develop \
-f scope=patch \
-f provider=shell \
-f dry_run=falseGITHUB_REPOSITORYorRELEASE_REPO_OWNER+RELEASE_REPO_NAMEmust identify the target repository.GITHUB_TOKENis required for MCP provider mutation operations.- Retry/backoff tuning for MCP API calls:
RELEASE_MCP_RETRIES(default3)RELEASE_MCP_BACKOFF_MS(default250)RELEASE_MCP_BACKOFF_FACTOR(default2)
Before triggering the release workflow, verify:
- You are a member of the
maintainersteam (authorization requirement). -
CHANGELOG.mdhas unreleased entries and passes schema validation (npm run validate:changelog). -
VERSIONfile is correct for the intended bump scope. - All feature branches are merged to
develop. - Lint/tests green (
npm run lint && npm test). - Agent/workflow alignment:
release.agent.js,release.agent.md,release.yml,changelog.yml. - Documentation current (links valid, branch flow accurate).
- No uncommitted changes in working tree (
git statusis clean).
Phase 1: Trigger release workflow
- Navigate to Actions β release workflow.
- Click Run workflow (or use CLI
gh workflow run). - Configure inputs: scope (patch/minor/major), provider (shell/mcp), dry_run (true/false).
- Click Run workflow.
Phase 2: Authorization & validation (automatic)
-
Trigger telemetry job:
- Validates actor is in
maintainersteam. - Blocks unauthorized attempts (workflow fails).
- Logs authorization attempt with reason.
- Validates actor is in
-
Lint & test jobs:
- Runs unified linting (
npm run lint). - Runs test suite (
npm test). - Both depend on successful authorization.
- Runs unified linting (
-
Changelog validation:
- Validates
CHANGELOG.mdschema. - Confirms unreleased section populated.
- Validates
Phase 3: Release agent execution (develop-first)
- Agent runs on
developbranch. - Validates readiness: VERSION + changelog schema + unreleased content.
- Creates
release/vX.Y.Zbranch fromdevelop. - Bumps
VERSIONfile. - Rolls
[Unreleased]section to[X.Y.Z] - YYYY-MM-DDinCHANGELOG.md. - Commits:
"chore: Release vX.Y.Z". - Creates PR #1:
release/vX.Y.Zβdevelop(changelog + version).- Title:
"chore: Release vX.Y.Z (changelog + version bump)" - Body: Link to this release process doc, version bump details.
- Title:
- Returns
release_versionandrelease_branchas workflow outputs.
Phase 4: Developer reviews PR #1 (develop)
- Open PR #1 in GitHub.
- Verify changelog entries and version bump.
- Approve and merge to
develop.
Phase 5: Agent creates PR #2 (stacked)
After PR #1 merges, agent automatically:
- Creates PR #2:
release/vX.Y.Zβmain(stacked on PR #1).- Title:
"release: vX.Y.Z" - Body: Compiled release notes (sections, highlights, breaking changes, contributors).
- Title:
- Creates annotated tag:
vX.Y.Z(signed if keys available). - Pushes tag to remote.
Phase 6: Developer reviews PR #2 (main)
- Open PR #2 in GitHub.
- Verify compiled release notes and tag.
- Approve and merge to
main. - GitHub automatically publishes Release from the tag.
Phase 7: Post-release sync (automatic)
After PR #2 merges:
post-release-syncworkflow runs.- Creates
chore/post-release-sync-main-to-developbranch frommain. - Merges
mainintodevelopto keep branches in sync. - Creates PR:
mainβdevelopfor developer review. - Developer merges to keep branches synchronized.
flowchart TD
accTitle: Post-release sync flow
accDescr: Automatic merge of main into develop after release, handling conflicts with manual PR if needed
A["PR #2 merges to main<br/>Release tagged & published"] -->|Trigger sync job| B["π post-release-sync<br/>Create branch from main"]
B -->|Attempt merge| C{Merge conflicts?}
C -->|No| D["β
Clean merge<br/>No conflicts"]
C -->|Yes| E["β οΈ Conflicts detected<br/>Manual intervention needed"]
D -->|Auto-merge| F["π PR: main β develop<br/>Merged automatically"]
F --> G["β
Sync complete<br/>Branches in sync"]
E -->|Create PR| H["π PR: main β develop<br/>Awaiting manual merge"]
H -->|Developer resolves<br/>& merges| G
style A fill:#fff9c4,color:#000,stroke:#000,stroke-width:2px
style B fill:#e0f2f1,color:#000,stroke:#000,stroke-width:2px
style C fill:#fff3e0,color:#000,stroke:#000,stroke-width:2px
style D fill:#c8e6c9,color:#000,stroke:#000,stroke-width:2px
style E fill:#ffe0b2,color:#000,stroke:#000,stroke-width:2px
style F fill:#c8e6c9,color:#000,stroke:#000,stroke-width:2px
style G fill:#c8e6c9,color:#000,stroke:#000,stroke-width:2px
style H fill:#ffccbc,color:#000,stroke:#000,stroke-width:2px
See ADR-003: Post-Release Sync Automation for detailed rationale.
- Format: Keep a Changelog.
- Schema:
../.schemas/changelog.schema.jsonenforced by:scripts/validation/validate-changelog.cjsscripts/agents/includes/changelogUtils.cjs --validate/--unreleased
- Requirements:
[Unreleased]section must exist and contain entries before release.- Sections allowed: Added, Changed, Deprecated, Removed, Fixed, Security, Documentation, Performance.
release.agent.js compiles notes using:
- Changelog sections (ordered).
- Highlights (prioritising Added/Changed/Security).
- Breaking changes callout.
- Contributors from merged PRs between previous tag and new tag.
- Full changelog compare link.
- Changelog validation fails: run
node scripts/validation/validate-changelog.cjs CHANGELOG.mdand fix schema violations/empty sections. - No unreleased changes: add entries under
[Unreleased]before running release agent. - PR not created: ensure
ghCLI andGITHUB_TOKENavailable; otherwise create PR fromrelease/vX.Y.Zβmainmanually. - Tag conflicts: delete or move existing tag before rerunning; ensure working tree clean.
If a release is started but must be rolled back:
- Delete the release branch (
release/vX.Y.Z) if it should not proceed. - Delete the tag locally and remotely:
git tag -d vX.Y.Zgit push origin :refs/tags/vX.Y.Z
- If a GitHub Release was created, remove it:
gh release delete vX.Y.Z --yes
- Restore
VERSIONandCHANGELOG.mdto the last known good commit ondevelop. - Re-run the workflow in
dry_runmode first to validate fixes before re-attempting a live release.
Rollback utility supports provider-aware cleanup:
node .github/scripts/workflows/release/rollback.cjs --version=X.Y.Z --provider=shell
node .github/scripts/workflows/release/rollback.cjs --version=X.Y.Z --provider=mcp --dry-runflowchart TD
accTitle: Rollback decision tree
accDescr: Diagnose release failure timing and decide rollback scope (metadata only, main only, or full)
A["π¨ Release problem detected<br/>When did it occur?"] -->|Before merge| B{PR #1 or PR #2<br/>merged yet?}
A -->|After release| C["Released code is broken<br/>Assess impact"]
B -->|No| D["β
Simple fix<br/>Delete release branch<br/>Fix code, retry"]
B -->|Yes| E{Which branch<br/>needs revert?}
E -->|"release metadata only"| F["πΎ Rollback: release_only<br/>Delete release + tag<br/>Code cleanup later"]
E -->|"main only"| G["πΎ Rollback: release_and_main<br/>Revert main commits<br/>Delete tag"]
E -->|"both branches"| H["πΎ Rollback: full<br/>Revert both branches<br/>Delete tag & release"]
C -->|"Minor bug"| I["β οΈ Hotfix approach<br/>Create fix PR to main<br/>Release vX.Y.Z+1"]
C -->|"Critical issue"| J["π¨ Emergency rollback<br/>See ADR-004 for scope"]
J --> K["Trigger rollback.cjs<br/>Provide scope & reason"]
F -->|Execute| L["Post-rollback:<br/>Analyze failure<br/>Update process"]
G -->|Execute| L
H -->|Execute| L
I -->|Execute| L
style A fill:#ffccbc,color:#000,stroke:#000,stroke-width:2px
style B fill:#fff3e0,color:#000,stroke:#000,stroke-width:2px
style C fill:#ffccbc,color:#000,stroke:#000,stroke-width:2px
style D fill:#c8e6c9,color:#000,stroke:#000,stroke-width:2px
style E fill:#fff3e0,color:#000,stroke:#000,stroke-width:2px
style F fill:#ffe0b2,color:#000,stroke:#000,stroke-width:2px
style G fill:#ffe0b2,color:#000,stroke:#000,stroke-width:2px
style H fill:#ffcdd2,color:#000,stroke:#000,stroke-width:2px
style I fill:#ffe0b2,color:#000,stroke:#000,stroke-width:2px
style J fill:#ffcdd2,color:#000,stroke:#000,stroke-width:2px
style K fill:#ffcdd2,color:#000,stroke:#000,stroke-width:2px
style L fill:#e0e0e0,color:#000,stroke:#000,stroke-width:2px
See ADR-004: Rollback & Error Handling Strategy for detailed rationale and rollback scopes.
Built by π§± LightSpeedWP with β, π, and open-source spirit!