diff --git a/src/docs/Capabilities/index.md b/src/docs/Capabilities/index.md index b3b57f5..98f7b4b 100644 --- a/src/docs/Capabilities/index.md +++ b/src/docs/Capabilities/index.md @@ -20,7 +20,7 @@ the same spec-and-design shape as any other capability. | Section | Description | | --- | --- | -| [Release Management](release-management/index.md) | How a source change becomes a versioned, immutable artifact, driven entirely on the GitHub platform. | +| [Release Management](release-management/index.md) | Durable version resolution, publication, recovery, withdrawal, and consumer update policy for immutable releases. | | [Repository Governance](repository-governance/index.md) | How every repository in an organization is classified, protected, and continuously reconciled against the controls its classification declares. | | [Dependency Updates](dependency-updates/index.md) | How a repository's pinned dependencies are kept current and secure through automated update pull requests. | | [Merge Automation](merge-automation/index.md) | How a pull request's required status checks become the machine-readable signal that drives automated approval and merge — green merges, red holds, nothing bypasses the gate. | diff --git a/src/docs/Capabilities/release-management/accept-moved-release-tags.md b/src/docs/Capabilities/release-management/accept-moved-release-tags.md new file mode 100644 index 0000000..92298d7 --- /dev/null +++ b/src/docs/Capabilities/release-management/accept-moved-release-tags.md @@ -0,0 +1,119 @@ +--- +title: Accept Moved Release Tags +description: Refresh an owned moving release alias locally and configure Git to keep it current. +--- + +# Accept moved release tags + +Use this guide when an MSX-owned producer intentionally publishes a moving Git +tag such as `v3` or `v3.4` and your local clone still resolves it to an older +release. + +Do not use this procedure for exact version tags such as `v3.4.2`. Exact version +tags are immutable. A moved exact tag is a producer integrity incident, not a +normal update. + +Use a mutable alias only when the consumer's artifact-specific pinning standard +permits it. The owned Action/reusable-workflow major-tag exception does not +authorize minor aliases; the examples here describe generic alias recovery, not +permission to relax that narrower standard. + +## Why an ordinary fetch can leave a stale tag + +Git protects existing local tags from being overwritten. An ordinary +`git fetch` can therefore update branches while silently leaving a moving tag at +its old object. Fetching all tags explicitly can instead report: + +```text +! [rejected] v3 -> v3 (would clobber existing tag) +``` + +Both outcomes leave the consumer on the previous release until it accepts the +producer-owned alias movement explicitly. + +## Refresh tags once + +Before this command, back up any needed local-only tags **outside `refs/tags/`** +and verify the backup. Renaming a local tag within that namespace does not +protect it: the renamed tag is still absent from the remote and will be pruned. +A Git bundle can preserve the original tag objects and reachable history: + +```bash +git bundle create owned-tags-backup.bundle --tags +git bundle verify owned-tags-backup.bundle +``` + +Keep that backup private and outside checked-in paths. Pruning removes local tags +that do not exist on the selected remote, and force permits that remote's tag +values to replace local values. Confirm that `origin` is the intended trusted +producer and that no exact-version-tag integrity incident is being normalized. + +```bash +git fetch --tags --force --prune --prune-tags origin +``` + +This refreshes moved aliases and removes tags deleted from the configured +`origin` remote. Review the fetch output before building or resolving a +dependency; never let an implicit upstream choose the source of this refresh. + +## Keep owned aliases current + +For a clone that should continuously accept the `origin` repository's controlled +tag aliases, inspect the current fetch refspec: + +```bash +git config --get-all remote.origin.fetch +``` + +If the output does not already contain `+refs/tags/*:refs/tags/*`, add it once: + +```bash +git config --local --add remote.origin.fetch '+refs/tags/*:refs/tags/*' +git config --local remote.origin.prune true +git config --local remote.origin.pruneTags true +``` + +The leading `+` allows the remote's tag namespace to update local tags +non-fast-forward. The prune settings remove local tag references no longer +present on `origin`. These settings are specific to that remote, not clone-wide +`fetch.prune` or `fetch.pruneTags` defaults that could affect another remote. +Use this configuration only when `origin` is inside the consumer's permitted +trust boundary and the producer owns the moving aliases. + +After configuration, a normal fetch from `origin` keeps branches and tags +aligned: + +```bash +git fetch --prune origin +``` + +## Verify an alias + +Compare the local tag object with the remote tag object: + +```bash +git rev-parse refs/tags/v3 +git ls-remote --refs origin refs/tags/v3 +``` + +The first hash must equal the hash at the start of the second command's output. +Repeat with the actual alias, such as `v3.4`. + +Then resolve the exact release and immutable source before use: + +```bash +git show --no-patch --decorate refs/tags/v3 +``` + +A CI job can perform the same comparison and fail when the local alias is stale. +Consumers outside the producer's trust boundary must use an exact immutable +version or commit SHA instead of configuring automatic tag movement. + +## Where this connects + +- [Release Management design](design.md#current-version-discovery-and-aliases) — + when an owned alias can move. +- [Publishing Targets](design-publishing-targets.md#github-releases) — GitHub + exact tags, moving aliases, and release records. +- [GitHub Actions](../../Coding-Standards/GitHub-Actions.md#pin-actions-according-to-ownership) — + when an owned major tag is permitted. diff --git a/src/docs/Capabilities/release-management/design-publishing-targets.md b/src/docs/Capabilities/release-management/design-publishing-targets.md index 0b96812..9c94a25 100644 --- a/src/docs/Capabilities/release-management/design-publishing-targets.md +++ b/src/docs/Capabilities/release-management/design-publishing-targets.md @@ -1,71 +1,308 @@ --- title: Publishing Targets -description: The contract every publishing destination documents, with GitHub Releases as the reference target. +description: Destination contracts for version mapping, prereleases, immutability, withdrawal, aliases, constraints, and release records. --- # Release Management — Publishing Targets -A **publishing target** is any destination that accepts a versioned artifact and serves it to consumers. The [release pipeline](design.md#the-pipeline) publishes to targets through one contract, so the process is the same whether a repository has one destination or five. +A **publishing target** accepts a versioned artifact or release record and makes +it available to consumers. The [release lifecycle](design.md#build-verify-and-publish) +passes every target the same frozen version, retained artifact, note envelope, +and release-intent identity. -This page holds the contract and the targets that satisfy it. It is the boundary that lets a new destination be added without touching the [spec](spec.md). +Targets differ in native version syntax, prerelease channels, deletion, and +consumer resolution. This page makes those differences explicit so adding a +destination does not change the [release-management specification](spec.md). -## The contract +## Publishing-target contract -A target is described by six answers. They are the questions the release process needs answered in order to publish safely, and they are the questions that differ between destinations: +Every target documents seven dimensions: | Dimension | What it settles | | --- | --- | -| **Version scheme** | the exact string form a version takes, and what the target accepts as valid | -| **Prerelease representation** | how a prerelease is expressed, and how the target sorts it relative to stable versions | -| **Immutability** | whether a published version can be replaced, and what happens on a repeated publish of the same version | -| **Unpublish** | whether a version can be withdrawn, what withdrawal does to existing consumers, and whether the version number becomes reusable | -| **Floating tags** | whether the target supports mutable pointers such as `latest`, and how they are moved | -| **Release record** | where the durable, linkable evidence of the release lives | +| **Version scheme** | The native coordinate and its one-to-one mapping from canonical SemVer. | +| **Prerelease representation** | How prerelease identity and ordering map to the target's syntax or channel. | +| **Immutability** | Which reference cannot change and what a repeated publication does. | +| **Withdrawal** | The strongest supported hide, unlist, yank, or delete operation and whether existing consumers retain access. | +| **Alias families** | Whether `latest`, major, or minor moving references can be represented and reconciled. | +| **Version constraints** | Whether native ranges or permitted bounded update selection express the bound, or a permitted producer-controlled alias is required. | +| **Release record** | Where the durable, linkable destination evidence is stored. | -A target MUST document all six before it is used. An undocumented dimension is a surprise waiting for the first failed release — most often around immutability, where publishing the same version twice is a success on one target and a hard error on another. +A target MUST answer all seven before it is enabled. It MUST also define: + +- how an idempotent retry verifies that an existing coordinate identifies the + recorded artifact; +- whether target-native state can be read back after publication; +- how canonical stable or prerelease state maps to target-native state; and +- which evidence proves publication or withdrawal. + +Distinct canonical releases MUST NOT map to one native coordinate. A collision +or stable/prerelease mismatch fails before publication. Target deletion never +frees a canonical version or prerelease identifier for reuse. ## Target summary -| Target | Version scheme | Prerelease | Immutable | Unpublish | Floating tags | Release record | -| --- | --- | --- | --- | --- | --- | --- | -| **GitHub Releases** | `vMAJOR.MINOR.PATCH` git tag | SemVer suffix, flagged as prerelease | tag and assets are treated as immutable | delete is possible; treated as exceptional | yes — git tags | the Release itself | -| **PowerShell Gallery** | `MAJOR.MINOR.PATCH` module version | SemVer suffix on the module version | yes — a version is published once | unlist only; the version is never reusable | no | the gallery listing | -| **VS Code Marketplace** | `MAJOR.MINOR.PATCH` extension version | separate prerelease channel on the same version line | yes | unpublish removes the extension version | channel acts as the pointer | the marketplace listing | -| **NuGet** | `MAJOR.MINOR.PATCH` package version | SemVer suffix on the package version | yes | unlist only; the version is never reusable | no | the package listing | -| **Container registry** | `:` plus a content digest | SemVer suffix in the tag | the **digest** is immutable; the tag is not | tag or manifest deletion | yes — mutable tags | the digest | +| Target | Version scheme | Prerelease | Immutable reference | Withdrawal | Alias families | Native constraints | Release record | +| --- | --- | --- | --- | --- | --- | --- | --- | +| **GitHub Releases** | Exact `vMAJOR.MINOR.PATCH` or prerelease tag | SemVer suffix and native prerelease flag | Protected exact tag, source commit, and asset digest | Delete or mark unavailable; coordinate remains reserved | `latest`, major, minor as optional Git tags; native Latest separately | No range resolver | GitHub Release and durable release intent | +| **PowerShell Gallery** | Numeric manifest core plus validated `PSData.Prerelease` | Two-field module/label mapping | Published module version | Unlist; downloads already held remain; version is not reusable | None | NuGet ranges through PSResourceGet; mapped manifest constraints | Gallery listing joined from GitHub Release | +| **NuGet** | `MAJOR.MINOR.PATCH` package version | SemVer suffix | Published package version and package hash | Unlist or deprecate; version is not reusable | None | NuGet version ranges | Package listing joined from GitHub Release | +| **VS Code Marketplace** | Marketplace-compatible extension version mapped from canonical SemVer | Separate prerelease channel and flag | Published extension version | Unpublish where permitted; version is not reusable | Native stable/prerelease channel selection, not release aliases | No consumer-selected SemVer range | Marketplace listing joined from GitHub Release | +| **Container registry** | Version tag plus content digest | SemVer suffix in exact tag | Manifest or image digest | Delete tag or manifest where permitted; reservation remains | `latest`, major, minor as optional tags | No range resolver | Registry digest joined from GitHub Release | + +Two rules apply to every row: + +- **Coordinates are single-use.** Withdrawal changes availability or + recommendation, not ownership. A corrected artifact always receives a new + version. +- **Completion is an outcome, not a claim of transactional publication.** One + target may expose content before another fails. Durable per-target progress + keeps the release incomplete until all required targets succeed, then lets a + retry resume safely. + +## GitHub Releases + +GitHub Releases is the reference target and the cross-target join point. Every +repository governed by this capability creates a GitHub Release; a repository +with no external artifact publishes there only. External target coordinates and +evidence are linked from the same release record. + +### GitHub version and prerelease mapping + +Stable versions use an exact tag: + +```text +vMAJOR.MINOR.PATCH +``` + +Prereleases preserve the canonical suffix: + +```text +vMAJOR.MINOR.PATCH-series.N +``` + +The Release's native prerelease flag MUST agree with the canonical version. +GitHub's native **Latest** selection is advertising state, not release authority. +It is updated only after durable completion and MUST NOT include prereleases or +withdrawn releases. + +### GitHub immutability + +An exact version tag identifies the fixed source revision. Assets are uploaded +from the retained artifact and verified by fingerprint. A repeated publish is +successful only when the existing tag, assets, and release record match the +frozen release intent. + +Repositories enable GitHub's immutable-release protection where the platform +supports it. Verify the effective repository setting and producer controls from +read-back evidence rather than assuming they exist because this contract +describes them. Policy alone is not proof of platform enforcement; missing +controls are conformance gaps to record and resolve. + +### GitHub withdrawal + +GitHub permits a Release and tag to be deleted, but deletion does not erase +clones, caches, downloads, or durable lifecycle history. The withdrawal record +therefore remains authoritative and the version remains permanently reserved. +Deletion is used only where the approved withdrawal operation and repository +policy require it. + +Replaying a withdrawn intent does not recreate the Release or tag. Current +discovery and native Latest are reconciled to the greatest remaining eligible +completed stable release. + +### GitHub aliases and constraints + +Optional Git tags represent the closed `latest`, major, and minor alias families. +They are separate from exact version tags and can move only through +[alias reconciliation](design.md#current-version-discovery-and-aliases). +Consumers must explicitly accept moved owned tags; see +[Accept moved release tags](accept-moved-release-tags.md). + +Git reference fields do not resolve SemVer range expressions. Permitted bounded +update selection can choose an immutable commit within the requested major or +minor boundary without a producer alias. The corresponding permitted alias is +required only when no other permitted resolver supplies the bound. If no +permitted resolver or alias can express it, resolution fails as unsupported. +The consumer's artifact-specific pinning standard still governs the retained +reference. External consumers use an exact commit SHA or another immutable +fingerprint rather than a moving tag. + +### GitHub release record + +The GitHub Release contains the complete captured contributor-authored title and +description, publication envelope, fixed source, and links to every destination +coordinate. The single announcement boundary separates the leading shareable +summary, not the published record. The notes themselves carry all applicable +consumer adoption actions or an explicit no-action result; another repository +migration guide or changelog never supplies missing instructions. The durable release +intent remains the authority for lifecycle state, including partial publication, +retirement, withdrawal, alias reconciliation, and announcements. + +## PowerShell Gallery + +### PowerShell Gallery version and prerelease mapping + +The manifest's `ModuleVersion` is a numeric three-part `System.Version`, not a +SemVer prerelease string. For a prerelease, put its supported label in +`PrivateData.PSData.Prerelease`; the Gallery combines the numeric core and label +into its native package coordinate. + +For example, canonical `2.5.0-alpha` maps to `ModuleVersion = '2.5.0'` and +`PrivateData.PSData.Prerelease = 'alpha'`, not `ModuleVersion = '2.5.0-alpha'`. +The adapter validates the label against the selected publishing client's actual +syntax; dotted or build-metadata forms are not assumed to be accepted. A +non-preserving conversion requires an explicit collision-free, ordering-aware +mapping or fails as unsupported before publication. Record both canonical and +native identities. See [PowerShell prerelease module fields](https://learn.microsoft.com/en-us/powershell/gallery/concepts/module-prerelease-support). + +### PowerShell Gallery immutability and withdrawal + +A published module version is immutable. Retrying succeeds only when the +existing listing and package hash identify the recorded artifact. The gallery +supports unlisting rather than reclaiming a version; existing consumers and +caches can still hold it, and the coordinate remains permanently reserved. + +### PowerShell Gallery aliases and constraints + +The gallery does not offer producer-controlled moving release aliases. +PSResourceGet accepts NuGet version ranges, and module manifests map supported +bounds into their native fields. Consumers follow +[PowerShell Version Constraints](../../Coding-Standards/PowerShell/Version-Constraints.md) +rather than placing a range-like string in a field that accepts only one +version. + +### PowerShell Gallery release record + +The gallery listing and package hash are recorded as destination evidence. The +GitHub Release links the gallery coordinate to the canonical source, complete notes, and +cross-target release intent. + +## NuGet + +### NuGet version and prerelease mapping + +The package version uses canonical SemVer without a leading `v`, including its +prerelease suffix. The adapter rejects any normalization or server behavior that +would make two canonical identifiers address one native package version. + +### NuGet immutability and withdrawal + +A published package version is immutable and single-use. An idempotent retry +checks the existing package hash. NuGet unlisting or deprecation changes +discovery and guidance but does not remove packages already restored, and the +version remains reserved. + +### NuGet aliases and constraints + +NuGet has no producer-controlled moving alias for package versions. Consumers +express supported movement with native +[NuGet version ranges](../../Coding-Standards/PowerShell/Version-Constraints.md). +An exact package version remains the immutable release coordinate. + +### NuGet release record + +The package listing, content hash, and withdrawal or deprecation state are +destination evidence. The GitHub Release is the human-readable join point and +preserves the complete consumer record. + +## VS Code Marketplace + +The [VS Code Extension Framework design](../vscode-extension-framework/design.md) +owns the detailed VSIX and marketplace behavior. + +### VS Code Marketplace version and prerelease mapping + +The owning framework declares and verifies stable and prerelease mappings +together before enabling Marketplace publication. The Marketplace accepts a +numeric three-part extension version and a separate prerelease channel/flag, +not an arbitrary canonical prerelease suffix. There is no assumed direct stable +mapping plus an independently invented prerelease mapping. + +An odd-minor convention is valid only when the declared stable mapping reserves +that space and the series/counter allocation is proven collision-free across +every supported canonical identity. Record canonical and native versions +together; an unsupported mapping or coordinate collision blocks publication. +The [owning framework's mapping contract](../vscode-extension-framework/design.md#marketplace-version-mapping) +controls that conversion, while the immutable GitHub VSIX install path remains +available without Marketplace support. + +### VS Code Marketplace immutability and withdrawal + +A published extension version is immutable and single-use. Marketplace +unpublication is applied where permitted, but cannot recall installed VSIX +files or make the canonical version reusable. + +### VS Code Marketplace aliases and constraints + +Stable and prerelease marketplace channels influence update discovery but are +not the release-management `latest`, major, or minor alias families. The +marketplace does not expose consumer-selected SemVer ranges for extension +updates. Consumers that need an exact immutable artifact use the VSIX attached +to the GitHub Release. + +### VS Code Marketplace release record + +The marketplace listing and native channel state are destination evidence. The +GitHub Release always contains the same VSIX and joins the canonical release to +optional Marketplace or Open VSX publication. + +## Container registries + +### Container registry version and prerelease mapping + +An exact canonical version maps to an image tag, while the published manifest or +image digest identifies immutable content. Prerelease suffixes remain part of +the exact version tag. -Two patterns run through the table and shape how consumers are told to pin: +### Container registry immutability and withdrawal -- **Version numbers are single-use.** On every target above, a published version number is spent. Withdrawal removes availability, not the reservation. A fix is therefore always a new version — never a re-publish of the old one, which is the same conclusion the pipeline reaches from [build-once](design.md#the-pipeline). -- **Only content addresses are truly immutable.** Where a target offers both a name and a digest, the digest is the reference and the name is the convenience. +Registry tags are mutable; digests are not. Publication records both and treats +the digest as artifact identity. An idempotent retry succeeds only when the +exact version tag resolves to the recorded digest. Deleting a tag or manifest +does not free the canonical release version or prove that cached content +disappeared. -## GitHub Releases — the reference target +### Container registry aliases and constraints -GitHub Releases is the reference implementation: every repository governed by this capability publishes there, and a repository with no external artifact publishes there *only*. A target-specific concern is described relative to this one. +Optional tags can implement the `latest`, major, and minor alias families. +Reconciliation updates each enabled tag to the digest of the greatest eligible +matching release. Container references do not evaluate SemVer ranges, so +permitted bounded update selection resolves the eligible version outside that +field and can retain its digest without a moving alias. A corresponding +controlled alias is required only when no other permitted resolver supplies +the requested bound. That alias must satisfy the consumer's trust boundary and +artifact-specific pinning standard; an unsupported bound fails rather than +widening silently. -- **Version scheme.** A git tag `vMAJOR.MINOR.PATCH` on the release-branch commit. The tag is the artifact for Action, workflow, and source-distributed module repositories. -- **Prerelease.** The SemVer prerelease suffix, with the Release marked as a prerelease so it is excluded from *latest*. -- **Immutability.** The tag points at one commit and is not moved. Assets are uploaded once. A published version is never rewritten in place. -- **Unpublish.** A Release and its tag can be deleted, but doing so breaks consumers that resolved it, so it is reserved for a release that must not exist — a leaked secret, a legal removal — and the version number is not reused. -- **Floating tags.** Supported as additional git tags, subject to the [floating-tag rules](design.md#floating-tags). -- **Release record.** The Release itself: the version as its name, the release note as its body, and the immutable reference to whatever was published elsewhere. +### Container registry release record -Because every release produces a GitHub Release, it is also the **join point** across targets: a release published to a registry or marketplace records its reference there, so one link answers *what shipped, in what version, and where it went*. +The registry digest and exact tag are destination evidence. The GitHub Release +links that digest to source, notes, verification, and lifecycle state. ## Adding a target -1. Document the six contract dimensions above, in the summary table. -2. Confirm the target's immutability and prerelease behaviour are compatible with [SemVer](https://semver.org/) ordering. Where the target's native convention differs, the mapping is stated rather than assumed. -3. Add the publish step. It receives the already-built artifact and the - already-resolved version, and it MUST be idempotent: publishing a version the - target already holds is a success only when its immutable identity matches the - artifact being retried. A different artifact at the same version is an error. -4. Include the target in the [all-or-nothing](design.md#publishing-targets) set, so a version cannot be present on some destinations and absent from others. +1. Document all seven contract dimensions and add the target to the summary. +2. Define a total, collision-free mapping from canonical stable and prerelease + versions to native coordinates and flags. +3. Define the immutable identity and read-back check used by idempotent retry. +4. Define the strongest supported withdrawal behavior and its limitations. +5. State which native constraints, permitted bounded update selection, or + permitted alias families consumers can use. +6. Define publication, withdrawal, and reconciliation evidence. +7. Add the adapter to the required destination set so durable completion and + phase-aware recovery include it. -The spec does not change. That is the purpose of the contract. +The target receives an already built and verified artifact. It does not choose a +version, rebuild content, rewrite the note snapshot, or mark the overall release +complete by itself. ## Where this connects -- [Spec](spec.md) — the requirements this design serves. -- [Design](design.md) — the pipeline that publishes to these targets. -- [Security](../../Coding-Standards/Security.md#supply-chain) — why consumers pin to immutable references. +- [Spec](spec.md) — the normative release and target requirements. +- [Design](design.md) — durable state, publication, recovery, discovery, and + consumer policy. +- [Accept moved release tags](accept-moved-release-tags.md) — local Git + configuration for consumers of owned aliases. +- [Security](../../Coding-Standards/Security.md#supply-chain) — immutable + references and supply-chain controls. diff --git a/src/docs/Capabilities/release-management/design.md b/src/docs/Capabilities/release-management/design.md index c5c5bc5..23d25ad 100644 --- a/src/docs/Capabilities/release-management/design.md +++ b/src/docs/Capabilities/release-management/design.md @@ -1,123 +1,197 @@ --- title: Design -description: How release management is built — effective defaults, owned overrides, keyed release types, source-bound notes, and build-once publication. +description: Durable release intents, lifecycle transitions, recovery, withdrawal, aliases, announcements, and producer conformance. --- # Release Management — Design -The behavior in the [spec](spec.md) is delivered by shared release mechanics, -through a reusable workflow or a native producer pipeline. A repository's -effective release pipeline establishes which target routes are managed. The -release solution's optional `.github/release.settings.yml` file refines those settings; it does not -create a release pipeline, and its absence does not opt a managed route out. -Documented defaults make a managed route usable without per-PR bump labels; -repository settings, supported caller inputs, and owned labels provide overrides. +This page realizes the [release-management specification](spec.md) through +shared, GitHub-native release mechanics, including native producer pipelines. +It defines the durable lifecycle and input contract; the immutable producer +revision's documented interface and verified behavior establish conformance. +Documentation alone does not make a lifecycle control or setting available. + +## Design principles + +1. **The release intent is authoritative.** Tags, releases, workflow runs, and + destination listings are observations that must agree with durable state. +2. **Resolve once, then resume.** A retry continues frozen work; it never turns + current repository metadata into a different release under the same identity. +3. **Build once.** Verification and every destination receive the same retained + bytes. +4. **Complete everywhere before advertising anywhere.** Current discovery, + stable aliases, and completion announcements follow durable completion. +5. **Roll forward.** Failure, retirement, and withdrawal preserve history and + reservations. Corrections use a new approved version. +6. **Consumers choose policy within a trust boundary.** Producers expose only + the controlled references required to implement that policy. ## Applicability -Inspect the effective pipeline for the pull request's target route before -reading release settings or labels. A managed route produces a versioned, -immutable output that another system consumes by version. - -- When a shared workflow or native producer pipeline delivers that release - behavior, evaluate its release decision and require the pre-merge check. This - remains true when `.github/release.settings.yml` is absent or the route does not - call a named shared workflow. -- When the route has no versioned release behavior, Release Management does not - apply. Omit the `Release decisions` block and every `release:*` label, - including `release:skip`. A site deployment without a versioned artifact is - not by itself a release-managed route. - -Configuration can change branch types, paths, a default bump, and other release -behavior only after the effective pipeline establishes that the route is managed. - -Inspect the immutable producer revision's actual settings and label mapping. -Legacy or differing interfaces require a verified adapter or producer/configuration -migration before the canonical signals are used. Keep exact adoption instructions -in that producer's PR/release record. A missing mapping blocks finalization and -decision validation; it does not opt the route out or turn an ignored signal into -a successful skip. - -## Branching model - -A **release branch** is a branch assigned to one key in the `ReleaseType` -configuration object. The fixed keys are `Stable`, `Prerelease`, and -`ReleaseCandidate`; each key owns the properties for that release type, -including its `Branch`. These are routing types, not additional publication -modes: `Prerelease` and `ReleaseCandidate` both publish prereleases. - -- **Single branch (zero-config).** `ReleaseType.Stable.Branch` always resolves: - when `ReleaseType` or its `Stable` key is omitted, it uses the default branch. - `Prerelease` and `ReleaseCandidate` are optional. Prereleases remain available - from an open pull request through the mode label. -- **Multi-branch.** A full promotion path uses - `ReleaseType.Prerelease.Branch` for early testing, - `ReleaseType.ReleaseCandidate.Branch` for the promotable candidate, and - `ReleaseType.Stable.Branch` for production. Each configured branch publishes - only its keyed release type. Promotion to `ReleaseType.Stable.Branch` computes - from the latest stable release plus the merge pull request's resolved bump; - prerelease and release-candidate counters do not carry over. -- **One branch per routing type.** Each release-type key has at most one `Branch`, and - the same branch MUST NOT appear under more than one key. Unknown keys and - duplicate branch assignments are rejected after resolving inherited defaults. - The inherited default branch counts as the `Stable` assignment even when - `ReleaseType` or `Stable` is omitted; assigning that branch to `Prerelease` - or `ReleaseCandidate` is invalid. -- **One production authority.** `Stable.Branch` is the only branch that cuts a - stable release. A prerelease or release-candidate branch can never publish the - production version. -- **Bundled releases.** A **staging branch** collects feature PRs; merging it to - a release branch produces **exactly one** release for all bundled changes. +The effective pipeline establishes whether the target route produces a +versioned, immutable output consumed by version. Managed routes participate +whether they use a shared workflow or a native producer pipeline, and whether +`.github/release.settings.yml` exists. Unmanaged routes omit release metadata and +every `release:*` label; a Pages deployment alone is not a versioned release. -```yaml -# .github/release.settings.yml -DefaultBump: patch -ReleaseType: - Stable: - Branch: main - Prerelease: - Branch: dev - ReleaseCandidate: - Branch: rc -``` +## Release authority and interfaces {#branching-model} -## The pipeline +Release branches are keyed by `ReleaseType.Stable`, `ReleaseType.Prerelease`, +and `ReleaseType.ReleaseCandidate`, each with at most one `Branch`. These are +routing types, not additional publication modes: prerelease and release-candidate +routes both publish prereleases. -Every release runs the same four stages in order. The stage boundaries exist to -make **build-once** enforceable — each stage may only consume what the previous -stage produced. +The stable branch inherits the repository default branch when `ReleaseType` or +its `Stable` key is omitted; the other keys are optional. Validate unknown keys +and duplicate branch assignments after resolving inherited defaults. A branch +cannot belong to two types, so assigning the inherited stable branch to +`Prerelease` or `ReleaseCandidate` is invalid. -```mermaid -flowchart LR - resolve["Resolve
version decided"] --> build["Build
artifact created once"] - build --> test["Test
same artifact validated"] - test --> publish["Publish
same artifact released"] -``` +Exactly one configured stable line owns stable publication. A repository can +develop on several branches, but only a merge or approved manual request whose +fixed source is on that line can create a stable release. -| Stage | Produces | Invariant | +| Interface | Purpose | Release authority | | --- | --- | --- | -| **Resolve** | the version | the version is known before anything is built, so it can be baked in | -| **Build** | the artifact | the artifact is created exactly **once**, carrying its version | -| **Test** | a verdict | validation runs against the built artifact, not a rebuild of its source | -| **Publish** | released versions | the artifact is transferred unchanged to every target | - -Two consequences follow, and they are the point of the model: - -- **The version is identity, not metadata.** Because Resolve precedes Build, the - version is embedded in the artifact rather than attached to it. A manifest - version, an image label, and the tag agree because they came from one decision. -- **Recovery preserves artifact identity.** Retrying validation or publication of - an unchanged, already-built artifact reuses that artifact and its resolved - version. A correction that changes the output is a new release: it resolves a - new version and builds new bytes. An artifact is never patched, re-tagged, or - rebuilt under an existing version — that would publish something other than what - was tested. +| Pull-request decision check | Validates the owned release decision before merge. | None; it validates intent. | +| Merge to the stable line | Normal request for a release of the accumulated range. | Reviewed pull request and branch policy. | +| Direct push to the stable line | Validates source and leaves the change pending. | None; it never inherits `DefaultBump`. | +| Manual release request | Releases an explicit stable-line source and accumulated range. | Explicit bump, complete notes, and approval for that range. | +| Retry request | Resumes one durable release intent. | The original frozen approval and intent. | +| Retirement request | Ends recovery of one incomplete intent. | Explicit maintainer authorization. | +| Withdrawal request | Makes one completed release ineligible. | Explicit maintainer authorization. | +| Announcement retry | Redelivers an unacknowledged completion message. | The completed release record. | + +The merge path remains the default because it keeps the compatibility decision, +consumer evidence, source review, and release-note source together. Manual +release exists for approved accumulated change, not as a way around review. + +The [Branching and Merging](../../Ways-of-Working/Branching-and-Merging.md) +standard owns standing development-to-stable promotion. Release management +starts when reviewed source reaches the configured stable line; it does not +duplicate the promotion process. + +## Durable release intent + +Resolve creates or loads one durable release intent before Build. Its stable +identity makes duplicate delivery and retries idempotent. + +The identity includes the separately approved logical request, not only its +source commit. Duplicate delivery or retry of that request loads the same +frozen intent. A new corrective request requires distinct approval/request +identity and explicitly records which prior intent it replaces; it cannot reuse +the retired or withdrawn version, even when its reviewed source is unchanged. + +| Field | Frozen or progressive | Purpose | +| --- | --- | --- | +| Intent identity and idempotency key | Frozen | Identifies one logical release independently of a workflow run. | +| Trigger and approval evidence | Frozen | Shows who or what authorized the release and included range. | +| Stable line, fixed source, and range baseline | Frozen | Defines the exact source history being released. | +| Aggregate decision and its source | Frozen | Records major, minor, patch, or skip and the owned label, caller input, repository setting, or shared default that supplied it. | +| Version-computation base and consumer-change baseline | Frozen | Distinguishes the actual calculation base from the verified successful artifact-publication source for each product, target, and channel against which the consumer delta is described. | +| Canonical version | Frozen after Resolve | Identifies the coordinate selected for this intent. | +| Reservation state | Progressive, append-only | Starts provisional and becomes permanently reserved after successful build or external visibility; later events never free it for different content. | +| Release-note snapshot and provenance | Frozen | Preserves the reviewed contributor-authored account. | +| Required destinations and announcement routes | Frozen | Prevents configuration drift during recovery. | +| Lifecycle stage and failure detail | Progressive | Records the last successful boundary and actionable failure. | +| Artifact location and cryptographic fingerprint | Frozen after Build | Binds verification and publication to identical bytes. | +| Verification evidence | Progressive, append-only | Proves which retained artifact passed which checks. | +| Publication progress by destination | Progressive, append-only | Supports idempotent multi-target resume. | +| Alias reconciliation progress | Progressive, append-only | Records advertising after completion or withdrawal. | +| Announcement progress by destination | Progressive, append-only | Supports at-least-once delivery without republishing. | +| Retirement or withdrawal evidence | Append-only | Preserves a terminal operator decision without rewriting history. | + +The durable store can be implemented with any shared platform that provides +atomic writes, immutable history, and lookup by repository and intent. A +workflow run summary is useful evidence but is not sufficient storage: runs can +expire, retries use new run identifiers, and one run cannot safely coordinate +all later lifecycle operations. + +### Lifecycle states -## Version computation +```mermaid +stateDiagram-v2 + [*] --> Resolved + Resolved --> Built + Built --> Verified + Verified --> Publishing + Publishing --> Publishing: Record one destination + Publishing --> Complete: All required destinations recorded + Resolved --> Resolved: Build attempt failed + Built --> Built: Verification or recovery failed + Verified --> Verified: Publication attempt failed + Publishing --> Publishing: Publication attempt failed + Resolved --> Superseded: No successful build or external visibility + Resolved --> Retired: Externally visible and explicitly unrecoverable + Built --> Retired: Explicitly unrecoverable + Verified --> Retired: Explicitly unrecoverable + Publishing --> Retired: Explicitly unrecoverable + Complete --> Withdrawn: Explicit withdrawal +``` -For PR-driven releases on a managed route, the shared resolver reads the owned -labels and effective settings. It records the chosen bump, mode, and the input -that supplied each decision before Build. +A failed attempt does not create a second logical state machine. It records the +failure against the last successful lifecycle boundary. `Superseded` applies +only to work that never produced a successful build and never exposed a release +coordinate. `Retired` preserves a permanently reserved coordinate for an +incomplete release. `Withdrawn` preserves completion while changing eligibility. + +`Resolved → Retired` requires explicit maintainer authorization and a permanent +reservation established by external visibility; untouched unbuilt and unexposed +work follows the guarded supersession path instead. Reservation transitions are +append-only evidence, not mutations of the frozen canonical version. + +## Resolve + +Resolve is the only stage that selects source, scope, decision, version, note +snapshot, destinations, and announcement routes. Every later stage consumes +those fields. + +### Identify the accumulated range + +For a new stable request: + +1. Find the source revision of the greatest completed stable release that is an + ancestor of the fixed source. If no completed stable release exists, use the + beginning of repository history. +2. Enumerate every source change after that baseline through the fixed source. +3. Associate reviewed pull requests and their owned release decisions where + possible. Keep skipped changes in the range. +4. Calculate the highest known increment in the range: major, then minor, then + patch. +5. Require one reviewed aggregate decision that is no lower than that minimum + and explicitly covers any skipped or direct change whose compatibility impact + is otherwise unknown. +6. Freeze the full range and account for every source revision in this or an + earlier durable release intent. + +The current merged pull request normally supplies the aggregate approval. If +unreviewed direct changes make that approval incomplete, Resolve stops and +requires a manual release request covering the range. `DefaultBump` applies only +when the current associated pull request has no explicit decision and the range +otherwise has complete reviewed evidence. + +An unbuilt, unexposed pending request can be coalesced into a later approved +range and marked `Superseded`. A built or externally visible request cannot be +coalesced; it must complete or be retired before later work advances. + +### Resolve the owned instruction set + +Owned labels are the contributor-facing vocabulary. Their exact definitions and +conflicts live in [Automation Labels](../../Ways-of-Working/Automation-Labels.md). + +| Marker | Resolve effect | +| --- | --- | +| `release:major` | Selects a major aggregate increment. | +| `release:minor` | Selects a minor aggregate increment. | +| `release:patch` | Selects a patch aggregate increment. | +| `release:skip` | Validates and accumulates the change without immediate publication. | +| `release:prerelease` | Publishes an ordinary prerelease series using the resolved bump. | + +One owned bump label overrides the valid effective `DefaultBump`; without one, +that default supplies the level. Multiple bump labels and skip with any other +owned release label fail. Prerelease mode uses the resolved bump, not another +level. Release-candidate routing comes from `ReleaseType.ReleaseCandidate`; +announcement behavior comes from supported settings, not additional labels. ### Settings and caller overrides @@ -128,410 +202,521 @@ A filename convention does not implement a reader or rename its public input. Ve [Module settings](../../Initiatives/PSModule.md#module-settings) retain their supported initiative interface; the release-solution filename does not replace it. Follow [Smart defaults, local overrides](../../Ways-of-Working/Principles/Software-Design.md#smart-defaults-local-overrides): -shared defaults are overridden by repository settings in the optional -`.github/release.settings.yml`, then by explicitly supplied supported caller -inputs for that invocation. Omitted caller inputs preserve repository settings; -they do not overwrite them with an input's default. The implementing workflow -or action documents its exact supported caller surface and validates it through -the same settings resolver rather than maintaining another algorithm. +shared defaults are overridden by repository settings, then by explicitly +supplied supported caller inputs for the invocation. Omitted caller inputs +preserve repository settings; they do not reset them to an input's default. `DefaultBump` accepts exactly `patch`, `minor`, or `major`. Its documented shared -default is `patch`; omitting the configuration file or field inherits that value. -A caller can deliberately select another supported level. Unknown setting names, -null or empty values, and values outside the documented set fail validation, -even when a narrower override or bump label exists. +default is `patch`, including when the configuration file or field is omitted. +Validate every supplied setting before resolving labels: unknown names, null or +empty values, and invalid values fail even when a narrower override or explicit +bump would supply a valid level. Bare and unrelated labels do not participate. + +Verify the immutable producer revision's supported settings and owned-label +mapping. A differing released interface requires a verified adapter or +producer/configuration migration before canonical signals are used. Exact +adoption instructions stay in that producer's PR/release record. Missing +support blocks finalization and decision validation rather than becoming a +successful ignored skip or opting the managed route out. + +| PR input | Effective default and source | Result | +| --- | --- | --- | +| No owned bump label | No repository/caller override; shared `patch` | Patch; shared default recorded, no per-PR bump label required. | +| No owned bump label | Repository `major`; explicit caller `minor` | Minor; caller overrides repository policy. | +| `release:major` | Valid effective `patch` | Major; explicit label overrides the default. | +| `release:prerelease` alone | Valid effective `minor` | Minor in prerelease mode. | +| `release:skip` alone | Valid settings | No release; validation still runs. | +| Multiple bumps, or skip with another owned label | Any | Fail: conflicting owned signals. | +| Any, including explicit bump or skip | Invalid supplied setting | Fail: no fallback or masking by another input. | + +The required pull-request check recomputes when source, owned markers, release +settings, supported caller inputs, or the effective pipeline change. It publishes +one named required result and +fails closed for missing, conflicting, invalid, stale, or unsupported input. + +### Required pre-merge decision check {#required-pre-merge-decision-check} + +The decision check is the release resolver in validation mode. It validates the +current pull-request decision, valid effective default, modifier conflicts, source +scope, and whether the known accumulated range can be approved by this request. +It never publishes or reserves a version. Branch policy requires its exact +check name, so absent and stale results block merge as well as explicit failure. + +### Resolve the stable version {#version-computation} + +Version resolution starts from `0.0.0` when there is no stable history. Otherwise +it finds the greatest SemVer among: + +- completed stable releases; and +- permanently reserved stable versions from built, visible, retired, or + withdrawn intents. + +Resolve applies the aggregate increment to that baseline. For example, a first +patch release is `0.0.1`, a first minor release is `0.1.0`, and a deliberate +first stable major release is `1.0.0`. + +The resolved version is persisted as a provisional reservation before Build. +The reservation becomes permanent after the first successful build or any +external visibility. This separates safe coalescing of untouched work from the +non-negotiable rule that one visible or built coordinate always means one source +and one artifact. + +### Resolve prereleases and release candidates + +Configured prerelease and release-candidate branches publish their assigned +series on pushes. An open pull request carrying `release:prerelease` can publish +an ordinary prerelease using one explicit owned bump label, or the valid +effective `DefaultBump` when that label is absent. Neither route publishes a +stable version or advances stable discovery or aliases. + +A prerelease applies its aggregate increment to the stable baseline and permanent +stable reservations, then appends a native identifier that sorts before the +normal version: + +```text +-. +``` + +A prerelease branch uses its validated branch-name series identifier; an +open-pull-request prerelease uses its normalized source-branch name. A dedicated +release candidate always uses `rc`: -| Setting | Shared default | Repository override | Supported caller override | -| --- | --- | --- | --- | -| `DefaultBump` | `patch` | `DefaultBump` in `.github/release.settings.yml` | Explicitly supplied caller override supported and documented by the invoked producer for this setting. | +```text +2.5.0-rc.1 +2.5.0-rc.2 +2.5.0-rc.3 +``` -For example, a repository can select minor as its normal level without requiring -a minor label on every publishing PR: +The counter is the next unreserved numeric value for that core and series. It is +stored unpadded so SemVer numeric ordering remains correct. The normal core +`2.5.0` is not consumed by any prerelease, but every published prerelease +identifier remains bound to its original content even after cleanup. -```yaml -# .github/release.settings.yml -DefaultBump: minor +Promotion to stable uses the [stable version resolver](#version-computation); +neither prerelease nor release-candidate counters carry over. + +Each publishing target validates its native prerelease flag against the +canonical version. A stable canonical version cannot be sent through a native +prerelease channel, and a canonical prerelease cannot be represented as stable. + +## Build, Verify, and Publish + +The lifecycle retains the familiar four-stage pipeline: + +```text +Resolve -> Build -> Verify -> Publish ``` -| Label | Meaning | Valid combination | -| --- | --- | --- | -| `release:patch` | Resolve the next patch version, overriding the effective default. | Alone or with `release:prerelease`. | -| `release:minor` | Resolve the next minor version, overriding the effective default. | Alone or with `release:prerelease`. | -| `release:major` | Resolve the next major version, overriding the effective default. | Alone or with `release:prerelease`. | -| `release:prerelease` | Publish the open pull request as a prerelease using the resolved bump. | With one explicit bump or the effective default; never with `release:skip`. | -| `release:skip` | Run validation without resolving or publishing a version. | Alone. | - -Resolve the decision in this order: - -1. Validate supplied settings and the effective configuration, then reject - conflicting owned labels. An invalid setting is an error even when another - input would supply a valid level. Bare and unrelated labels do not participate. -2. Honor a valid `release:skip` as the explicit no-release decision and stop bump - resolution. -3. Use the single owned bump label when present; otherwise use the valid - effective `DefaultBump`. Record whether the label, caller input, repository - setting, or shared default supplied the level. -4. If neither resolves a valid level, fail with an actionable decision error. - Inheriting the documented patch default is a valid policy decision; choosing - patch to recover from invalid or unresolved input is forbidden. Prerelease - mode does not supply a bump. - -| PR input | Effective default and source | Decision-check result | +`Verify` is the specification's target-neutral name for the existing Test stage. +Workflow job names can continue to use `Test` while the stage verifies the +frozen artifact. + +### Build + +Build receives the fixed source and resolved version. It produces one artifact, +stores it in retained release storage, calculates a cryptographic fingerprint, +and records both before any verification or publication. + +Build does not discover a newer branch tip, query labels again, edit the note +snapshot, or publish. If Build fails before a complete artifact is recorded, it +can retry with the frozen inputs. Once Build succeeds, no later phase can rebuild +or mutate the artifact under that version. + +### Verify + +Verify downloads the retained artifact by intent and confirms its fingerprint +before testing it. Source-level checks can run earlier, but release evidence must +show that the artifact intended for publication passed the required target, +integration, signing, and policy checks. + +Successful verification records the fingerprint, check identity, result, and +evidence location. A retry can reuse valid frozen evidence or repeat a +non-mutating check against the same bytes. + +### Publish + +Publish creates the destination-native artifact and release record from the +retained bytes and frozen note envelope. Every destination follows +[Publishing Targets](design-publishing-targets.md). + +For each required destination, Publish: + +1. Looks for a durable successful publication record. +2. If one exists, verifies that the destination coordinate still identifies the + recorded fingerprint and reuses it. +3. If none exists, verifies that the coordinate is free or already identifies + the same artifact. +4. Publishes the retained bytes and canonical metadata. +5. Reads the destination back where supported. +6. Records the coordinate, fingerprint, release-record location, and evidence. + +The release becomes `Complete` only when every frozen required destination has a +successful durable record. Native Latest state, moving aliases, and +announcements run after that transition. A partial release remains +`Publishing`, even if one destination makes its coordinate externally visible. + +## Phase-aware recovery + +A retry names the release-intent identity. The system loads durable state and +chooses the first unfinished boundary: + +| Recorded boundary | Retry action | +| --- | --- | +| `Resolved` with no successful artifact | Run Build again from frozen inputs. | +| `Built` | Restore the retained artifact, verify its fingerprint, and run Verify. | +| `Verified` | Restore the retained artifact and begin Publish. | +| `Publishing` | Verify recorded destinations and publish only unfinished ones. | +| `Complete` | Return the completed outcome; reconcile only independently retryable advertising work. | +| `Retired` | Return the retirement outcome without rebuilding or publishing. | +| `Withdrawn` | Return the withdrawal outcome without recreating or announcing the release. | + +Recovery never asks current labels, configuration, branch head, tag order, or +edited pull-request text to redefine frozen intent. Configuration can gain a new +destination only for a new release. It cannot silently broaden an in-flight +release. + +If retained storage is missing, the operator can restore a copy only when its +cryptographic fingerprint matches the recorded successful build. Otherwise the +intent must remain failed or be explicitly retired. Rebuilding similar source is +not proof of identical bytes and is forbidden under the reserved version. + +## Retirement and withdrawal + +Retirement and withdrawal solve different problems: + +| Operation | Applies to | Effect | | --- | --- | --- | -| `release:major` | Valid `patch` from any supported scope | Pass: explicit major overrides the default. | -| No owned release labels | No repository or caller override; shared `patch` | Pass: documented patch default; no per-PR bump label required. | -| No owned release labels | Repository `minor`; caller omitted | Pass: repository minor; record the setting as the source. | -| No owned release labels | Repository `major`; explicit caller `patch` | Pass: caller patch overrides repository major. | -| `release:skip` alone | Valid default | Pass: no release; no bump is required. | -| `release:prerelease` alone | Valid effective `minor` | Pass: default minor in prerelease mode. | -| No bump label | No valid effective default can be resolved | Fail: unresolved decision; no invented fallback. | -| Multiple bump labels, or skip with another owned release label | Any | Fail: conflicting decisions; no fallback. | -| Any, including an explicit bump or skip | Invalid supplied setting | Fail: invalid configuration; no fallback or masking by an override. | - -- **First release** uses `0.0.0` as the version base when no stable history - exists. The resolved patch, minor, or deliberate major bump produces - `0.0.1`, `0.1.0`, or `1.0.0`, respectively. Pre-`1.0.0` breaking changes - use minor per [SemVer §4](https://semver.org/#spec-item-4); reaching `1.0.0` - is a deliberate major decision, never an auto-detected breaking classification. -- The tag is created on the commit now at the head of the release branch — - squash, merge-commit, and rebase strategies alike. - -### Pull request decision record - -After inspecting the complete change, an author on an invoked route records the -human-readable decision in the conditional `Release decisions` block from [PR -Format](../../Ways-of-Working/PR-Format.md#record-release-decisions-only-when-invoked). -The block and the effective resolver inputs must agree: - -- `Release: Yes` states why the change affects the artifact or a supported - consumer contract, then records `Version bump`, `Mode`, and `Labels`. -- `Release: No` states the no-release evidence and reports only - `release:skip`. -- `Labels` names the exact applied `release:*` labels. Use `None` when a valid - configured default supplies a release level without an override label. - -The resolver still reads labels and configuration as its machine inputs; the -block makes the reviewed decision and its evidence visible. A route without an -invocation has neither the block nor any `release:*` labels. - -### Required pre-merge decision check - -PR CI runs the resolver read-only against the candidate release settings, -supported caller inputs, and current owned labels, without creating tags, -releases, or published artifacts. -An existing version-resolution check may own this validation; do not duplicate -the resolver. The check reports the effective decision and its source, not a -promised final stable version. - -The validator runs for every PR targeting a managed release route, including -native producer routes, changes that will not publish, and repositories without -a release configuration file. It re-runs when source, release labels, release -settings, supported caller inputs, or the effective pipeline changes, so a stale -result is not evidence for different inputs. Missing, invalid, or conflicting decisions produce a failed -check with an actionable error. A valid skip reports success with a no-release -result; path filters do not skip the validator. - -Configure the check's exact name as required in the protected branch's ruleset -or branch protection, following [Merge Automation](../merge-automation/spec.md). -Manual merge and auto-merge both wait for it: failure, pending execution, and -absence block merge. A warning or an advisory, unrequired check is insufficient. -Human review still assesses whether the resolved level matches the audience -impact; CI validates the deterministic decision contract. - -The release run validates its actual inputs again before Resolve and Build. -Pre-merge validation does not replace release-time validation, but a known -missing decision is never deferred until after merge. - -### Optional ad hoc releases - -The standard release path is a pull request with a validated decision merged into a release -branch. `workflow_dispatch` is an optional extension, not part of the minimum -implementation. An implementation SHOULD omit it unless its product has a real -need to release already-reviewed content outside the merge flow. - -Where an ad hoc path exists, it requires an explicit bump, source ref, complete -release-note context meeting the [release evidence contract](#release-notes), and -reason. It resolves the source ref to an immutable commit and enters -the same Resolve → Build → Test → Publish pipeline as a merged pull request. It -does not infer a bump, bypass validation, rebuild an existing version, or make a -direct push into a release interface. - -Do not create an empty pull request to manufacture a release. It contains no -artifact-affecting change and makes the review trail imply a change that did not -happen. Retrying failed validation or publication is not an ad hoc release -either: rerun the existing release with the same artifact and version under the -[recovery rule](#the-pipeline). - -## Prereleases and release candidates - -- **Prerelease branch** — `ReleaseType.Prerelease.Branch` publishes on every - push, using the branch name as the identifier: `v1.3.0-dev.1`, - `v1.3.0-dev.2`, … -- **Release-candidate branch** — `ReleaseType.ReleaseCandidate.Branch` publishes - `v1.3.0-rc.1`, `v1.3.0-rc.2`, … and never promotes one to latest. -- **PR-level** — `release:prerelease` on an open PR publishes - `v-.`: `base` is the next version from one explicit - owned bump label when present, otherwise from the resolved `DefaultBump`; - `identifier` is the normalized branch name, and `counter` auto-increments per - push. -- Artifact-specific conventions replace the SemVer suffix where they exist - (`-alpha.N` for npm, `.devN` for Python). -- **Cleanup** deletes prerelease tags, releases, and artifacts after the PR - closes (configurable); stable releases are never touched. - -## Path filtering - -When present, `.github/release.settings.yml` declares `release-paths` as ordered -include/exclude globs (excludes win). On an invoked route, the workflow -**always runs** so validation executes on every merge; only the release step is -skipped when no artifact-affecting path changed. - -Derive these paths from the delivered product and its -[audience-facing contracts](../../Ways-of-Working/PR-Format.md#identify-the-readers), -not directory names alone. Include callable workflows and build configuration -that changes delivered runtime requirements or behavior. Do not retain an -exclusion that overrides an included consumer interface or artifact input. - -This example represents a workflow producer with a public `reusable.yml` entry -point and its local implementation; each producer lists its own artifact inputs. +| Supersede | Resolved, unbuilt, unexposed intent | Coalesces untouched work into a later reviewed range. | +| Retire | Built or externally visible incomplete intent | Stops recovery, preserves failure and reservation, and leaves the release incomplete. | +| Withdraw | Completed release | Makes the release ineligible while preserving completion history and reservation. | + +An authorized withdrawal writes an append-only withdrawal event, requests the +strongest target-native hide, unlist, yank, or delete operation, and then +reconciles discovery and aliases. Target limitations are recorded rather than +represented as stronger guarantees. For example, a target may permit unlisting +but not deletion of already downloaded content. + +Withdrawal does not delete the release intent, free the version, alter the +artifact, rewrite notes, or imply that consumers already holding the artifact no +longer have it. A fixed release uses a new reviewed version. + +## Current-version discovery and aliases + +Durable completed state is the authority for current-version discovery. The +algorithm: + +1. Select stable intents whose required destinations completed. +2. Exclude retired or withdrawn releases and any release that policy marks + ineligible. +3. Order canonical versions by SemVer precedence. +4. Return the greatest match or an explicit no-current-version result. + +An API, tag, or release lookup failure is an error. It is never converted into a +successful empty result. + +Aliases are optional advertising references layered on top of discovery. The +closed families are: + +- `latest`, one alias for the greatest eligible completed stable version; +- major, one alias such as `v3` for the greatest eligible `3.x.y`; and +- minor, one alias such as `v3.4` for the greatest eligible `3.4.x`. + +Each family defaults off and is enabled independently. Completion and withdrawal +run the same reconciliation algorithm. A normal completion never moves an alias +backward. Withdrawal can move one backward to the greatest remaining eligible +match, and removes it when no match remains. Prereleases never update stable +aliases. + +Moving Git tags require explicit consumer-side fetch behavior. Operators who +consume an owned alias follow [Accept moved release tags](accept-moved-release-tags.md). + +## Completion announcements + +Announcements are a post-completion delivery channel, not part of artifact +publication. Resolve freezes whether announcements are enabled and which +configured destinations apply. Complete freezes the canonical message context: +version, immutable release link, summary, and release-intent identity. + +The summary is exactly the leading text before the single +`` marker required by [PR Format](../../Ways-of-Working/PR-Format.md). +Validate that boundary outside code fences before preparing the message; +missing or duplicate markers are errors, not instructions to truncate elsewhere. +The complete captured title and body remain in the GitHub release record. +Announcement delivery never extracts a subset of that record for publication. + +Each announcement destination has its own delivery record and idempotency key. +The sender records attempts and acknowledgements. If acknowledgement is missing, +it may resend the same logical message; receivers therefore observe +at-least-once delivery and should deduplicate by release identity. + +Announcement failure does not roll back a complete release and does not permit a +new version, build, or publication. It remains an independently retryable +post-completion task. + +## Release notes and evidence {#release-notes} + +Release notes preserve contributor-authored pull-request content rather than +reducing it to a generated commit list. The frozen note snapshot follows +[PR Format](../../Ways-of-Working/PR-Format.md) and covers the complete +publication-to-candidate consumer range, including earlier merged, skipped, +direct, or unshipped changes. + +Before writing the record or capturing its snapshot, follow [Resolve release +coverage first](../../Ways-of-Working/PR-Format.md#resolve-release-coverage-first). +For each product, artifact target, and channel, verify the latest successful +artifact publication, its receipt and immutable artifact/source provenance, and +the exact fixed candidate. Compare those immutable source trees and retain the +ordinary scoped PR-base comparison separately. A metadata-only mirror's +completion does not replace the artifact target's publication baseline. +With no predecessor, verify initial publication and review the whole initial +consumer surface rather than using `0.0.0` as consumed history. + +Re-read the publication baseline and candidate before readiness and Resolve. +Changed identities require refreshed coverage, instructions, and compatibility +evidence before capture. Resolve freezes these baselines with the approved note; +a retry preserves that snapshot rather than re-resolving later metadata. +This authoring range does not change aggregate approval of the accumulated +range, version allocation, or overall destination completion. + +Each result's prose and examples contain what changed, why, affected uses, +compatibility, exact actions with prerequisites, ordering, and verification, or +an explicit no-action result with its compatibility basis. Required old-to-new +instructions stay in the note, not repository migration guides or a parallel +changelog. Result-scoped technical details hold supporting evidence; internal-only +work without a classifier uses the block after the summary marker. + +Verify format adherence from the actual title, complete body, source coverage, +and scoped supporting evidence. Similarity to an inherited scaffold or central +copy is not evidence. An affected product integration template still requires +its verified immutable identity and producer-compatibility evidence. + +Consumers read every applicable source-bound record after their actual consumed +baseline through the target and compose the actions in release order. Later +notes explain superseded actions while retaining required intermediate +installations or runs. Reading a record does not require installing its version +unless the note explicitly requires that operation. PRs and diffs provide +optional depth; resource docs describe supported target-state behavior and usage. + +Manual requests supply equivalent reviewed context for direct or otherwise +unaccounted changes. Resolve fails if a source revision in the range lacks +reviewed note context or required consumer instructions. An evidence-gap record +identifies missing facts but never waives the complete consumer record or +authorizes a publisher to invent a no-action result. + +The publication envelope augments, but does not rewrite, the authored note: + +- canonical and destination-native versions; +- version-computation base, including the stable version or permanently reserved + coordinate actually used, and explicit `0.0.0` when no stable version or + permanent reservation exists; +- consumer-change baseline: each product/target/channel's latest successful + artifact publication, its receipt and verified immutable source, and the + comparison to the exact candidate; explicitly no predecessor only for a + verified initial publication; +- fixed source and included range; +- aggregate decision and its owned-label or effective-setting source; +- artifact fingerprint and verification evidence; +- destination coordinates and release-record links; +- note snapshot provenance; and +- completion, retirement, withdrawal, alias, and announcement events. + +These bases can differ. With completed `1.4.2`, a permanently reserved retired +`1.5.0` attempt containing an approved minor change not published to the selected +artifact target still establishes minor as the minimum aggregate increment. +A reviewed minor resolves from the reserved base `1.5.0` to `1.6.0`, while that +target's consumer delta starts from published source `1.4.2`; patch is rejected +for the accumulated range. Freeze and publish both bases rather than presenting +the consumer-change source as proof of the calculation. + +For module notes, successful Gallery publication advances the consumer-change +baseline even when later GitHub completion fails. Finishing that older GitHub +record retains its original source-bound snapshot and does not make its +already-published module changes new again. Completed-state discovery, +advertising, recovery, and reservation rules remain unchanged. + +A later explanatory correction is another append-only metadata event containing +before and after text, reason, actor, time, and source evidence. It cannot change +the version, source, artifact, decision, or behavior attributed to the release. + +## Release scope + +`release-paths` is evaluated over the complete accumulated range. With no explicit +configuration, every path is release-affecting. A repository can narrow scope +only after accounting for direct and transitive artifact inputs. ```yaml release-paths: - - ".github/workflows/reusable.yml" # public caller contract - - ".github/actions/**" # this workflow's local implementation - - "src/**" + - src/** + - module/** + - build/** ``` -## Release notes - -The GitHub Release **name** is the resolved version. Its **body** preserves the -release-bound PR title and complete description, using -[PR Format](../../Ways-of-Working/PR-Format.md#write-the-description) as the -authoring contract. The concise summary, shareable-summary marker, non-empty -classified change sections, reader-facing prose, section-scoped `Technical -details` and `Related references`, and conditional `Release decisions` block -stay intact. There is no parallel JSON/YAML contract and no extraction of only -selected headings or evidence blocks. - -The notes are the sufficient consumer upgrade record. Each release explains its -complete delta from the [actual successful artifact publication -baseline](../../Ways-of-Working/PR-Format.md#resolve-release-coverage-first): what changed, why, -affected supported uses, compatibility, and exact actions with applicable -prerequisites, ordering, and verification, or an explicit no-action result and -its compatibility basis. Necessary old-to-new examples stay in the note. -Consumer instructions appear in each result's prose and examples; scoped -technical details retain their supporting evidence. - -For internal-only work without a classified section, the summary contains the -adoption or explicit no-action result and the supporting block after its marker -contains the applicable evidence. Preserve that permitted PR Format shape rather -than manufacturing a classified result solely to hold metadata. - -Consumers read every applicable record after their actual consumed baseline -through their chosen target in release order, then compose the necessary -actions. Later notes explain superseded actions while preserving required -intermediate operations. Reading the records does not require installing every -intermediate version; an intermediate installation or run is required only when -explicitly stated. PR and diff links provide optional depth. Missing required -instructions are an evidence gap to resolve in the notes, not a routine source -investigation for consumers. - -[Resource docs](../../Ways-of-Working/Documentation-Model.md#current-product-guidance-and-release-history) -show supported target-state behavior and usage. They do not hold release-specific -migration history or replace the instructions in the release record. - -### Bind the note to the released source - -1. **Resolve the evidence with the version.** Identify the release-bound PR or - ad hoc context and the immutable source to build. Resolve the version base - and the [artifact-publication source comparison - baseline](../../Ways-of-Working/PR-Format.md#resolve-release-coverage-first); - pin it and the exact candidate independently of the scoped PR-base diff. - Reconcile every delivered consumer delta in that range, including earlier - merged, skipped, or unshipped changes. Confirm that each - applicable section contains sufficient adoption instructions or a supported - no-action result and that its technical details support the claim. Capture - the applicable title and complete body together with the PR URL or context - reference, source identity, and snapshot time. Retain that snapshot as release - evidence. -2. **Keep identity separate from authored prose.** Resolve the actual publication - coordinates through the existing version pipeline, not a number assigned by - the PR author. Carry them and the snapshot through Build and Test with the - same artifact. An authored statement that coordinates resolve at publication - is not replaced with a manual prediction. -3. **Publish the complete record.** Preserve the captured title and body - unchanged, with a clearly separated publication envelope. Compare the - published authored portion with the snapshot; truncation, summarization, - missing evidence, or a source mismatch is a publication failure, not success. - Hand the same complete record to every note-bearing publishing target and - [Downstream Release Propagation](../downstream-release-propagation/design.md). - -The envelope records these resolved facts without becoming a second authored -release note: - -| Field | Value | -| --- | --- | -| Release identity | Actual version, stable/prerelease mode, tag, immutable source commit, and artifact identity or digest where applicable. | -| Effective decision | The resolved semantic effect and its owned-label or configured-policy source; [version computation](#version-computation) remains authoritative. | -| Version base | The actual version/source used to compute the version, or the explicit initial versioning baseline. | -| Change baseline | Product/target/channel, actual latest successful artifact and publication evidence, its verified immutable source, and comparison to the exact candidate; explicitly no predecessor only for a verified initial publication. | -| Note provenance | Release-bound PR URL or ad hoc context, its associated source identity, and snapshot time. The retained authored snapshot is the content reference, not the PR's later mutable body. | - -Version base and change baseline can differ, particularly for prereleases and -bundled promotion. Recording both avoids presenting a versioning calculation as -proof of the code a consumer crosses. When a result affects an integration -template, its identity and compatibility evidence come from that result's -technical details; a publisher does not substitute the latest template or infer -historical compatibility from current documentation. +Validation still runs when no path is eligible, when the decision is skip, or +when a direct push cannot publish. No universal exclusion exists for docs, +tests, or workflows: a documentation site ships docs, test fixtures can be +packaged, and workflow files can define release behavior. -For module notes, successful Gallery publication advances the consumer-change -baseline even when later GitHub completion fails. Finishing that older GitHub -record does not change the module's already-published range. This authoring -baseline does not redefine version allocation or overall destination completion. +## Consumer update policies -### Release-bound records +The consumer resolves one of five policies: -| Publication path | Authored record | -| --- | --- | -| Single merged PR | That PR's complete title and description, reconciled with the resolved source comparison. | -| Bundled release | The release-bound integration PR covers every bundled consumer delta and required action from the declared change baseline, including earlier merged or skipped changes, not just the most recent feature PR. Grouping results retains those instructions; contributing PRs are optional supporting depth. | -| Optional ad hoc dispatch | Complete reviewed release-note context with the same classified structure and applicable result-specific evidence. Record the dispatch source and reason; do not create or imply an empty PR. | -| Prerelease | The PR or integration record appropriate to that published source, captured for that release. Later edits to the final PR do not overwrite the prerelease snapshot or attribute unreleased behavior to it. | - -If the relationship between a record and its source cannot be established, or -required consumer instructions are missing, stop the affected publication and -register the evidence gap. The process does not substitute the newest note, -guess a baseline, or treat missing instructions or applicable supporting -evidence as a no-action result. - -### Correct published metadata without changing history - -A note correction is an audited metadata operation, not another release run: - -1. Establish the release-to-source and PR relationship from immutable source - comparisons and contemporary evidence. Preserve source-specific prerelease - records rather than copying a later final-PR body over them. -2. Capture original and proposed content, reason, evidence links, actor, and - time in a linked audit issue or durable attached artifact. Coordinate active - PR ownership; do not add closing keywords to audit prose. -3. Re-read each target before writing. If another edit changed it, reconcile the - correction rather than overwriting that edit. Apply only the established - PR/release metadata changes and retain their correspondence. -4. Re-read the result and confirm that the correction changes no artifact, - asset, tag, SHA, release decision, or behavior attributed to an old version. - Record unverifiable facts as unresolved gaps instead of inventing actions. - -The audit belongs in GitHub issues and release/PR metadata, not a product -documentation changelog. A correction to bytes still follows the -[new-artifact recovery rule](#the-pipeline); editing notes never bypasses it. - -## Release output - -1. A git tag `vX.Y.Z` on the release-branch commit — always. -2. The published artifact where one lives outside git — a container image - (`:` and `@`), a package in its registry. For Action, - workflow, and module artifacts the tag itself **is** the artifact. -3. A GitHub Release whose name is the version, carrying the note and the - publication envelope, including the tag's resolved source commit and the - immutable artifact identity. - -## Publishing targets - -Publish is the only stage that knows where an artifact goes, and it reaches every -destination through one abstraction: a **publishing target**. A target is any -destination that accepts a versioned artifact and serves it to consumers — the -GitHub Release itself, a package registry, an extension marketplace, a container -registry. - -The release process is written against the target *contract*, never against a -specific target. Each target documents how it answers six questions — version -scheme, prerelease representation and sort order, immutability, unpublish -behaviour, floating-tag support, and where its release record lives — in -[Publishing Targets](design-publishing-targets.md). Adding a destination means -writing that contract and a publish step; it does not change Resolve, Build, -Test, or the spec. - -Where a repository has more than one target, publishing is **all-or-nothing** for -a version: - -- Targets are attempted in a defined order, and each is idempotent — publishing - an already-published version is a success only when it identifies the same - immutable artifact. A version collision with different bytes is an error, so a - re-run completes the set rather than accepting changed output. -- A target that rejects the version fails the release. The version is not - advertised as available until every target holds it. -- A partial publication resumes Publish for the **same** artifact and the same - version. It never resolves a new version to work around a single failed target, - because the targets that already succeeded hold that immutable version. - -## Floating tags - -Floating tags are optional, mutable pointers published alongside the immutable -version tag, for consumers that want to track a line rather than a point: - -| Tag | Points at | Moves when | +| Policy | Producer mechanism | Durable consumer reference | | --- | --- | --- | -| `latest` | the newest stable version | any stable release | -| `vMAJOR` | the newest stable version in that major | a stable release within that major | -| `vMAJOR.MINOR` | the newest stable patch in that minor | a stable patch within that minor | - -Three rules keep them safe: - -- **Prereleases never move a floating tag.** Only a stable release advances one, - so a floating tag never points at something not promoted for adoption. -- **A floating tag never moves backwards.** It only advances, so a consumer - following it never silently downgrades. -- **Only controlled release automation moves a floating tag.** Humans and ad hoc - workflows do not create or repoint one. The automation publishes the immutable - version first, then moves only the aliases that release is eligible to advance. -- **A major tag stays inside its compatibility line.** `vMAJOR` advances only - for compatible stable patch and minor releases in that major. A breaking - release creates the next major tag and leaves the previous one in place. -- **Floating tags are controlled references only for owned automation.** An - organization- or initiative-owned Action or reusable workflow may be consumed - through its controlled `vMAJOR` tag. External automation and anything requiring - byte-for-byte reproducibility pins to the immutable version, digest, or SHA - ([supply chain](../../Coding-Standards/Security.md#supply-chain)). - -## Serialised releases - -Release runs for the same ref are **serialised** and **queue rather than -cancel** — an in-flight release is never aborted mid-write, since it may be -part-way through creating a tag or pushing an artifact. The shared workflow -declares a concurrency group keyed by workflow and ref, with -`cancel-in-progress` disabled: +| `latest` | Discover the greatest eligible completed stable release. | The discovered immutable version or fingerprint required by trust policy. | +| `lock-major-boundary` | Supported native major-bounded constraint, permitted bounded update selection, or a permitted producer major alias. | The resolved immutable reference required by trust policy, or an allowed owned alias. | +| `lock-minor-boundary` | Supported native minor-bounded constraint, permitted bounded update selection, or a permitted producer minor alias. | The resolved immutable reference required by trust policy, or an allowed owned alias. | +| `lock-specific-version` | Exact immutable version. | Exact version. | +| `lock-immutable-fingerprint` | Content digest, commit SHA, or equivalent. | Immutable fingerprint. | + +A requested boundary is unsupported only when neither the consumer/target's +native constraints, permitted bounded update selection, nor a permitted +matching producer alias can express it; it is never widened silently. +PowerShell Gallery and NuGet consumers can use their supported native bounds +without producer aliases. Bounded update selection can likewise retain an +immutable reference without requiring an alias. When a field accepts only one +opaque Git or container reference, a text such as `>=3,<4` is not a range +expression. Without a permitted resolver outside that field, its bound +requires the corresponding permitted controlled alias. + +Moving references are allowed only for producers inside the consumer's declared +trust boundary. Another team is external even when it belongs to the same +company. External dependencies use the strongest immutable reference available. +The [Dependencies standard](../../Coding-Standards/Dependencies.md) owns the +broader pinning and update trade-off. + +The consumer's artifact-specific pinning standard is an additional constraint, +not replaced by trust or alias availability. The owned-automation controlled +major-tag exception does not authorize a minor alias for Actions or reusable +workflows. Such a consumer can express patch-only update selection while +retaining the required immutable reference; it does not acquire broader mutable +pinning permission from this policy table. + +Before `1.0.0`, SemVer permits a breaking change in each minor version. A major +alias such as `v0` therefore crosses potentially breaking `0.y.z` releases. A +consumer that wants patch-only movement before `1.0.0` uses a native minor bound +or a permitted minor alias such as `v0.4`, not `v0`. Where its narrower pinning +standard permits only an immutable reference, bounded update selection resolves +and records the exact version or fingerprint instead. + +## Ordering, idempotency, and reconciliation + +Workflow concurrency groups queue same-line work and never cancel an in-flight +publication. Durable reconciliation provides the stronger guarantee that source +order survives missed events, worker replacement, manual retries, and finite +execution queues. + +For each stable line, the reconciler finds source revisions not yet accounted +for by a complete, retired, or superseding intent. It processes the oldest +unsettled range first. Duplicate triggers use an idempotency key derived from the +repository, stable line, fixed source, request kind, and immutable approved +request identity, and return the existing intent rather than creating another. +A separately approved corrective release has a new request identity and version +and retains its relationship to the prior intent; changing a delivery ID alone +does not authorize a new release. + +This ordering prevents a later fast run from taking a version or release range +that belongs to earlier unresolved work. + +## Configuration + +The release solution's optional settings file is: + +```text +.github/release.settings.yml +``` + +### Configuration surface + +The canonical settings surface is: ```yaml -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false +DefaultBump: patch +ReleaseType: + Stable: + Branch: main + Prerelease: + Branch: dev + ReleaseCandidate: + Branch: rc +release-paths: + - src/** + - module/** ``` -Serialisation is provided once by the reusable workflow so every repository -inherits it; the mechanism is the -[GitHub Actions standard](../../Coding-Standards/GitHub-Actions.md#concurrency). -The single-stable-branch rule above is what keeps the production version under -one authority — `ReleaseType.Stable.Branch` is the only ref that ever cuts a -production release, and its runs are serialised like any other. +- `ReleaseType` owns the keyed branch mapping. `Stable.Branch` defaults to the + repository default branch; `Prerelease` and `ReleaseCandidate` are optional. + Unknown keys and duplicate branch assignments fail after resolving inherited + defaults, as defined by [release authority](#branching-model). +- `DefaultBump` has the shared default `patch` and accepts only `patch`, `minor`, + or `major`. Repository settings and supported caller inputs override it, then + a single owned bump label overrides the valid effective default for the + associated reviewed pull request. +- `release-paths` declares artifact-affecting scope. Omission means every path. -## Configuration surface +Lifecycle controls include independently disabled +`latest`, major, and minor aliases; publishing destinations; announcement +destinations; retained-artifact policy; and destination-specific native mapping. +An implementation documents the exact keys and caller inputs it supports; +this contract does not make unsupported controls available. Unknown fields and +values fail closed; repositories must not invent local release schema. -| Surface | Where | -| --- | --- | -| Release Management applicability | effective target-route pipeline, including shared workflows and native producers | -| Release types + branch mapping | `ReleaseType` in `.github/release.settings.yml` | -| Default bump and overrides | documented shared `DefaultBump: patch`, repository settings, then supported caller inputs | -| Explicit bump / prerelease / skip | `release:` PR label on an invoked route | -| Pre-merge decision validation | named PR check required by the branch ruleset or protection | -| Optional ad hoc release | `workflow_dispatch` inputs | -| Path filter | `.github/release.settings.yml` | -| Prerelease cleanup toggle | release config / workflow input | -| Publishing targets | reusable-workflow input + GitHub environment; see [Publishing Targets](design-publishing-targets.md) | +Repositories choose policy. Shared automation owns resolution, durable state, +SemVer calculation, artifact handoff, retry rules, target adapters, discovery, +alias reconciliation, and evidence format. + +## Implementation conformance {#intended-and-implemented-behavior} + +Each repository is governed by the immutable producer revision it invokes. +Verify the documented setting and label mappings, actual supported lifecycle +controls, and conformance evidence before relying on them. A configuration +example or normative requirement is not proof that an older producer implements +it. Version-specific implementation deltas and required adoption actions belong +in that producer's PR/release record, not a parallel status or migration history +in this design. + +For an incomplete release, an operator MUST NOT work around a +partial release by rerunning against changed metadata or rebuilding under the +same exposed version. Stop, preserve evidence, and roll forward with a newly +approved version when byte identity cannot be proven. + +## Design decisions + +### The durable intent, not a tag, is the release authority + +Tags can be missing, moved when explicitly used as aliases, or present before all +destinations complete. A durable intent can state which artifact, source, +approval, and destinations the tag is expected to represent and can distinguish +partial publication from completion. + +### Manual release uses the same pipeline + +A separate manual pipeline would create a second version algorithm and weaker +evidence path. Manual requests therefore change only the trigger and approval +source; they still Resolve, Build, Verify, and Publish one retained artifact. + +### Announcements follow completion + +Treating a chat or webhook message as a publishing destination would either +announce partial releases or make a transient message failure roll back a valid +artifact. Separating delivery retains truthful completion and retryable +communication. + +### Withdrawal changes eligibility, not history + +Deleting history would make audit, replay, and version reservation ambiguous. +An append-only withdrawal preserves what happened while letting discovery and +aliases stop recommending the release. ## Where this connects -- [Spec](spec.md) — the requirements this design delivers. -- [Publishing Targets](design-publishing-targets.md) — the contract each destination documents. -- [Downstream Release Propagation](../downstream-release-propagation/design.md) — consumes the release note and immutable reference. -- [GitHub Actions](../../Coding-Standards/GitHub-Actions.md) — how the workflow itself is authored (SHA pins, least privilege, concurrency). -- [Security](../../Coding-Standards/Security.md#supply-chain) — why consumers pin to immutable references. +- [Spec](spec.md) — the normative release contract. +- [Publishing Targets](design-publishing-targets.md) — destination-native + mappings and guarantees. +- [Accept moved release tags](accept-moved-release-tags.md) — consumer recovery + for controlled aliases implemented as Git tags. +- [Automation Labels](../../Ways-of-Working/Automation-Labels.md) — exact marker + meanings and conflicts. +- [GitHub Actions](../../Coding-Standards/GitHub-Actions.md) — immutable external + action pins and controlled owned aliases. +- [PR Format](../../Ways-of-Working/PR-Format.md) — release-note and consumer + evidence source. diff --git a/src/docs/Capabilities/release-management/index.md b/src/docs/Capabilities/release-management/index.md index aa9b991..9d4fe8b 100644 --- a/src/docs/Capabilities/release-management/index.md +++ b/src/docs/Capabilities/release-management/index.md @@ -1,23 +1,28 @@ --- title: Release Management -description: How a source change becomes a versioned, immutable artifact, driven entirely on the GitHub platform. +description: Durable version resolution, publication, recovery, withdrawal, and consumer update policy for immutable releases. --- # Release Management -Turning a merged change into a versioned, immutable artifact — a container -image, a GitHub Action or reusable workflow, a language package, a Terraform -module — paired with a GitHub Release and a git tag, driven by a configured -default and pull-request label overrides. An implementation may add a -GitHub-native ad hoc release path when its product needs one. No release CLI, -no hand-edited version file, no tagging ritual. +Turning approved source into a versioned, immutable artifact — a container +image, GitHub Action or reusable workflow, language package, Terraform module, +or documentation site — with durable release state and evidence. The ordinary +path starts from a reviewed merge; an approved manual request can release an +accumulated range. Both paths resolve once, build once, resume by recorded +phase, and advertise only completed eligible releases. + +Contributors express release intent through GitHub. Maintainers can recover, +retire, or withdraw releases without reusing versions or rewriting history, and +consumers select an update policy that matches their trust boundary. | Page | Description | | --- | --- | -| [Spec](spec.md) | Requirements for release management — validated defaults, reviewed overrides, immutable artifacts, and complete consumer release records. | -| [Design](design.md) | How release management is built — effective defaults, owned overrides, keyed release types, source-bound notes, and build-once publication. | -| [Publishing Targets](design-publishing-targets.md) | The contract every publishing destination documents, with GitHub Releases as the reference target. | +| [Spec](spec.md) | Requirements for durable, recoverable, policy-driven releases and trustworthy consumer updates. | +| [Design](design.md) | Durable release intents, lifecycle transitions, recovery, withdrawal, aliases, announcements, and producer conformance. | +| [Publishing Targets](design-publishing-targets.md) | Destination contracts for version mapping, prereleases, immutability, withdrawal, aliases, constraints, and release records. | +| [Accept Moved Release Tags](accept-moved-release-tags.md) | Refresh an owned moving release alias locally and configure Git to keep it current. | diff --git a/src/docs/Capabilities/release-management/spec.md b/src/docs/Capabilities/release-management/spec.md index 593fc7c..34b89f1 100644 --- a/src/docs/Capabilities/release-management/spec.md +++ b/src/docs/Capabilities/release-management/spec.md @@ -1,118 +1,785 @@ --- title: Spec -description: Requirements for release management — validated defaults, reviewed overrides, immutable artifacts, and complete consumer release records. +description: Requirements for durable, recoverable, policy-driven releases and trustworthy consumer updates. --- # Release Management — Spec ## Premise -A release turns a source change on a release branch into a **versioned, -immutable artifact** that other systems depend on. Merging a pull request *is* -releasing. Releasing MUST be automatic, predictable, and driven entirely on the -GitHub platform — a contributor focuses on the code they contribute, not a -release CLI, a hand-edited version file, or a tagging convention. +Release management turns approved source into a **versioned, immutable release** +that consumers can trust. A release is normally the result of merging reviewed +change, while a controlled manual request can release already-reviewed +accumulated change. Both paths use the same decision, build, verification, +publication, and evidence controls. -A target route participates when its effective pipeline produces a versioned, -immutable output consumed by version. This includes a shared Release Management -workflow and a native producer pipeline; the contract is not conditional on one -implementation's name. The release solution's optional `.github/release.settings.yml` file refines a -managed route's settings but does not create a release pipeline, and its absence -does not opt a managed route out. +The capability preserves enough durable state to finish an interrupted release +without rebuilding or assigning its version to different content. Consumers can +discover only completed, eligible stable releases and can select an explicit +update policy that matches their trust boundary. -### Principles +## Problem and importance -This capability rests on the [Principles](../../Ways-of-Working/Principles/index.md): +A tag or successful workflow run is not enough to prove that a release completed. +Publication can expose one destination before another fails, mutable pull-request +metadata can change before a retry, and a later commit can reach the stable branch +while an earlier artifact is still incomplete. Recomputing from current state can +then publish different bytes under a reserved version, omit accumulated changes +from the notes, or advertise an incomplete release as current. -- **[Everything as Code](../../Ways-of-Working/Principles/Engineering-Practices.md#everything-as-code).** The release process and version decision are version-controlled, never a GUI action or manual tag. -- **[Decision before change](../../Ways-of-Working/Principles/AI-First-Development.md#decision-before-change).** The pull request is the decision point; its review gate approves the code *and* the release. An owned bump label records a per-change override; a documented effective default records the policy used when no level is supplied. -- **[Smart defaults, local overrides](../../Ways-of-Working/Principles/Software-Design.md#smart-defaults-local-overrides).** Shared defaults make an unconfigured managed route useful; repository settings and supported caller inputs provide deliberate narrower overrides. -- **[Extensible by default](../../Ways-of-Working/Principles/Software-Design.md#extensible-by-default).** The rules are technology-agnostic at the core, with defined extension points per artifact type. A new artifact type supplies a convention and a publish step, not a new process. +Release management makes the release intent durable. It fixes the approved source, +decision, version, notes, artifact identity, required destinations, and stage +progress, then advances that intent safely through publication. This gives +contributors a predictable release path, operators a recoverable process, and +consumers an accurate version and update contract. + +## Users and jobs + +- **A contributor** records the compatibility impact and consumer evidence while + proposing a change, without operating separate release tooling. +- **A reviewer** approves the source, release decision, and complete consumer + effect together. +- **A maintainer** can release approved accumulated change, resume an interrupted + attempt, retire an unrecoverable attempt, or withdraw a completed release + without rewriting history. +- **A consumer** can discover the current completed stable release and choose how + far a dependency may move within the applicable trust boundary. ## Scope -Applies to a target route that produces a versioned artifact, whether through a -shared workflow or a native producer pipeline. One product test decides -applicability: **does merging produce a versioned, immutable output that -something else consumes by version?** If yes, this contract governs the route. -If no, there is nothing to release and the pull request carries neither the -`Release decisions` block nor `release:*` labels. +In scope: + +- Version resolution, release approval, and publication for repositories that + produce a versioned artifact. +- Stable releases, prereleases, release candidates, manual release requests, + retries, retirement, and withdrawal. +- Release notes, immutable release evidence, announcements, current-version + discovery, and optional moving aliases. +- Consumer update policies and the producer references needed to express them. + +Out of scope: + +- Deploying a released artifact into a runtime environment. +- Processing a release in dependent repositories. +- Repository rulesets and branch protections, except for the release checks they + must require. + +The effective target route determines applicability, including native producer +pipelines that publish versioned, immutable outputs without a named shared +workflow. A route without versioned release behavior carries no release metadata +or `release:*` labels. Absence of `.github/release.settings.yml` does not opt a +managed route out. ## Requirements -- **Semantic versioning.** Versions follow [SemVer 2.0.0](https://semver.org/) (`vMAJOR.MINOR.PATCH`), derived automatically — never written by hand. -- **Release behavior determines applicability.** Release Management MUST evaluate every pull request targeting a managed release route, including native producer routes and routes without `.github/release.settings.yml`. A route without versioned release behavior MUST NOT require, apply, or report the `Release decisions` block or any `release:*` label. -- **Producer compatibility is verified, not inferred.** Authors MUST verify the effective producer revision's settings and owned-label mapping. A differing released interface MUST have a verified adapter or producer/configuration migration before the canonical signals are used. Unsupported signals MUST block finalization and required decision validation, not become ignored skip requests or an excuse to classify a managed route as unmanaged. The producer's PR/release record owns its exact adoption actions. -- **Settings have documented defaults and supported overrides.** Release automation MUST document its setting names, accepted values, shared defaults, and supported caller overrides. Repository settings MUST override shared defaults; explicitly supplied supported caller inputs MUST override repository settings for that invocation. Unknown settings and invalid supplied values MUST fail even when a narrower override or explicit bump label exists. Omission MUST inherit a documented default rather than require configuration to become usable. -- **A resolved PR release decision.** `DefaultBump` accepts exactly `patch`, `minor`, or `major` and has the shared default `patch`. With no owned bump label, automation MUST use the valid effective default without requiring a per-PR bump label. Exactly one of `release:patch`, `release:minor`, or `release:major` MUST override that default. `release:prerelease` uses the resolved bump; mode alone supplies no level. Multiple owned bump labels, or `release:skip` with another owned release label, MUST fail. A valid skip MUST suppress publication without resolving a bump. If no valid level resolves, automation MUST fail rather than select a fallback. Bare and unrelated labels MUST be ignored. Conventional commit messages are **not** required. -- **A release per eligible merge.** One artifact-affecting merged PR with a resolved bump to a managed release route is one release, and the PR review gate is the release gate. The bump comes from one explicit owned bump label or the valid effective `DefaultBump`. `release:skip` and out-of-scope changes validate without publishing. This pull-request path is the required release interface. -- **Ad hoc release is optional.** An implementation MAY expose `workflow_dispatch` when its product needs an ad hoc release outside the merge flow; implementations are not required to support it. A dispatch MUST require an explicit release decision and release-note context, and MUST use the same version, build, validation, immutability, and publication controls as a merged pull request. A direct push MUST NOT be an ad hoc release interface, and an empty pull request MUST NOT be created solely to trigger a release. -- **Version before build.** The version MUST be resolved before the artifact is built, so the version is part of the artifact's identity rather than a label attached afterwards. -- **Build once.** The artifact MUST be built exactly once and MUST NOT be altered after it is built. The same bytes flow through validation and publishing. Rebuilding to publish means the tested artifact and the published artifact are different artifacts. -- **Stable and prerelease.** Every release is either **stable** (the latest version to adopt) or a **prerelease** (testable, not promoted to latest). A configured release candidate is a prerelease with its own candidate series, not an additional owned release label. -- **Release routing types.** Every release branch MUST map to exactly one routing type: **stable**, **prerelease**, or **release candidate**. A release-candidate route MUST publish prereleases in its candidate series and MUST NOT publish stable releases or promote a candidate to latest. A branch MUST NOT map to more than one routing type. A prerelease MUST also be obtainable from an open pull request carrying `release:prerelease`, using its explicit owned bump label or the resolved `DefaultBump`. -- **Routing configuration validates the effective mapping.** The supported `ReleaseType` keys are exactly `Stable`, `Prerelease`, and `ReleaseCandidate`; unknown keys MUST fail. Omitted stable routing MUST inherit the repository default branch. Branch-assignment validation MUST run after resolving inheritance and MUST reject a branch assigned to more than one routing type, whether assignments are explicit or inherited. -- **Decision validation blocks merge.** Every PR targeting a managed release route MUST receive a named release-decision CI check required by the branch ruleset or protection. Missing decisions, invalid defaults, and conflicting owned labels MUST fail that check before merge, not only during publication. Source, release labels, release settings, supported caller inputs, and effective-pipeline changes MUST re-evaluate the decision. A failing, pending, or absent required result MUST block both manual and automated merge; a log message, warning, or skipped validator is not enforcement. A valid `release:skip` MUST report a successful no-release decision, not skip the check. -- **Serialised releases.** Only one release process runs against a given version of the codebase (the same ref) at a time. A release mutates shared, version-anchored state — the tag, the version counter, the published artifact — so overlapping runs on the same ref MUST NOT race, and an in-flight release is never interrupted. -- **A single production authority.** Exactly one branch is in charge of the production (stable) version, so consumers get one unambiguous latest stable release and two branches can never publish competing production releases. -- **Notes from the contributor's own words.** The GitHub Release name is the version; its body MUST preserve the release-bound pull request title and complete description, or equivalent complete release-note context for an optional ad hoc dispatch. The authored record follows [PR Format](../../Ways-of-Working/PR-Format.md#write-the-description); classified results and their section-scoped technical details and related references MUST NOT be omitted or summarized away. -- **Only artifact-affecting changes release.** On a managed release route, a change that does not affect the delivered artifact or its supported consumer contracts MUST carry `release:skip` and MUST NOT produce a release — though validation still runs on every merge. Unmanaged routes omit that label and all release metadata. Documentation and internal CI configuration qualify only when they meet the artifact-impact condition. An Action or reusable workflow is itself a product for its callers; its interface and behavior MUST NOT be dismissed as internal tooling because of the file path. -- **Immutable references.** Consumers pin to the most immutable reference available — a container digest or a commit SHA — never a mutable tag. -- **Publish through a target contract.** Every publishing destination is reached through the same [publishing-target contract](design-publishing-targets.md), so the release process stays one process regardless of how many destinations a repository has. Adding a destination supplies a contract and a publish step; it MUST NOT change the release process. -- **All-or-nothing across targets.** Where a repository publishes one artifact to more than one destination, a version MUST NOT end up present on some destinations and absent from others. Partial publication is a failure, reported as one, and resumed by completing the remaining destinations with the same immutable artifact and version. -- **Recovery distinguishes retries from changed output.** Retrying validation or publication of unchanged bytes MUST reuse their artifact and version. A correction that changes the bytes MUST create a new versioned artifact; an existing version is never overwritten or reused. -- **Standard GitHub primitives only.** Pull requests, labels, comments, and, where implemented, workflow dispatch — no external tooling beyond `gh` and GitHub Actions. - -### Release evidence - -- **Evidence stays with the result it supports.** Consumer instructions MUST appear in the applicable change classifier's reader-facing prose and examples. Supporting validation, source, and affected product-integration evidence MUST appear in that section's `Technical details` block. For internal-only work with no classified section, the summary MUST carry the adoption or no-action result and the supporting block after its marker MUST carry the applicable evidence under [PR Format](../../Ways-of-Working/PR-Format.md#close-each-section-with-supporting-details). Otherwise, a global consumer, product-integration, or maintainer evidence block MUST NOT substitute for result-specific evidence. Canonical format adherence MUST be verified from the actual title, complete body, source coverage, and supporting evidence under [PR Format](../../Ways-of-Working/PR-Format.md#check-readiness). Evidence for an affected product integration template remains required. -- **Incremental consumer contract.** Every release MUST explain its complete consumer-facing change using the [publication-baseline-first authoring procedure](../../Ways-of-Working/PR-Format.md#resolve-release-coverage-first): what changed, why, affected supported uses, compatibility, and exact applicable adoption actions with prerequisites, ordering, and verification, or an explicit no-action result with its compatibility basis. Breaking behavior MUST be explicit independently of its semantic-version classification. When an integration template is affected, identify its repository and verified compatible immutable commit, with producer-source compatibility evidence and linked template work or a justified no-change result. -- **Sufficient upgrade history.** Reading every applicable release record after a consumer's actual consumed baseline through its chosen target in release order MUST be sufficient to compose the required alignment actions. Notes MUST identify any required intermediate installation or run and explain when later instructions supersede earlier actions; reading a release MUST NOT itself require installing that version. PRs, diffs, and historical product docs MAY provide optional depth, but MUST NOT be necessary to discover required instructions. Missing instructions MUST be treated as evidence gaps, not implicit no-action results. Resource docs remain focused on supported target-state behavior and usage under the [Documentation Model](../../Ways-of-Working/Documentation-Model.md#current-product-guidance-and-release-history). -- **Resolved coordinates.** The release process MUST record the actual target version, tag, immutable source and artifact identity, effective release decision and its source, version-computation base, and consumer-change baseline. The version base and change baseline MUST be distinguished when they differ. A first release MUST identify its initial versioning baseline and lack of a prior release. Authors MUST NOT assign a final version before resolution. -- **Traceable publication.** Generated identity and provenance MAY surround the authored note as a distinct envelope; they MUST NOT replace or rewrite it. The release MUST retain the note's source identity and snapshot provenance so its relationship to the published code is inspectable. Every destination carrying release notes, including downstream propagation, MUST receive the complete record. -- **Correct release scope.** The release-bound record MUST cover the complete source-tree change from the actual latest successful artifact publication for the same product/target/channel to the exact candidate source, including earlier merged, skipped, or unshipped changes; a verified first publication MUST instead have explicit reviewed initial coverage. The scoped PR-base diff MUST remain separate reviewer evidence. Artifact publication success MUST count even when a later destination or overall release completion fails; completing an older record MUST NOT make its already-published artifact changes new again. A bundled release's integration PR MUST preserve every bundled consumer delta and required action when grouping results. An optional ad hoc release MUST provide equivalent evidence without implying a nonexistent PR. A prerelease MUST preserve the note appropriate to its immutable published source, not a later final-PR description that describes different code. -- **Metadata-only correction.** A correction to published notes MUST retain an audit of the original and corrected content, reason, supporting source evidence, actor, and time. It MUST NOT alter release artifacts, tags, source identities, or the behavior attributed to a version. Unverifiable historical facts MUST be registered as gaps, not guessed. - -### Consumer update policies - -A consumer chooses how much version movement it accepts. Selecting a policy is a **consumer-side** concern — the release capability's obligation is to publish versions that make every policy expressible: - -| Policy | Accepts | Suits | -| --- | --- | --- | -| **Latest** | any newer version, including major | consumers that track the current release and have tests to catch breakage | -| **Lock major boundary** | newer minor and patch within one major | the default for a library dependency under SemVer | -| **Lock minor boundary** | newer patch only | consumers that accept fixes but no new surface | -| **Lock specific version** | nothing; movement is an explicit change | consumers under change control | -| **Lock immutable fingerprint** | nothing; the reference is a digest or SHA | consumers that require the exact bytes to be provable | - -Because versions are semantic, immutable, and published once, a consumer can adopt any of these without the producer knowing which one it chose. - -## Success criteria - -- Merging an eligible artifact-affecting PR with a resolved bump to a managed release route produces a GitHub Release, a git tag, and (where one exists) a published artifact with no manual step. Skip and out-of-scope changes validate without publishing. -- A publishing PR inherits the documented patch default when no repository setting, supported caller override, or owned bump label is supplied. Repository settings, supported caller inputs, and an explicit owned bump label override it in that order, with the chosen source recorded. -- Invalid supplied settings and conflicting owned labels fail even when another input supplies a valid level. Removing a bump override re-evaluates the required check against the effective default; source, settings, and caller changes invalidate prior evidence. -- A managed native producer route and a route without `.github/release.settings.yml` still receive release-decision validation; an unmanaged route carries no release metadata or labels. -- An open pull request carrying `release:prerelease` publishes a prerelease without promoting it to latest, using one explicit owned bump label when present and the resolved `DefaultBump` otherwise. -- A configured prerelease branch publishes prerelease versions, and a configured release-candidate branch publishes `-rc.N` versions; neither promotes an artifact to latest. -- The artifact that consumers download is byte-identical to the artifact that passed validation. -- On a managed route, a merge outside the artifact/consumer scope carrying `release:skip` produces no version but still runs its checks; an unmanaged route omits release labels and metadata entirely. -- Two release runs for the same ref never overlap; the second waits for the first to finish rather than racing it. -- Only the single production branch ever publishes a stable release. -- A version that reaches one publishing target reaches all of them, or the release is reported as failed. -- Every release is linkable and records its immutable artifact reference. -- The published authored title and body match the release-bound snapshot in full, including every classified result and its section-scoped technical details and related references. -- A consumer can compose the applicable actions from its actual baseline through a chosen target using only the ordered release records, including explicit no-action releases. Intermediate installations or runs are required only when stated in those records. -- Required old-to-new instructions remain in the notes; resource docs describe supported target-state usage, and an omitted instruction is reported as an evidence gap rather than delegated to PR or source inspection. -- Where a result affects consumers or an integration template, a consumer can identify the actual target, version base, change baseline, and compatible template without relying on a moving branch, alias, or today's documentation. -- A bundled or ad hoc note covers its complete change range, and a prerelease note never gains instructions for code absent from that prerelease. -- A published-note correction is traceable to released-source evidence while all artifact and source identities remain unchanged. +Requirements use [BCP 14](https://www.rfc-editor.org/info/bcp14) keywords. +Identifiers are append-only and MUST NOT be renumbered or reused. + +### FR1 — Every release has an approved decision and fixed source {#fr1} + +The release decision MUST be major, minor, patch, or skip. For a change with an +associated reviewed pull request, one explicit owned bump label MUST override +the valid effective `DefaultBump`. Without that label, documented defaults, +repository settings, and supported caller overrides MUST supply the level +without requiring a per-PR bump label. `DefaultBump` accepts exactly `patch`, +`minor`, or `major` and has the shared default `patch`. Unknown settings, invalid +supplied values, and conflicting owned signals MUST fail even when another +input supplies a valid level. A skip decision MUST suppress immediate publication without +removing the change from a later release range. Commit-message syntax MUST NOT +select the decision. + +The owned labels are exactly `release:patch`, `release:minor`, `release:major`, +`release:prerelease`, and `release:skip`. Prerelease mode uses the resolved bump; +it MUST NOT supply a level or combine with skip. Multiple owned bump labels and +skip with another owned release label MUST fail. Bare and unrelated labels MUST +be ignored. Release-candidate and announcement behavior are settings, not +additional owned release labels. + +The effective producer revision's supported settings and owned-label mapping +MUST be verified. A differing released interface MUST have a verified adapter +or producer/configuration migration before canonical signals are used. Missing +support MUST block finalization and required validation; exact adoption actions +belong in that producer's PR/release record, not a repository migration guide. + +The ordinary stable release path MUST be an approved pull request merged to the +stable line. A direct push with no associated review MUST validate but MUST NOT +inherit the repository fallback or publish a stable release. + +A manual stable release MUST name a fixed source revision on the stable line, an +explicit aggregate increment, complete release-note context, and approval that +covers the entire included change range. Invoking the release mechanism is not +approval. A retry MUST identify an existing release intent and reuse its frozen +decision. + +#### FR1 scenarios + +```gherkin +Scenario: An explicit decision overrides the fallback + Given the repository fallback is patch + And an approved pull request carries the owned minor decision + When release resolution runs + Then the resolved decision is minor + And the fallback is recorded as not used + +Scenario: A documented default needs no per-PR bump label + Given a reviewed pull request has no owned bump label + And no repository setting or caller override changes the shared patch default + When release resolution runs + Then the resolved decision is patch + And the shared default is recorded as its source + +Scenario: A supported caller override wins over repository settings + Given the repository DefaultBump is major + And the invocation explicitly sets DefaultBump to minor + And the reviewed pull request has no owned bump label + When release resolution runs + Then the resolved decision is minor + And the caller input is recorded as its source + +Scenario: An explicit bump cannot mask invalid settings + Given a supplied DefaultBump value is invalid + And the reviewed pull request carries release:major + When release resolution runs + Then validation fails before build + And no fallback level is selected + +Scenario: A direct push does not inherit the fallback + Given the repository fallback is patch + And a direct push has no associated pull request + When release resolution runs + Then validation runs + And no release is published +``` + +### FR2 — The increment states the complete compatibility impact {#fr2} + +Versions MUST follow [Semantic Versioning 2.0.0](https://semver.org/). For a +stable public contract at `1.0.0` or later, breaking change requires major, +backward-compatible capability requires minor, and backward-compatible repair +requires patch. Before `1.0.0`, breaking or additive change requires minor and a +compatible repair requires patch; reaching `1.0.0` remains a deliberate major +decision. + +The effective increment MUST cover every unreleased change included from the +previous completed stable source through the fixed release source. Known pending +increments establish a minimum aggregate increment: major takes precedence over +minor, which takes precedence over patch. Skipped and direct changes remain in +the range and require an explicit reviewed aggregate decision when their +compatibility impact is not already approved. + +#### FR2 scenarios + +```gherkin +Scenario: Accumulated decisions establish a minimum increment + Given the unreleased range contains approved patch and major changes + When an aggregate release decision is resolved + Then the effective increment is at least major + And a patch decision is rejected +``` + +### FR3 — One stable authority produces one discoverable current version {#fr3} + +Exactly one source line MUST be authorized to produce stable releases. +Release branches MUST map to exactly one of `ReleaseType.Stable`, +`ReleaseType.Prerelease`, or `ReleaseType.ReleaseCandidate`; unknown keys MUST +fail. Omitted stable routing MUST inherit the repository default branch. +Branch-assignment validation MUST run after resolving inherited defaults and +MUST reject duplicate assignments, including a non-stable route assigned to the +inherited stable branch. + +Current-version discovery MUST select the greatest eligible completed stable +version by SemVer precedence, independently of tag listing order or optional +aliases. + +Incomplete, failed, retired, and explicitly withdrawn releases MUST NOT be +current. If no eligible completed stable release remains, discovery MUST report +that no current stable version exists. A lookup error MUST be surfaced and MUST +NOT be interpreted as withdrawal or absence. + +#### FR3 scenarios + +```gherkin +Scenario: Partial publication is not current + Given version 2.4.0 has reached only one of two required destinations + And version 2.3.2 is the greatest eligible completed stable release + When current-version discovery runs + Then it returns version 2.3.2 + And version 2.4.0 remains incomplete + +Scenario: Inherited stable routing cannot also be a candidate route + Given the repository default branch is main + And ReleaseType.Stable is omitted + And ReleaseType.ReleaseCandidate.Branch is main + When effective release routing is validated + Then validation fails because main is assigned to two routing types + And no release is published +``` + +### FR4 — Prereleases and release candidates identify the next stable candidate {#fr4} + +A published prerelease MUST use the core version produced by applying its +resolved aggregate increment to the stable baseline and permanent reservations. +It MUST sort before the normal version with the same core and MUST NOT consume +that stable version. Successive versions in one prerelease series MUST increase +by SemVer precedence. + +Configured prerelease and release-candidate routes MUST publish only their +assigned prerelease series, never stable releases. An ordinary prerelease MUST +also be obtainable from an open pull request carrying `release:prerelease`, +using one explicit owned bump label when present and the valid effective +`DefaultBump` otherwise. + +An ordinary prerelease MUST identify its source series. A dedicated release +candidate MUST use the constant `rc` identifier and an unpadded increasing +counter, such as `2.5.0-rc.1`. A single request MUST NOT select both ordinary +prerelease and release-candidate modes. + +A prerelease MUST be testable but MUST NOT become the current stable release or +advance stable aliases. Cleanup MAY remove an exact prerelease and its record, +but MUST NOT reuse its identifier for different content. + +#### FR4 scenarios + +```gherkin +Scenario: A release candidate leaves its stable version available + Given the stable baseline is 2.4.1 + And the approved aggregate decision is minor + When release candidates are published + Then they use 2.5.0-rc.1, 2.5.0-rc.2, and later counters + And the stable version 2.5.0 remains available +``` + +### FR5 — A durable record is authoritative for release lifecycle state {#fr5} + +Before build, the capability MUST persist a release intent containing its +identity, fixed source and included range, approval evidence, aggregate decision, +resolved version, notes, required destinations, and current stage. After build it +MUST also retain the artifact fingerprint and verification and publication +progress. + +The lifecycle MUST distinguish pending or resolved work, successful build, +successful verification, partial publication, completion, failure, explicit +retirement, and withdrawal of a completed release. Workflow runs, tags, and +destination listings are observations of that lifecycle; none alone is the +authoritative state. + +Resolve MAY allocate a version before build. The reservation becomes permanent +when a build succeeds or any release coordinate becomes externally visible. A +permanent reservation survives failure, retirement, withdrawal, and deletion at +a destination. A resolved request with no successful build and no external +visibility MAY be explicitly superseded while coalescing pending work. + +#### FR5 scenarios + +```gherkin +Scenario: External visibility permanently reserves a version + Given a release exposes version 3.1.0 at one destination + And publication then fails elsewhere + When a later release is resolved + Then version 3.1.0 remains reserved for the original source and artifact + And the later release cannot reuse it +``` + +### FR6 — The verified artifact is the artifact that is published {#fr6} + +The version MUST be fixed before the artifact is built. Build MUST create the +artifact once, after which the artifact MUST remain unchanged. Verification and +every publishing destination MUST consume those same bytes. + +The successful artifact, its fingerprint, and verification evidence MUST remain +available for the entire unresolved lifetime of its release intent. A failed +build that produced no complete artifact MAY run again. A correction that changes +bytes MUST resolve a new version and produce a new artifact. + +#### FR6 scenarios + +```gherkin +Scenario: Build, verification, and publication share one artifact + Given a release is resolved to version 1.8.0 + When build, verification, and publication succeed + Then the published fingerprint equals the verified build fingerprint + And no later stage rebuilt or modified the artifact +``` + +### FR7 — Recovery resumes the recorded phase without changing identity {#fr7} + +A retry MUST load the original release intent and resume only unfinished or +failed work. It MUST reuse the frozen source, decision, version, notes, +destinations, successful artifact fingerprint, verification evidence, and +completed publications. It MUST NOT re-resolve from edited labels, changed +configuration, a later branch tip, or a later pull-request description. + +If the retained artifact is missing, recovery MUST stop with an explicit error. +Restoring bytes that match the recorded fingerprint MAY permit resumption; +rebuilding under the same permanently reserved version MUST NOT. + +An unrecoverable built or externally visible intent MAY be explicitly retired. +Retirement MUST preserve its failure record and permanent reservation and MUST +NOT mark it complete. A corrective release requires separate approval and a new +version. + +#### FR7 scenarios + +```gherkin +Scenario: A partial publication resumes at the missing destination + Given a verified artifact reached one of two required destinations + When the release is retried + Then the completed publication is verified and reused + And the same artifact is published only to the unfinished destination + +Scenario: Missing retained bytes stop recovery + Given a release has a successful recorded build + And no retained or published copy matches its fingerprint + When recovery runs + Then recovery fails with a missing-artifact error + And no rebuild occurs under the reserved version +``` + +### FR8 — Every required destination completes before the release completes {#fr8} + +One release MAY publish to multiple destinations, but MUST use the same canonical +identity and notes at each. The release MUST remain incomplete until every +required artifact and release record is published successfully. Completed +destination work MUST be recorded and reused idempotently. + +Completion announcements MUST be separate from artifact publication. Delivery +progress MUST be retained per release and destination, and an unacknowledged +delivery MAY be retried with at-least-once semantics. Announcement failure MUST +NOT create another version, rebuild, republish the artifact, or revoke an +otherwise complete release. + +#### FR8 scenarios + +```gherkin +Scenario: Announcement retry does not create another release + Given version 4.2.0 is complete + And one announcement destination did not acknowledge delivery + When announcement delivery is retried + Then the same release identity and message context are reused + And no new version, build, or artifact publication occurs +``` + +### FR9 — Every destination satisfies the publishing-target contract {#fr9} + +Every publishing destination MUST document: + +- its native version syntax and canonical SemVer mapping; +- prerelease representation, validation, and ordering; +- immutability and the strongest consumer reference; +- withdrawal, unpublish, or yank behavior; +- supported moving-alias families; +- native version-constraint support; and +- the location of its durable release record. + +Distinct canonical releases MUST NOT map to one native coordinate. A mapping +collision or disagreement between the canonical prerelease status and a native +prerelease flag MUST fail rather than overwrite or misrepresent a release. + +#### FR9 scenarios + +```gherkin +Scenario: A native coordinate collision blocks publication + Given two canonical releases map to one destination version + When the second mapping is validated + Then publication fails before content is overwritten + And the original coordinate remains unchanged +``` + +### FR10 — The release record covers the complete included change {#fr10} + +Release-bound records MUST account for the complete source-tree change from the +actual latest successful artifact publication for the same product, target, and +channel through the fixed candidate source, including earlier merged, skipped, +direct, or unshipped changes. Authors MUST resolve and verify that range before +writing the record under the [publication-baseline-first +procedure](../../Ways-of-Working/PR-Format.md#resolve-release-coverage-first). +The scoped PR-base comparison MUST remain separate implementation evidence. +A verified initial publication MUST have explicit initial coverage rather than +an invented predecessor or a version-computation baseline. + +Successful artifact publication MUST count even when a later destination or +overall release completion fails. Completing an older record MUST NOT make its +already-published artifact changes new again. Record coverage MUST NOT redefine +aggregate approval, version reservations, or completed-state discovery. + +The record MUST preserve contributor-authored title, complete description, +adoption guidance, consumer-change evidence, and maintainer evidence under +[PR Format](../../Ways-of-Working/PR-Format.md), or equivalent reviewed aggregate +context for a manual release. A prerelease MUST retain the snapshot appropriate +to its immutable source, not a later description of different code. + +#### Release evidence + +The complete description MUST contain exactly one announcement boundary after +its leading summary. Each result MUST explain what changed, why, affected +supported uses, compatibility, exact adoption actions with prerequisites, +ordering, and verification, or an explicit no-action result with its +compatibility basis. Supporting evidence MUST remain scoped to its result; +internal-only work without a classifier uses the supporting block after the +summary marker. Announcements MUST NOT truncate the full release record. + +Canonical format adherence MUST be verified from the actual title, complete +body, source coverage, and supporting evidence under +[PR Format](../../Ways-of-Working/PR-Format.md#check-readiness). +An inherited scaffold or similarity to a central copy MUST NOT substitute for +that record evidence. + +An affected product integration template MUST be identified by repository and verified +compatible immutable commit, with evidence for the exact producer source and +linked template delivery or a justified no-change result. A moving branch, +today's template, or an unsupported compatibility claim MUST NOT replace that +evidence. If no template applies, record that result and its basis explicitly. + +Reading every applicable source-bound release record after the actual consumed +baseline through the target in release order MUST be sufficient to compose the +required actions. Notes MUST explain superseded actions while retaining required +intermediate installations or runs; reading a record MUST NOT itself require +installing its version. PRs and diffs MAY provide optional depth but MUST NOT +replace required instructions. Missing instructions MUST block the affected +publication as evidence gaps. Resource docs describe supported target-state +behavior and usage, not version-specific migration history or a parallel changelog. + +The resolved version, version baseline, product/target/channel publication +baseline, publication receipt, verified immutable artifact/source provenance, +fixed candidate source, comparison and freshness evidence, artifact fingerprint, +effective decision and source, destination coordinates, and note snapshot +provenance MUST be retained as a publication envelope without replacing or +rewriting the authored note. Missing or unverifiable publication/source evidence +MUST block the affected publication rather than become a no-action result. + +#### FR10 scenarios + +```gherkin +Scenario: A later release includes a skipped change + Given change A was merged with a skip decision + And approved change B triggers the next release + When release notes are frozen + Then the notes and aggregate decision cover A and B exactly once + +Scenario: Partial destination completion does not repeat published module changes + Given module source B was successfully published to Gallery + And its later GitHub completion failed + And exact candidate source C adds another module change + When the module release record is frozen + Then its consumer change comparison is B through C + And completing B's older GitHub record does not make B's module changes new again + And completed-state discovery still requires every required destination to complete +``` + +### FR11 — Eligibility evaluates the complete unreleased artifact scope {#fr11} + +Only changes that affect the delivered artifact or a supported consumer contract +MUST produce a release. Eligibility MUST evaluate the complete unreleased range, +including direct and transitive build inputs, rather than only the latest change. +Validation MUST still run when publication is skipped. + +Without configured scope, every changed path MUST be treated conservatively as +release-affecting. A repository MAY narrow the scope explicitly. File categories +such as documentation or workflow definitions MUST NOT be universally excluded, +because they can be the product or an artifact input. + +#### FR11 scenarios + +```gherkin +Scenario: An out-of-scope change validates without publishing + Given the repository declares its artifact-affecting scope + And the full unreleased range contains no matching change + When release processing runs + Then validation runs + And no version or release is published +``` + +### FR12 — Release requests are serialized and accounted for in source order {#fr12} + +Release work on the same release line MUST NOT run concurrently and MUST queue +rather than cancel in-flight publication. Requests MUST be reconciled in source +history order, independently of worker start order. Duplicate event delivery +MUST return the same intent or outcome. + +A missed event, replaced worker, or bounded execution queue MUST NOT silently +discard a release request. Pending work MUST remain recoverable from durable +state or source history before later source revisions advance the line. + +After intervening changes, a built or externally visible intent MUST first be +resumed or explicitly retired. Remaining unbuilt and unexposed requests MAY be +backfilled individually or coalesced under a reviewed aggregate decision. Every +original request MUST record the release that accounts for it. + +#### FR12 scenarios + +```gherkin +Scenario: Later work cannot overtake an unresolved intent + Given changes A and B reached the stable line in that order + And A has a built incomplete release + When B is processed + Then A is resumed or explicitly retired first + And B cannot replace A's reserved version or artifact +``` + +### FR13 — Moving aliases are closed, optional, and withdrawal-aware {#fr13} + +The available moving-alias families MUST be exactly `latest`, major, and minor. +Each family MUST be enabled independently and MUST default to disabled. Unknown +families or unsupported target combinations MUST fail configuration validation. + +An enabled alias MUST resolve to the greatest eligible completed stable version +matching its family. A new older release MUST NOT move an alias backward. +Withdrawal MUST reselect the greatest remaining eligible match, which may be an +older version. If no eligible match remains, the alias MUST be removed or +disabled rather than left on an ineligible release. Prereleases MUST NOT move a +stable alias. + +#### FR13 scenarios + +```gherkin +Scenario: Withdrawal reselects an alias + Given the major alias points to completed stable version 3.4.0 + And completed stable version 3.3.2 remains eligible + When version 3.4.0 is explicitly withdrawn + Then the major alias is reconciled to 3.3.2 + And the withdrawn version remains reserved +``` + +### FR14 — Consumer update policy is explicit and trust-aware {#fr14} + +A consumer MAY select exactly one of these policies: + +| Policy | Accepted movement | +| --- | --- | +| `latest` | The newest eligible stable release, including a new major. | +| `lock-major-boundary` | Minor and patch releases within one selected major. | +| `lock-minor-boundary` | Patch releases within one selected major and minor. | +| `lock-specific-version` | No automatic movement from one exact version. | +| `lock-immutable-fingerprint` | No automatic movement from one exact content identity. | + +When no policy is selected, the consumer MUST use the most immutable reference +available. A boundary policy MUST use a supported native version constraint, +permitted bounded update selection, or a permitted corresponding producer major +or minor alias. Native bounded ranges and permitted bounded update selection +MUST NOT require a moving alias merely to express that bound. When neither +resolver is available, an opaque reference field requires the matching +permitted alias; otherwise the boundary is unsupported. Resolve and retain the +immutable reference required by the trust policy. A range-like string in an +opaque Git or container reference field MUST NOT be treated as a resolver +expression. + +Mutable aliases MAY be used only within the consumer's permitted trust boundary. +An external producer, including one owned by another team in the same company, +MUST be pinned to an immutable fingerprint or exact immutable version. `latest` +MAY discover a release without an alias, then resolve it to the reference allowed +by the trust boundary. + +Policy expressibility does not grant permission to retain a mutable reference. +Consumers MUST also satisfy their artifact-specific pinning standard. For owned +Actions and reusable workflows, the controlled major-tag exception does not +authorize a minor alias; a patch-only policy uses immutable references with +bounded update selection when that standard requires them. Producer alias +availability alone MUST NOT relax the narrower consumer rule. + +#### FR14 scenarios + +```gherkin +Scenario: An unsupported boundary policy fails explicitly + Given a consumer requests lock-minor-boundary + And its consumer field has no native range resolver + And permitted bounded update selection is unavailable + And the producer does not publish the minor alias family + When the policy is resolved + Then the request fails as unsupported + And it is not widened to latest or a major boundary + +Scenario: Bounded updates retain a SHA-pinned reference + Given an Action or reusable-workflow consumer requests lock-minor-boundary + And permitted bounded update selection is available + And its reference is a full commit SHA + And the producer publishes no minor alias + When a newer compatible patch is selected + Then the reference is updated to that patch's full commit SHA + And the selected major and minor remain unchanged + And no minor alias is retained + +Scenario: A native bounded range does not require an alias + Given a consumer requests lock-minor-boundary + And its target supports a native minor-bounded range + And the producer publishes no minor alias + When the policy is resolved + Then the supported native range supplies the bound + And the selected version is retained with the immutable reference required by trust policy + +Scenario: An external producer remains immutable + Given a consumer selects latest for a producer outside its trust boundary + When the current release is discovered + Then the durable consumer reference is its immutable fingerprint + And no moving producer alias is retained +``` + +### FR15 — Release-decision validation blocks an invalid merge {#fr15} + +Every pull request targeting a release line MUST receive a named release-decision +check required by branch policy, including native producer routes and routes +without a release configuration file. The check MUST re-evaluate when source, +owned release markers, release settings, supported caller inputs, or the +effective pipeline changes. A missing, conflicting, or invalid +decision MUST fail; a valid skip MUST pass with an explicit no-release result. +A failed, pending, absent, or stale required result MUST block manual and +automated merge. + +#### FR15 scenarios + +```gherkin +Scenario: Decision input changes invalidate the prior result + Given a pull request has a successful release-decision check + When its owned bump override is removed + Then the check is rerun + And the valid effective DefaultBump supplies the new level + And merge remains blocked until the new inputs resolve validly +``` + +### FR16 — Withdrawal preserves history and requires explicit authorization {#fr16} + +A completed release MAY be withdrawn only through an explicit +maintainer-authorized operation. Withdrawal MUST be recorded separately from the +original completion and MUST preserve source history, approval evidence, +artifact identity, destination outcomes, announcements, and version reservation. +Ordinary deprecation or a temporary lookup failure MUST NOT imply withdrawal. + +Replaying a withdrawn release MUST report its withdrawn outcome. It MUST NOT +recreate removed records, resend the completion announcement, or make the +release current again. Correcting a bad release MUST roll forward with a newly +approved version. + +#### FR16 scenarios + +```gherkin +Scenario: Withdrawal does not erase completion + Given version 5.1.0 completed and was announced + When an authorized maintainer withdraws it + Then its completion and announcement history remain recorded + And replay reports the withdrawal without recreating or re-announcing it +``` + +### FR17 — Published-note corrections are auditable metadata changes {#fr17} + +A correction to published explanatory metadata MUST retain the original and +corrected content, reason, source evidence, actor, and time. It MUST NOT change +the artifact, exact version reference, fixed source, release decision, or +behavior attributed to that version. Unverifiable historical facts MUST be +recorded as evidence gaps rather than guessed. + +#### FR17 scenarios + +```gherkin +Scenario: A note correction cannot change release identity + Given a completed release has incorrect explanatory text + When an authorized correction is applied + Then the original and corrected text and evidence are retained + And the version, source, and artifact fingerprint remain unchanged +``` + +## Non-functional requirements + +### NFR1 — Published versions are immutable {#nfr1} + +One hundred percent of published stable versions MUST remain bound permanently +to their original source and artifact. A version identifier MUST NOT be reused +after failure, retirement, withdrawal, unpublish, or repository recreation. +Destination-native immutability controls MUST be enabled where available. + +#### NFR1 scenarios + +```gherkin +Scenario: A withdrawn version cannot be reused + Given stable version 2.0.0 was published and later withdrawn + When different content requests version 2.0.0 + Then publication is rejected + And the original reservation remains authoritative +``` + +### NFR2 — Recovery retains all unresolved release evidence {#nfr2} + +One hundred percent of unresolved built release intents MUST retain the artifact +fingerprint, frozen inputs, and phase progress required for safe resumption until +they complete or are explicitly retired. + +#### NFR2 scenarios + +```gherkin +Scenario: An unresolved release remains recoverable + Given a verified release is incomplete + When its worker and workflow run no longer exist + Then its durable record still identifies the artifact and remaining work +``` + +### NFR3 — Release behavior is shared and GitHub-native {#nfr3} + +One hundred percent of governed repositories MUST inherit one shared release +behavior, with zero repository-local copies of the resolution or lifecycle +algorithm. Contributors and maintainers MUST be able to drive decisions, manual +requests, retries, retirement, withdrawal, and evidence through GitHub pull +requests, owned labels, comments, checks, and workflow dispatch without a +separate local release tool. + +#### NFR3 scenarios + +```gherkin +Scenario: Equivalent repositories resolve equivalent requests consistently + Given two repositories inherit the same release capability and policy + When equivalent approved release requests are processed + Then they apply the same lifecycle, version, and recovery rules +``` + +### NFR4 — New destinations do not change the lifecycle contract {#nfr4} + +One hundred percent of new artifact types and destinations MUST be added through +the publishing-target contract without changing the requirements or release +lifecycle. + +#### NFR4 scenarios + +```gherkin +Scenario: A new destination uses the existing lifecycle + Given a destination satisfies the publishing-target contract + When it is added to a release + Then Resolve, Build, Verify, Publish, completion, and recovery retain their existing meaning +``` + +## Acceptance criteria + +```gherkin +Scenario: AC1 A reviewed merge completes one durable stable release + # Verifies: FR1, FR2, FR3, FR5, FR6, FR8, FR10 + Given the current stable release is 1.4.2 + And an approved in-scope pull request resolves to minor + When every required destination succeeds + Then exactly one completed release 1.5.0 is recorded + And its notes, fixed source, artifact fingerprint, and destination coordinates are durable + And current-version discovery returns 1.5.0 + +Scenario: AC2 An interrupted release resumes without identity drift + # Verifies: FR5, FR6, FR7, FR8, FR12 + Given a verified artifact is published to only one required destination + And later commits reach the stable line + When the original release is retried + Then it completes the missing destination with the recorded artifact + And its source, version, notes, and completed publication remain unchanged + +Scenario: AC3 Withdrawal changes eligibility without changing history + # Verifies: FR3, FR5, FR13, FR16, NFR1 + Given two completed stable releases and aliases pointing to the newer one + When the newer release is explicitly withdrawn + Then discovery and aliases select the older eligible release + And the withdrawn release's completion history and version reservation remain +``` ## Where this connects -- [Design](design.md) — how these requirements are delivered. -- [Publishing Targets](design-publishing-targets.md) — the contract each destination documents. -- [Documentation Model](../../Ways-of-Working/Documentation-Model.md) — why this spec holds only the why and the what. -- [Automation Labels](../../Ways-of-Working/Automation-Labels.md) — why release labels are owned by the `release:` namespace. -- [PR Format](../../Ways-of-Working/PR-Format.md) — the authored release record and conditional release-decision block. -- [Dependency Updates](../dependency-updates/spec.md) — update PRs are artifact-affecting and release through this capability. +- [Design](design.md) — the lifecycle, input resolution, and producer conformance + that realize these requirements. +- [Publishing Targets](design-publishing-targets.md) — the destination contract + and native behavior. +- [Automation Labels](../../Ways-of-Working/Automation-Labels.md) — the owned + release instruction vocabulary. +- [PR Format](../../Ways-of-Working/PR-Format.md) — the authored release note and + consumer evidence. +- [Dependencies](../../Coding-Standards/Dependencies.md) — dependency pinning and + update trade-offs. diff --git a/src/docs/Coding-Standards/GitHub-Actions.md b/src/docs/Coding-Standards/GitHub-Actions.md index 347e8cb..192895c 100644 --- a/src/docs/Coding-Standards/GitHub-Actions.md +++ b/src/docs/Coding-Standards/GitHub-Actions.md @@ -38,6 +38,12 @@ exception is a floating major tag on automation whose release path MSX controls: release creates the next major tag; it never repoints the existing major tag across the compatibility boundary. +A local clone that consumes an allowed owned major tag must explicitly accept +producer-controlled tag movement. Follow +[Accept moved release tags](../Capabilities/release-management/accept-moved-release-tags.md) +to repair a stale tag and configure trusted fetches. This does not relax the +immutable-SHA requirement for external actions. + ```yaml # External — immutable SHA; comment carries the readable version - name: Check out the repository diff --git a/src/zensical.toml b/src/zensical.toml index 6e19106..e48c103 100644 --- a/src/zensical.toml +++ b/src/zensical.toml @@ -124,6 +124,7 @@ nav = [ {"Spec" = "Capabilities/release-management/spec.md"}, {"Design" = "Capabilities/release-management/design.md"}, {"Publishing Targets" = "Capabilities/release-management/design-publishing-targets.md"}, + {"Accept Moved Release Tags" = "Capabilities/release-management/accept-moved-release-tags.md"}, ]}, {"Repository Governance" = [ "Capabilities/repository-governance/index.md",