diff --git a/src/docs/Capabilities/index.md b/src/docs/Capabilities/index.md index b3b57f5..01539b0 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) | A generic framework for producing verified, immutable versioned release artifacts. | | [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/design-publishing-targets.md b/src/docs/Capabilities/release-management/design-publishing-targets.md index 0b96812..ada56c7 100644 --- a/src/docs/Capabilities/release-management/design-publishing-targets.md +++ b/src/docs/Capabilities/release-management/design-publishing-targets.md @@ -1,71 +1,81 @@ --- -title: Publishing Targets -description: The contract every publishing destination documents, with GitHub Releases as the reference target. +title: Publishing Target Design +description: The generic adapter contract for publishing versioned release artifacts and records. --- -# Release Management — Publishing Targets +# Release Management — Publishing Target Design -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 adapter receives a resolved version, a verified immutable +artifact, a frozen release-note snapshot, and provenance. The core pipeline does +not depend on target-specific behavior. -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). +## Adapter contract -## The contract +Each adapter declares the following conventions before the target enters a +required target set. -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: - -| Dimension | What it settles | +| Convention | Adapter responsibility | | --- | --- | -| **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 | Map the semantic version to the target's accepted version form. | +| Prerelease | Map and order prerelease versions below their related stable version. | +| Immutability | Reject replacement of an existing stable version with different bytes. | +| Withdrawal | Define how availability is removed while preserving version reservation. | +| Aliases | Declare whether `latest`, major, and minor aliases are supported. | +| Release record | Persist a durable record that joins source, artifact, version, and notes. | + +An adapter reports one of three outcomes: unpublished, published with a +matching immutable identity, or conflicting. A matching existing publication is +a successful retry. A conflict fails the release and requires a new version. -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. +## Target transaction behavior -## Target summary +The framework stages every required adapter under the frozen release intent. +Adapters publish in a deterministic order. Completion is recorded only after all +required adapters report a matching immutable identity and release record. +There is no claim that independently operated targets provide a distributed +transaction; atomic completion is the framework's durable state rule. -| 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 | +Withdrawal is exceptional. It changes availability but never permits a stable +version identity to be reused. Prerelease cleanup uses the same rule and is +restricted to prerelease identities. -Two patterns run through the table and shape how consumers are told to pin: +## Alias behavior -- **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. +Aliases are optional controlled references, not release identities. If enabled, +the adapter supports only: -## GitHub Releases — the reference target +| Alias | Eligible destination | +| --- | --- | +| `latest` | Newest eligible stable version | +| Major | Newest eligible stable version in that major compatibility line | +| Minor | Newest eligible stable version in that minor compatibility line | -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. +The adapter updates aliases after immutable publication completes. Prereleases +never advance aliases. An alias update that would move backward, cross its +compatibility line, or point to an unverified artifact fails. -- **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. +## Release hosting adapter -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*. +A release-hosting adapter is appropriate when the target repository already +uses a public release-record feature. It publishes a version marker, attaches +or references the verified artifact, records the complete reviewed notes and +provenance, and marks prereleases so they are excluded from the current stable +release. -## Adding a target +For a GitHub Releases adapter, use the public platform documentation for the +target behavior: . +The adapter treats the version tag and release assets as immutable release +identities, records stable and prerelease state separately, and never reuses a +withdrawn version. -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. +## Adding an adapter -The spec does not change. That is the purpose of the contract. +Adding an artifact or destination consists of implementing this contract, +declaring it in the target set, and proving it handles matching retries and +conflicting identities. The core resolution, build, verification, recovery, +and release-record behavior remains unchanged. -## Where this connects +## Related records -- [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. +- [Specification](spec.md) +- [Logical Core Design](design.md) diff --git a/src/docs/Capabilities/release-management/design.md b/src/docs/Capabilities/release-management/design.md index ff0a24f..207aa1b 100644 --- a/src/docs/Capabilities/release-management/design.md +++ b/src/docs/Capabilities/release-management/design.md @@ -1,397 +1,151 @@ --- -title: Design -description: How release management is built — a shared reusable workflow that resolves an explicit or configured SemVer bump, builds once, and publishes. +title: Logical Core Design +description: The generic resolution, build, verification, and publication model for release management. --- -# Release Management — Design - -The behaviour in the [spec](spec.md) is delivered by a **shared reusable release -workflow**. A repository opts in with a short caller workflow and a small -`.github/release.config.yml`. The workflow supplies the shared mechanics; an -explicit label or an intentionally configured default supplies the release level. - -## Branching model - -A **release branch** is any branch configured as a release target, each with a -**release type** — `stable` or `prerelease`. - -- **Single branch (zero-config).** One release branch (the default branch) - produces stable releases. Prereleases are opt-in via a PR label. -- **Multi-branch.** `dev` (prerelease) collects PRs and publishes a prerelease - on every merge; `main` (stable) receives `dev`. Merging `dev → main` computes - the stable version from the **latest stable release** plus the merge PR's - resolved bump — the prerelease counter does not carry over. -- **One production authority.** At most one branch is `release-type: stable`; - every other release branch is `prerelease`. The single stable branch - (typically `main`) owns the production version — a prerelease branch can never - cut a stable release. -- **Bundled releases.** A **staging branch** collects feature PRs; merging it to - a release branch produces **exactly one** release for all bundled changes. - -```yaml -# .github/release.config.yml -release-branches: - - branch: main - release-type: stable - - branch: dev - release-type: prerelease -``` +# Release Management — Logical Core Design -## The pipeline +This design realizes the specification through one reusable pipeline and a +target adapter boundary. It defines logical responsibilities, not a specific +automation product or hosting service. -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. +## Release pipeline ```mermaid flowchart LR - resolve["Resolve
version decided"] --> build["Build
artifact created once"] - build --> test["Test
same artifact validated"] - test --> publish["Publish
same artifact released"] + trigger[Trigger] --> resolve[Resolve] + resolve --> intent[Frozen release intent] + intent --> build[Build] + build --> test[Test] + test --> publish[Publish] + publish --> record[Release output] ``` -| Stage | Produces | Invariant | +| Stage | Responsibility | Output | | --- | --- | --- | -| **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. +| Trigger | Admit an approved change, direct source update, or authorized manual request. | Candidate source and context | +| Resolve | Determine eligibility, decision, version line, version, notes, and targets. | Frozen release intent | +| Build | Produce the version-marked artifact once. | Immutable artifact identity | +| Test | Verify the built artifact and its version marker. | Verification evidence | +| Publish | Transfer the verified artifact and record to every required target. | Target confirmations | +| Record | Assemble durable release evidence and eligible aliases. | Completed release output | + +## Trigger and branch mapping + +The framework accepts three trigger classes: + +- **Reviewed change:** an approved change on a release line creates the normal + release candidate. +- **Direct source update:** an approved direct update is resolved with the same + policy and evidence requirements as a reviewed change. +- **Manual request:** an authorized request supplies an explicit source identity, + decision, and reviewed note context; it never bypasses resolution or testing. + +Release-line mapping declares one stable line and zero or more prerelease lines. +The stable line is the sole authority for stable versions. A prerelease line +maps to a stable version base plus a unique prerelease identifier and counter. +Promotion from an integration line to the stable line is a new stable intent: +it resolves from the latest stable version, carries the approved aggregate +change scope, and never promotes a prerelease identity in place. + +## Resolution and release intent + +Resolve evaluates artifact-affecting paths, approved metadata, and configured +policy. It rejects multiple version decisions, a `skip` decision combined with a +version decision, and an invalid default. Its strict order is: + +1. Validate the configured default, if present. +2. Reject conflicting decisions. +3. Honor an explicit `skip`. +4. Prefer one explicit `major`, `minor`, or `patch` decision. +5. Use the valid configured default. +6. Fail with a missing-decision error. + +The result is written as an immutable, durable release intent before Build. It +contains source identity; resolved decision and its source; stable or +prerelease mode; calculated version and authoritative version base; declared +artifact scope; reviewed note snapshot; required targets; and a unique release +key. The release key makes retries idempotent. ## Version computation -For PR-driven releases, the shared resolver reads the owned labels and optional -`DefaultBump` in `.github/release.config.yml`. The setting accepts `patch`, -`minor`, or `major`; omitting it does not supply a level. For example, this -configuration explicitly chooses patch releases when no bump label is provided: +Version computation reads only the frozen decision and the latest stable version +from the configured authority. It increments the major, minor, or patch +component according to the resolved decision. A prerelease derives its base from +that computed version and adds its configured identifier and monotonically +increasing counter. Prerelease versions never become the base for a stable +version calculation. -```yaml -# .github/release.config.yml -DefaultBump: patch -``` +For the first stable release, the version authority declares an initial stable +baseline. The framework records that baseline in the release intent so the +calculation remains explainable and retry-safe. -| Label | Meaning | Valid combination | -| --- | --- | --- | -| `release:patch` | Resolve the next patch version, overriding the configured default. | Alone or with `release:pre-release`. | -| `release:minor` | Resolve the next minor version, overriding the configured default. | Alone or with `release:pre-release`. | -| `release:major` | Resolve the next major version, overriding the configured default. | Alone or with `release:pre-release`. | -| `release:pre-release` | Publish the open pull request as a prerelease using the resolved bump. | With one explicit bump or a configured default; never with `release:skip`. | -| `release:skip` | Run validation without resolving or publishing a version. | Alone. | - -Resolve the decision in this order: - -1. Validate `DefaultBump` when present and reject conflicting owned labels. - An invalid setting is an error even when an explicit label is supplied. - 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 configured - `DefaultBump`. Record the chosen level and whether the label or setting - supplied it. -4. If neither supplies a level, fail with a missing-decision error that tells the - author to select a bump, configure the default, or choose `release:skip`. - There is no built-in patch fallback. Prerelease mode does not supply a bump. - -| PR input | `DefaultBump` | Decision-check result | -| --- | --- | --- | -| `release:major` | `patch` or absent | Pass: explicit major overrides the default. | -| No owned release labels | `minor` | Pass: configured minor; record the setting as the source. | -| No release decision | Absent | Fail: missing decision; merge blocked. | -| `release:skip` alone | Valid or absent | Pass: no release; no bump is required. | -| `release:pre-release` alone | `patch` | Pass: configured patch in prerelease mode. | -| `release:pre-release` alone | Absent | Fail: mode does not supply a bump; merge blocked. | -| Multiple bump labels, or skip with another owned release label | Any | Fail: conflicting decisions; no fallback. | -| Any | Invalid value | Fail: invalid configuration; no fallback. | - -- **First release** starts from a baseline (`v0.1.0` or `v1.0.0`). Pre-`1.0.0` - breaking changes are `release:minor` per [SemVer §4](https://semver.org/#spec-item-4); - `release:major` is never auto-detected pre-`1.0.0`. -- The tag is created on the commit now at the head of the release branch — - squash, merge-commit, and rebase strategies alike. - -### Required pre-merge decision check - -PR CI runs the resolver read-only against the candidate release settings 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 release branch, including changes -that will not publish. It re-runs when source, release labels, or release settings -change, 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 - -- **Branch-level** — a prerelease-type branch publishes on every push, using the - branch name as the identifier: `v1.3.0-dev.1`, `v1.3.0-dev.2`, … -- **PR-level** — `release:pre-release` with a resolved explicit or configured bump publishes - `v-.`: `base` is the next version from that bump, - `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). Release candidates use `-rc.N`, - auto-incrementing. -- **Cleanup** deletes prerelease tags, releases, and artifacts after the PR - closes (configurable); stable releases are never touched. - -## Path filtering - -`.github/release.config.yml` declares `release-paths` as ordered include/exclude -globs (excludes win). 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#detecting-the-change-type), -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. - -```yaml -release-paths: - - ".github/workflows/reusable.yml" # public caller contract - - ".github/actions/**" # this workflow's local implementation - - "src/**" -``` +## Required pre-merge decision check + +Before an approved change enters a release line, a read-only decision check +evaluates the same eligibility and decision rules as Resolve. The check reports +the effective decision or an actionable error for missing, invalid, or +conflicting inputs. It runs again when the source, decision metadata, policy, or +artifact scope changes. A valid `skip` is a successful no-release result. + +The branch's required-check policy makes this result a release gate. Release +execution repeats resolution against the actual source; the pre-merge check is +evidence, not a substitute for release-time validation. + +## Build, test, and publish + +Build receives only the frozen intent and emits one version-marked artifact plus +an immutable artifact identity. Test verifies that artifact without rebuilding +it. Publish accepts only a verified artifact identity and checks each target for +an existing matching version before writing. + +Required targets are bundled as one logical release. The framework stages +target confirmations and marks the release complete only after all required +targets succeed. A failed target leaves the intent recoverable. Recovery +replays only incomplete operations using the same version and artifact. If +source or artifact inputs changed, Resolve creates a new intent and version. + +## Scope, serialization, and aggregation + +Path filtering is a declared mapping from delivered artifact inputs and +consumer-facing contracts to release eligibility. Exclusions cannot override a +declared artifact input. Resolution still runs for every candidate so that +`skip`, missing decisions, and conflicts are visible. + +A serialization key combines the release line and artifact identity scope. +Later candidates queue behind an active release on that key. When intervening +changes arrive after a failure, an operator either resumes the original intent +or approves an aggregate intent that records every included change; the system +does not infer aggregation. ## 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#description-structure) as the -authoring contract. Summary, user-facing changes, adoption, release impact, -consumer change records, template evidence, and both ending details blocks stay -intact. There is no parallel JSON/YAML contract and no extraction of only the -user-facing headings. - -### 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 source comparison baseline; confirm that the consumer record - describes that delta. 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 | The release and immutable source against which the consumer delta is described, plus a source comparison link; explicitly no predecessor for an initial release. | -| 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. The target template identity and -compatibility evidence come from the authored record; a publisher does not -substitute the latest template or infer historical compatibility from current -documentation. - -### Release-bound records - -| 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 delta from the declared change baseline, not just the most recent feature PR. It links the contributing work as supporting evidence. | -| Optional ad hoc dispatch | Complete reviewed release-note context with the same adoption, consumer-change, template, and release-impact 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, -stop the affected publication and register the evidence gap. The process does -not substitute the newest note, guess a baseline, or treat an empty adoption -section 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. +Resolve snapshots the title and complete reviewed change description with the +immutable source identity. Publication adds provenance separately: version, +decision source, version base, artifact identity, target outcomes, and time. +The complete output consists of the stable or prerelease version marker, the +verified artifact, and a durable release record. ## 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 | -| --- | --- | --- | -| `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: - -```yaml -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false -``` - -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 — the stable branch is the only ref that ever cuts a production -release, and its runs are serialised like any other. +The completed record includes the version marker, immutable source and artifact +identities, verification evidence, complete reviewed note snapshot, decision +and version base, and confirmation from every required target. Alias updates +appear only after this record is complete. -## Configuration surface +## Logical configuration interface -| Surface | Where | +| Concept | Required behavior | | --- | --- | -| Release branches + type | `.github/release.config.yml` | -| Optional default bump | `DefaultBump` in `.github/release.config.yml` | -| Explicit bump / prerelease / skip | `release:` PR label | -| 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.config.yml` | -| Prerelease cleanup toggle | release config / workflow input | -| Publishing targets | reusable-workflow input + GitHub environment; see [Publishing Targets](design-publishing-targets.md) | - -## 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. +| Release-line map | Defines one stable authority and optional prerelease lines. | +| Decision policy | Declares valid explicit decisions and an optional strict default. | +| Artifact scope | Lists artifact-affecting inputs and consumer contracts. | +| Version authority | Names the one stable-version source. | +| Target set | Lists required target adapters and their conventions. | +| Alias policy | Enables only the closed stable alias set and its eligibility rules. | +| Recovery policy | Defines retention and approval for retry or aggregation. | + +## Related records + +- [Specification](spec.md) +- [Publishing Target Design](design-publishing-targets.md) diff --git a/src/docs/Capabilities/release-management/index.md b/src/docs/Capabilities/release-management/index.md index cf760a7..45ba790 100644 --- a/src/docs/Capabilities/release-management/index.md +++ b/src/docs/Capabilities/release-management/index.md @@ -1,23 +1,20 @@ --- title: Release Management -description: How a source change becomes a versioned, immutable artifact, driven entirely on the GitHub platform. +description: A generic framework for producing verified, immutable versioned release artifacts. --- # 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, normally driven by -pull-request labels. 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 an approved artifact-affecting change into a versioned, immutable, +verified artifact and durable release record. The framework resolves an explicit +release decision, builds once, and publishes safely to one or more targets. | Page | Description | | --- | --- | -| [Spec](spec.md) | Requirements for release management — automatic, policy-driven, versioned releases driven entirely on the GitHub platform. | -| [Design](design.md) | How release management is built — a shared reusable workflow that resolves an explicit or configured SemVer bump, builds once, and publishes. | -| [Publishing Targets](design-publishing-targets.md) | The contract every publishing destination documents, with GitHub Releases as the reference target. | +| [Spec](spec.md) | Testable requirements for a generic, policy-driven release-management framework. | +| [Logical Core Design](design.md) | The generic resolution, build, verification, and publication model for release management. | +| [Publishing Target Design](design-publishing-targets.md) | The generic adapter contract for publishing versioned release artifacts and records. | diff --git a/src/docs/Capabilities/release-management/spec.md b/src/docs/Capabilities/release-management/spec.md index 67579fd..1280641 100644 --- a/src/docs/Capabilities/release-management/spec.md +++ b/src/docs/Capabilities/release-management/spec.md @@ -1,98 +1,100 @@ --- title: Spec -description: Requirements for release management — automatic, policy-driven, versioned releases driven entirely on the GitHub platform. +description: Testable requirements for a generic, policy-driven release-management framework. --- -# 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. - -### Principles - -This capability rests on the [Principles](../../Ways-of-Working/Principles/index.md): - -- **[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 decision; a version-controlled repository default records the policy used when no level is supplied. -- **[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. - -## Scope - -Applies to any repository that produces a versioned artifact on merge to a -release branch. One test decides applicability: **does merging produce a -versioned, immutable output that something else consumes by version?** If yes, -this capability governs the release. If no, there is nothing to release. - -## Requirements - -- **Semantic versioning.** Versions follow [SemVer 2.0.0](https://semver.org/) (`vMAJOR.MINOR.PATCH`), derived automatically — never written by hand. -- **A resolved PR release decision.** The repository MAY configure `DefaultBump` as `patch`, `minor`, or `major`; invalid values MUST fail. Multiple owned bump labels, or `release:skip` combined with another owned release label, MUST fail. A valid `release:skip` MUST select no release without resolving a bump. For publishing decisions, one owned `release:patch`, `release:minor`, or `release:major` label MUST take precedence over the configured default; without a bump label, a valid `DefaultBump` MUST supply the level; without either source, automation MUST fail with a missing-decision error, never assume `patch`. `release:pre-release` MAY use either the explicit or configured bump; the mode alone does not supply a level. Bare or unrelated labels MUST be ignored. Conventional commit messages are **not** required. -- **A release per merge.** One eligible merged PR with a resolved bump to a release branch is one release, and the PR review gate is the release gate. `release:skip` validates without publishing. This pull-request path is the required release interface. -- **Decision validation blocks merge.** Every PR targeting a release branch 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-label, and release-settings 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. -- **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 prerelease MUST be obtainable from an open pull request carrying `release:pre-release`, using its explicit or configured bump, and/or from a prerelease branch. -- **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#description-structure); adoption and technical details MUST NOT be omitted or summarized away. -- **Only artifact-affecting changes release.** 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. Documentation and internal CI configuration qualify only when they meet that 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 - -- **Incremental consumer contract.** Every release MUST describe its consumer-facing delta against an identified release/source baseline, including applicability, exact actions, and verification, or explicit no-action evidence. Breaking behavior MUST be documented independently of its semantic-version classification. An applicable integration template MUST be identified by repository and verified compatible immutable commit, with producer-source compatibility evidence and linked template work or a justified no-change result. -- **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.** A bundled release's integration PR MUST cover every bundled consumer delta. 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 PR with an explicit or configured bump produces a GitHub Release, a git tag, and (where one exists) a published artifact, with no manual step. -- For a publishing PR, an explicit owned bump label overrides the configured default; without that label, a valid `DefaultBump` supplies the level and is recorded as its source. -- A PR with no explicit level, configured default, or valid no-release decision fails the required decision check and cannot merge. Invalid defaults and conflicting owned labels also block merge rather than selecting a fallback. -- Removing the only decision source or changing its inputs re-evaluates the PR check; a prior result does not validate different inputs. -- An open pull request carrying `release:pre-release` and an explicit or configured bump publishes a prerelease without promoting it to latest. -- The artifact that consumers download is byte-identical to the artifact that passed validation. -- A documentation-only merge carrying `release:skip` produces no new version but still runs its CI checks. -- 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 adoption, consumer/template evidence, and maintainer details. -- A consumer can identify the actual target, version base, change baseline, and any applicable 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. - -## 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 change-type labels that drive the bump. -- [Dependency Updates](../dependency-updates/spec.md) — update PRs are artifact-affecting and release through this capability. +# Release Management — Specification + +## Purpose and scope + +Release Management turns approved, artifact-affecting changes into versioned, +immutable artifacts that consumers can adopt safely. The framework applies to +any producer that distributes an artifact by version. It does not prescribe a +source-control provider, package ecosystem, branch name, or deployment +environment. + +## Functional requirements + +- **FR-001 Exactly-once eligible releases.** The framework MUST create one + release for each approved, eligible change set. Retrying the same release + intent MUST continue that release rather than create another one. +- **FR-002 Explicit release decision.** Each release intent MUST resolve to + `major`, `minor`, `patch`, or `skip`. An explicit valid decision takes + precedence over a configured default. The default policy MUST be explicit; + when it is absent, invalid, or inapplicable, resolution MUST fail rather than + infer a version change. Conflicting decisions MUST be rejected. +- **FR-003 No commit-message dependency.** Release eligibility and version + decisions MUST NOT require a commit-message convention. +- **FR-004 Stable version authority.** The framework MUST discover the current + stable version from one configured authoritative source. Prereleases MUST NOT + be treated as the current stable version. +- **FR-005 Prerelease lifecycle.** A prerelease MUST remain distinct from a + stable release and MUST NOT advance stable aliases. Cleanup MAY remove only + prereleases; it MUST NOT remove stable release identities. +- **FR-006 Frozen release intent.** Before building, the framework MUST record + a durable release intent containing the immutable source identity, resolved + decision, calculated version, release mode, change scope, and reviewed note + source. Retries MUST use this intent. +- **FR-007 Build once, publish verified bytes.** The framework MUST build each + artifact once after resolving its version, test that artifact, and publish + exactly the verified bytes. An artifact change requires a new version and + release intent. +- **FR-008 Atomic logical completion.** A release spanning required destinations + MUST complete only when every destination contains the same immutable artifact + and release record. A partial failure MUST remain recoverable by safely + retrying unfinished destinations with the frozen intent. +- **FR-009 Target conventions.** Every target MUST declare its version format, + prerelease representation, immutability behavior, withdrawal behavior, alias + behavior, and durable release-record convention before it participates in a + release. +- **FR-010 Immutable identities.** A stable version identity MUST be + single-use and immutable. Withdrawal MUST NOT make its version reusable. +- **FR-011 Complete release output.** Each completed release MUST provide a + version marker, the versioned artifact, and a durable release record that + identifies the source and artifact. +- **FR-012 Moving aliases.** A target MAY offer the closed alias set `latest`, + major, and minor. Only an eligible stable release MAY advance an alias, and an + alias MUST NOT move backwards or across its declared compatibility boundary. +- **FR-013 Artifact-based scope.** Eligibility MUST be calculated from + artifact-affecting inputs and supported consumer contracts, not from path + names alone. A non-eligible change MUST resolve to `skip`. +- **FR-014 Per-line serialization.** Releases for the same stable or prerelease + line MUST be serialized. An in-progress release MUST finish or enter explicit + recovery before a later intent on that line publishes. +- **FR-015 Recovery and aggregation.** After failure or intervening changes, the + framework MUST recover by resuming the frozen intent or create an explicitly + approved aggregate intent. It MUST NOT silently merge change sets or replace + an existing version. +- **FR-016 Reviewed release notes.** Release notes MUST preserve the complete + reviewed change description bound to the released source. Generated provenance + MAY accompany, but MUST NOT replace, that description. +- **FR-017 Consumer-update policy.** A consumer MAY select an update policy. + When selected, the framework MUST honor it and make its risk explicit. + Producers MUST support consumer adoption by immutable version identity. Where + consumers cannot express version bounds, producers MUST offer controlled + aliases that represent the supported bounds. + +## Non-functional requirements + +- **NFR-001 Zero configuration.** The framework MUST define strict behavior when + no optional configuration is supplied. Missing required release decisions + MUST fail clearly rather than use an implicit fallback. +- **NFR-002 SemVer compatibility.** Stable and prerelease versions, ordering, + and compatibility boundaries MUST conform to Semantic Versioning. +- **NFR-003 Extensible targets and artifacts.** Adding an artifact type or + publishing target MUST require only its declared adapter and conventions, not + a change to this specification. +- **NFR-004 Shared behavior.** Resolution, intent persistence, serialization, + recovery, artifact verification, and release recording MUST each be defined + once and reused by all target adapters. + +## Acceptance criteria + +The framework satisfies this specification when every requirement can be +verified through a release decision, durable intent, artifact identity, target +record, and recovery outcome for the same release. + +## Related designs + +- [Logical Core Design](design.md) +- [Publishing Target Design](design-publishing-targets.md)