Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ A target is described by six answers. They are the questions the release process
| **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 |
| **Sliding tags** | whether the target supports mutable pointers such as `latest`, and how they are moved |
| **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 |

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 summary

| Target | Version scheme | Prerelease | Immutable | Unpublish | Sliding tags | Release record |
| 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 |
Expand All @@ -47,7 +47,7 @@ GitHub Releases is the reference implementation: every repository governed by th
- **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.
- **Sliding tags.** Supported as additional git tags, subject to the [sliding-tag rules](design.md#sliding-tags).
- **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.

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*.
Expand Down
25 changes: 16 additions & 9 deletions src/docs/Capabilities/release-management/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ 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, sliding-tag support, and where its release record lives — in
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.
Expand All @@ -181,9 +181,9 @@ a version:
version. It never resolves a new version to work around a single failed target,
because the targets that already succeeded hold that immutable version.

## Sliding tags
## Floating tags

Sliding tags are optional, mutable pointers published alongside the immutable
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 |
Expand All @@ -194,13 +194,20 @@ version tag, for consumers that want to track a line rather than a point:

Three rules keep them safe:

- **Prereleases never move a sliding tag.** Only a stable release advances one,
so a sliding tag never points at something not promoted for adoption.
- **A sliding tag never moves backwards.** It only advances, so a consumer
- **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.
- **Sliding tags are conveniences, not references.** They are how a consumer
*finds* a version, not how one **pins** to it; anything requiring
reproducibility pins to the immutable version, digest, or SHA
- **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
Expand Down
2 changes: 1 addition & 1 deletion src/docs/Capabilities/vscode-extension-framework/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ and ref with `cancel-in-progress` disabled, inherited from the
[GitHub Actions standard](../../Coding-Standards/GitHub-Actions.md#concurrency).
Pull-request validation runs, by contrast, use a per-branch concurrency group
with `cancel-in-progress` enabled, so a new push supersedes stale in-flight runs.
Every Action is pinned to a commit SHA.
Every external Action is pinned to a commit SHA; organization- or initiative-owned automation may use the [controlled major-tag exception](../../Coding-Standards/GitHub-Actions.md#pin-actions-according-to-ownership).

## Configuration surface

Expand Down
18 changes: 10 additions & 8 deletions src/docs/Coding-Standards/Dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: How dependencies are pinned and kept current — the locking spectr

Every consumed dependency — a PowerShell module, a GitHub Action, a container base image, a .NET package, a Terraform provider — is both a convenience and part of the [attack surface](Security.md#supply-chain). Depending on one is a single decision made twice: **how tightly to pin it** (how much version drift is acceptable) and **how it moves forward** (how new versions arrive). Getting the balance wrong in either direction has a cost.

This is the ecosystem-agnostic standard; the per-tool standards apply it. [PowerShell → Version Constraints](PowerShell/Version-Constraints.md) expresses it for modules and packages, and [GitHub Actions → Pin every action to a full commit SHA](GitHub-Actions.md#pin-every-action-to-a-full-commit-sha) expresses it for Actions and images. The [Dependency Updates](../Capabilities/dependency-updates/index.md) capability is the automation that keeps pins current.
This is the ecosystem-agnostic standard; the per-tool standards apply it. [PowerShell → Version Constraints](PowerShell/Version-Constraints.md) expresses it for modules and packages, and [GitHub Actions → Pin actions according to ownership](GitHub-Actions.md#pin-actions-according-to-ownership) expresses it for Actions and reusable workflows. The [Dependency Updates](../Capabilities/dependency-updates/index.md) capability is the automation that keeps pins current.

## Two decisions, two axes

Expand All @@ -27,6 +27,7 @@ From tightest to loosest, each step trades safety for speed:
| Lock | What can change | Update velocity | Supply-chain exposure | Reproducible |
| --- | --- | --- | --- | --- |
| **Identity + exact** (`GUID` / SHA / digest, exact version) | Nothing until you re-pin | Only via a reviewed re-pin PR | **Lowest** — nothing lands unvetted | Yes |
| **Controlled owned major** (`@v8`) | Compatible stable releases from organization- or initiative-owned automation | Patch and minor releases roll out centrally | Low when only controlled release automation can move the tag | No |
| **Patch** (`x.y.*`) | Fix-level releases | Fixes flow in | Low | With a lockfile |
| **Minor** (`x.*`) | Additive features and fixes | Features and fixes flow in | Moderate | With a lockfile |
| **Major** (floor only, `>= x`) | Anything from the floor up, including breaking releases | Everything flows in | Higher | With a lockfile |
Expand All @@ -39,13 +40,13 @@ Both ends are a risk; the standard is to avoid living at either extreme.
- **Too loose** (toward latest) maximizes velocity but hands control to the upstream. A newly published version — including a **compromised** one — runs before anyone reviews it, and the build stops being reproducible because two runs resolve different code. This is the classic supply-chain attack path: a malicious release, or a taken-over package, that lands automatically because nothing gated it.
- **Too tight** (a bare exact pin, never moved) maximizes control but rots. The dependency keeps shipping bug and **security** fixes you never take; a disclosed advisory turns yesterday's safe pin into today's vulnerability, and now the exact pin is the very thing stopping you from patching fast enough.

The resolution is not to pick a point and freeze — it is to **pin for integrity and automate the movement**:
The resolution is not to pick a point and freeze — it is to **control integrity and automate the movement**:

1. **Pin tightly** — an identity pin plus a deliberate version, or a lockfile — so every build is reproducible and nothing changes unvetted.
1. **Pin tightly by default** — an identity pin plus a deliberate version, or a lockfile — so every build is reproducible and nothing changes unvetted. A controlled owned-major tag is the narrow exception for automation whose release path the organization or initiative operates.
2. **Automate updates** so currency never depends on a human watching upstream: the [Dependency Updates](../Capabilities/dependency-updates/index.md) bot opens one reviewed pull request per bump.
3. **Gate every update through CI and review** — patch and minor updates auto-merge on green checks; **major** updates require a human; security advisories are raised out of band and prioritized.

Tight pinning is safe *because* the updates are automated: the bot closes the currency gap and CI plus review close the vetting gap. You get the reproducibility of an exact pin **and** the patch velocity of a loose range, without the unvetted drift of either.
Tight pinning is safe *because* the updates are automated: the bot closes the currency gap and CI plus review close the vetting gap. A controlled owned-major tag applies the same principle at the producer: compatible releases pass the controlled release gate once, then the major pointer rolls them out centrally. It trades consumer-level reproducibility for coordinated rollout and is never valid for an external dependency.

## Update tracks — who each is for, and whether you need it

Expand All @@ -54,21 +55,22 @@ A "track" is how a given dependency is allowed to move. You do **not** need ever
| Track | Fits a dependency that… | Typical handling |
| --- | --- | --- |
| **Identity + exact** | runs with privilege or has a wide blast radius (Actions, base images), or must be byte-for-byte reproducible | the bot still opens the re-pin PR, but a human reviews every one — never auto-merged |
| **Controlled owned major** | is an Action or reusable workflow owned by the organization or initiative, with a compatible release line and centrally controlled release automation | release automation alone advances the major tag for patch and minor releases; a fleet campaign moves consumers to a breaking major |
| **Patch** | is trusted and whose patches are fixes you always want (most dependencies) | auto-merge on green CI |
| **Minor** | is trusted and whose additive releases are safe to absorb | auto-merge on green CI (a repo may require review) |
| **Major** | you actively co-evolve with and can absorb breaking changes for | always human-reviewed |
| **Latest / floating** | is throwaway — an ephemeral local experiment, never shipped or run in CI | not for shipped or CI-run code |

Two questions decide the mix:

- **Are you a library or an application?** A **library** — a module or Action others consume — declares the *widest range it is compatible with* (a floor, rarely a ceiling) so it does not over-constrain its consumers. An **application or end artifact** — a workflow, a deployable, a CI pipeline — pins to *exact resolved versions* for reproducibility and relies on the updater to move them. The same dependency is pinned differently depending on who depends on it.
- **How much do you trust the source, and how large is the blast radius?** The less you trust it, or the more damage a bad version could do, the further toward identity + exact you sit — and the more you lean on automation to stay current, so tightness never becomes staleness.
- **Are you a library or an application?** A **library** — a module or Action others consume — declares the *widest range it is compatible with* (a floor, rarely a ceiling) so it does not over-constrain its consumers. An **application or end artifact** — a workflow, a deployable, a CI pipeline — pins external dependencies to *exact resolved versions* for reproducibility and relies on the updater to move them. The controlled owned-major exception trades that consumer-level reproducibility for central rollout only where the producer and release path are owned.
- **How much control do you have over the source and release path, and how large is the blast radius?** External sources stay toward identity + exact regardless of reputation. Owned automation may use a controlled major only when the organization or initiative operates the release gate and accepts the central rollout blast radius.

The healthy default across the ecosystem: **identity-pin what runs (SHAs, digests), floor-declare what you are a library for, lockfile-pin what you ship, and let the [updater](../Capabilities/dependency-updates/index.md) auto-merge patch and minor while a human reviews major.** Reach for a bare exact pin only when reproducibility genuinely demands it, and for floating latest almost never.
The healthy default across the ecosystem: **identity-pin external code that runs (SHAs, digests), use controlled major tags only for owned automation, floor-declare what you are a library for, lockfile-pin what you ship, and let the [updater](../Capabilities/dependency-updates/index.md) auto-merge patch and minor while a human reviews major.** Floating latest remains unsuitable for shipped or CI-run code.

## Where this is implemented

- [PowerShell → Version Constraints](PowerShell/Version-Constraints.md) — the spectrum in NuGet version-range syntax and `#Requires` module specifications, including the module `GUID` identity pin.
- [GitHub Actions → Pin every action to a full commit SHA](GitHub-Actions.md#pin-every-action-to-a-full-commit-sha) and [Keep pinned actions current](GitHub-Actions.md#keep-pinned-actions-current) — identity pinning by commit SHA, kept current by the updater.
- [GitHub Actions → Pin actions according to ownership](GitHub-Actions.md#pin-actions-according-to-ownership) and [Keep pinned actions current](GitHub-Actions.md#keep-pinned-actions-current) — immutable external SHA pins and controlled major tags for owned automation.
- [Security → Supply chain](Security.md#supply-chain) — why dependencies are attack surface.
- [Dependency Updates](../Capabilities/dependency-updates/index.md) — the automation that opens, labels, and routes the update pull requests.
Loading
Loading