Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions src/docs/Capabilities/dependency-updates/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down
55 changes: 35 additions & 20 deletions src/docs/Capabilities/release-management/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand All @@ -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.
Expand All @@ -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>-<identifier>.<counter>`: `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`,
Expand Down Expand Up @@ -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) |
Expand Down
16 changes: 9 additions & 7 deletions src/docs/Capabilities/release-management/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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.
16 changes: 8 additions & 8 deletions src/docs/Capabilities/vscode-extension-framework/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading
Loading