release-readiness: preview readiness — blessed-build source + subscription-wiring & feed-drift checks (evolve #36268) - #36213
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 36213Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 36213" |
Skill Validation Results
✅ Skill Validation Results —
|
There was a problem hiding this comment.
Pull request overview
Adds a deterministic “access gate” for preview release-readiness so the dependency-flow skill can choose between an authoritative internal source (when available) vs public BAR/Maestro-only guidance (when not), without leaking private-source existence on the no-access path.
Changes:
- Document a new “Preview release readiness” workflow in
dependency-flowwith access-tiered behavior and privacy guardrails. - Add
Get-PreviewReleaseReadiness.ps1to classify environment access (NO_ACCESS/AVAILABLE_NOT_ENABLED/AVAILABLE_ENABLED) without fetching release data.
Show a summary per file
| File | Description |
|---|---|
| .github/skills/dependency-flow/SKILL.md | Adds preview release-readiness guidance with a deterministic gate + tiered behavior and guardrails. |
| .github/skills/dependency-flow/scripts/Get-PreviewReleaseReadiness.ps1 | New PowerShell classifier script that probes repo access + local plugin enablement and emits a status token. |
Copilot's findings
- Files reviewed: 2/2 changed files
- Comments generated: 1
| # Project-scope settings, if invoked from within a repo checkout. | ||
| $candidates += (Join-Path (Get-Location) '.github/copilot/settings.json') | ||
|
|
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 (leak-grepped + ran the gate under every uncertain path).
✅ LGTM (high confidence) — with non-blocking hardening
A clean, well-designed access gate that meets its security/privacy goals. I independently confirmed:
- Leak audit CLEAN — no real Azure AD GUIDs,
api://audiences, or internal hostnames/endpoints in either file; the onlyapi://…/Azure-AD mentions are the guardrail text itself (placeholders telling agents NOT to copy such things), and the only coordinates are the sanctioneddotnet/release+dotnet-release-trackermarketplace pointer. (The pre-existingdev.azure.com/dncengURLs are not added by this PR — 0 added lines reference them.) - Fail-closed — structurally (
AVAILABLE_*is reachable only inside theelseofif (-not $access.Access)) and empirically:ghmissing, 404/no-access, garbage input, and JSON mode all resolve toNO_ACCESS, exit 0, no data fetched. A false-AVAILABLErequires a genuine 200 from the privatedotnet/releaserepo. - Probe safe — GitHub API path only; body (
--silent) + stderr (2>$null) suppressed; no token, no internal URL.
| Model | Verdict | Confidence |
|---|---|---|
| Claude Opus 4.8 | LGTM | high |
| Gemini 3.1 Pro | LGTM | high |
| GPT-5.5 | NEEDS_CHANGES | high (driven by a false-positive — see note) |
Note on the split: GPT-5.5 returned NEEDS_CHANGES primarily on "internal coordinates in SKILL.md" — but those
dnceng/maestro-configurationURLs are pre-existing (0 lines added by this PR) and are AzDO repo paths, not secrets. Its other two concerns are real and non-blocking (below).
💡 Non-blocking hardening suggestions
- Wrap the
ghprobe in try/catch (L79). Today the native non-zero exit doesn't throw (so 404/auth-fail →NO_ACCESScorrectly), but if an invoking session sets$PSNativeCommandUseErrorActionPreference = $true, the probe would throw and the script would exit non-zero emitting no token — still privacy-safe (neverAVAILABLE_*), but it violates the documented "always exits 0 / single-token" contract the SKILL.md branch table relies on. (GPT-5.5 + Opus 4.8) - Project-scope settings scan vs user-scope-only opt-in (L97).
Test-PluginEnabledreads the committable.github/copilot/settings.json, while the Tier-B opt-in correctly targets the personal~/.copilot/settings.jsonmarked "do NOT commit". Because the gate reads the project path, a contributor could paste+commit the snippet and auto-enable the plugin for all checkouts/forks — undercutting the "forks and no-access users are unaffected" guarantee. (No data leak — public pointer only — but a behavior inconsistency.) Drop the project-scope candidate, or add an explicit "never commit this to.github/copilot/settings.json" line. (GPT-5.5 + Opus 4.8) - Document the private-repo assumption — the first gate equates "can read
dotnet/release" with authorization; a one-line NOTE that this rests on the repo staying private would guard the assumption. (Opus 4.8) - Silence-guardrail consistency — on
NO_ACCESSthe gate prints a reason naming the repo; a note that the gate's stdout is for internal branching and shouldn't be surfaced verbatim keeps behavior consistent with the "stay silent about the internal source" guardrail. (Opus 4.8)
Reviewed at head 013445dc. CI: license/cla ✅ pass; maui-pr skipping (by-design .github/** path-exclusion). Verified by reading both full files + executing the gate under failure paths.
|
|
||
| # --silent suppresses the repo JSON body; we only care about the exit code. | ||
| # A 404 (no access) or auth failure both yield a non-zero exit -> no access. | ||
| & gh api "repos/$Repo" --silent 2>$null | Out-Null |
There was a problem hiding this comment.
🤖 AI (multi-model panel) · 💡 non-blocking
The native gh non-zero exit doesn't throw today (so 404/auth-fail → NO_ACCESS correctly), but if an invoking session has $PSNativeCommandUseErrorActionPreference = $true (with $ErrorActionPreference='Stop' set above), this probe would throw and the script would exit non-zero emitting no token — still privacy-safe (never AVAILABLE_*), but it breaks the documented "always exits 0 / single status token" contract the SKILL.md branch table depends on. Wrap the probe (or the whole body) in try/catch that resolves to NO_ACCESS. (GPT-5.5 + Opus 4.8)
| if ($env:HOME) { $candidates += (Join-Path $env:HOME '.copilot/settings.json') } | ||
| if ($env:USERPROFILE) { $candidates += (Join-Path $env:USERPROFILE '.copilot/settings.json') } | ||
| # Project-scope settings, if invoked from within a repo checkout. | ||
| $candidates += (Join-Path (Get-Location) '.github/copilot/settings.json') |
There was a problem hiding this comment.
🤖 AI (multi-model panel) · 💡 non-blocking
Test-PluginEnabled scans the committable project-scope .github/copilot/settings.json here, but the Tier-B opt-in in SKILL.md correctly targets the personal ~/.copilot/settings.json and says "do NOT commit this". Because the gate reads the project path, a contributor could paste + commit the snippet and auto-enable the plugin for all checkouts/forks — undercutting the "forks and no-access users are unaffected" guarantee. No data leak (the snippet is only the public pointer), but it's a behavior inconsistency. Either drop this project-scope candidate, or add an explicit "never commit this" guard. (GPT-5.5 + Opus 4.8)
|
Note 🤖 AI-generated note (multi-model review housekeeping). This PR appears superseded by #36268. All three reviewers (Opus 4.8, GPT-5.5, Gemini 3.1 Pro) independently flagged that #36268 — the public-safe rework — modifies the identical two files ( Leaving both open risks a double-merge / conflict. Recommend closing this one in favor of #36268 (which I've approved). Flagging for a maintainer to decide — I won't close it automatically. |
|
Note 🔍 AI-generated review note (automated multi-model orchestrator, on behalf of @kubaflo) — non-blocking, flagging an overlap for maintainer decision.
|
| # Match an enabled entry, tolerant of a marketplace suffix being present or | ||
| # absent. Anchored to the start of a line (after optional whitespace) so a | ||
| # commented-out entry such as `// "dotnet-release-tracker@x": true` is ignored. | ||
| # Examples that match: "dotnet-release-tracker": true | ||
| # "dotnet-release-tracker@dotnet-release": true | ||
| $pattern = '(?m)^\s*"' + [regex]::Escape($Plugin) + '(@[^"]+)?"\s*:\s*true' | ||
| foreach ($path in ($candidates | Select-Object -Unique)) { |
| if ($enabled) { | ||
| $status = 'AVAILABLE_ENABLED' | ||
| $reason = "enabled-via=$($pluginState.Source)" |
| { | ||
| "extraKnownMarketplaces": { | ||
| "dotnet-release": { "source": { "source": "github", "repo": "dotnet/release" } } | ||
| }, | ||
| "enabledPlugins": { | ||
| "dotnet-release-tracker@dotnet-release": true | ||
| } | ||
| } |
…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>
| } else { | ||
| Write-Host " ❌ SR9 tracker missing" -ForegroundColor Red; $script:failed++ | ||
| Write-Host " ❌ no in-flight SR to anchor candidate numbering" -ForegroundColor Red; $script:failed++ | ||
| } |
| Write-Output "RELEASE_TRACKER_STATUS=$status" | ||
| Write-Output "# access=$($access.Access.ToString().ToLower()) enabled=$($enabled.ToString().ToLower()) reason=$reason" |
| catch { | ||
| # Unreachable in normal operation, but the always-exit-0 / always-emit-a-line | ||
| # contract must hold even if an unexpected terminating error occurs. Fall back | ||
| # to the safe privacy default and never leak the error detail. | ||
| Write-Output 'RELEASE_TRACKER_STATUS=NO_ACCESS' | ||
| } |
| # dotnet/dotnet (VMR runtime/SDK) -> Microsoft.NETCore.App.Ref (11.0.0-preview.N.<date>.<rev>) | ||
| # dotnet/android -> Microsoft.Android.Sdk.Windows (android's own scheme, e.g. 37.0.0-ci.main.NN on net11) | ||
| # dotnet/macios -> Microsoft.iOS.Sdk.net11.0_26.5 (+ MacCatalyst/macOS/tvOS; 26.5.<build>-net11-pN) |
Adversarial-review round fixes on the preview readiness engine: - Wrap Get-MergedPullRequests (flow-signal fetch) in try/catch so a non-retryable gh failure (403 secondary-rate-limit, 500) degrades the Flow-signal column to open-PR-only inference instead of aborting the whole readiness report. Every other data section already degrades; this best-effort section was the lone abort path. - Get-UpstreamDriftSignal: symmetric guard so a compare payload with ahead_by but missing behind_by degrades to 'unknown' rather than throwing a StrictMode PropertyNotFoundException on the unguarded behind_by read. - Tighten the VMR/android/macios pin-selection URI patterns with a (?![\w-]) negative-lookahead so 'github.com/dotnet/dotnet' can't collide with 'dotnet-optimization' etc. (matches the sibling flow-signal guard). - Test-PluginEnabled (dependency-flow access gate): strip /* ... */ block comments before matching so a block-commented plugin entry isn't misread as enabled — the code comment already claimed this tolerance. - Access-gate final catch now honors -Json and emits the diagnostic line, keeping the documented output contract on the unexpected-error path. - dependency-flow SKILL.md Check C: note the engine's automated pins table anchors the VMR on Microsoft.NET.Sdk (SDK band) vs this manual check's Microsoft.NETCore.App.Ref (runtime band) — same repo/SHA, compare by SHA. - Add drift unit tests: pure-behind (ahead 0, behind >0 -> diverged, locks check order) and missing-behind_by -> unknown (guards the deref). Suite: 921 passed / 0 failed. Local preview6 render verified (5-col pins table intact; VMR 4-ahead 'VMR churn', android/macios current). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
| $refs = & $Fetcher "repos/$Repo/git/matching-refs/heads/$BranchName" | ||
| $branchFound = @($refs | Where-Object { $_.ref -eq "refs/heads/$BranchName" }).Count -gt 0 |
| try { | ||
| $cmp = & $Fetcher "repos/$Repo/compare/$Sha...$BranchName" | ||
| } catch { |
… signals Fixes found by the round-2 adversarial pass (2/3 + sharp 1/3 findings): - Test-PluginEnabled (dependency-flow gate): the round-2 block-comment strip was NOT string-aware and could DELETE a genuinely-enabled plugin entry when stray /* */ // sequences appear inside neighbouring JSON string VALUES (path globs, URLs) — a false-negative regression. Replace it with a new pure, string-aware Remove-JsoncComments helper that alternates a full quoted string against the two comment forms and keeps only strings. (2/3 reviewers, self-introduced regression.) - Add a dot-source guard to Get-PreviewReleaseReadiness.ps1 (mirrors the engine's guard) so its helpers are unit-testable without running the gate or hitting the trailing . Adds 6 Remove-JsoncComments tests. - Get-PreviewReadiness.ps1 flow section: a merged-PR-history fetch failure previously still rendered '❌ none seen — sub may be missing', falsely asserting a subscription is absent when we simply couldn't check. Thread a flag and render an honest 'history unavailable' cell instead. Extract the render into a testable Format-FlowSignalCell helper (+7 tests). (1/3, valid — completes the round-2 try/catch.) - Get-UpstreamDriftSignal: extend the count guard to also reject present- but-null ahead_by/behind_by so a null-count payload degrades to 'unknown' rather than [int]$null → 0 → falsely 'current'. (+1 test.) Suite: 935 passed / 0 failed. Local preview6 render verified (flow cells + 5-col pins table intact via the extracted helper). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
| } finally { | ||
| Remove-Item function:global:Get-ContentFromRepo -ErrorAction SilentlyContinue | ||
| } |
| } else { | ||
| Write-Output "RELEASE_TRACKER_STATUS=$status" | ||
| Write-Output "# access=$($access.Access.ToString().ToLower()) enabled=$($enabled.ToString().ToLower()) reason=$reason" | ||
| } |
| } else { | ||
| Write-Output 'RELEASE_TRACKER_STATUS=NO_ACCESS' | ||
| Write-Output '# access=false enabled=false reason=unexpected-error' | ||
| } |
Round-3 adversarial review (gpt-5.5) found the dot-source guard's `$MyInvocation.Line -match '^\.\s'` fallback can false-skip a real `&`/ `-File` invocation that follows a dot-source on the SAME command line, because $MyInvocation.Line carries the whole command-line text (which starts with the earlier dot-source). Empirically, `InvocationName -eq '.'` alone already detects every dot-source form (literal, \$var, parenthesized, absolute path) and stays false for every call form, so the fallback added zero coverage while being the sole cause of the false-skip. Simplified all three mirrored guards to `InvocationName -eq '.'` only (Get-PreviewReleaseReadiness.ps1, Get-PreviewReadiness.ps1, Find-ReleaseReadinessTrackers.ps1) and added a child-pwsh regression test that locks both halves (dot-source skips the gate; a real call after a dot-source still runs it). Suite: 937/0. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
| $ageDays = if ($mergedUtc) { [Math]::Round(($Now - $mergedUtc).TotalDays) } else { $null } | ||
| $status = if ($null -ne $ageDays -and $ageDays -gt $StaleDays) { 'stale' } else { 'fresh' } |
| $PR.title | ||
| } else { $null } | ||
|
|
||
| return [bool]($title -and $title -match '(?i)\bBump\b.*dotnet/(dotnet|sdk)\b') |
kubaflo
left a comment
There was a problem hiding this comment.
🔍 AI-generated review (multi-model: Opus 4.8 · GPT-5.5 · Gemini 3.1 Pro), on behalf of @kubaflo.
✅ LGTM — approving (prior hold resolved)
I previously held this at 937ccd7c because it collided with the already-merged #36268 (would have deleted #36268's "Quick reference: lifecycle commands by phase" table). That's now reconciled: I diffed the head against main and confirmed the lifecycle table (heading + all 7 rows) is fully preserved at its new location, and the preview-readiness content is layered on top rather than replacing it.
What I verified this round:
- No regression of merged work — the #36268 lifecycle table survives intact.
- All 4 PowerShell scripts parse clean (
Get-PreviewReleaseReadiness.ps1,Find-ReleaseReadinessTrackers.ps1,Get-PreviewReadiness.ps1,Test-ReleaseReadiness.ps1) via the PS parser. - Privacy/access-tier guardrails intact — the
RELEASE_TRACKER_STATUSstate machine keeps the privatedotnet-release-trackerplugin name out of theNO_ACCESSpath (falls back to public preview-feed data, explicitly labelled "may not be the final blessed build"), and distinguishesAVAILABLE_NOT_ENABLED/ACCESS_ON_INACTIVE_ACCOUNTcleanly. - Wiring/feed-drift checks (Check A subscriptions, Check B feed-vs-branch) are well-scoped with FYI-vs-blocker calibration.
CI: 10 pass, 7 skipping (.github-only), Build Analysis pending — no red.
Scope note: this is a large evolution of an existing skill (+1593/−285); I reviewed structure, content-preservation, parse-validity, and the privacy/security surface rather than line-by-line runtime behavior of the full readiness engine — the extensive Test-ReleaseReadiness.ps1 harness covers that.
Round-4 adversarial review (gpt-5.5) flagged that the new guard regression test invoked the real access gate via `& $gateScript`, which calls Test-MarketplaceAccess -> `gh api`, so the fast (-SkipE2E) tier made a live GitHub call (the fast tier is otherwise network-free by convention, e.g. detect-script tests pass -NoFetch). Replaced it with a hermetic fixture: read the REAL guard line out of the gate script (so the test cannot drift from the production guard) plus a sentinel, then exercise both halves via child pwsh with the fixture path passed through an env var (also fixes a theoretical path-quoting edge from the same review). Verified empirically: (a) pure dot-source skips the body (sentinel absent); (b) a real `&` call after a dot-source on the same command line runs the body (sentinel present); and restoring the old `-or ... -match '^\.\s'` fallback flips (b) to absent, proving it is a real regression guard. No network. Suite: 937/0 (845/0 -SkipE2E). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
| # A string-aware JSONC comment scrub below also removes block-commented and | ||
| # inline-commented entries before matching, so neither is read as enabled. | ||
| # Examples that match: "dotnet-release-tracker": true | ||
| # "dotnet-release-tracker@dotnet-release": true | ||
| $pattern = '(?m)^\s*"' + [regex]::Escape($Plugin) + '(@[^"]+)?"\s*:\s*true' |
kubaflo
left a comment
There was a problem hiding this comment.
🔍 AI-generated review (multi-model: Opus 4.8 · GPT-5.5 · Gemini 3.1 Pro), on behalf of @kubaflo.
✅ LGTM — re-affirming (6e3ccdb1 → f7d3e541)
The single new commit ("Make dot-source guard regression test hermetic") touches only Test-ReleaseReadiness.ps1 (+37/−15) — a test-harness hardening that makes the dot-source guard regression test self-contained. Re-parsed the file: clean. No change to the shipped skill scripts or the #36268 reconciliation I verified last round. Approval stands.
…Copilot follow-ups from #36213) (#36483) <!-- 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 Two small follow-up fixes to the release-readiness reporting skill, closing two **low-severity** edge cases that the GitHub Copilot reviewer flagged on #36213 and that shipped into `main`. Both are docs/skill-only (PowerShell + tests) — no product code, no public API. **1. `Test-PluginEnabled` — minified `settings.json` false negative** `.github/skills/dependency-flow/scripts/Get-PreviewReleaseReadiness.ps1` The enabled-plugin matcher was anchored to the start of a physical line (`(?m)^\s*`). A **minified / single-line** `settings.json` (e.g. `{"enabledPlugins":{"dotnet-release-tracker@dotnet-release":true}}`) therefore failed to match, so an *enabled* plugin was reported as **not** enabled (a false-negative that wrongly degrades to `AVAILABLE_NOT_ENABLED`). It fails safe — it never produces a false *enabled* — but it's still wrong for anyone whose settings file isn't pretty-printed. Fix: anchor the key to a JSON boundary (`{`, `,`, or whitespace) via a look-behind `(?<=[{,\s])` instead of a line start. Comment-avoidance is already handled by the string-aware `Remove-JsoncComments` scrub applied just below, so the line anchor was redundant. **2. `Test-IsSdkBumpPr` — `dotnet-optimization` collision** `.github/skills/release-readiness/scripts/Get-PreviewReadiness.ps1` `'(?i)\bBump\b.*dotnet/(dotnet|sdk)\b'` — the trailing `\b` sits between `t` and `-`, so `Bump dotnet/dotnet-optimization …` was misclassified as an SDK/VMR bump (which would attach a spurious "verify blessed build locally" emphasis). Fix: use the `(?![\w-])` boundary that its sibling matchers already use (`selectPin`, `Get-ComponentFlowSignal`). Practically dormant on maui today (real dep-flow PRs are titled `[netN.0] Update dependencies from…`), but now correct. ### Tests Added hermetic regression guards in `Test-ReleaseReadiness.ps1`: - `Test-PluginEnabled`: minified, pretty, suffix-only-key (no false positive), and absent-entry cases (writes fixtures into a throwaway `HOME`/`USERPROFILE`, restored in `finally`; no `gh`/network). - `Test-IsSdkBumpPr`: `dotnet/dotnet-optimization` does **not** collide → `false`; a real `dotnet/sdk` later in the same title still → `true`. This mirrors the `Get-ComponentFlowSignal` collision guard that already existed — the sibling matcher just never got the parallel assertion (the exact gap this closes). Suite: **853 passed / 0 failed** (`-SkipE2E`). ### Why low-risk Skill/tooling only. Fix 1 only ever *widens* a previously-too-narrow match and still can't produce a false enable; Fix 2 only *narrows* an over-broad match to exclude a hyphenated sibling. Both are covered by new tests that fail against the old patterns. 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>
…Copilot follow-ups from dotnet#36213) (dotnet#36483) <!-- 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 Two small follow-up fixes to the release-readiness reporting skill, closing two **low-severity** edge cases that the GitHub Copilot reviewer flagged on dotnet#36213 and that shipped into `main`. Both are docs/skill-only (PowerShell + tests) — no product code, no public API. **1. `Test-PluginEnabled` — minified `settings.json` false negative** `.github/skills/dependency-flow/scripts/Get-PreviewReleaseReadiness.ps1` The enabled-plugin matcher was anchored to the start of a physical line (`(?m)^\s*`). A **minified / single-line** `settings.json` (e.g. `{"enabledPlugins":{"dotnet-release-tracker@dotnet-release":true}}`) therefore failed to match, so an *enabled* plugin was reported as **not** enabled (a false-negative that wrongly degrades to `AVAILABLE_NOT_ENABLED`). It fails safe — it never produces a false *enabled* — but it's still wrong for anyone whose settings file isn't pretty-printed. Fix: anchor the key to a JSON boundary (`{`, `,`, or whitespace) via a look-behind `(?<=[{,\s])` instead of a line start. Comment-avoidance is already handled by the string-aware `Remove-JsoncComments` scrub applied just below, so the line anchor was redundant. **2. `Test-IsSdkBumpPr` — `dotnet-optimization` collision** `.github/skills/release-readiness/scripts/Get-PreviewReadiness.ps1` `'(?i)\bBump\b.*dotnet/(dotnet|sdk)\b'` — the trailing `\b` sits between `t` and `-`, so `Bump dotnet/dotnet-optimization …` was misclassified as an SDK/VMR bump (which would attach a spurious "verify blessed build locally" emphasis). Fix: use the `(?![\w-])` boundary that its sibling matchers already use (`selectPin`, `Get-ComponentFlowSignal`). Practically dormant on maui today (real dep-flow PRs are titled `[netN.0] Update dependencies from…`), but now correct. ### Tests Added hermetic regression guards in `Test-ReleaseReadiness.ps1`: - `Test-PluginEnabled`: minified, pretty, suffix-only-key (no false positive), and absent-entry cases (writes fixtures into a throwaway `HOME`/`USERPROFILE`, restored in `finally`; no `gh`/network). - `Test-IsSdkBumpPr`: `dotnet/dotnet-optimization` does **not** collide → `false`; a real `dotnet/sdk` later in the same title still → `true`. This mirrors the `Get-ComponentFlowSignal` collision guard that already existed — the sibling matcher just never got the parallel assertion (the exact gap this closes). Suite: **853 passed / 0 failed** (`-SkipE2E`). ### Why low-risk Skill/tooling only. Fix 1 only ever *widens* a previously-too-narrow match and still can't produce a false enable; Fix 2 only *narrows* an over-broad match to exclude a hyphenated sibling. Both are covered by new tests that fail against the old patterns. Co-authored-by: PureWeen <223556219+Copilot@users.noreply.github.com>
<!-- 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 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-readinessskill, after its public survey, runs the access gate and — when the caller has access and the plugin is enabled — invokes the privatedotnet-release-trackerplugin 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 openregressed-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.ps1emits: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 onmain:ConvertFrom-Jsononmainchokes on the//-commented opt-in snippet the skill itself documents;$USERPROFILEsupport (Windows user scope) alongside$HOME;ghidentity can't read the repo but a logged-in inactive account can, the gate emitsACCESS_ON_INACTIVE_ACCOUNTand advisesgh auth switch --user <account>instead of a false-positiveAVAILABLE(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 silentNO_ACCESS;-ReleaseRepo/-PluginId/-Jsonparameters and a public-safe.NOTEScontract;NO_ACCESSdefault;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:
release/11.0.1xx-previewNhas 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)..NET 11.0.1xx SDK Preview Nchannel (maestro_latest_build) againstorigin/release/11.0.1xx-previewNHEAD. Branch ahead of the promoted build = stale feed → flag.dotnet/android,dotnet/macios, anddotnet/dotnet(VMR) builds MAUI bundles (version + SHA fromeng/Version.Details.xml) and confirm they match the inflightnetN.0branch the preview was cut from. Match = clean cut ✅; divergence or an off-band pin (macios/dotnet missing the-net11-pN/preview.Nstamp) → flag. The.NET Release Trackerexposes 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.NNscheme 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) independency-flow/SKILL.md(its Maestro/subscription domain);release-readiness/SKILL.mdgets 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 targetnetN.0(the inflight branch) inside the preview tracker's own "Maestro / dependency-flow PRs" section. Once a preview is branched, thosenetN.0bumps belong to the inflight branch's own readiness, not the preview tracker.Get-CategorizedPullRequestsnow computes the Maestro bucket from$TargetPRs(the survey ref) only instead of target + inflight, sonetN.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 isnetN.0and 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:rmarinho, "Bump dotnet/dotnet (BAR 321614), dotnet/android (BAR 321622) and dotnet/macios (BAR 321780)", headupdate-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 newTest-IsDependencyFlowPrhelper now flags a PR as dependency-flow if it matches any of:dotnet-maestroauthor OR aBump dotnet/(dotnet|android|macios|runtime|sdk|…) … (BAR NNN)title OR anupdate-<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 …, headmerge/…) are intentionally not matched.tests/Test-ReleaseReadiness.ps1run 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 fromNew-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-scopedGITHUB_TOKENcannot 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.ps1now emits a best-effort component-build section sourced from the branch's owneng/Version.Details.xml(a public git source always readable in CI). A newGet-BranchComponentPinsreads thedotnet/dotnet(VMR/SDK),dotnet/androidanddotnet/maciosanchor 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-BranchComponentPinshandles the[xml]attribute-vs-child gotcha (Name/Versionare attributes;Uri/Shaare child elements), prefers the most representative dependency name per repo with aUri-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.125vs…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 therelease-readinessskill, 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:
api://…audiences, backend hostnames, or internal endpoint paths — only the sanctioned marketplace pointer (repo name + plugin name);NO_ACCESSfor 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;~/.copilot/settings.json), so forks and no-access users are unaffected;Testing
scripts/Get-PreviewReleaseReadiness.ps1was exercised across all states locally withpwsh(token and-Jsonforms):dotnet/release), plugin enabled in~/.copilot/settings.json(JSONC w/ comments)AVAILABLE_ENABLED, exit 0AVAILABLE_NOT_ENABLED, exit 0ACCESS_ON_INACTIVE_ACCOUNT+gh auth switch --user <account>advice, exit 0 (andGH_TOKENrestored after probing): falseAVAILABLE_NOT_ENABLED(correctly not matched), exit 0NO_ACCESS, exit 0The 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/dotnet11.0.0-preview.6.26325.125, dotnet/macios26.5.11717-net11-p6, dotnet/android37.0.0-ci.main.51all byte-identical tonet11.0HEAD = 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 fromeng/Version.Details.xmland 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.