diff --git a/src/docs/Capabilities/release-management/design-publishing-targets.md b/src/docs/Capabilities/release-management/design-publishing-targets.md index af3a67f..0b96812 100644 --- a/src/docs/Capabilities/release-management/design-publishing-targets.md +++ b/src/docs/Capabilities/release-management/design-publishing-targets.md @@ -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 | @@ -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*. diff --git a/src/docs/Capabilities/release-management/design.md b/src/docs/Capabilities/release-management/design.md index 558f8d7..ae59dec 100644 --- a/src/docs/Capabilities/release-management/design.md +++ b/src/docs/Capabilities/release-management/design.md @@ -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. @@ -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 | @@ -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 diff --git a/src/docs/Capabilities/vscode-extension-framework/design.md b/src/docs/Capabilities/vscode-extension-framework/design.md index 602addc..e6df17c 100644 --- a/src/docs/Capabilities/vscode-extension-framework/design.md +++ b/src/docs/Capabilities/vscode-extension-framework/design.md @@ -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 diff --git a/src/docs/Coding-Standards/Dependencies.md b/src/docs/Coding-Standards/Dependencies.md index 89dc5aa..f0355a8 100644 --- a/src/docs/Coding-Standards/Dependencies.md +++ b/src/docs/Coding-Standards/Dependencies.md @@ -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 @@ -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 | @@ -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 @@ -54,6 +55,7 @@ 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 | @@ -61,14 +63,14 @@ A "track" is how a given dependency is allowed to move. You do **not** need ever 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. diff --git a/src/docs/Coding-Standards/GitHub-Actions.md b/src/docs/Coding-Standards/GitHub-Actions.md index acb6ecd..85478d6 100644 --- a/src/docs/Coding-Standards/GitHub-Actions.md +++ b/src/docs/Coding-Standards/GitHub-Actions.md @@ -15,44 +15,56 @@ threat model behind action pinning and vendoring, see [Security → Supply chain](Security.md#supply-chain); this standard is the canonical "how to author" reference that the security control points to. -## Pin every action to a full commit SHA +## Pin actions according to ownership -Pinning an action by SHA is the GitHub Actions expression of the [Dependencies](Dependencies.md) standard — an **identity pin** to immutable bytes, kept current by automation (below). +An external `uses:` dependency is pinned by SHA, which is the GitHub Actions +expression of the [Dependencies](Dependencies.md) standard: an **identity pin** +to immutable bytes, kept current by automation (below). A `uses:` reference accepts a tag, a branch, or a commit SHA. Tags and branches are **mutable** — a maintainer (or an attacker who compromises one) can move -them to point at different code. A full commit SHA is **immutable**. - -- **Pin every `uses:` to a full 40-character commit SHA.** Keep the human - version as a trailing comment so reviewers know the intended release. -- This applies to **all** actions — third-party, first-party, and internally authored - internal actions alike. +them to different code. A full commit SHA is **immutable**. The only controlled +exception is a floating major tag on automation whose release path MSX controls: + +- **External actions and reusable workflows MUST use a full 40-character commit + SHA.** Keep the human version as a trailing comment so reviewers know the + intended release. GitHub-owned and marketplace actions are external because + the organization or initiative does not control their release automation. +- **Organization- or initiative-owned actions and reusable workflows MAY use a + floating major tag** such as `@v8`. Only controlled release automation may + create or move that tag, and it may advance only to compatible stable releases + within the same major line. +- **Humans and ad hoc workflows MUST NOT move a floating major tag.** A breaking + release creates the next major tag; it never repoints the existing major tag + across the compatibility boundary. ```yaml -# Correct — immutable SHA; comment carries the readable version +# External — immutable SHA; comment carries the readable version - name: Check out the repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 -- name: Set up Node - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 +# Owned — controlled major tag rolls compatible releases out centrally +jobs: + process: + uses: PSModule/Process-PSModule/.github/workflows/Process-PSModule.yml@v8 -# Avoid — mutable tag; the referenced code can change without notice +# Avoid — the organization does not control this external tag - name: Check out the repository uses: actions/checkout@v6 ``` -Internal actions follow the same rule. - ## Keep pinned actions current -A SHA pin is immutable — which also means it does not move when the action -publishes a fix. Pinning and updating are two halves of one practice: pin to a -SHA for safety, then let automation propose the newer SHA so pins never rot into -stale, unpatched code. +The update path follows ownership. An external SHA pin is immutable, so the +consumer's dependency automation proposes each newer SHA. An owned floating +major tag is advanced once by the producer's controlled release automation, so +compatible patch and minor releases reach every consumer on that major without +a fleet of pin-update pull requests. - **Enable automated updates for the `github-actions` ecosystem** in - `.github/dependabot.yml`. The updater opens a pull request that rewrites the - pin to the new commit SHA and refreshes the trailing version comment. + `.github/dependabot.yml`. For external dependencies, the updater opens a pull + request that rewrites the pin to the new commit SHA and refreshes the trailing + version comment. - **Apply Dependabot's default three-day cooldown** before adopting a freshly published version. Omit an explicit `cooldown` mapping unless the repository deliberately adopts a non-default duration. - **Label the update PR** with `dependencies` + `github-actions`, plus the dependency's own level (`update:major` / `update:minor` / `update:patch`). @@ -62,6 +74,13 @@ stale, unpatched code. share one label set. - **Review `update:major` by hand** — a major action bump can change inputs, outputs, or behaviour. Lower levels may auto-merge once checks pass. +- **Publish owned floating major tags only through controlled release + automation.** The release gate validates compatibility, publishes the immutable + version tag first, and then advances the major tag to that stable release. +- **Move consumers to a new owned major through a deliberate + [fleet campaign](../Ways-of-Working/Fleet-Orchestration.md#breaking-major-migrations).** + A breaking release publishes the next major tag, but consumers remain on their + current line until that campaign changes their `uses:` references. The full mechanism — schedule, cooldown, labels, and auto-merge policy — is the [Dependency Updates](../Capabilities/dependency-updates/design.md) capability; @@ -71,7 +90,7 @@ this section is the Actions-specific view of it. [Dependabot](https://docs.github.com/code-security/dependabot/dependabot-version-updates) remains the ongoing updater for the `github-actions` ecosystem. It proposes reviewable pull requests as releases are published, applies the configured cooldown and labels, and is the normal way a repository stays current. -Use the reusable [`Update-GitHubActionPin.ps1`](https://github.com/MSXOrg/docs/blob/main/.github/scripts/Update-GitHubActionPin.ps1) utility for an audit or a deliberate manual synchronization: for example, when onboarding an existing repository, reconciling a repository after a Dependabot outage, or checking proposed changes before an update pull request is opened. It is not a replacement for enabling Dependabot. +Use the reusable [`Update-GitHubActionPin.ps1`](https://github.com/MSXOrg/docs/blob/main/.github/scripts/Update-GitHubActionPin.ps1) utility to audit or deliberately synchronize external SHA pins: for example, when onboarding an existing repository, reconciling a repository after a Dependabot outage, or checking proposed changes before an update pull request is opened. It is not a replacement for enabling Dependabot and does not move owned floating major tags. The script needs PowerShell 7, network access to the GitHub REST API, and a target repository with a `.github` directory. Public actions can be resolved anonymously; set `GITHUB_TOKEN` or `GH_TOKEN` to raise the API rate limit or to resolve actions that require authentication. The token is sent only as an API request header. @@ -553,9 +572,9 @@ Both follow the same lifecycle: **start as a local action or workflow**, referenced by path (`./.github/...`) so it runs at the checked-out commit, and **promote it to a standalone repository only once a second consumer appears** (see [Start local; promote when it is reused](#start-local-promote-when-it-is-reused)). -Once standalone — like any third-party dependency — it is **consumed by full -commit SHA** (see -[Pin every action to a full commit SHA](#pin-every-action-to-a-full-commit-sha)). +Once standalone, it is consumed by a controlled major tag while its release path +remains organization- or initiative-owned, or by full commit SHA when external +(see [Pin actions according to ownership](#pin-actions-according-to-ownership)). A reusable workflow additionally takes its secrets **explicitly by name, never `secrets: inherit`** (see [Distinguish `vars` from `secrets`](#distinguish-vars-from-secrets)). @@ -625,9 +644,10 @@ A same-repository caller may still name the workflow itself by a local path; the constraint is on the actions the workflow reaches for. - **Reference every action from a shared reusable workflow by full path** — - `OWNER/REPO/path@`, which resolves the same way regardless of which - repository is checked out. Pin it by SHA like any other dependency (see - [Pin every action to a full commit SHA](#pin-every-action-to-a-full-commit-sha)). + `OWNER/REPO/path@`, which resolves the same way regardless of which + repository is checked out. Use a full SHA for an external action or a + controlled major tag for owned automation (see + [Pin actions according to ownership](#pin-actions-according-to-ownership)). - **A composite action may still call a sibling with `./`.** Inside a composite action, `./` resolves within *that action's own repository at the same ref* — the opposite of the workflow case — so colocated actions call each other with @@ -827,8 +847,9 @@ appears. the right home for logic used by one repository. - **Promote to a standalone repository** only when the action is genuinely reused across repositories. At that point it gains its own versioning and is - consumed by SHA like any other third-party action (see - [Pin every action to a full commit SHA](#pin-every-action-to-a-full-commit-sha)). + consumed according to ownership: a controlled major tag while its release path + remains organization- or initiative-owned, or a full SHA when external (see + [Pin actions according to ownership](#pin-actions-according-to-ownership)). Do not reach for a separate repo preemptively — the cost of a shared release surface is only worth paying once there is a second consumer. - **A reusable workflow's actions are the exception** — a shared reusable @@ -1009,8 +1030,9 @@ concurrency: code as in the portal**, stays a **stable handle** for links and log searches when the command underneath it changes, and names a failure by intent rather than by a decoded command line. Under the - [SHA-pinning rule](#pin-every-action-to-a-full-commit-sha) it matters all the - more: an unnamed action step wears its 40-character SHA as its label. + [external SHA-pinning rule](#pin-actions-according-to-ownership) it matters all + the more: an unnamed external action step wears its 40-character SHA as its + label. - **Separate each job and each step with a single blank line.** One blank line between consecutive steps, and one between consecutive jobs, makes every unit a self-contained block that is easy to scan, reorder, and read in a diff. Use diff --git a/src/docs/Coding-Standards/PowerShell/Requires-Modules.md b/src/docs/Coding-Standards/PowerShell/Requires-Modules.md index 50f4971..1765c96 100644 --- a/src/docs/Coding-Standards/PowerShell/Requires-Modules.md +++ b/src/docs/Coding-Standards/PowerShell/Requires-Modules.md @@ -72,7 +72,7 @@ The `#Requires -Modules` lines inside `*.Tests.ps1` files stay as they are. Pest Match the constraint to how much drift you can safely absorb: - **Modules** — declare a **minimum** by default so security patches flow in; **major-lock** (`ModuleVersion` + `MaximumVersion = 'N.*'`) a dependency whose next major would break you; avoid **exact** pins. Add a `GUID` only when identity assurance is required. -- **GitHub Actions / container images** — pin to an immutable **commit SHA** / **digest**, not a moving tag (see [Security → Supply chain](../Security.md#supply-chain) and [GitHub Actions](../GitHub-Actions.md)). +- **External GitHub Actions / container images** — pin to an immutable **commit SHA** / **digest**, not a moving tag. Organization- or initiative-owned Actions may use the [controlled major-tag exception](../GitHub-Actions.md#pin-actions-according-to-ownership) (see [Security → Supply chain](../Security.md#supply-chain)). - Keep pins current with automated update PRs — see [Dependency Updates](../../Capabilities/dependency-updates/index.md). ## Proof diff --git a/src/docs/Coding-Standards/PowerShell/Version-Constraints.md b/src/docs/Coding-Standards/PowerShell/Version-Constraints.md index fb3d073..44ab120 100644 --- a/src/docs/Coding-Standards/PowerShell/Version-Constraints.md +++ b/src/docs/Coding-Standards/PowerShell/Version-Constraints.md @@ -100,4 +100,4 @@ Here a bare `Version="16.0.0"` is a *minimum* — NuGet's own semantics — the ## Not a version range — pin by digest -Ranges are for packages resolved from a gallery. Dependencies that are not gallery packages are pinned differently: **GitHub Actions and container images pin to an immutable commit SHA or image digest** — an [identity pin](../Dependencies.md#two-decisions-two-axes) — never a moving tag or a range. See [Security → Supply chain](../Security.md#supply-chain) and [GitHub Actions](../GitHub-Actions.md). +Ranges are for packages resolved from a gallery. Dependencies that are not gallery packages are pinned differently: **external GitHub Actions and container images pin to an immutable commit SHA or image digest** — an [identity pin](../Dependencies.md#two-decisions-two-axes) — never a moving tag or a range. Organization- or initiative-owned Actions may use the controlled major-tag exception in the [GitHub Actions standard](../GitHub-Actions.md#pin-actions-according-to-ownership). See [Security → Supply chain](../Security.md#supply-chain). diff --git a/src/docs/Coding-Standards/Security.md b/src/docs/Coding-Standards/Security.md index 8897e3a..c4989c6 100644 --- a/src/docs/Coding-Standards/Security.md +++ b/src/docs/Coding-Standards/Security.md @@ -40,7 +40,7 @@ All code is written to be free of the vulnerabilities in the [OWASP Top 10](http Dependencies are part of the attack surface. -- **Pin dependencies** to a known-good version. Pin GitHub Actions to a full commit SHA, not a moving tag. +- **Pin external automation** to immutable identity. External GitHub Actions and reusable workflows use a full commit SHA, never a moving tag. Organization- or initiative-owned automation may use a floating major tag only when controlled release automation exclusively maintains it and keeps it within that compatible major line. - **Automate updates** with a dependency bot, so patches land quickly and reviewably. - **Make builds reproducible.** Lock or vendor dependencies so a build resolves the same inputs every time — and can run without network access where that matters. - **Generate provenance.** Build artifacts carry a software bill of materials (SBOM) and build attestations, so what is inside a release is verifiable. diff --git a/src/docs/Ways-of-Working/Fleet-Orchestration.md b/src/docs/Ways-of-Working/Fleet-Orchestration.md index c6c0f37..ed34da6 100644 --- a/src/docs/Ways-of-Working/Fleet-Orchestration.md +++ b/src/docs/Ways-of-Working/Fleet-Orchestration.md @@ -25,6 +25,9 @@ tool, can read and drive a campaign with the GitHub CLI alone. - Use it when the *same* change must land in *many* repositories: a shared workflow or dependency bump rolled out to every consumer, a convention applied fleet-wide, or a mechanical migration. +- Use it when consumers of an organization- or initiative-owned Action or + reusable workflow must move to a new breaking major. Compatible releases stay + on the existing controlled major tag and do not need a consumer campaign. - For a change in a single repository, there is no campaign — just follow the [Contribution Workflow](Contribution-Workflow.md). @@ -164,6 +167,20 @@ flowchart TD The campaign's job is to get every pull request to *Ready*; [Branching and Merging](Branching-and-Merging.md) governs how it merges. +## Breaking-major migrations + +Organization- or initiative-owned Actions and reusable workflows may publish +compatible patch and minor releases through a controlled floating major tag. +That central rollout stops at the major boundary. A breaking release publishes a +new major tag and leaves the previous major line in place; consumers do not move +until a deliberate campaign changes each `uses:` reference. + +The campaign records the compatibility decision rather than hiding it in release +automation. Its delivery leaves update the major reference, apply any required +input, output, permission, or behavior migration, and verify the consumer before +merge. The producer's controlled release automation MUST NOT repoint an existing +major tag to perform this migration. + ## What a rollout surfaces Applying one change across a whole fleet is also a fleet-wide audit. The same diff --git a/src/docs/Ways-of-Working/Repository-Standard.md b/src/docs/Ways-of-Working/Repository-Standard.md index a692c94..d031944 100644 --- a/src/docs/Ways-of-Working/Repository-Standard.md +++ b/src/docs/Ways-of-Working/Repository-Standard.md @@ -131,7 +131,7 @@ Dependency update pull requests must: - Use the provisioned update namespace to identify the dependency category and ecosystem, separate from the `release:*` bump namespace. - Pass the same CI and review gates as human-authored changes. -- Keep SHA-pinned actions pinned to immutable commit SHAs with a version comment when possible. +- Keep external actions and reusable workflows pinned to immutable commit SHAs with a version comment when possible; allow an owned floating major only under the controlled-release conditions in the [GitHub Actions standard](../Coding-Standards/GitHub-Actions.md#pin-actions-according-to-ownership). - Be reviewed before merge, even when auto-merge is allowed for low-risk updates. See [Dependency Updates](../Capabilities/dependency-updates/spec.md) for the central requirements. diff --git a/src/docs/Ways-of-Working/Workflow-Stages/Review.md b/src/docs/Ways-of-Working/Workflow-Stages/Review.md index a754621..0fd096d 100644 --- a/src/docs/Ways-of-Working/Workflow-Stages/Review.md +++ b/src/docs/Ways-of-Working/Workflow-Stages/Review.md @@ -31,7 +31,7 @@ Check each dimension per [Review Etiquette](../Review-Etiquette.md): - **Delivery** — does the diff meet the acceptance criteria, without scope it did not ask for? - **Taste** — readability, naming, structure, tests that exercise behaviour. -- **Security** — input validation, no secrets in logs, SHA-pinned actions, least privilege. Escalate a deep pass to [Security Review](Security-Review.md). +- **Security** — input validation, no secrets in logs, external action and reusable-workflow references pinned to full commit SHAs, owned major-tag references meeting the controlled-release exception, and least privilege. Escalate a deep pass to [Security Review](Security-Review.md). - **Documentation** — updated where user-facing behaviour changed. - **Standards and framework alignment** — the pull request records the author's [alignment pass](Implement.md#5-standards-and-framework-alignment-pass), the result covers every changed surface, and each exception links a real follow-up issue. Spot-check at least one row against the canonical standard or framework page rather than trusting the summary; a missing or hollow pass is a blocking finding. - **Issue convergence sweep** — the pull request records the author's [sweep](Implement.md#6-issue-convergence-sweep), including scoped search coverage and any convergent issues linked with closing keywords. Spot-check at least one linked closing issue against the delivered diff; a claimed convergence with no visible delivery evidence is a blocking finding.