From 03f750118622e014df21e1c01514f34ff4bff188 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 28 Aug 2026 21:46:01 +0200 Subject: [PATCH 1/4] Namespace release management labels Standardize the release:patch, release:minor, release:major, release:pre-release, and release:skip contract. Keep release decisions separate from Dependabot's bare SemVer labels. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Capabilities/dependency-updates/design.md | 22 ++++---- .../Capabilities/release-management/design.md | 55 ++++++++++++------- .../Capabilities/release-management/spec.md | 16 +++--- .../vscode-extension-framework/design.md | 16 +++--- .../vscode-extension-framework/spec.md | 2 +- src/docs/Coding-Standards/Natural-Language.md | 4 +- src/docs/Ways-of-Working/Automation-Labels.md | 41 ++++++++------ src/docs/Ways-of-Working/PR-Format.md | 8 ++- 8 files changed, 97 insertions(+), 67 deletions(-) diff --git a/src/docs/Capabilities/dependency-updates/design.md b/src/docs/Capabilities/dependency-updates/design.md index 1117083..51478a4 100644 --- a/src/docs/Capabilities/dependency-updates/design.md +++ b/src/docs/Capabilities/dependency-updates/design.md @@ -5,9 +5,9 @@ description: How dependency updates are built — Dependabot update PRs, a label # Dependency Updates — Design -The behaviour in the [spec](spec.md) is delivered by the platform-native updater -([Dependabot](https://docs.github.com/code-security/dependabot)) configured in -`.github/dependabot.yml`, plus a small labelling and auto-merge layer. +The baseline updater for dependency management on GitHub is +[Dependabot](https://docs.github.com/code-security/dependabot), configured in +`.github/dependabot.yml` and extended by a small labeling and auto-merge layer. ## What gets checked @@ -110,7 +110,7 @@ bare words: | Dimension | Question | Label set | Owned by | | --- | --- | --- | --- | -| **Release bump** | How much does *this repository's* version change? | `release:major` · `release:minor` · `release:patch` · `release:none` | [Release Management](../release-management/spec.md) | +| **Release decision** | Does *this repository* release, by how much, and in which mode? | `release:patch` · `release:minor` · `release:major` · `release:pre-release` · `release:skip` | [Release Management](../release-management/spec.md) | | **Dependency update level** | How much did the *upstream dependency* change? | `update:major` · `update:minor` · `update:patch` | This capability | A dependency update is an **artifact-affecting change**, so merging it produces a @@ -122,17 +122,19 @@ repository's version and is the label the release workflow reads; the **`update: is advisory metadata that drives review routing, never the bump. Namespacing both sides is what makes this hold in practice rather than by convention. -Hosted Dependabot applies a SemVer label to its own pull requests **when a repository has -labels named `major`, `minor`, or `patch`** — it matches on those bare words. Had the -release set kept the bare vocabulary, every Dependabot pull request would arrive with the +Dependabot's +[pull-request labeler](https://github.com/dependabot/dependabot-core/blob/main/common/lib/dependabot/pull_request_creator/labeler.rb) +applies one of `major`, `minor`, or `patch` when all three bare labels exist. Had the +release set kept that vocabulary, a Dependabot pull request would arrive with the repository's own version decision pre-set by a bot, describing the upstream bump. Because no bare label exists, Dependabot finds nothing to apply, and a dependency pull request is release-safe by default: it carries an accurate `update:*` level and no release decision until a maintainer makes one. -The `skip-release` workaround sometimes suggested for this is a **no-op on hosted -Dependabot**; the labelling behavior cannot be configured from the repository. Which label -names exist is the only control, which is why both dimensions are namespaced. +Dependabot recognizes a bare `skip-release` label as a repository-level compatibility +sentinel that suppresses its SemVer labels. MSX does not depend on that special case: +`release:skip` is an owned release-management instruction, and structural namespacing +keeps both dimensions safe without reserving another bare label. ## Review posture diff --git a/src/docs/Capabilities/release-management/design.md b/src/docs/Capabilities/release-management/design.md index ae59dec..46aa230 100644 --- a/src/docs/Capabilities/release-management/design.md +++ b/src/docs/Capabilities/release-management/design.md @@ -71,11 +71,23 @@ Two consequences follow, and they are the point of the model: ## Version computation -- The bump comes from the PR label (`release:major` / `release:minor` / `release:patch` / `release:none`). - Exactly one is required; **no default** is applied. A missing label, multiple - SemVer labels, or a SemVer label alongside `release:none` are all **rejected**, so - the version is always a decision someone made. For `workflow_dispatch`, the - bump is an input. +Release automation reads only labels in its `release:` namespace: + +| Label | Meaning | Valid combination | +| --- | --- | --- | +| `release:patch` | Resolve the next patch version. | Exactly one bump label. | +| `release:minor` | Resolve the next minor version. | Exactly one bump label. | +| `release:major` | Resolve the next major version. | Exactly one bump label. | +| `release:pre-release` | Publish the open pull request as a prerelease. | With exactly one bump label. | +| `release:skip` | Run validation without resolving or publishing a version. | Alone. | + +Exactly one bump label or `release:skip` is required; **no default** is applied. +`release:pre-release` is an optional mode label, not a bump. A missing decision, +multiple bump labels, `release:skip` with another release label, or +`release:pre-release` without one bump label is **rejected**, so the outcome is +always a decision someone made. Bare `major`, `minor`, and `patch` labels are +ignored. For `workflow_dispatch`, the same decision is an input. + - **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`. @@ -84,24 +96,27 @@ Two consequences follow, and they are the point of the model: ### Why the labels are namespaced -The bump vocabulary is namespaced under `release:` rather than using the bare words -`Major`, `Minor`, and `Patch`, and the reason is concrete rather than cosmetic. +The release vocabulary is namespaced under `release:` rather than using bare words, +and the reason is concrete rather than cosmetic. -Hosted Dependabot applies a SemVer label to its own pull requests **when a repository has -labels named `major`, `minor`, or `patch`**. It matches on those bare words. In a -repository where the release bump set is unprefixed, an upstream patch bump therefore -arrives already carrying a label that this workflow reads as the repository's own version -decision — set by a bot, describing something else entirely, with nobody having decided it. +Dependabot is the baseline updater for dependencies managed on GitHub. Its +[pull-request labeler](https://github.com/dependabot/dependabot-core/blob/main/common/lib/dependabot/pull_request_creator/labeler.rb) +applies one of the bare `major`, `minor`, or `patch` labels when all three exist. +In a repository where the release bump set is unprefixed, an upstream patch bump +therefore arrives already carrying a label that this workflow reads as the +repository's own version decision — set by a bot, describing something else +entirely, with nobody having decided it. Namespacing removes the collision at its source. There is no bare `major`, `minor`, or `patch` label for Dependabot to find, so a dependency pull request arrives with **no** -release decision attached, fails closed like any other unlabelled pull request, and a +release decision attached, fails closed like any other unlabeled pull request, and a maintainer makes the call at the pull request gate. -Do not reach for a suppression workaround instead. The `skip-release` label and the -equivalent configuration flag are a **no-op on hosted Dependabot** — the labelling -behavior is not configurable from the repository. The only lever a repository actually has -is which label names exist, so that is the lever this design pulls. +Dependabot suppresses its SemVer labels when a repository contains the bare +`skip-release` compatibility label. This design does not rely on that special case. +It removes the collision structurally by not provisioning bare bump labels. +`release:skip` remains an owned instruction to release management and has no +Dependabot meaning. See [Automation Labels](../../Ways-of-Working/Automation-Labels.md#every-set-is-namespaced) for the general rule. @@ -110,9 +125,9 @@ for the general rule. - **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** — a prerelease label on an open PR publishes +- **PR-level** — `release:pre-release` alongside exactly one bump label on an open PR publishes `v-.`: `base` is the next version from the PR's bump - label, `identifier` is the normalised branch name, and `counter` + label, `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`, @@ -236,7 +251,7 @@ release, and its runs are serialised like any other. | Surface | Where | | --- | --- | | Release branches + type | `.github/release.config.yml` | -| Bump label / prerelease / RC | PR label, or `workflow_dispatch` input | +| Release decision / prerelease / RC | `release:` PR label, or `workflow_dispatch` input | | 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) | diff --git a/src/docs/Capabilities/release-management/spec.md b/src/docs/Capabilities/release-management/spec.md index 62829a3..49ab116 100644 --- a/src/docs/Capabilities/release-management/spec.md +++ b/src/docs/Capabilities/release-management/spec.md @@ -31,15 +31,15 @@ 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. -- **Label-driven bump, stated explicitly.** The bump level is a pull-request label — `release:major` / `release:minor` / `release:patch` / `release:none`. Exactly one bump label MUST be present, and there is **no default**: an unlabelled pull request is not releasable, and the release fails closed rather than assuming the smallest bump. Requiring the label makes the versioning decision a reviewed decision instead of an omission. Conventional commit messages are **not** required. -- **A release per merge.** One merged PR to a release branch is one release, and the PR review gate is the release gate. Direct pushes and manual dispatch also release. +- **Namespaced release decision, stated explicitly.** Release automation reads only the `release:` namespace. Exactly one of `release:patch`, `release:minor`, `release:major`, or `release:skip` MUST be present, and there is **no default**: an unlabeled pull request is not releasable, and the release fails closed rather than assuming the smallest bump. `release:pre-release` MAY accompany exactly one bump label on an open pull request and MUST NOT be combined with `release:skip`. Bare `patch`, `minor`, and `major` labels are not release decisions. Requiring an owned label makes versioning a reviewed decision instead of an omission. Conventional commit messages are **not** required. +- **A release per merge.** One merged PR carrying `release:patch`, `release:minor`, or `release:major` to a release branch is one release, and the PR review gate is the release gate. `release:skip` validates without publishing. Direct pushes and manual dispatch MUST provide the same release decision explicitly. - **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 and/or from a prerelease branch. +- **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` and a bump label 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 is assembled from material the contributor already wrote (PR title + description, or commit message, or collected history). The PR description is therefore written for consumers. -- **Only artifact-affecting changes release.** A change that does not flow into the artifact (documentation, CI config) MUST NOT produce a release — though validation still runs on every merge. +- **Only artifact-affecting changes release.** A change that does not flow into the artifact (documentation, CI config) MUST carry `release:skip` and MUST NOT produce a release — though validation still runs on every merge. - **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. @@ -62,10 +62,11 @@ Because versions are semantic, immutable, and published once, a consumer can ado ## Success criteria -- Merging a labelled PR to a release branch produces a GitHub Release, a git tag, and (where one exists) a published artifact, with no manual step. -- The version bump matches the PR's label every time; a missing, conflicting, or ambiguous label set is **rejected**, never guessed. +- Merging a PR with exactly one namespaced bump label to a release branch produces a GitHub Release, a git tag, and (where one exists) a published artifact, with no manual step. +- The version bump matches the PR's `release:` label every time; a missing, conflicting, ambiguous, or bare label set is **rejected**, never guessed. +- An open pull request carrying `release:pre-release` and exactly one bump label 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 produces no new version but still runs its CI checks. +- 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. @@ -76,5 +77,6 @@ Because versions are semantic, immutable, and published once, a consumer can ado - [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. diff --git a/src/docs/Capabilities/vscode-extension-framework/design.md b/src/docs/Capabilities/vscode-extension-framework/design.md index e6df17c..ca05cf4 100644 --- a/src/docs/Capabilities/vscode-extension-framework/design.md +++ b/src/docs/Capabilities/vscode-extension-framework/design.md @@ -98,16 +98,16 @@ pass before the release stage runs, alongside a green test result. Versioning is [Release Management](../release-management/design.md) applied to a VSIX artifact — this framework does not re-implement it: -- The bump is the PR label (`release:major` / `release:minor` / `release:patch` / `release:none`, - defaulting to `release:patch`); multiple SemVer labels, or a SemVer label with - `release:none`, are rejected. +- The release decision is exactly one of `release:patch`, `release:minor`, + `release:major`, or `release:skip`, with no default. Multiple bump labels and + `release:skip` with another release label are rejected. - The version is computed once and stamped into the manifest; it is never hand-edited. -- A prerelease is requested by a `Prerelease` label on an open pull request (or - a prerelease branch), producing a prerelease VSIX that is never promoted to - latest. When such a build is also published to the VS Code Marketplace, it goes - out with `@vscode/vsce publish --pre-release` and an odd minor-version number, - the Marketplace's pre-release-channel convention. +- A prerelease is requested by `release:pre-release` alongside one bump label on + an open pull request (or by a prerelease branch), producing a prerelease VSIX + that is never promoted to latest. When such a build is also published to the + VS Code Marketplace, it goes out with `@vscode/vsce publish --pre-release` and + an odd minor-version number, the Marketplace's pre-release-channel convention. ## Publishing and distribution diff --git a/src/docs/Capabilities/vscode-extension-framework/spec.md b/src/docs/Capabilities/vscode-extension-framework/spec.md index cfb5f8b..8e73c1a 100644 --- a/src/docs/Capabilities/vscode-extension-framework/spec.md +++ b/src/docs/Capabilities/vscode-extension-framework/spec.md @@ -60,7 +60,7 @@ framework itself. - **Tested on a real host.** The extension is tested against a real VS Code host, across the host versions and operating systems the extension declares as supported. Tests exercise the exact bundle that ships — never a separately compiled copy. - **A static quality gate.** Every change is linted and type-checked, and the pipeline holds on any error. Quality is validated at pull-request time, not after merge. - **Built once, shipped once.** The version is computed once, stamped into the manifest, and the same packaged VSIX is what is tested and what is published. Build, test, and release MUST NOT diverge. -- **Label-driven, semantic versioning.** Versioning follows [Release Management](../release-management/spec.md): the bump is a pull-request label (`release:major` / `release:minor` / `release:patch` / `release:none`, defaulting to `release:patch`), the version is [SemVer](https://semver.org/), and it is derived automatically — never hand-edited in the manifest. +- **Label-driven, semantic versioning.** Versioning follows [Release Management](../release-management/spec.md): exactly one of `release:patch`, `release:minor`, `release:major`, or `release:skip` records the release decision with no default; `release:pre-release` MAY accompany one bump label. The version is [SemVer](https://semver.org/) and is derived automatically — never hand-edited in the manifest. - **An installable artifact on every release.** Each release produces an installable VSIX attached to its [GitHub Release](https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases), together with an immutable reference. A user MUST be able to install a specific released version without a marketplace account. - **Optional marketplace publication.** Where configured, the same VSIX is also published to an extension marketplace (the VS Code Marketplace and/or Open VSX). Marketplace publication is opt-in and MUST NOT be a prerequisite for the GitHub-Release install path. - **A prerelease from an open pull request.** A prerelease VSIX MUST be obtainable from an open pull request for testing before merge, without being promoted to the latest stable version. diff --git a/src/docs/Coding-Standards/Natural-Language.md b/src/docs/Coding-Standards/Natural-Language.md index 5d9f4b1..3e947fc 100644 --- a/src/docs/Coding-Standards/Natural-Language.md +++ b/src/docs/Coding-Standards/Natural-Language.md @@ -76,7 +76,7 @@ Prefer specific, observable language over vague intent. | Prefer | Avoid | | --- | --- | | The workflow fails when the version label is missing. | There may be some issues with labels. | -| Add `release:none` to documentation-only PRs. | Make sure docs PRs are handled correctly. | +| Add `release:skip` to documentation-only PRs. | Make sure docs PRs are handled correctly. | | The agent reads `AGENTS.md` before editing files. | The agent should probably look at the instructions. | Use **MUST**, **SHOULD**, and **MAY** only when a sentence is intentionally normative. If a rule is optional, say what trade-off decides it. @@ -155,7 +155,7 @@ A good error message includes: Prefer: ```text -Release label is missing. Add exactly one of release:major, release:minor, release:patch, or release:none. +Release label is missing. Add exactly one of release:patch, release:minor, release:major, or release:skip. Add release:pre-release only with a bump label. ``` Avoid: diff --git a/src/docs/Ways-of-Working/Automation-Labels.md b/src/docs/Ways-of-Working/Automation-Labels.md index 879934c..1de760d 100644 --- a/src/docs/Ways-of-Working/Automation-Labels.md +++ b/src/docs/Ways-of-Working/Automation-Labels.md @@ -51,16 +51,21 @@ pull request, and only a namespace keeps them apart ## Every set is namespaced There is no reserved unprefixed vocabulary. Every label set an automation reads is -namespaced, including the release bump set: +namespaced, including the release set: -```text -release:major release:none -release:minor -release:patch -``` +| Label | Instruction | +| --- | --- | +| `release:patch` | Publish a patch release. | +| `release:minor` | Publish a minor release. | +| `release:major` | Publish a major release. | +| `release:pre-release` | Publish a prerelease from the open pull request. | +| `release:skip` | Validate the change without publishing a release. | -`release:major`, `release:minor`, `release:patch`, and `release:none` are read by +These labels are read by [release management](../Capabilities/release-management/spec.md) and by nothing else. +Exactly one bump label or `release:skip` records the release decision. +`release:pre-release` is an optional mode used with exactly one bump label, never +with `release:skip`. Reserving bare words would be the weaker mechanism, because it depends on a documented prohibition rather than on the label's own name — and for the release set specifically, @@ -68,21 +73,23 @@ the prohibition is not the only thing at stake. ### Why the release set in particular -Hosted Dependabot applies a SemVer label to its own pull requests **when a repository -has labels named `major`, `minor`, or `patch`**. It matches on those bare words. A -repository that reserves the bare bump vocabulary for release management therefore hands -Dependabot the ability to set the repository's next published version as a side effect of -an upstream patch bump — with no human deciding it. +Dependabot is the baseline updater for dependencies managed on GitHub across MSX. +Its +[pull-request labeler](https://github.com/dependabot/dependabot-core/blob/main/common/lib/dependabot/pull_request_creator/labeler.rb) +applies one of `major`, `minor`, or `patch` when all three bare labels exist in a +repository. A repository that reserves that bare vocabulary for release management +therefore hands Dependabot the ability to set the repository's next published version +as a side effect of an upstream bump — with no human deciding it. Namespacing removes the collision at the source. Dependabot finds no bare `major`, `minor`, or `patch` label to apply, so its pull requests arrive with no release decision attached and a maintainer makes that call at the pull request gate like any other. -There is a documented workaround for suppressing Dependabot's labelling, sometimes -written as a `skip-release` label or a configuration flag. On **hosted** Dependabot it is -a no-op: the labelling behavior is not configurable from the repository, so the only -control a repository actually has is which label names exist. Namespacing is that -control. +Dependabot suppresses those SemVer labels when a repository contains a bare +`skip-release` label. MSX does not depend on that Dependabot-specific compatibility +sentinel: release safety comes from not provisioning the bare bump labels at all. +`release:skip` is a different label with a different owner — it tells release +management not to publish this change. ## Automation ignores what it does not own diff --git a/src/docs/Ways-of-Working/PR-Format.md b/src/docs/Ways-of-Working/PR-Format.md index 2b1b834..04054be 100644 --- a/src/docs/Ways-of-Working/PR-Format.md +++ b/src/docs/Ways-of-Working/PR-Format.md @@ -40,8 +40,12 @@ Pull requests in the MSX ecosystem double as **release notes**. The description | Feature | 🚀 | `release:minor` | New features or enhancements | | Patch | 🩹 | `release:patch` | Small fixes or improvements | | Fix | 🪲 | `release:patch` | Bugfixes (patch-level release impact) | -| Docs | 📖 | `release:none` | Documentation changes only | -| Maintenance | ⚙️ | `release:none` | CI/CD, build configs, AI/agent files, internal upkeep | +| Docs | 📖 | `release:skip` | Documentation changes only | +| Maintenance | ⚙️ | `release:skip` | CI/CD, build configs, AI/agent files, internal upkeep | + +`release:pre-release` is a release mode, not a change type. Apply it alongside +exactly one of `release:patch`, `release:minor`, or `release:major` when an open +pull request must publish a prerelease. Never combine it with `release:skip`. ### Detecting the change type From f061b18d8331d8f383cf9ecf6f812ece437cb599 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 28 Aug 2026 21:54:18 +0200 Subject: [PATCH 2/4] Use American spelling in VS Code release criteria Address the review finding by changing labelled to labeled in the touched release guidance. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/docs/Capabilities/vscode-extension-framework/spec.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/docs/Capabilities/vscode-extension-framework/spec.md b/src/docs/Capabilities/vscode-extension-framework/spec.md index 8e73c1a..48b747f 100644 --- a/src/docs/Capabilities/vscode-extension-framework/spec.md +++ b/src/docs/Capabilities/vscode-extension-framework/spec.md @@ -72,7 +72,7 @@ framework itself. ## Success criteria - Creating a repository from the template and pushing a first change yields a green build, a passing test run, and a packaged VSIX with no configuration written. -- A labelled pull request merged to a release branch produces a GitHub Release carrying an installable VSIX whose version matches the label's bump — a conflicting or ambiguous label set is rejected, never guessed. +- A labeled pull request merged to a release branch produces a GitHub Release carrying an installable VSIX whose version matches the label's bump — a conflicting or ambiguous label set is rejected, never guessed. - The tests that gate the release exercise the exact VSIX that is released, on every supported host version and operating system. - A documentation-only or CI-only change runs its checks but produces no new version. - A user installs any released version straight from its GitHub Release with no marketplace account; where marketplace publishing is enabled, that same version also appears in the marketplace. From 22aedc9911bc3dc516d88bb855dbda71158165a0 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 29 Aug 2026 10:18:01 +0200 Subject: [PATCH 3/4] Make ad hoc release dispatch optional Keep labeled pull request merges as the required release path. Allow workflow dispatch only as an explicit optional extension and reject empty pull requests as release triggers. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Capabilities/release-management/design.md | 32 +++++++++++++++---- .../Capabilities/release-management/index.md | 7 ++-- .../Capabilities/release-management/spec.md | 7 ++-- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/docs/Capabilities/release-management/design.md b/src/docs/Capabilities/release-management/design.md index 46aa230..4deb49a 100644 --- a/src/docs/Capabilities/release-management/design.md +++ b/src/docs/Capabilities/release-management/design.md @@ -86,7 +86,7 @@ Exactly one bump label or `release:skip` is required; **no default** is applied. multiple bump labels, `release:skip` with another release label, or `release:pre-release` without one bump label is **rejected**, so the outcome is always a decision someone made. Bare `major`, `minor`, and `patch` labels are -ignored. For `workflow_dispatch`, the same decision is an input. +ignored. - **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); @@ -94,6 +94,25 @@ ignored. For `workflow_dispatch`, the same decision is an input. - The tag is created on the commit now at the head of the release branch — squash, merge-commit, and rebase strategies alike. +### Optional ad hoc releases + +The standard release path is a labeled pull request 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, release-note +summary, 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). + ### Why the labels are namespaced The release vocabulary is namespaced under `release:` rather than using bare words, @@ -153,10 +172,10 @@ release-paths: ## Release notes -The GitHub Release **name** is the version; the **body** depends on the trigger: -`# ` + description (merged PR), `# ` + remainder -(direct push), or `# ` + collected history (dispatch). The same note is -handed to [Downstream Release Propagation](../downstream-release-propagation/design.md). +The GitHub Release **name** is the version. Its **body** is the pull request title +and description for the standard merge path, or the required release-note summary +for an optional ad hoc dispatch. The same note is handed to +[Downstream Release Propagation](../downstream-release-propagation/design.md). ## Release output @@ -251,7 +270,8 @@ release, and its runs are serialised like any other. | Surface | Where | | --- | --- | | Release branches + type | `.github/release.config.yml` | -| Release decision / prerelease / RC | `release:` PR label, or `workflow_dispatch` input | +| Release decision / prerelease / RC | `release:` PR label | +| 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) | diff --git a/src/docs/Capabilities/release-management/index.md b/src/docs/Capabilities/release-management/index.md index cc4d98a..de03cc5 100644 --- a/src/docs/Capabilities/release-management/index.md +++ b/src/docs/Capabilities/release-management/index.md @@ -7,9 +7,10 @@ description: How a source change becomes a versioned, immutable artifact, driven 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 entirely by -pull-request labels. No release CLI, no hand-edited version file, no tagging -ritual. +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. diff --git a/src/docs/Capabilities/release-management/spec.md b/src/docs/Capabilities/release-management/spec.md index 49ab116..1f5252f 100644 --- a/src/docs/Capabilities/release-management/spec.md +++ b/src/docs/Capabilities/release-management/spec.md @@ -32,19 +32,20 @@ this capability governs the release. If no, there is nothing to release. - **Semantic versioning.** Versions follow [SemVer 2.0.0](https://semver.org/) (`vMAJOR.MINOR.PATCH`), derived automatically — never written by hand. - **Namespaced release decision, stated explicitly.** Release automation reads only the `release:` namespace. Exactly one of `release:patch`, `release:minor`, `release:major`, or `release:skip` MUST be present, and there is **no default**: an unlabeled pull request is not releasable, and the release fails closed rather than assuming the smallest bump. `release:pre-release` MAY accompany exactly one bump label on an open pull request and MUST NOT be combined with `release:skip`. Bare `patch`, `minor`, and `major` labels are not release decisions. Requiring an owned label makes versioning a reviewed decision instead of an omission. Conventional commit messages are **not** required. -- **A release per merge.** One merged PR carrying `release:patch`, `release:minor`, or `release:major` to a release branch is one release, and the PR review gate is the release gate. `release:skip` validates without publishing. Direct pushes and manual dispatch MUST provide the same release decision explicitly. +- **A release per merge.** One merged PR carrying `release:patch`, `release:minor`, or `release:major` 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. +- **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` and a bump label 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 is assembled from material the contributor already wrote (PR title + description, or commit message, or collected history). The PR description is therefore written for consumers. +- **Notes from the contributor's own words.** The GitHub Release name is the version; its body comes from the pull request title and description, or from the required release-note context of an optional ad hoc dispatch. The PR description is therefore written for consumers. - **Only artifact-affecting changes release.** A change that does not flow into the artifact (documentation, CI config) MUST carry `release:skip` and MUST NOT produce a release — though validation still runs on every merge. - **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 workflow dispatch — no external tooling beyond `gh` and GitHub Actions. +- **Standard GitHub primitives only.** Pull requests, labels, comments, and, where implemented, workflow dispatch — no external tooling beyond `gh` and GitHub Actions. ### Consumer update policies From f74d5fd6ae401c2f394ee6273b9cafc68d4efa90 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 29 Aug 2026 10:24:57 +0200 Subject: [PATCH 4/4] Require skip label for non-release extension changes Align the VS Code framework success criterion with the explicit release decision contract. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/docs/Capabilities/vscode-extension-framework/spec.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/docs/Capabilities/vscode-extension-framework/spec.md b/src/docs/Capabilities/vscode-extension-framework/spec.md index 48b747f..2e99954 100644 --- a/src/docs/Capabilities/vscode-extension-framework/spec.md +++ b/src/docs/Capabilities/vscode-extension-framework/spec.md @@ -74,7 +74,7 @@ framework itself. - Creating a repository from the template and pushing a first change yields a green build, a passing test run, and a packaged VSIX with no configuration written. - A labeled pull request merged to a release branch produces a GitHub Release carrying an installable VSIX whose version matches the label's bump — a conflicting or ambiguous label set is rejected, never guessed. - The tests that gate the release exercise the exact VSIX that is released, on every supported host version and operating system. -- A documentation-only or CI-only change runs its checks but produces no new version. +- A documentation-only or CI-only change carrying `release:skip` runs its checks but produces no new version. - A user installs any released version straight from its GitHub Release with no marketplace account; where marketplace publishing is enabled, that same version also appears in the marketplace. - An open pull request can publish a prerelease VSIX for testing that never becomes the latest stable version, and it is cleaned up when the pull request closes. - Adopting the framework in a new extension is a short caller plus a settings file — the pipeline itself is never copied into the repository.