This document defines the release contract for AtlassianPS PowerShell modules. Repository workflows implement the contract; this document does not duplicate their YAML.
- Release reviewed pull requests without asking contributors to choose a version.
- Keep versions, changelog entries, module metadata, tags, packages, and GitHub releases consistent.
- Build and validate the publishable artifact without release credentials.
- Publish the exact candidate produced by CI without checking out or executing repository code.
- Keep module repositories similar through shared Standards actions and reusable workflows.
- Every pull request declares exactly one
release:*intent before merge. - Only protected
mastercommits can become release candidates. - Only a workflow run triggered by the
atlassianps-release-botGitHub App can promote a prepared release commit. - The highest impact among unreleased merged pull requests determines the next version.
- The candidate commit contains the final changelog section and exact source manifest version.
- CI builds, stamps, packages, and validates one final candidate artifact.
- Promotion downloads the immutable candidate from the exact successful CI run.
- Promotion never rebuilds the module or runs repository scripts.
- The tag, PSGallery package, GitHub release, and website entry use the same version.
- Annotated
v*tags are immutable. - Publishing credentials never enter pull-request validation or candidate CI.
Use one v-prefixed three-part version wherever a release is identified.
| Artifact | Format | Example |
|---|---|---|
| Git tag | vX.Y.Z |
v3.0.0 |
| Changelog heading | ## vX.Y.Z - YYYY-MM-DD |
## v3.0.0 - 2026-05-10 |
| Module manifest | X.Y.Z |
ModuleVersion = '3.0.0' |
CHANGELOG.md is the only release-note source. The same version section populates the package manifest
and GitHub release body. The committed source manifest keeps ReleaseNotes empty.
Each pull request needs exactly one of:
release:nonerelease:patchrelease:minorrelease:major
A releasing pull request also needs either one changelog:* label or one reviewed fragment named:
.changelog/<pr-number>.<patch|minor|major>.<added|changed|fixed|removed|deprecated|security|breaking>.md
Fragment contents should be Markdown list items without a ### heading. Release preparation groups
them under standard release-note headings based on the filename type and consolidates duplicate
standard headings already present under ## Unreleased. Existing standard and custom sections keep
their first-seen order; fragment-only headings are appended in standard order.
release:none contributes no version impact or public note. It does not remove the merged code from a
future package and does not suppress other releasable changes in the same batch.
Use release:none for internal maintenance that does not justify publishing a new package by itself.
For an uncommon coordinated release, maintain the combined notes directly under ## Unreleased, keep
automatic release disabled while the held pull requests merge, and manually dispatch the final impact.
Do not manufacture extra patch releases solely to distribute workflow-pin or housekeeping changes.
Use pull_request_target only to inspect labels, changed-file metadata, and trusted base-branch action
code. Never check out or execute contributor code in that workflow.
Because title and body edits cannot change the validated inputs, release-intent workflows do not
subscribe to the edited activity type.
A major release is a separate product decision and requires fresh, explicit confirmation from a
human maintainer that names both the repository and intended major version.
A release:major label, breaking-change notes, a merged pull request, automatic version calculation,
or a general request to continue release work expresses intent but does not authorize the release.
Obtain that confirmation before merging a change with pending major-release intent, enabling
continuous delivery while major intent is pending, or manually dispatching release_impact: major.
Until confirmation is given, keep publication disabled and report the proposed major release instead.
Do not push a major release metadata commit, create or push its tag, or publish its package.
Release Intentvalidates labels and changelog input on the pull request.- The protected-branch CI gate validates the reviewed change.
- After merge, the serialized planner reconciles all merged pull requests since the latest stable tag.
- If every pending pull request uses
release:none, planning stops successfully. - Otherwise, the planner calculates the next version, folds changelog fragments, stamps the source
manifest version, and pushes one
Prepare vX.Y.Z releasemetadata commit with the release GitHub App. - The secretless CI build job stamps release notes into the built module, creates the GitHub archive,
validates them, and uploads
Release. - Every supported platform test consumes that same built module directory.
- After CI succeeds, the publishing job downloads GitHub's immutable
Releaseartifact from that exact run. The official download action verifies the artifact digest. - Without checking out the repository, the publisher creates the annotated tag, passes the candidate
module directory to
Publish-Module, creates the GitHub release from the candidate notes and archive, and notifies the website.
Shared CI and release jobs use explicit timeouts so a hung command cannot consume GitHub's six-hour default. Candidate and test-result artifacts are retained for 14 days, which leaves time for release promotion and failure analysis without keeping routine artifacts for the repository-wide default.
GitHub Actions events wake the planner; they are not release work items. GitHub concurrency can replace a pending event, so the planner must reconcile durable Git and pull-request history rather than rely on one event payload.
For a release metadata commit, Release must contain:
| Path | Purpose |
|---|---|
<ModuleName>/ |
Validated module directory published to PSGallery |
<ModuleName>.zip |
GitHub release asset |
release-notes.md |
GitHub release body |
The publishing job may use trusted pinned third-party actions and inline deployment commands. It must not
check out repository contents, invoke the repository build, load local actions, or modify the candidate.
Publish-Module packages the validated directory during publication; the pipeline does not prebuild a
.nupkg.
Use GitHub's failed-job rerun for transient failures while the original CI candidate remains available. Tag creation and PSGallery detection should remain idempotent so such reruns are safe.
For a lasting failure, fix the problem through a reviewed pull request and release the next version. Do not delete, move, or recreate an existing tag, and never republish an immutable PSGallery version. If PSGallery succeeded but a later step failed, a maintainer may repair the GitHub release or website entry manually.
There is no historical recovery_tag workflow or durable release state machine. The small maintainer
team accepts an unpublished version gap in exchange for a simpler pipeline.
Manual dispatch handles an unreleased bucket already on master; it is not a recovery mechanism.
- Run
Continuous Releasefrommaster. - Choose the highest required
release_impactfor the pending changes. - Let the normal metadata-commit, CI-candidate, and promotion flow finish.
The operator never enters the final version or source commit.
Required organization or repository variable:
ATLASSIANPS_RELEASE_APP_CLIENT_ID
Required organization or repository secrets:
ATLASSIANPS_RELEASE_APP_PRIVATE_KEY
PSGALLERY_API_KEY
HOMEPAGE_PAT
Configure a release environment restricted to master. The release GitHub App should have only the
repository permissions needed to push release metadata and create tags. Workflows mint short-lived
installation tokens with a SHA-pinned actions/create-github-app-token; they do not store installation
tokens or use personal access tokens for routine release writes.
Protect master with required CI Result, Release Intent, signed commits, review-thread resolution,
and CODEOWNERS review. Protect the refs/tags/v* namespace from creation, update, and deletion except by
the release App. Require full commit SHAs for Actions.
The publish job must authenticate github.event.workflow_run.actor.login as
atlassianps-release-bot[bot]. Commit author and committer names are metadata supplied by Git clients
and must not be used as the release identity boundary.
Keep module-domain behavior in these build tasks:
SetSourceVersionupdates the committed source manifest version.SetVersionstamps changelog-derived release notes into the built candidate.Packagecreates the release archive.VerifyReleaseArtifactvalidates the module version, release notes, and archive contents.
The shared Standards module_release.yml reusable workflow owns the complete prepare and publish
orchestration, including its event conditions. A module repository keeps only the trigger, immutable
Standards workflow pin, module name, and whether publication should notify the website.
name: Continuous Release
on:
workflow_run:
workflows: [CI]
types: [completed]
branches: [master]
workflow_dispatch:
inputs:
release_impact:
required: true
type: choice
options: [patch, minor, major]
permissions:
actions: read
contents: read
issues: read
pull-requests: read
jobs:
release:
uses: AtlassianPS/AtlassianPS.Standards/.github/workflows/module_release.yml@<standards-sha>
with:
module-name: ExampleModule
release-impact: ${{ inputs.release_impact }}
secrets: inheritThe caller and the reusable workflow's preparation job both need issues: read because GitHub
exposes pull request labels through the issues labels API.
The reusable workflow checks out its own implementation at job.workflow_sha during preparation, so
its composite actions and scripts come from the same immutable Standards commit as the workflow. The
publisher does not check out either repository.
Pin all Standards and third-party Actions references to 40-character commit SHAs. Downstream repositories must pin all Standards actions used by one workflow to the same released Standards commit.
Adopt this flow only after Standards completes a real automatic release.
For each module repository:
- Update the pinned Standards module and workflow commit together.
- Add the required labels and
Release Intentcheck. - Add candidate creation to CI and make
CI Resultrequire it. - Add the thin continuous-release caller pinned to the released Standards commit.
- Configure the release environment, App access, PSGallery key, and website token.
- Apply the branch, tag, signature, review, and Actions pinning rules.
- Remove older tag-triggered or rebuild-on-publish workflows.
- Run a shadow candidate build before enabling publication.
- Merge one small real patch and verify the complete release path.
Do not copy the release implementation into module repositories. Call the released reusable workflow at its immutable commit SHA.
Tests in each module repository should verify that:
- Standards dependencies and action pins agree;
- action references use immutable SHAs;
- pull-request validation never checks out contributor code;
- candidate CI contains no publishing credentials;
- candidate CI stamps, packages, validates, and uploads one immutable artifact;
- continuous release calls the shared workflow at the same immutable Standards commit;
- the source manifest keeps release notes empty;
- no parallel tag-triggered or recovery workflow remains.
Standards tests own the shared workflow's prepare/publish conditions, immutable artifact download, digest verification, checkout boundary, tag ordering, dependency installation, idempotency, and publication assertions.
Before merging release changes, run:
actionlint .github/workflows/ci.yml .github/workflows/release_intent.yml .github/workflows/continuous_release.yml .github/workflows/module_release.yml
git diff --checkInvoke-Build -Task Lint, Build, Test- Publishing a package rebuilt after CI.
- Making publishing secrets available to candidate build or test jobs.
- Checking out repository code in the publishing job.
- Treating a workflow event as the only record of pending release intent.
- Using
GITHUB_TOKENfor a metadata push that must trigger follow-up CI. - Using a personal token as the routine release identity.
- Creating lightweight, mutable, or unprotected release tags.
- Generating GitHub and PSGallery release notes from different sources.
- Adding recovery branches before the repository's operating model needs them.