Skip to content
99 changes: 96 additions & 3 deletions .github/skills/release-readiness/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: Assesses ship-readiness for .NET MAUI release branches — Servicin
metadata:
author: dotnet-maui
version: "2.0"
compatibility: Requires `gh` CLI authenticated with `repo` + `read:org` scopes. `az` CLI is optional but recommended for internal pipeline status. Run from a checkout of `dotnet/maui`.
compatibility: Requires `gh` CLI authenticated with `repo` + `read:org` scopes. `az` CLI is optional but recommended for internal pipeline status. Preview installability uses NuGet v3 feeds; an optional short-lived Azure DevOps PAT with Packaging Read scope may be needed for an authenticated shipping feed. Run from a checkout of `dotnet/maui`.
---

# Release Readiness
Expand All @@ -29,13 +29,14 @@ This skill **reports**. It does **not** execute release operations against dotne

## Architecture

This skill has **three** PowerShell entry points and one workflow:
This skill has **three** PowerShell entry points, one Preview helper, and one workflow:

| Script | Branch type | Purpose |
|--------|-------------|---------|
| [`Find-ReleaseReadinessTrackers.ps1`](scripts/Find-ReleaseReadinessTrackers.ps1) | both | Detects active in-flight & candidate trackers (SR and Preview) across all active majors using a four-lane algorithm and the **tag-existence rule** ("a release is in flight unless its tag already exists"). Emits a single tracker JSON consumed by the workflow. |
| [`Get-ReleaseReadiness.ps1`](scripts/Get-ReleaseReadiness.ps1) | SR | Full readiness report for a single SR branch (in-flight, `-Candidate`, or `-Shipped`). `-Shipped` surveys the same branch with post-ship verdict, carry-forward, and hotfix-vs-next-SR guidance semantics. |
| [`Get-PreviewReadiness.ps1`](scripts/Get-PreviewReadiness.ps1) | Preview | Full readiness report for a single Preview branch (in-flight or candidate via `-Mode candidate -SurveyRef net<major>.0`). |
| [`Get-PreviewReadiness.ps1`](scripts/Get-PreviewReadiness.ps1) | Preview | Full readiness report for a single Preview branch (in-flight or candidate via `-Mode candidate -SurveyRef net<major>.0`), including consumer-installability evidence. |
| [`PreviewInstallability.ps1`](scripts/PreviewInstallability.ps1) | Preview helper | Resolves the workload-set package, validates branch-pin coherence, probes manifest and representative pack availability, extracts platform prerequisites, and emits an isolated NuGet configuration for local validation. |
| [`release-readiness.yml`](../../workflows/release-readiness.yml) | both | Three-hourly daytime UTC schedule + event-driven refreshes + manual dispatch + PR validation. Non-PR triggers run `Find-Trackers -AllActiveMajors`, fan out a matrix job per tracker, and write idempotent `[Release Readiness]` issues; PR triggers validate outputs only. |

Shared support code lives in [`PublicReportSanitizer.ps1`](scripts/PublicReportSanitizer.ps1) for public Markdown/JSON redaction and [`TrackerIssueLifecycle.sh`](scripts/TrackerIssueLifecycle.sh) for tested issue-selection and race-compensation primitives.
Expand Down Expand Up @@ -115,6 +116,11 @@ pwsh .github/skills/release-readiness/scripts/Get-PreviewReadiness.ps1 \
-OutputDir CustomAgentLogsTmp/release-readiness/preview6-candidate
```

The unattended public survey does not know the release-owner-confirmed workload-set
version or private shipping source. It therefore keeps **Consumer installability**
`UNKNOWN` rather than guessing that the newest coherent package is the blessed one.
Complete the local gate below before declaring a Preview ready.

### Preview: authoritative blessed-build source (.NET Release Tracker)

For **Previews**, this skill's public survey (CI health + regression classification on `net<major>.0` or the preview branch) tells you whether the code is *ready*, but it **cannot on its own name which staged build is the official, blessed preview** — that designation lives in the private **.NET Release Tracker** plugin. So when answering *"run release readiness … is net11 preview6 ready?"* / *"which build is the official preview6?"*, consult that authoritative source **in addition to** running `Get-PreviewReadiness.ps1`:
Expand All @@ -138,6 +144,90 @@ The full tier table, the user-scope opt-in snippet, and the privacy guardrails l

**Don't maintain a standing "🏷️ Official (blessed) preview build" table in the tracker.** The deterministic CI body already owns the public build-pin handling: its **"🏷️ Preview N component build — branch pins + update paths"** section states the pins are explicitly *not* the blessed build, carries the drift-proof "verify locally" prompt, infers Android/macOS-iOS subscription health from the public PR trail, and identifies VMR as a local official-build reconciliation path. Because the blessed build number is embargoed (withheld from the public issue), a standing public table just renders "🔒 withheld" and duplicates that callout. So a local run with tracker access should **report the blessed SDK/runtime build in its conversational answer**, and only add a line to _Release Captain Notes_ when there's a **decision or exception worth persisting** — e.g. the blessed build differs from the branch pin, a promoted build was rejected, or an Android/macOS-iOS subscription is confirmed broken. Don't re-create the section the CI body already renders.

### Preview: consumer-installability gate

The branch being green is insufficient: a customer must be able to acquire the
exact SDK workload set, its component manifests, and representative Android,
Apple (including tvOS), Emscripten, MAUI, and runtime packs from a clean source
configuration.

Use the exact workload-set **CLI version** confirmed by the release owner. Do not
substitute the branch SDK version, and do not assume the newest coherent package
is blessed. Workload-set CLI and NuGet versions have different normalization:
`11.0.100-preview.6.26363.2` maps to
`11.100.0-preview.6.26363.2` for the NuGet package.

If all assets are public, the confirmed version is enough:

```bash
pwsh .github/skills/release-readiness/scripts/Get-PreviewReadiness.ps1 \
-Branch release/11.0.1xx-preview6 \
-Mode in-flight \
-ConfirmedWorkloadSetVersion 11.0.100-preview.6.26363.2 \
'-PublicSafe:$false' \
-OutputDir CustomAgentLogsTmp/release-readiness/preview6-local
```

If an authenticated shipping feed is required:

1. Create a short-lived PAT at
[`https://dev.azure.com/dnceng/_usersSettings/tokens`](https://dev.azure.com/dnceng/_usersSettings/tokens).
Select the `dnceng` organization and grant only **Packaging > Read**. Use the
shortest practical expiration. Never paste the PAT into a command argument,
NuGet.Config, report, issue, PR, chat transcript, or repository file.
2. Put the credential in NuGet's standard environment variable. The suffix must
exactly match the source name passed to `-AdditionalPackageSource`.

```bash
read -s -p "dnceng Packaging Read PAT: " DNCENG_PACKAGING_PAT; echo
export NuGetPackageSourceCredentials_internal_preview6="Username=release-readiness;Password=${DNCENG_PACKAGING_PAT};ValidAuthenticationTypes=Basic"
unset DNCENG_PACKAGING_PAT
```

3. Run the local report with the source in `name=https://...` form:

```bash
pwsh .github/skills/release-readiness/scripts/Get-PreviewReadiness.ps1 \
-Branch release/11.0.1xx-preview6 \
-Mode in-flight \
-ConfirmedWorkloadSetVersion 11.0.100-preview.6.26363.2 \
-AdditionalPackageSource 'internal_preview6=<shipping-feed-v3-index-url>' \
'-PublicSafe:$false' \
-OutputDir CustomAgentLogsTmp/release-readiness/preview6-local
```

4. Use the generated local-only `<clear />` NuGet configuration and install
command from `preview-readiness.md`. Then remove the credential:

```bash
unset NuGetPackageSourceCredentials_internal_preview6
```

`-PublicSafe $false` intentionally includes exact source URLs and installation
instructions, so keep that output local. The default public-safe report removes
the release-owner-confirmed workload-set version and any candidate version
learned from an authenticated/internal source, including versions repeated in
nested manifest and pack evidence. It also removes additional source names,
URLs, nested source metadata, credentials, and the generated NuGet
configuration. Unconfirmed candidates discovered entirely from public feeds
remain visible as diagnostic evidence.

The gate classifies evidence as follows:

| Installability | Readiness | Meaning |
|----------------|-----------|---------|
| `installable` | `READY` | Confirmed CLI version, branch pins, required manifests, and representative packs all agree and resolve. |
| `missing` | `BLOCKED` | A confirmed package or asset is absent from every accessible supplied source. |
| `mismatched` | `BLOCKED` | The workload set disagrees with the branch SDK, Android, Apple, or runtime pins, or with the target MAUI Preview train. |
| `unknown` | `UNKNOWN` | Version is unconfirmed, a source is inaccessible, or evidence could not be read. HTTP 401/403 is never treated as proof that a package is missing. |

The isolated source set is deliberate: `dotnet-workloads` owns the workload-set
package, `dotnet<major>-workloads` owns platform manifests/assets,
`dotnet<major>` owns MAUI and Apple manifests/assets,
`dotnet<major>-transport` owns runtime transport assets, and `dotnet-public`
plus NuGet.org provide shared dependencies. Do not inherit stale feeds from a
machine-wide NuGet.Config.

### Preview: is the branch actually plumbed? (subscription wiring + feed drift)

A preview can pass CI and even have a blessed build yet still not be *ship-wired* —
Expand Down Expand Up @@ -240,6 +330,8 @@ work. Those belong only in the Preview N+1 candidate/in-flight readiness report.
| `-OutputFormat` | No | `markdown` | `markdown`, `json`, or `both`. |
| `-IncludeInternal`, `-InternalBuildId` | No | — | Release-captain only — augments report with internal pipeline status when AzDO auth is available. |
| `-PublicSafe` | No | `$true` | Sanitizes private/internal coordinates from Preview Markdown and JSON. |
| `-ConfirmedWorkloadSetVersion` | No | — | Exact release-owner-confirmed workload-set CLI version. Required before Consumer installability can become `READY`. |
| `-AdditionalPackageSource` | No | — | Repeatable `name=https://...` authenticated dnceng Azure Artifacts source without user information, query parameters, or fragments. Credentials come from `NuGetPackageSourceCredentials_<name>`, never from the argument, and must explicitly select `ValidAuthenticationTypes=Basic`. |

## Outputs

Expand Down Expand Up @@ -473,3 +565,4 @@ The harness covers:
- **`Get-ReleaseReadiness`** verdict classification using known-answer data from the SR7 readiness analysis (e.g. #35313 → `in-sr-active`, #35344 → `in-sr-active` via the SafeArea follow-on fix, #35771 → `no-fix-yet`)
- **Idempotent body hash** stability across re-runs — **SR trackers only** (the scheduled/event-driven workflow compares the embedded `<!-- release-readiness-hash: sha=... -->` marker against the live issue and skips the edit when the semantic content is unchanged, so re-runs don't churn the tracker). Preview trackers carry no hash marker and are refreshed on every scheduled run.
- **Nightly dogfood feed banner** (`NightlyFeed.ps1`) — offline unit coverage for the lane-label honest-labeling rule (`Format-NightlyFeedLaneLabel`), the `ci.inflight`-first / `ci.main`-false-green resolver, age→tier bucketing, the fail-open feed query (mocked `-Fetcher`), and the banner's fold into `Get-ReportSemanticHash` (tier change refreshes, same-tier day tick does not). All network-free via injected fixtures and explicit `-Now`.
- **Preview consumer installability** (`PreviewInstallability.ps1`) — offline fixtures cover CLI/NuGet version conversion, workload-set discovery with MSI exclusion, branch-pin coherence, source-role resolution, real manifest `alias-to` resolution for Android/Emscripten/runtime representative packs, platform prerequisites, isolated `<clear />` configuration, malformed/401/403=`UNKNOWN` semantics, verdict mapping, and public-output redaction.
140 changes: 139 additions & 1 deletion .github/skills/release-readiness/references/methodology.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Release Readiness — Methodology

This document captures the algorithms used by `Get-ReleaseReadiness.ps1` and **the seven gotchas** discovered through real SR analysis that the algorithms exist to prevent.
This document captures the deterministic algorithms used by the SR and Preview readiness engines, including **the seven gotchas** discovered through real SR analysis and the Preview **consumer-installability gate**.

## Gotcha #1: Cherry-Pick Number Swap

Expand Down Expand Up @@ -370,6 +370,144 @@ The skill must derive which `regressed-in-X.Y.Z` labels matter for a given SR:
| `closed-fix-unlinked` | `no-fix-yet` would apply, BUT the issue is CLOSED and a closing comment **explicitly names** a fix PR (fix/resolve/close language) that is MERGED and whose commit is on `$SrBranch` (verified by SHA-ancestry OR the `(#<num>)` squash-subject token). A bare PR mention (the cause-PR blame pattern) is rejected. Surfaces a missing PR↔issue link rather than a false "no fix" alarm. Non-blocking (Tier 3) |
| `needs-human-review` | Only weak evidence; OR multiple candidate PRs with conflicting verdicts |

## Preview Consumer Installability

A green Preview branch or promoted SDK build does not prove that a customer can
install the exact workload set from a clean machine. The gate in
`PreviewInstallability.ps1` evaluates package identity, branch pins, source
availability, and platform prerequisites before contributing to the Preview
verdict.

### Inputs and Trust Boundaries

The evaluator uses:

1. Branch pins for the VMR/runtime SDK, Android, and Apple. The VMR pin also
anchors Mono toolchain and Emscripten; MAUI is checked against the target
Preview train because the MAUI branch does not statically pin its own build
version.
2. Public NuGet v3 sources derived from the SDK major version.
3. An optional release-owner-confirmed workload-set CLI version.
4. Optional additional HTTPS package sources in `name=https://...` form.

Only a release owner can identify the blessed workload-set version. Discovery
may find several coherent candidates, but "newest coherent" does not mean
"approved for release." An unconfirmed version therefore cannot produce
`READY`.

Additional sources are accepted only for dnceng Azure Artifacts HTTPS
endpoints. NuGet.org is already part of the fixed public source set and cannot
be supplied under an arbitrary credential-bearing name. User information
embedded in a URL, query parameters, and fragments are rejected. Credentials come from NuGet's
`NuGetPackageSourceCredentials_<name>` environment variable, where `<name>`
exactly matches the source name. The value must contain non-empty `Username`
and `Password` fields and exactly `ValidAuthenticationTypes=Basic`; any other
authentication contract is rejected before an Authorization header is created.
Credentials are attached only to HTTPS `pkgs.dev.azure.com/dnceng/...`
requests, including service-index-derived search, flat-container, and package
URLs.

### Workload-Set Version Normalization

The workload-set CLI version and NuGet package version differ:

```text
CLI: <major>.0.<feature-band>-preview.<N>.<build>
NuGet: <major>.<feature-band>.0-preview.<N>.<build>
```

For example:

```text
11.0.100-preview.6.26363.2
-> 11.100.0-preview.6.26363.2
```

The package ID is derived from the SDK feature band and Preview number:

```text
Microsoft.NET.Workloads.<major>.0.<feature-band>-preview.<N>
```

MSI packages are excluded from discovery because they are installer artifacts,
not workload-set metadata packages.

### Source Roles and Isolation

The evaluator builds a clean source list instead of inheriting machine-wide
NuGet configuration:

| Source role | Expected contents |
|-------------|-------------------|
| `dotnet-workloads` | Workload-set metadata package |
| `dotnet<major>-workloads` | Android and other platform manifest assets |
| `dotnet<major>` | MAUI and Apple manifests and packs |
| `dotnet<major>-transport` | Runtime transport packs |
| `dotnet-public` | Shared dotnet dependencies |
| NuGet.org | Public ecosystem dependencies |
| Additional authenticated source | Release-specific assets not yet present on public feeds |

The generated local NuGet configuration starts with `<clear />`. This prevents
an old major-version or stale shipping feed from making an installation appear
to work accidentally.

### Evaluation Algorithm

1. Derive the SDK major, feature band, Preview number, package ID, CLI version,
and normalized NuGet version.
2. If no version is confirmed, discover prerelease candidates from accessible
sources as diagnostic evidence and return `unknown`.
3. Download and extract the exact confirmed workload-set package.
4. Compare its Android, Apple, runtime/VMR, and Emscripten versions with the
branch pins, and verify that its MAUI manifest targets the expected Preview
train.
5. Probe every required manifest package.
6. Probe representative Android, Apple (including tvOS), Emscripten, MAUI, and
runtime transport packs. When a manifest pack uses `alias-to`, resolve the
current runtime identifier (or `any`) to the physical NuGet package ID before
probing it.
7. Extract the JDK, Android SDK, Xcode, Apple SDK, and Windows App SDK
prerequisites when present.
8. Emit an isolated local NuGet configuration and exact install command only
when public-safe mode is disabled.

Package probes distinguish absence from unavailable evidence:

- A not-found response from all accessible sources can establish `missing`.
- HTTP 401/403, an inaccessible authenticated source, timeout, malformed
metadata, or another read failure establishes only `unknown`.
- A package found on one supplied source is available even if another source is
inaccessible.

### State and Verdict Mapping

| Gate state | Readiness check | Rule |
|------------|-----------------|------|
| `installable` | `READY` | The version is confirmed, pins agree, and all required manifests and representative packs resolve. |
| `missing` | `BLOCKED` | A confirmed package or asset is absent from every accessible supplied source. |
| `mismatched` | `BLOCKED` | Workload-set component versions disagree with branch pins or the target MAUI Preview train. |
| `unknown` | `UNKNOWN` | Confirmation or trustworthy package evidence is unavailable. |

`BLOCKED` prevents readiness; `UNKNOWN` prevents an unconditional `READY`.

### Public-Output Safety

Local release-captain output may contain the exact source list, generated NuGet
configuration, resolved-source diagnostics, and install command. Public-safe
serialization recursively removes:

- additional source names and URLs
- private source metadata, including nested resolved-source objects
- generated NuGet configuration paths and content
- authentication state and credential details
- local installation commands that reference private sources
- release-owner-confirmed versions and candidate versions learned from an
authenticated/internal source, including nested manifest and pack versions

The public report retains the state, safe package identities and public-feed
candidate versions, pin-comparison status, prerequisite summary, and remediation
category needed to explain the readiness result.

## CI Freshness

A passing CI build is only meaningful if it ran **at or after** the current SR HEAD. The skill records:
Expand Down
Loading
Loading