Add public-safe preview release readiness gate to dependency-flow skill - #36268
Conversation
Adds a deterministic access gate script and a 'Preview release readiness' section to the dependency-flow skill. The gate probes local GitHub read access to dotnet/release and reports one of three status tokens (NO_ACCESS / AVAILABLE_ENABLED / AVAILABLE_NOT_ENABLED), emitting no private data and always exiting 0. The skill documents A/B/C tiering: use the private dotnet-release-tracker plugin when enabled, offer a personal opt-in when access is present but the plugin is not enabled, and silently fall back to public BAR/Maestro data (without revealing the private source) when there is no access. No secrets, endpoints, or AAD GUIDs are introduced; no fetch-and-exec. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 36268Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 36268" |
Skill Validation Results
✅ Skill Validation Results —
|
There was a problem hiding this comment.
Pull request overview
Adds a “Preview release readiness” access-gating workflow to the dependency-flow skill so the skill can deterministically choose between an authoritative source (when available) and a public BAR/Maestro fallback.
Changes:
- Appends a new “Preview release readiness” section to the dependency-flow skill docs, including token-based branching guidance.
- Introduces a PowerShell gate script that probes
gh api repos/dotnet/releaseaccess and checks~/.copilot/settings.jsonfor an enabled plugin, emitting a single status line.
Show a summary per file
| File | Description |
|---|---|
| .github/skills/dependency-flow/SKILL.md | Documents the new preview readiness workflow, including the gate script invocation and tiered behavior. |
| .github/skills/dependency-flow/scripts/Get-PreviewReleaseReadiness.ps1 | Implements the deterministic status-token gate (repo access + personal settings plugin check). |
Copilot's findings
- Files reviewed: 2/2 changed files
- Comments generated: 4
| `release/X.0.Yxx-previewN` branch, the best available answer depends on whether | ||
| that contributor has access to an authoritative, sometimes-embargoed release | ||
| source. This section defines a tiered workflow that lets contributors **with | ||
| access** benefit automatically while contributors **without access** fall back | ||
| safely to public BAR/Maestro data — and never learn that a private source exists. |
| - **The private source is invisible to no-access users.** In the `NO_ACCESS` | ||
| tier you must NOT mention that any private plugin or embargoed data source | ||
| exists. Answer purely from public data. |
| #!/usr/bin/env pwsh | ||
| #Requires -Version 7.0 |
| ```jsonc | ||
| // ~/.copilot/settings.json | ||
| "extraKnownMarketplaces": { "dotnet-release": { "source": { "source": "github", "repo": "dotnet/release" } } }, | ||
| "enabledPlugins": { "dotnet-release-tracker@dotnet-release": true } | ||
| ``` |
kubaflo
left a comment
There was a problem hiding this comment.
🤖 AI-generated review — multi-model panel (Claude Opus 4.8 · GPT-5.5 · Gemini 3.1 Pro), independent reviews + cross-pollination. Security/privacy findings empirically verified (ran the gate under every failure path).
✅ LGTM (high confidence) — hardened rework of #36213
This is the public-safe rework of #36213, and it cleanly addressed every hardening point from that review:
ghprobe wrapped in try/catch + explicit$PSNativeCommandUseErrorActionPreference = $false— the always-exit-0 contract now holds regardless of session preferences. (my #36213 finding #2)- Dropped the committable project-scope
.github/copilot/settings.jsonscan — now only the personal~/.copilot/settings.json. (my #36213 finding #3) - Emits only a single status token — removed the old
# access=… reason=…diagnostic line and the-Jsondump. (my #36213 finding #4)
Empirically verified (Opus ran the script under every failure path)
- Leak audit CLEAN — no real Azure AD GUIDs,
api://audiences, hostnames, endpoints, or secrets; only the sanctioneddotnet/releaserepo +dotnet-release-trackerplugin names. The sole stdout emitter prints exactly one of three hardcoded literals (45 bytes / 0 stderr on a real run). (Thednceng/maestro-configurationURLs GPT-5.5 flagged are pre-existing — 0 lines added by this PR — and are AzDO repo paths, not secrets.) - Fail-closed confirmed under
gh-missing, unauthenticated, bad-token (401), unreachable network, malformed JSON, JSONC comments, trailing comma, substring-injection, null/empty/missing settings, andHOME-unset — every path →NO_ACCESS(or not-enabled), exit 0, one token.AVAILABLE_*is unreachable without a genuine HTTP 200 from the privatedotnet/release.
| Model | Verdict | Confidence |
|---|---|---|
| Claude Opus 4.8 | LGTM | high |
| Gemini 3.1 Pro | LGTM | high |
| GPT-5.5 | NEEDS_CHANGES | high (false-positive — see above) |
⚠️ Action item (unanimous across all 3 models)
Close #36213. It's still open on the identical two files, same author/base; #36268 is a strict hardening superset and fully supersedes it. Leaving both open risks a double-merge / conflict / reviewer confusion.
💡 Non-blocking suggestions
Test-ReleaseTrackerEnabledmatches on$prop.Valuetruthiness, so a non-boolean like"…": "yes"resolves toAVAILABLE_ENABLED. Harmless (still gated by genuine private-repo read access; it's only the user's own opt-in signal), but use$prop.Value -eq $trueif strict boolean semantics are intended. (Opus)Join-Path -Path $HOME …(L73) sits just outside the try/catch — verified safe today (HOME-unset still →NO_ACCESS/exit-0), but moving it inside would defensively cover any future$HOMEedge case. (Opus)
Reviewed at head 7018b1ba. CI: license/cla ✅ pass; maui-pr skipping (by-design .github/** path-exclusion). Verified by reading both full files + executing the gate under failure paths.
|
|
||
| foreach ($prop in $enabled.PSObject.Properties) { | ||
| # Match the plugin name with or without a marketplace suffix. | ||
| if ($prop.Name -match '^dotnet-release-tracker(@.*)?$' -and $prop.Value) { |
There was a problem hiding this comment.
🤖 AI (multi-model panel) · 💡 non-blocking
Test-ReleaseTrackerEnabled matches on $prop.Value truthiness, so a non-boolean opt-in value like "dotnet-release-tracker@dotnet-release": "yes" (or a non-zero number) resolves to AVAILABLE_ENABLED. Harmless — the tier is still gated by genuine private-repo read access, and this only reflects the user's own opt-in signal — but if strict boolean semantics are intended, use $prop.Value -eq $true. (Opus 4.8)
| # Returns $true when the personal settings file enables a | ||
| # dotnet-release-tracker plugin under any marketplace suffix | ||
| # (e.g. dotnet-release-tracker@dotnet-release) with a truthy value. | ||
| $settingsPath = Join-Path -Path $HOME -ChildPath '.copilot/settings.json' |
There was a problem hiding this comment.
🤖 AI (multi-model panel) · 💡 non-blocking (defensive nit)
Join-Path -Path $HOME … sits just outside the try/catch. Verified safe today (with ErrorActionPreference=SilentlyContinue and this path only reached when repo access is true, a HOME-unset run still yields NO_ACCESS/exit-0/no crash), but moving it inside the try block would guard any future $HOME edge case from producing a stray non-terminating error line. (Opus 4.8)
Resolves add/add conflicts on the preview release-readiness gate (Get-PreviewReleaseReadiness.ps1 + dependency-flow SKILL.md section) by keeping this branch's richer implementation, which supersedes the simpler version merged via #36268. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Addresses consensus findings from a 3-model review (gpt-5.5, claude-opus-4.6, gemini-3.1-pro) comparing this branch's gate against the simpler version merged via #36268: * Always-exit-0 contract: restore native-command safety by pinning $PSNativeCommandUseErrorActionPreference = $false and wrapping the main body in try/catch that falls back to NO_ACCESS. Prevents a failing 'gh api' on the common NO_ACCESS path from throwing on hosts where the PowerShell 7.4+ native-error default is $true. * Plugin detection: make the marketplace @suffix optional again ('dotnet-release-tracker' as well as 'dotnet-release-tracker@...'), matching the behavior of the merged version, and anchor the regex to line start so commented-out JSONC entries no longer false-positive. Verified on pwsh 7.4.5: parses clean; NO_ACCESS path emits a line and exits 0 even with the native-error preference forced true; bare-name and suffixed enables detected; commented-out and explicit-false not. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…p reverting shipped hardening) #36213 rewrote the two dependency-flow files that #36268 already merged to main. The rewrite is a net improvement (JSONC-tolerant enabled-plugin parser, $USERPROFILE Windows support, $ReleaseRepo/$PluginId/$Json params, try/catch hardening, the "why a special source" / Preview 6 trap prose), but it also silently reverted two things #36268 shipped. Remove those reverts so the PR is additive, not competing: - Drop the committable project-scope scan (.github/copilot/settings.json) from Test-PluginEnabled. The private-plugin opt-in must stay user-scope only so forks and no-access users are never silently opted in — this restores #36268's hardening. The documented opt-in target is ~/.copilot/settings.json (user scope), so the flow is unchanged: the gate still resolves AVAILABLE_ENABLED via the user settings file (verified, both token and -Json forms). - Restore the "Comment-only on GitHub" guardrail bullet that the rewrite dropped. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ption-wiring & feed-drift checks (evolve #36268) Adds the preview lane to the release-readiness skill: an authoritative blessed-build source (.NET Release Tracker) behind a deterministic, privacy-preserving access gate, plus subscription-wiring (Check A), feed-vs-branch drift (Check B), and component-pin coherence (Check C) checks. Dependency-flow PRs (darc + human-authored component bumps) are detected via Test-IsDependencyFlowPr and scoped to the target branch so a branched preview tracker no longer reports net<major>.0 inflight bumps. Rebased onto main (squash of the release-readiness-skill branch, 15 commits) with conflicts reconciled: - Get-PreviewReadiness.ps1: kept main's two-hop merge-up structure (Raw target+inflight, $mergeUpChainLabel) and layered the PR's dependency-flow detection + blessed-build / component-pin rendering on top. - Test-ReleaseReadiness.ps1: adopted the PR's drift-proof SR E2E rewrite and updated it for main's 3-mode detector (shipped refresh tracker + in-flight + candidate), so the suite stays green across SR cuts/ships with no per-cut edit. Review fixes folded in: - Document ACCESS_ON_INACTIVE_ACCOUNT in the example status-token comments. - Gate script no longer emits the caller's settings path in the reason line (emits a fixed user-settings scope label instead). Full suite: 880 passed / 0 failed (incl. live-repo E2E). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ption-wiring & feed-drift checks (evolve #36268) (#36213) <!-- 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! ### What Makes the local ask **"run release readiness skill to see if net11 preview6 is ready"** consult the *authoritative* official-preview build **and** verify the preview branch is actually ship-wired — not just public CI/regression health. Three files: **1. `release-readiness/SKILL.md` — net-new bridge (the star of this PR).** A new **Preview: authoritative blessed-build source** subsection so the `release-readiness` skill, after its public survey, runs the access gate and — when the caller has access **and** the plugin is enabled — invokes the private **`dotnet-release-tracker`** plugin for the blessed build / BAR id + stage, then combines that with the CI/regression verdict. It branches on the gate token (`AVAILABLE_ENABLED` → use the plugin; `AVAILABLE_NOT_ENABLED` → offer the opt-in; `ACCESS_ON_INACTIVE_ACCOUNT` → advise an account switch; `NO_ACCESS` → **public-feed fallback**: report the latest build on the public Preview N channel as a *labeled candidate* that may not be the official/blessed build, without naming the private tool) and adds a **"Blessed ≠ green"** caveat so the blessed build never masks open `regressed-in-*` blockers. DRY — it cross-references dependency-flow's tier table / opt-in / guardrails rather than duplicating them. **2. `dependency-flow/` gate — evolves the version already merged in #36268.** The deterministic classifier `scripts/Get-PreviewReleaseReadiness.ps1` emits: ``` RELEASE_TRACKER_STATUS = NO_ACCESS | ACCESS_ON_INACTIVE_ACCOUNT | AVAILABLE_NOT_ENABLED | AVAILABLE_ENABLED ``` It checks (1) GitHub read access to the private marketplace repo that hosts the internal **.NET Release Tracker** plugin and (2) whether that plugin is enabled locally. It fetches **no** release data and always exits `0`. This PR carries improvements over the version on `main`: - **JSONC-tolerant** enabled-plugin parser — the strict `ConvertFrom-Json` on `main` chokes on the `//`-commented opt-in snippet the skill itself documents; - `$USERPROFILE` support (Windows user scope) alongside `$HOME`; - **multi-account advisory** — when the active `gh` identity can't read the repo but a logged-in *inactive* account can, the gate emits `ACCESS_ON_INACTIVE_ACCOUNT` and advises `gh auth switch --user <account>` instead of a false-positive `AVAILABLE` (the plugin loads under the *active* identity). Only fires when access is confirmed on some account, so a true no-access caller still gets a silent `NO_ACCESS`; - `-ReleaseRepo` / `-PluginId` / `-Json` parameters and a public-safe `.NOTES` contract; - try/catch hardening so an unexpected terminating error still emits the safe `NO_ACCESS` default; - the *"why a special source"* / Preview 6 (#35364) trap prose in `SKILL.md`. > **Relationship to #36268.** #36268 ("public-safe preview release readiness gate") already merged the first version of this gate. This PR is a **forward-evolution** of those two files, **plus** the net-new release-readiness bridge — it does **not** revert any shipped behavior. In particular the plugin opt-in stays **user-scope only** (`~/.copilot/settings.json`); there is no committable project-scope enablement, so forks and no-access users are never silently opted in. The "comment-only on GitHub" guardrail is retained. **3. Preview wiring checks — subscriptions + feed drift + component pins (net-new).** A preview can pass CI and even have a blessed build yet still not be *ship-wired*. Three **public** (BAR/Maestro + git) checks close that gap: - **Check A — subscriptions wired?** Confirm `release/11.0.1xx-previewN` has its default-channel mapping **and** the baseline three subs (android + macios + dotnet on `.NET 11.0.1xx SDK Preview N`). Branch cut + default-channel present but **zero subs** = a start-of-preview flow gap → surfaced as an **FYI note** (not a ship blocker); the skill still knows how to remediate via the existing **combined-PR pattern** (DRY, honoring its confirm/draft-PR gate). - **Check B — feed matches the branch?** Compare the latest build promoted to the `.NET 11.0.1xx SDK Preview N` channel (`maestro_latest_build`) against `origin/release/11.0.1xx-previewN` HEAD. Branch ahead of the promoted build = stale feed → flag. - **Check C — component pins coherent?** Report which `dotnet/android`, `dotnet/macios`, and `dotnet/dotnet` (VMR) builds MAUI bundles (version + SHA from `eng/Version.Details.xml`) and confirm they **match the inflight `netN.0` branch the preview was cut from**. Match = clean cut ✅; divergence or an off-band pin (macios/dotnet missing the `-net11-pN`/`preview.N` stamp) → flag. The `.NET Release Tracker` exposes **only** SDK/runtime-level data, so there is **no** "blessed" per-component android/macios build to look up — this is git+BAR only. "Behind the latest component build" is *expected* for a cut branch (don't flag it); android's `-ci.main.NN` scheme is normal for net11 and validated against inflight rather than alarmed on. Mechanics (exact MCP/`darc`/git commands, interpretation tables, remediation, and **live net11 Preview 6 worked examples**) live in a new **"Wiring checks: is Preview N actually plumbed?"** subsection (Checks A/B/C) in `dependency-flow/SKILL.md` (its Maestro/subscription domain); `release-readiness/SKILL.md` gets a short orchestration hook that cross-references it and folds the results into the preview report. **4. Preview generator — scope Maestro PRs to the target branch (net-new bug fix).** `scripts/Get-PreviewReadiness.ps1` (the deterministic generator the GitHub Action runs to author the `[Release Readiness]` preview tracker, e.g. #35866) was listing Maestro / dependency-flow PRs that target `netN.0` (the **inflight** branch) inside the preview tracker's own "Maestro / dependency-flow PRs" section. Once a preview is **branched**, those `netN.0` bumps belong to the inflight branch's own readiness, not the preview tracker. `Get-CategorizedPullRequests` now computes the Maestro bucket from `$TargetPRs` (the survey ref) **only** instead of target + inflight, so `netN.0` (inflight) Maestro PRs land in no rendered bucket and are intentionally dropped from a branched preview tracker. Non-Maestro inflight PRs still surface unchanged in the Inflight-human bucket. In **candidate** mode the survey ref *is* `netN.0` and the inflight list is empty, so target-only is correct there too. Unit tests (`tests/Test-ReleaseReadiness.ps1`, precedence + AutomationNull null-safety) updated to assert target-only scoping, including a new assertion that an inflight Maestro PR appears in no bucket. **5. Surface human-authored dependency-bump PRs in High-priority items + drift-proof SR E2E tests (net-new).** Two follow-ups in `scripts/Get-PreviewReadiness.ps1`: - **Dependency-bump detection was author-only.** The component-bump PR that *is* the release (e.g. #36433 — `rmarinho`, "Bump dotnet/dotnet (BAR 321614), dotnet/android (BAR 321622) and dotnet/macios (BAR 321780)", head `update-321614`, no labels) was authored by a human, so the old `-match "dotnet-maestro"` author filters missed it and it fell into the generic release-branch bucket instead of **High-priority items**. A new `Test-IsDependencyFlowPr` helper now flags a PR as dependency-flow if it matches *any* of: `dotnet-maestro` author **OR** a `Bump dotnet/(dotnet|android|macios|runtime|sdk|…) … (BAR NNN)` title **OR** an `update-<id>` head ref. The three maestro bucket filters were rewired to use it, and the high-priority row kind was renamed `📦 Maestro PR` → `📦 Dependency-flow PR`. Merge-up PRs (`[automated] Merge branch …`, head `merge/…`) are intentionally *not* matched. - **Human-notes block repositioned.** The blessed-build / wiring / component-pin notes now render directly under **High-priority items** (previously below the Target section), so the authoritative-build context sits next to the items it qualifies. - **Drift-proof SR E2E tests.** The end-to-end tests in `tests/Test-ReleaseReadiness.ps1` run the *real* detector against the live repo and had pinned a specific SR as the not-yet-cut candidate; when that SR shipped/cut the fixtures rotted (7 stale failures). They now **derive** the in-flight/candidate split structurally from the detector output (≥1 SR, exactly one candidate numbered one past the highest in-flight SR, clean partition, regression-label formula mirrored from `New-RegressionLabelList`), so they survive every future SR cut/ship without a per-cut edit. **6. Action-owned best-effort component-build section (net-new).** The authoritative *blessed* build lives in the private **.NET Release Tracker** (`dotnet/release`), which the GitHub Action's repo-scoped `GITHUB_TOKEN` **cannot** reach — so on an automated run that row can only come from a maintainer's local notes (spliced into the preserved human-notes block). To give the Action *something* to say on its own, `scripts/Get-PreviewReadiness.ps1` now emits a **best-effort** component-build section sourced from the branch's own `eng/Version.Details.xml` (a **public** git source always readable in CI). A new `Get-BranchComponentPins` reads the `dotnet/dotnet` (VMR/SDK), `dotnet/android` and `dotnet/macios` anchor pins (version + commit SHA) and renders a `🏷️ Preview N component build — branch pins (best-effort)` table. It is **explicitly labeled NOT a confirmed blessed build** — it reports what's *currently bundled on the branch* and points maintainers to the **Release Captain Notes** block (filled locally with tracker access) for the authoritative designation. The section renders **outside** the human-notes markers so it **self-refreshes** on every automated re-run (verified: after #36433 merged, the pins advanced to the post-bump build automatically), and it surfaces an open component-bump PR (e.g. #36433) as a *pending advance* when one is still open. `Get-BranchComponentPins` handles the `[xml]` attribute-vs-child gotcha (`Name`/`Version` are attributes; `Uri`/`Sha` are child elements), prefers the most representative dependency name per repo with a `Uri`-based fallback, and returns `$null` (no throw) when the file can't be read/parsed. Adds 9 unit assertions. Full suite: **794 passed / 0 failed**. ### Why During the Preview 6 cycle (see #35364), two same-band VMR builds (e.g. `…26325.125` vs `…26326.122`) looked interchangeable. Public BAR/Maestro data can enumerate candidate builds but **cannot, on its own, identify which staged build releases.dot.net has *blessed* as the official preview**. That authoritative signal lives in the internal release tracker. This change lets developers *with access* get the authoritative answer automatically from the `release-readiness` skill, while developers *without access* fall back to the **public preview-feed candidate** — the latest build promoted to the public Preview N channel, explicitly labeled as possibly-not-the-official build — with the private *tool* never named or implied. The wiring checks add the complementary *"is the branch even receiving flow, and is its feed current?"* signal — validated live: net11 Preview 6 is branched with a promoted build **but has no subscriptions authored yet** (Preview 5's set was never rolled forward), exactly the gap Check A surfaces (as an FYI). ### Privacy / safety The plugin is **double-gated** (GitHub read access to load it + an authorized Azure AD identity to pull data), so referencing it from this public repo is safe. The change deliberately: - contains **no** Azure AD resource ids / `api://…` audiences, backend hostnames, or internal endpoint paths — only the sanctioned *marketplace pointer* (repo name + plugin name); - performs **no** fetch-and-exec of remote code; - defaults to `NO_ACCESS` for any unconfirmed-access case, so the agent **never** reveals the private plugin *tool* to users who can't use it — the NO_ACCESS path now emits an honest, public-source-labeled preview-feed candidate (public data), which reveals nothing about the gated tooling — the multi-account advisory only fires when access is *confirmed* on some logged-in account, and never prints a token; - keeps the opt-in **user-scope** (personal `~/.copilot/settings.json`), so forks and no-access users are unaffected; - the wiring checks read only **public** BAR/Maestro + git data and never mutate config — remediation is opt-in and routes through the documented confirm/draft-PR gate. ### Testing `scripts/Get-PreviewReleaseReadiness.ps1` was exercised across all states locally with `pwsh` (token **and** `-Json` forms): | Scenario | Result | |----------|--------| | Real access probe (`dotnet/release`), plugin enabled in `~/.copilot/settings.json` (JSONC w/ comments) | `AVAILABLE_ENABLED`, exit 0 | | Real access probe, plugin not enabled | `AVAILABLE_NOT_ENABLED`, exit 0 | | Active identity lacks access, but a logged-in inactive account has it | `ACCESS_ON_INACTIVE_ACCOUNT` + `gh auth switch --user <account>` advice, exit 0 (and `GH_TOKEN` restored after probing) | | Same user settings with `: false` | `AVAILABLE_NOT_ENABLED` (correctly not matched), exit 0 | | Nonexistent repo (no account can read) | `NO_ACCESS`, exit 0 | The wiring checks (items **3A/3B**) were validated against **live Maestro/BAR + git** for net11 Preview 6: `maestro_subscriptions(targetBranch="release/11.0.1xx-preview6")` → **0 rows** (FYI note correctly surfaced); `maestro_latest_build(".NET 11.0.1xx SDK Preview 6")` → build #321033 @ `6e35dc58d0` == branch HEAD (feed current); Check C (component pins) → dotnet/dotnet `11.0.0-preview.6.26325.125`, dotnet/macios `26.5.11717-net11-p6`, dotnet/android `37.0.0-ci.main.51` all **byte-identical to `net11.0` HEAD** = clean cut. The **Action-owned best-effort component section** (item **6**) is unit-tested (9 assertions over `Get-BranchComponentPins`: parse correctness, name preference, `[xml]` attribute/child handling, unreadable-file → `$null`) and validated end-to-end by dispatching the real GitHub Action against this PR branch — the section rendered on #35866 sourced purely from `eng/Version.Details.xml` and self-refreshed to the post-#36433 pins with no local tracker access. Full suite: **794 passed / 0 failed**. Docs/skill-only change — no product code or public API is affected. --------- Co-authored-by: PureWeen <223556219+Copilot@users.noreply.github.com>
…ption-wiring & feed-drift checks (evolve dotnet#36268) (dotnet#36213) <!-- 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! ### What Makes the local ask **"run release readiness skill to see if net11 preview6 is ready"** consult the *authoritative* official-preview build **and** verify the preview branch is actually ship-wired — not just public CI/regression health. Three files: **1. `release-readiness/SKILL.md` — net-new bridge (the star of this PR).** A new **Preview: authoritative blessed-build source** subsection so the `release-readiness` skill, after its public survey, runs the access gate and — when the caller has access **and** the plugin is enabled — invokes the private **`dotnet-release-tracker`** plugin for the blessed build / BAR id + stage, then combines that with the CI/regression verdict. It branches on the gate token (`AVAILABLE_ENABLED` → use the plugin; `AVAILABLE_NOT_ENABLED` → offer the opt-in; `ACCESS_ON_INACTIVE_ACCOUNT` → advise an account switch; `NO_ACCESS` → **public-feed fallback**: report the latest build on the public Preview N channel as a *labeled candidate* that may not be the official/blessed build, without naming the private tool) and adds a **"Blessed ≠ green"** caveat so the blessed build never masks open `regressed-in-*` blockers. DRY — it cross-references dependency-flow's tier table / opt-in / guardrails rather than duplicating them. **2. `dependency-flow/` gate — evolves the version already merged in dotnet#36268.** The deterministic classifier `scripts/Get-PreviewReleaseReadiness.ps1` emits: ``` RELEASE_TRACKER_STATUS = NO_ACCESS | ACCESS_ON_INACTIVE_ACCOUNT | AVAILABLE_NOT_ENABLED | AVAILABLE_ENABLED ``` It checks (1) GitHub read access to the private marketplace repo that hosts the internal **.NET Release Tracker** plugin and (2) whether that plugin is enabled locally. It fetches **no** release data and always exits `0`. This PR carries improvements over the version on `main`: - **JSONC-tolerant** enabled-plugin parser — the strict `ConvertFrom-Json` on `main` chokes on the `//`-commented opt-in snippet the skill itself documents; - `$USERPROFILE` support (Windows user scope) alongside `$HOME`; - **multi-account advisory** — when the active `gh` identity can't read the repo but a logged-in *inactive* account can, the gate emits `ACCESS_ON_INACTIVE_ACCOUNT` and advises `gh auth switch --user <account>` instead of a false-positive `AVAILABLE` (the plugin loads under the *active* identity). Only fires when access is confirmed on some account, so a true no-access caller still gets a silent `NO_ACCESS`; - `-ReleaseRepo` / `-PluginId` / `-Json` parameters and a public-safe `.NOTES` contract; - try/catch hardening so an unexpected terminating error still emits the safe `NO_ACCESS` default; - the *"why a special source"* / Preview 6 (dotnet#35364) trap prose in `SKILL.md`. > **Relationship to dotnet#36268.** dotnet#36268 ("public-safe preview release readiness gate") already merged the first version of this gate. This PR is a **forward-evolution** of those two files, **plus** the net-new release-readiness bridge — it does **not** revert any shipped behavior. In particular the plugin opt-in stays **user-scope only** (`~/.copilot/settings.json`); there is no committable project-scope enablement, so forks and no-access users are never silently opted in. The "comment-only on GitHub" guardrail is retained. **3. Preview wiring checks — subscriptions + feed drift + component pins (net-new).** A preview can pass CI and even have a blessed build yet still not be *ship-wired*. Three **public** (BAR/Maestro + git) checks close that gap: - **Check A — subscriptions wired?** Confirm `release/11.0.1xx-previewN` has its default-channel mapping **and** the baseline three subs (android + macios + dotnet on `.NET 11.0.1xx SDK Preview N`). Branch cut + default-channel present but **zero subs** = a start-of-preview flow gap → surfaced as an **FYI note** (not a ship blocker); the skill still knows how to remediate via the existing **combined-PR pattern** (DRY, honoring its confirm/draft-PR gate). - **Check B — feed matches the branch?** Compare the latest build promoted to the `.NET 11.0.1xx SDK Preview N` channel (`maestro_latest_build`) against `origin/release/11.0.1xx-previewN` HEAD. Branch ahead of the promoted build = stale feed → flag. - **Check C — component pins coherent?** Report which `dotnet/android`, `dotnet/macios`, and `dotnet/dotnet` (VMR) builds MAUI bundles (version + SHA from `eng/Version.Details.xml`) and confirm they **match the inflight `netN.0` branch the preview was cut from**. Match = clean cut ✅; divergence or an off-band pin (macios/dotnet missing the `-net11-pN`/`preview.N` stamp) → flag. The `.NET Release Tracker` exposes **only** SDK/runtime-level data, so there is **no** "blessed" per-component android/macios build to look up — this is git+BAR only. "Behind the latest component build" is *expected* for a cut branch (don't flag it); android's `-ci.main.NN` scheme is normal for net11 and validated against inflight rather than alarmed on. Mechanics (exact MCP/`darc`/git commands, interpretation tables, remediation, and **live net11 Preview 6 worked examples**) live in a new **"Wiring checks: is Preview N actually plumbed?"** subsection (Checks A/B/C) in `dependency-flow/SKILL.md` (its Maestro/subscription domain); `release-readiness/SKILL.md` gets a short orchestration hook that cross-references it and folds the results into the preview report. **4. Preview generator — scope Maestro PRs to the target branch (net-new bug fix).** `scripts/Get-PreviewReadiness.ps1` (the deterministic generator the GitHub Action runs to author the `[Release Readiness]` preview tracker, e.g. dotnet#35866) was listing Maestro / dependency-flow PRs that target `netN.0` (the **inflight** branch) inside the preview tracker's own "Maestro / dependency-flow PRs" section. Once a preview is **branched**, those `netN.0` bumps belong to the inflight branch's own readiness, not the preview tracker. `Get-CategorizedPullRequests` now computes the Maestro bucket from `$TargetPRs` (the survey ref) **only** instead of target + inflight, so `netN.0` (inflight) Maestro PRs land in no rendered bucket and are intentionally dropped from a branched preview tracker. Non-Maestro inflight PRs still surface unchanged in the Inflight-human bucket. In **candidate** mode the survey ref *is* `netN.0` and the inflight list is empty, so target-only is correct there too. Unit tests (`tests/Test-ReleaseReadiness.ps1`, precedence + AutomationNull null-safety) updated to assert target-only scoping, including a new assertion that an inflight Maestro PR appears in no bucket. **5. Surface human-authored dependency-bump PRs in High-priority items + drift-proof SR E2E tests (net-new).** Two follow-ups in `scripts/Get-PreviewReadiness.ps1`: - **Dependency-bump detection was author-only.** The component-bump PR that *is* the release (e.g. dotnet#36433 — `rmarinho`, "Bump dotnet/dotnet (BAR 321614), dotnet/android (BAR 321622) and dotnet/macios (BAR 321780)", head `update-321614`, no labels) was authored by a human, so the old `-match "dotnet-maestro"` author filters missed it and it fell into the generic release-branch bucket instead of **High-priority items**. A new `Test-IsDependencyFlowPr` helper now flags a PR as dependency-flow if it matches *any* of: `dotnet-maestro` author **OR** a `Bump dotnet/(dotnet|android|macios|runtime|sdk|…) … (BAR NNN)` title **OR** an `update-<id>` head ref. The three maestro bucket filters were rewired to use it, and the high-priority row kind was renamed `📦 Maestro PR` → `📦 Dependency-flow PR`. Merge-up PRs (`[automated] Merge branch …`, head `merge/…`) are intentionally *not* matched. - **Human-notes block repositioned.** The blessed-build / wiring / component-pin notes now render directly under **High-priority items** (previously below the Target section), so the authoritative-build context sits next to the items it qualifies. - **Drift-proof SR E2E tests.** The end-to-end tests in `tests/Test-ReleaseReadiness.ps1` run the *real* detector against the live repo and had pinned a specific SR as the not-yet-cut candidate; when that SR shipped/cut the fixtures rotted (7 stale failures). They now **derive** the in-flight/candidate split structurally from the detector output (≥1 SR, exactly one candidate numbered one past the highest in-flight SR, clean partition, regression-label formula mirrored from `New-RegressionLabelList`), so they survive every future SR cut/ship without a per-cut edit. **6. Action-owned best-effort component-build section (net-new).** The authoritative *blessed* build lives in the private **.NET Release Tracker** (`dotnet/release`), which the GitHub Action's repo-scoped `GITHUB_TOKEN` **cannot** reach — so on an automated run that row can only come from a maintainer's local notes (spliced into the preserved human-notes block). To give the Action *something* to say on its own, `scripts/Get-PreviewReadiness.ps1` now emits a **best-effort** component-build section sourced from the branch's own `eng/Version.Details.xml` (a **public** git source always readable in CI). A new `Get-BranchComponentPins` reads the `dotnet/dotnet` (VMR/SDK), `dotnet/android` and `dotnet/macios` anchor pins (version + commit SHA) and renders a `🏷️ Preview N component build — branch pins (best-effort)` table. It is **explicitly labeled NOT a confirmed blessed build** — it reports what's *currently bundled on the branch* and points maintainers to the **Release Captain Notes** block (filled locally with tracker access) for the authoritative designation. The section renders **outside** the human-notes markers so it **self-refreshes** on every automated re-run (verified: after dotnet#36433 merged, the pins advanced to the post-bump build automatically), and it surfaces an open component-bump PR (e.g. dotnet#36433) as a *pending advance* when one is still open. `Get-BranchComponentPins` handles the `[xml]` attribute-vs-child gotcha (`Name`/`Version` are attributes; `Uri`/`Sha` are child elements), prefers the most representative dependency name per repo with a `Uri`-based fallback, and returns `$null` (no throw) when the file can't be read/parsed. Adds 9 unit assertions. Full suite: **794 passed / 0 failed**. ### Why During the Preview 6 cycle (see dotnet#35364), two same-band VMR builds (e.g. `…26325.125` vs `…26326.122`) looked interchangeable. Public BAR/Maestro data can enumerate candidate builds but **cannot, on its own, identify which staged build releases.dot.net has *blessed* as the official preview**. That authoritative signal lives in the internal release tracker. This change lets developers *with access* get the authoritative answer automatically from the `release-readiness` skill, while developers *without access* fall back to the **public preview-feed candidate** — the latest build promoted to the public Preview N channel, explicitly labeled as possibly-not-the-official build — with the private *tool* never named or implied. The wiring checks add the complementary *"is the branch even receiving flow, and is its feed current?"* signal — validated live: net11 Preview 6 is branched with a promoted build **but has no subscriptions authored yet** (Preview 5's set was never rolled forward), exactly the gap Check A surfaces (as an FYI). ### Privacy / safety The plugin is **double-gated** (GitHub read access to load it + an authorized Azure AD identity to pull data), so referencing it from this public repo is safe. The change deliberately: - contains **no** Azure AD resource ids / `api://…` audiences, backend hostnames, or internal endpoint paths — only the sanctioned *marketplace pointer* (repo name + plugin name); - performs **no** fetch-and-exec of remote code; - defaults to `NO_ACCESS` for any unconfirmed-access case, so the agent **never** reveals the private plugin *tool* to users who can't use it — the NO_ACCESS path now emits an honest, public-source-labeled preview-feed candidate (public data), which reveals nothing about the gated tooling — the multi-account advisory only fires when access is *confirmed* on some logged-in account, and never prints a token; - keeps the opt-in **user-scope** (personal `~/.copilot/settings.json`), so forks and no-access users are unaffected; - the wiring checks read only **public** BAR/Maestro + git data and never mutate config — remediation is opt-in and routes through the documented confirm/draft-PR gate. ### Testing `scripts/Get-PreviewReleaseReadiness.ps1` was exercised across all states locally with `pwsh` (token **and** `-Json` forms): | Scenario | Result | |----------|--------| | Real access probe (`dotnet/release`), plugin enabled in `~/.copilot/settings.json` (JSONC w/ comments) | `AVAILABLE_ENABLED`, exit 0 | | Real access probe, plugin not enabled | `AVAILABLE_NOT_ENABLED`, exit 0 | | Active identity lacks access, but a logged-in inactive account has it | `ACCESS_ON_INACTIVE_ACCOUNT` + `gh auth switch --user <account>` advice, exit 0 (and `GH_TOKEN` restored after probing) | | Same user settings with `: false` | `AVAILABLE_NOT_ENABLED` (correctly not matched), exit 0 | | Nonexistent repo (no account can read) | `NO_ACCESS`, exit 0 | The wiring checks (items **3A/3B**) were validated against **live Maestro/BAR + git** for net11 Preview 6: `maestro_subscriptions(targetBranch="release/11.0.1xx-preview6")` → **0 rows** (FYI note correctly surfaced); `maestro_latest_build(".NET 11.0.1xx SDK Preview 6")` → build #321033 @ `6e35dc58d0` == branch HEAD (feed current); Check C (component pins) → dotnet/dotnet `11.0.0-preview.6.26325.125`, dotnet/macios `26.5.11717-net11-p6`, dotnet/android `37.0.0-ci.main.51` all **byte-identical to `net11.0` HEAD** = clean cut. The **Action-owned best-effort component section** (item **6**) is unit-tested (9 assertions over `Get-BranchComponentPins`: parse correctness, name preference, `[xml]` attribute/child handling, unreadable-file → `$null`) and validated end-to-end by dispatching the real GitHub Action against this PR branch — the section rendered on dotnet#35866 sourced purely from `eng/Version.Details.xml` and self-refreshed to the post-dotnet#36433 pins with no local tracker access. Full suite: **794 passed / 0 failed**. Docs/skill-only change — no product code or public API is affected. --------- Co-authored-by: PureWeen <223556219+Copilot@users.noreply.github.com>
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 from this PR and let us know in a comment if this change resolves your issue. Thank you!
What this adds
A public-safe "Preview release readiness" capability in the
dependency-flowskill. This is a docs/skill + script authoring change only — no API changes, no dependency bumps.Two files:
.github/skills/dependency-flow/scripts/Get-PreviewReleaseReadiness.ps1— a deterministic access gate. It runs a single localgh api repos/dotnet/release --silentread probe and checks the user's personal~/.copilot/settings.jsonfor an enableddotnet-release-trackerplugin (tolerant of any marketplace suffix). It emits exactly one status line to stdout and always exits 0 so it can never fail its caller:RELEASE_TRACKER_STATUS=NO_ACCESSRELEASE_TRACKER_STATUS=AVAILABLE_ENABLEDRELEASE_TRACKER_STATUS=AVAILABLE_NOT_ENABLED.github/skills/dependency-flow/SKILL.md— a new "## Preview release readiness" section (appended; existing content untouched) documenting how to run the gate and how to branch on the token.A/B/C tiering
dotnet-release-trackerplugin as the authoritative source.~/.copilot/settings.json(never commit plugin enablement to the repo).Privacy / security guardrails
dotnet/release, used purely for the access probe.gh apiread check and prints a status token; the probe body and stderr are discarded so no private data leaks to stdout.dotnet/release+ an authorized AAD identity handled by the plugin, not by this script).Honest public-data limitation
Public data (BAR/Maestro +
dotnet/coremetadata) can surface build/channel/coherency and subscription health, but cannot identify which staged build is "blessed" as the official preview — that determination lives in the authoritative source. The doc calls this out explicitly for the no-access and un-enabled tiers.Testing performed
AVAILABLE_NOT_ENABLED, exit 0, exactly one line.NO_ACCESS, exit 0, one line; original script untouched.ghonPATH→NO_ACCESS, exit 0.false/ unrelated plugin / missing file / malformed JSON / noenabledPluginskey) — all correct and always exit 0.Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com