You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[release-readiness] Add RC release support (#37856)
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!
### Description of Change
Adds first-class Release Candidate support to the .NET MAUI
release-readiness workflow.
The workflow now:
- discovers strict `release/<major>.0.<band>xx-rc<N>` branches and RC
tags;
- tracks RC candidate, in-flight, shipped, and cut-before-bump
transitions without inventing Preview 8;
- derives the matching SDK channel, such as `.NET 11.0.1xx SDK RC 1`;
- validates the outward MAUI default-channel mapping independently from
inbound Android and macOS/iOS subscriptions;
- requires RC subscriptions to be enabled and on the matching channel
while preserving Preview's existing non-blocking subscription severity;
- keeps VMR reconciliation local and blocks only an enabled
`dotnet/dotnet` subscription;
- emits missing mappings as explicit blockers in JSON and Markdown;
- routes RC trackers through workflow reporting and release-handoff
generation;
- filters release-relevant issues by the active Preview/RC stage.
### Root Cause
Tracker discovery and the prerelease report engine only understood
Preview branches. RC branches were ignored or rejected, so the normal
report could not expose a missing mapping such as:
`release/11.0.1xx-rc1` → `.NET 11.0.1xx SDK RC 1`
The existing dependency-flow presentation also did not deterministically
separate MAUI's outward default-channel mapping from Android/macOS-iOS
inbound subscriptions and local VMR reconciliation.
### Key Technical Details
- **Outward flow:** MAUI branch builds require an enabled
default-channel mapping to the matching Preview/RC SDK channel.
- **Inbound flow:** Android and macOS/iOS subscriptions are checked
separately for target branch, channel, and enabled state.
- **VMR:** no enabled `dotnet/dotnet` subscription is expected; the
official SDK/runtime pin is reconciled locally.
- **CLI behavior:** the exact `darc get-subscriptions` empty-result
response (exit 42 plus `No subscriptions found matching the specified
criteria.`) is normalized to an empty successful query so missing RC
subscriptions become deterministic blockers rather than `UNKNOWN`.
- **Public safety:** environments without authenticated `darc` continue
to report dependency-flow evidence as `UNKNOWN` instead of guessing.
### Tests
- `pwsh -NoProfile -File
.github/skills/release-readiness/tests/Test-ReleaseReadiness.ps1`
- **2,466 passed, 0 failed**
- Live RC1 report validation confirmed the generated JSON and Markdown
identify the missing default-channel mapping as `BLOCKED` while
reporting the Android/macOS-iOS subscriptions and local VMR model
separately.
- Live enabled Preview mapping validation confirmed existing Preview
mapping detection remains `READY`.
- Focused fixtures cover RC branch/tag discovery, RC1/RC2 transitions,
channel derivation, mapping missing/present, subscription state and
severity, VMR enabled/disabled behavior, stage-aware issue filtering,
and exact `darc` empty-result handling.
### Limitations
RC package-level consumer installability remains `UNKNOWN` until the
workload-set package resolver supports RC package identities.
### Issues Fixed
N/A — release tooling enhancement.
Copy file name to clipboardExpand all lines: .github/agents/release-readiness-agent.agent.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -82,6 +82,7 @@ If the user wants the raw deterministic report with no judgment layer (e.g. for
82
82
83
83
-`release/<major>.0.1xx-sr<N>` → **SR lane** → `Get-ReleaseReadiness.ps1` (`-Candidate` if the branch doesn't exist yet; `-Shipped` if that SR cycle has a stable tag)
84
84
-`release/<major>.0.1xx-preview<N>` → **Preview lane** → `Get-PreviewReadiness.ps1` (`-Mode candidate -SurveyRef net<major>.0` if the preview branch doesn't exist yet)
85
+
-`release/<major>.0.1xx-rc<N>` → **RC lane** → `Get-PreviewReadiness.ps1` (`-Mode candidate -SurveyRef net<major>.0` if the RC branch doesn't exist yet)
85
86
86
87
**If the user asked a portfolio / cross-release question** (plural "releases", "status overview", "what needs attention across releases", "what's next" — no single branch named) → **Portfolio path (§0a)**. Do NOT ask "which release?" — the whole point is they may not know which releases exist.
Copy file name to clipboardExpand all lines: .github/skills/release-readiness/SKILL.md
+27-15Lines changed: 27 additions & 15 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -33,11 +33,11 @@ This skill has **four** PowerShell entry points, one Preview helper, and one wor
33
33
34
34
| Script | Branch type | Purpose |
35
35
|--------|-------------|---------|
36
-
|[`Find-ReleaseReadinessTrackers.ps1`](scripts/Find-ReleaseReadinessTrackers.ps1)|both| Detects active in-flight & candidate trackers (SRand 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. |
36
+
|[`Find-ReleaseReadinessTrackers.ps1`](scripts/Find-ReleaseReadinessTrackers.ps1)|all| Detects active in-flight & candidate trackers (SR, Preview, and RC) across all active majors using a five-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. |
37
37
|[`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. |
38
-
|[`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. |
38
+
|[`Get-PreviewReadiness.ps1`](scripts/Get-PreviewReadiness.ps1)| Preview / RC | Full readiness report for a single prerelease branch (in-flight or candidate via `-Mode candidate -SurveyRef net<major>.0`). Preview reports include consumer-installability evidence; RC reports retain that check as `UNKNOWN` until RC workload-set package resolution is supported. |
39
39
|[`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. |
40
-
|[`New-ReleaseHandoff.ps1`](scripts/New-ReleaseHandoff.ps1)|both| Projects existing readiness JSON plus separately verified public release evidence into copy-ready Markdown and normalized JSON. Missing facts remain `TBD`; it never selects builds or mutates release state. |
40
+
|[`New-ReleaseHandoff.ps1`](scripts/New-ReleaseHandoff.ps1)|all| Projects existing readiness JSON plus separately verified public release evidence into copy-ready Markdown and normalized JSON. Missing facts remain `TBD`; it never selects builds or mutates release state. |
41
41
|[`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. |
42
42
43
43
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.
@@ -48,6 +48,7 @@ The trackers detector is grounded in **tag existence as the source of truth for
48
48
49
49
- SR shipped tag pattern: `<major>.0.<patch>` (e.g. `10.0.71` shipped → SR7 retired, no longer produces a tracker)
50
50
- Preview shipped tag pattern: `<major>.0.0-preview.<N>.<date>[.<build>]` (e.g. `11.0.0-preview.5.26304.4` shipped → preview5 no longer produces a tracker)
51
+
- RC shipped tag pattern: `<major>.0.0-rc.<N>.<date>[.<build>]` (e.g. `11.0.0-rc.1.26425.128` shipped → RC1 no longer produces a tracker)
51
52
52
53
**Post-ship lifecycle (`shipped` mode).** Most shipped SRs are retired the moment their tag exists. The **one exception** is the *most-recently-shipped* SR (highest shipped patch), which keeps emitting as `mode='shipped'` so its tracker issue stays useful through post-ship follow-up — adding the new build to the GitHub issue version dropdown, publishing release notes, closing out the milestone. Shipped reports never retroactively return `Not Ready`: unresolved work is split into urgent hotfix-vs-next-SR follow-ups and structured carry-forward items. Each immutable shipped tag is **create-once**: if it was never tracked (for example, the tag appeared before a scheduled updater ran), the workflow creates it; once a human closes that exact tagged generation, scheduled runs do not resurrect it. An untagged hotfix is explicitly marked `hotfixInProgress=true`, forces a yellow follow-up verdict, and is likewise create-once. Hotfix closure is scoped to the live version **and branch commit**: the same generation stays closed, while new commits or a new version can create fresh evidence. Workflow decisions use the generated report markers, not detector-time hotfix fields, so tag/commit changes between detection and reporting cannot apply stale lifecycle state. Older shipped SRs remain retired.
The unattended public survey does not know the release-owner-confirmed workload-set
123
135
version or private shipping source. It therefore keeps **Consumer installability**
124
136
`UNKNOWN` rather than guessing that the newest coherent package is the blessed one.
@@ -335,19 +347,19 @@ inferred ✅ but the sub points at the wrong channel, or inferred ⚠️/❌ con
335
347
real gap with the missing source repos named. When the local check simply agrees with
336
348
the inferred signal, report it conversationally and leave the tracker to the CI body.
337
349
338
-
### Preview action ordering (newly cut branch)
350
+
### Prerelease action ordering (newly cut Preview or RC branch)
339
351
340
352
When the preview branch exists but its plumbing is incomplete, present the remediation
341
353
as a dependency-ordered sequence. Do **not** sort unrelated `BLOCKED`/`WATCH` rows ahead
342
354
of these prerequisites:
343
355
344
-
1. Add the Preview N default-channel mapping.
356
+
1. Add the matching Preview/RC default-channel mapping for MAUI's outward build flow.
345
357
2. Add the baseline Android and macOS/iOS subscriptions. Wait for the
346
358
`maestro-configuration` PR to merge into `production`, then verify BAR ingestion
347
359
with `darc get-subscriptions --target-repo https://github.com/dotnet/maui --target-branch <preview-branch>`.
348
-
3. Reconcile MAUI's SDK/VMR pin **locally** with the official Preview N build and
360
+
3. Reconcile MAUI's SDK/VMR pin **locally** with the official Preview/RC build and
349
361
open the resulting focused component-bump PR. Do not add a VMR subscription.
350
-
4. Build branch HEAD and promote the resulting MAUI build to the Preview N channel.
362
+
4. Build branch HEAD and promote the resulting MAUI build to the matching Preview/RC channel.
351
363
5. Clear current-preview CI/device/UI failures and finish release validation.
352
364
353
365
Keep the report scoped to Preview N. Do **not** mention the `netN.0` Preview N+1
@@ -393,15 +405,15 @@ work. Those belong only in the Preview N+1 candidate/in-flight readiness report.
393
405
|`-IncludeInternal`| No | off | Release-captain only — augments the local survey with internal pipeline status when AzDO auth is available. |
394
406
|`-PublicSafe`| No |`$true`| Sanitizes private/internal coordinates from SR Markdown and JSON. Set false only for local artifacts that will not be posted publicly. |
395
407
396
-
### `Get-PreviewReadiness.ps1` (Preview)
408
+
### `Get-PreviewReadiness.ps1` (Preview / RC)
397
409
398
410
| Parameter | Required | Default | Description |
399
411
|-----------|----------|---------|-------------|
400
-
|`-Branch`| Yes | — | Preview branch name (e.g. `release/11.0.1xx-preview6`). Required even for candidate runs — used to derive milestone, tracker key, and regression labels. |
412
+
|`-Branch`| Yes | — | Preview or RC branch name (e.g. `release/11.0.1xx-preview6` or `release/11.0.1xx-rc1`). Required even for candidate runs — used to derive milestone, tracker key, and regression labels. |
401
413
|`-Mode`| No |`in-flight`|`in-flight` (survey the preview branch itself) or `candidate` (survey `-SurveyRef` instead — typically `net<major>.0`). |
402
414
|`-SurveyRef`| No | computed | Ref to actually survey. Defaults to `$Branch` for in-flight; `net<major>.0` for candidate. |
403
415
|`-Repository`| No |`dotnet/maui`| Repository in `owner/name` form. |
404
-
|`-TrackerKey`| No | derived | Canonical key (default: `net<major>-preview<N>`) embedded for idempotent issue lookup. |
416
+
|`-TrackerKey`| No | derived | Canonical key (default: `net<major>-preview<N>` or `net<major>-rc<N>`) embedded for idempotent issue lookup. |
405
417
|`-OutputDir`| No | — | If set, writes `preview-readiness.{json,md}`. |
406
418
|`-OutputFormat`| No |`markdown`|`markdown`, `json`, or `both`. |
407
419
|`-IncludeInternal`| No | off | Compatibility override that requests sanitized internal classification even with `-PublicSafe`; enriched local skill/agent runs pass `'-PublicSafe:$false'`. GitHub Actions still skips. |
@@ -418,15 +430,15 @@ work. Those belong only in the Preview N+1 candidate/in-flight readiness report.
418
430
|`release-readiness.{json,md}`| Get-ReleaseReadiness | Full SR readiness report |
419
431
|`sr-source-prs.txt`| Get-ReleaseReadiness | Flat newline-delimited source PR list; use `grep -qxF NNNNN file` for instant cherry-pick verification |
420
432
|`sr-commits.json`| Get-ReleaseReadiness | Raw SR-only commit metadata |
421
-
|`preview-readiness.{json,md}`| Get-PreviewReadiness | Full Preview readiness report. Local non-public-safe net11 JSON includes `InternalOfficialBuilds`; public-safe JSON omits that property. |
433
+
|`preview-readiness.{json,md}`| Get-PreviewReadiness | Full Preview/RC readiness report. Local non-public-safe net11 JSON includes `InternalOfficialBuilds`; public-safe JSON omits that property. |
422
434
423
435
## Tracker refresh workflow
424
436
425
437
`.github/workflows/release-readiness.yml` runs every three hours from **08:30–20:30 UTC daily**, plus targeted `issues`, `milestone`, and `push` refreshes, `workflow_dispatch`, and `pull_request` validation:
426
438
427
439
1.**`detect-trackers`** — runs `Find-Trackers -AllActiveMajors`, emits a matrix of tracker descriptors.
428
440
2.**`per-tracker-report`** — matrix-expanded job per tracker:
429
-
- Dispatches to `Get-ReleaseReadiness.ps1` (SR) or `Get-PreviewReadiness.ps1` (Preview) based on `branchType`.
441
+
- Dispatches to `Get-ReleaseReadiness.ps1` (SR) or `Get-PreviewReadiness.ps1` (Preview/RC) based on `branchType`.
430
442
- Looks for an open tracker issue by the canonical marker `<!-- release-readiness-tracker: <key> -->`.
431
443
-**Refresh path**: in shipped mode, prefer an open issue carrying the exact current shipped/hotfix generation marker; otherwise refresh the oldest labeled tracker in place so Release Captain Notes and subscriptions survive commit-to-commit generation changes. If the exact generation was intentionally closed, retire any stale generic opens and do not recreate it. For non-shipped trackers, adopt the oldest labeled tracker issue and close remaining duplicates.
432
444
-**Create path**: open a new issue with the mandatory `area-infrastructure` ownership label (creation fails rather than producing a tracker the lifecycle lookup cannot recognize); attach `report` / `s/triaged` best-effort.
@@ -565,7 +577,7 @@ Two consequences the report must get right:
565
577
1.**Roll-forward milestone.** The cycle after `preview7` is `.NET <major>.0-rc1`, *not*`.NET <major>.0-preview8`. `Get-PreviewTrainMilestoneTitle` in [`Get-ReleaseReadiness.ps1`](scripts/Get-ReleaseReadiness.ps1) owns this mapping (ordinals `1..7` → previews, `8` → rc1, `9` → rc2, `10+` → `$null`); `$script:FinalPreviewNumber` is the single constant to change if the cadence ever moves. (.NET 5 shipped 8 previews; the 7-preview cadence has held for every major since .NET 6.)
566
578
2.**`preview7` is the feature/API-lock gate.** Because no eighth preview exists, work that misses the `preview7` cut does **not** roll into "the next preview" — RC is normally API-locked and go-live-licensed, so in practice it slips to the **next major**. When reporting on a `preview7` branch or candidate, say so explicitly: public-API PRs still open at the `preview7` cut are a *decide-now* item, not a defer-later one.
567
579
568
-
> **Where the `rc` mapping actually fires (and where it doesn't).**`Get-MilestoneHygieneChecks` lives only in the **SR lane**(`Get-ReleaseReadiness.ps1`); the automated preview lane (`Get-PreviewReadiness.ps1` → `Test-PreviewMilestoneExists`) validates only the *current* preview's own milestone and never derives a next-cycle title. So an `rc`-shaped milestone title is produced **only** when the SR-lane hygiene check is driven with a preview-shaped branch — an ad-hoc `Get-ReleaseReadiness.ps1 -SrBranch release/<major>.0.1xx-preview7` run, or candidate mode off `preview7`. An **in-flight survey of an actual `release/*-rc1` branch produces no milestone checks at all**: rc-shaped branches match neither the SR nor the preview shape, so the parser skips them (scenario M15). Treat the `preview→rc` mapping as defensive normalization for preview-lane inputs, **not** a live milestone gate on cut `rc` branches.
580
+
> **RC is a first-class tracker lane.**`Find-ReleaseReadinessTrackers.ps1` discovers strict `release/<major>.0.<band>xx-rc<N>` branches and RC tags independently of Preview. `Get-PreviewReadiness.ps1` validates the current `.NET <major>.0-rc<N>` milestone, expected `rc/N` version metadata, the outward MAUI default-channel mapping, Android/macOS-iOS inbound subscriptions, and the no-VMR-subscription reconciliation model.
0 commit comments