Skip to content

[automated] Merge branch 'main' => 'net11.0' - #36085

Closed
github-actions[bot] wants to merge 23 commits into
net11.0from
merge/main-to-net11.0
Closed

[automated] Merge branch 'main' => 'net11.0'#36085
github-actions[bot] wants to merge 23 commits into
net11.0from
merge/main-to-net11.0

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

I detected changes in the main branch which have not been merged yet to net11.0. I'm a robot and am configured to help you automatically keep net11.0 up to date, so I've opened this PR.

This PR merges commits made on main by the following committers:

  • PureWeen
  • dependabot[bot]
  • kubaflo
  • akoeplinger

Instructions for merging from UI

This PR will not be auto-merged. When pull request checks pass, complete this PR by creating a merge commit, not a squash or rebase commit.

merge button instructions

If this repo does not allow creating merge commits from the GitHub UI, use command line instructions.

Instructions for merging via command line

Run these commands to merge this pull request from the command line.

git fetch
git checkout main
git pull --ff-only
git checkout net11.0
git pull --ff-only
git merge --no-ff main

# If there are merge conflicts, resolve them and then run git merge --continue to complete the merge
# Pushing the changes to the PR branch will re-trigger PR validation.
git push https://github.com/dotnet/maui HEAD:merge/main-to-net11.0
or if you are using SSH
git push git@github.com:dotnet/maui HEAD:merge/main-to-net11.0

After PR checks are complete push the branch

git push

Instructions for resolving conflicts

⚠️ If there are merge conflicts, you will need to resolve them manually before merging. You can do this using GitHub or using the command line.

Instructions for updating this pull request

Contributors to this repo have permission update this pull request by pushing to the branch 'merge/main-to-net11.0'. This can be done to resolve conflicts or make other changes to this pull request before it is merged.
The provided examples assume that the remote is named 'origin'. If you have a different remote name, please replace 'origin' with the name of your remote.

git fetch
git checkout -b merge/main-to-net11.0 origin/net11.0
git pull https://github.com/dotnet/maui merge/main-to-net11.0
(make changes)
git commit -m "Updated PR with my changes"
git push https://github.com/dotnet/maui HEAD:merge/main-to-net11.0
or if you are using SSH
git fetch
git checkout -b merge/main-to-net11.0 origin/net11.0
git pull git@github.com:dotnet/maui merge/main-to-net11.0
(make changes)
git commit -m "Updated PR with my changes"
git push git@github.com:dotnet/maui HEAD:merge/main-to-net11.0

Contact .NET Core Engineering (dotnet/dnceng) if you have questions or issues.
Also, if this PR was generated incorrectly, help us fix it. See https://github.com/dotnet/arcade/blob/main/.github/workflows/scripts/inter-branch-merge.ps1.

akoeplinger and others added 23 commits June 15, 2026 22:11
)

> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Description of Change

Several request-interception device tests were running an
`[InlineData("https://echo.free.beeceptor.com/...")]` row on Windows and
Android Helix queues even though that external service is known to be
flaky (tracked in #33927). The sibling tests in the same files already
skip that scenario; these four were missed:

-
`BlazorWebViewTests.RequestsCanBeInterceptedAndCustomDataReturnedForDifferentHosts`
-
`BlazorWebViewTests.RequestsCanBeInterceptedAndCancelledForDifferentHosts`
-
`BlazorWebViewTests.RequestsCanBeInterceptedAndCaseInsensitiveHeadersRead`
-
`HybridWebViewTests_Interception.RequestsCanBeInterceptedAndCancelledForDifferentHosts`

Reflection on the built `Microsoft.Maui.Controls.DeviceTests.dll`
(net10.0-windows10.0.19041.0) confirmed each of these methods had a
`Microsoft.Maui.TheoryAttribute` with **no** `Skip` and the
`https://echo.free.beeceptor.com/` `InlineData` attached — they would
attempt a real outbound TLS request from the Helix worker whenever
interception did not fire.

### Fix

Split the single `[Theory]` into two: one inside the `#if !ANDROID &&
!WINDOWS` block (paired with the `app://echoservice/` row) and one
inside the `#if !IOS && !MACCATALYST` block carrying `Skip = "Flaky due
to external service dependency (echo.free.beeceptor.com). See
https://github.com/dotnet/maui/issues/33927"` (paired with the
`https://` row).

This matches the convention already used by the three other sibling
methods (`CustomDataReturned`, `HeadersAdded`,
`CaseInsensitiveHeadersRead` in `HybridWebViewTests_Interception.cs`).

Importantly, **iOS / MacCatalyst coverage is preserved** — only the
`https://` rows are skipped. The `app://echoservice/` rows are fully
self-contained (the OS cannot resolve the `app://` scheme, so the
request never touches the network — it is satisfied entirely by the
`WebResourceRequested` handler inside the test).

### Per-TFM safety check

The custom `Microsoft.Maui.TheoryAttribute` has `AllowMultiple = false`,
and the `#if` guards make the two `[Theory]` attributes mutually
exclusive across every TFM in `MauiDeviceTestsPlatforms`:

| TFM | `[Theory]` (app://) | `[Theory(Skip)]` (https://) |
|---|---|---|
| net10.0-android | excluded | included |
| net10.0-windows10.x | excluded | included |
| net10.0-ios | included | excluded |
| net10.0-maccatalyst | included | excluded |

### Issues Fixed

Related to #33927.

Co-authored-by: Copilot <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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Description of Change

Restricts `/review rerun` eligibility so PRs are only queued for rerun
when there is new PR-author activity after the latest AI Summary or
previous rerun checkpoint:

- new non-command comments from the PR author
- new commits / head changes

Reviewer or maintainer reminder comments no longer satisfy the rerun
evidence check.

### Issues Fixed

Prevents `/review rerun` from applying `s/agent-ready-for-rerun` when
only a reviewer/maintainer comment was added after the latest AI
Summary.

---------

Co-authored-by: Copilot <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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Description of Change

Updates the `Skill Validation Results` PR comment to match the visual
style used by AI Summary and Test Failure Review comments:

- stable marker plus `## Skill Validation Results` title
- PR author/commit context
- status badges for overall, static checks, LLM evaluation, skills, and
agents
- a single expandable session with commit metadata
- existing static-check and LLM-evaluation details preserved inside the
session

### Issues Fixed

No issue filed.

### Validation

- Extracted and syntax-checked the `actions/github-script` post-comment
JavaScript with `node --check`
- `git diff --check`

---------

Co-authored-by: Copilot <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](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!

## Summary

Adds a deterministic, evidence-backed release-readiness skill that
produces a single "Is `release/X.Y.Zxx-srN` (or preview) ready to ship?"
report for .NET MAUI release branches — both **Servicing Releases (SR)**
and **Previews**, in both **in-flight** and **candidate** (pre-cut)
modes.

Supersedes #35754.

## What it does

`Get-ReleaseReadiness.ps1` walks the SR branch, classifies open
`regressed-in-*` issues against branch contents, computes the source-PR
list (handling cherry-pick number swaps + non-main forward-flow), and
rolls up an "is this ready to ship?" verdict with a **Blocking** summary
hoisted to the top of the report.

Posts/refreshes a single `[Release Readiness]` GitHub tracking issue per
release lane (idempotent via a semantic hash marker — only reposts when
something meaningfully changed). See **[issue #35876
(SR8)](#35876 for a live
example.

## Ship-readiness checks

A release captain sees these surface as 🟢 READY / 🟡 WATCH / 🔴 BLOCKED /
⚪ UNKNOWN rows. All BLOCKED rows roll up into the **Blocking** summary
at the top.

| Check | Catches |
|-------|---------|
| **Versions.props bump** | SR cycle hasn't been bumped on the SR branch
|
| **Versions.props servicing flip** | `PreReleaseVersionLabel=servicing`
+ `StabilizePackageVersion=true` not applied — branch silently builds
prerelease packages |
| **Bug template lists SR version** | Users can't file bugs against the
new version |
| **Main bumped to next SR cycle** | Post-SR-cut PRs on main would
falsely claim to ship in the SR being shipped |
| **BAR default-channel mapping** | SR branch not wired to `.NET <band>
SDK` in BAR — caught the real SR8 outage |
| **BAR build for SR HEAD** | No published build at the SR HEAD commit |
| **Milestone for current cycle** | Fixed issues have nowhere to land |
| **Milestone for next cycle** | Open issues can't roll forward when
current ships |
| **Stale open milestones** | Already-shipped releases accumulating
untriaged issues (scoped to same major + same cycle type, 7-day grace) |
| **CI Failure Scanner signals** | Fresh ci-scan issues filed in the
last 24h |
| **Known Build Errors** | Open KBE issues that may explain background
CI noise |

Each check that needs external tooling (darc, gh, milestone API)
degrades to **UNKNOWN** with the exact verification command embedded —
the report never silently skips.

## Expected ship date

Header line surfaces the deadline. Cadence is patch-aware:
- `PatchVersion` ends in 0 (`80`, `90`, `100`…) or `0` (previews) → 2nd
Tuesday of the month
- Anything else (`81`, `82`, `91`…) → **ASAP** hotfix, no cadence

## Custom agent

`.github/agents/release-readiness-agent.agent.md` wraps the skill —
handles regression-label confirmation, runs the script, then uses
**WorkIQ** + **maestro MCP** to:
- Patch UNKNOWN BAR rows live (e.g. when darc isn't on CI's PATH)
- Add narrative context for `rejected-from-sr` PRs (chat history, review
feedback)
- Present the final READY / Conditionally Ready / Not Ready verdict with
citations

## Testing

```bash
pwsh .github/skills/release-readiness/tests/Test-ReleaseReadiness.ps1
# 447 pass / 0 fail
```

Dogfooded live against SR7 + SR8 + the .NET 11 preview lane. Caught
real-world bugs:
- **SR8** missing from BAR default-channel mappings (verified via
`maestro_default_channels` MCP)
- `.NET 10 SR6` + `.NET 10 SR7` milestones open with 76 + 63 open
issues, past due
- `.github/ISSUE_TEMPLATE/bug-report.yml` missing `10.0.80` entry

## Methodology gotchas (documented in `references/methodology.md`)

1. **Cherry-pick number swap** — SR backports get NEW PR numbers; can't
naively grep source PR numbers
2. **Timeline cross-references** — `closedByPullRequestsReferences`
returns empty for most MAUI issues; must walk `gh api
.../issues/N/timeline` cross-referenced events
3. **Forward-flow / non-main merges** — a fix can merge into
`inflight/current` only, not `main` (real example: PR #35609)

## Files

- `.github/skills/release-readiness/SKILL.md` — skill entry point +
reference docs
- `.github/skills/release-readiness/scripts/Get-ReleaseReadiness.ps1` —
main orchestrator (deterministic, no MCP)
- `.github/skills/release-readiness/tests/Test-ReleaseReadiness.ps1` —
447-assertion test suite
- `.github/skills/release-readiness/references/methodology.md` — gotchas
and patterns
- `.github/agents/release-readiness-agent.agent.md` — MCP-enriched agent
wrapping the skill

---------

Co-authored-by: bot <bot@test>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: VSC Agent <vsc-agent@example.com>
…35942)

<!-- 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!

## Summary

Migrates the `skill-validation.yml` workflow from the legacy
`dotnet/skills` binary (`skill-validator check` + `skill-validator run`)
to
**[@microsoft/vally-cli@0.6.0](https://github.com/nicknow-ms/vally)**.
All 5 skill-eval suites are ported and validated green in CI.

## What Changed

### Workflow (`skill-validation.yml`)
- Replaced `skill-validator` install/check/run steps with `vally lint
--strict` and `vally run --eval-spec`
- Added **hermeticity negative-control gate** that verifies the sandbox
doesn't have unauthorized network access (rate-limit auth detection)
- Added `workflow_dispatch` manual trigger with `skills` and `runs`
inputs for on-demand evaluation
- Changed fixture fetch depth from `--depth=1` to `--depth=2` so `git
diff HEAD^ HEAD` works in worktree environments
- Removed all `skill-validator` references and legacy `eval.yaml` files

### Eval Specs Ported (all in `.github/skills/<skill>/tests/`)
| Skill | File | Stimuli | Status |
|-------|------|---------|--------|
| code-review | `eval.capability.vally.yaml` | 9 capability scenarios |
✅ 9/9 |
| code-review | `eval.vally.yaml` | 2 regression scenarios | ✅ 2/2 |
| code-review | `hermeticity.vally.yaml` | 1 negative control | ✅ |
| agentic-labeler | `eval.vally.yaml` | 3 labeling scenarios | ✅ |
| try-fix | `eval.vally.yaml` | 2 fix scenarios | ✅ |
| verify-tests-fail-without-fix | `eval.vally.yaml` | 2 verification
scenarios | ✅ |
| evaluate-pr-tests | `eval.vally.yaml` | 2 evaluation scenarios | ✅ |

### Key Design Decisions
- **Structural graders** (output-matches, output-not-contains) verify
hard behavioral requirements (e.g., never approve via API, verdict
markers present)
- **LLM prompt graders** (scale_1_5) assess quality and depth with
calibrated thresholds
- Regression stimuli use **frozen git worktrees** pinned to
known-regressing commits — no network calls, fully reproducible
- Capability stimuli target **real merged PRs** to test against actual
code review scenarios

## Validation

Final CI run
[#27635665319](https://github.com/dotnet/maui/actions/runs/27635665319):
**11/11 stimuli pass, 0 failures**.

Iteratively validated across 9 CI runs, fixing:
- Fixture fetch depth (parent commit needed for `git diff`)
- Structural floor regex (added 🔴 marker, broadened to accept
`Verdict`/`Finding`)
- Merged-PR prompt engineering (agents short-circuit reviews on merged
PRs)
- Environment constraints (Vally sandbox lacks GH_TOKEN — adjusted
rubrics accordingly)
- LLM judge threshold calibration (structural graders verify
correctness; LLM judges assess quality)

## Removed
- All `eval.yaml` files (legacy skill-validator format)
- `skill-validator` binary installation steps
- `--allow-repo-traversal` flag usage (Vally worktrees provide full repo
access natively)

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…compile all to v0.79.8 (#35951)

<!-- 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!

## Summary

Adds top-level `environment: gh-aw-agents` gating to the 4 gh-aw
workflows that have write capabilities or uncapped inference spend,
following the [gh-aw best
practice](https://github.github.com/gh-aw/reference/cost-management/):
*"Gate any write-capable or spendy agentic workflow behind such an
environment."*

Uses a **single shared environment** (`gh-aw-agents`) since the AzDO
federation secrets are also used by `review-trigger.yml`, so
per-workflow secret isolation isn't achievable.

Also brings **all 7** gh-aw workflow lock files to the current compiler
(v0.79.8 / AWF 0.27.2).

**Validated:** triggered `ci-status-main` on this branch — activation ✅,
agent job running ✅ (confirms `COPILOT_GITHUB_TOKEN` from environment is
accessible).

## Changes

### Commit 1: Environment gating + deprecated field migration

| Workflow | Write surface |
|----------|---------------|
| `rerun-review-scanner` | Labels, reactions, AzDO pipeline triggers,
federation secrets |
| `ci-status-main` | Creates up to 5 tracking issues/run |
| `ci-status-net11` | Creates up to 5 tracking issues/run |
| `daily-repo-status` | Creates issues + closes older ones |

All 4 now use `environment: gh-aw-agents`. Also migrates the deprecated
`max-effective-tokens: -1` → `max-ai-credits: -1` in both CI scanner
workflows.

### Commit 2: Lock freshness

Recompiles `agentic-labeler.lock.yml` and
`copilot-review-tests.lock.yml` to current gh-aw v0.79.8 / AWF 0.27.2.
No source `.md` changes — lock-only refresh.

### Commit 3: Fix copilot-evaluate-tests + recompile

Fixes `workflow_dispatch.inputs.pr_number.required: true` → `false` —
v0.79.8 correctly rejects `required: true` when `slash_command:` is also
configured (auto-dispatch can't fill required inputs). The workflow
already falls back to `github.event.issue.number` from slash command
context.

## Intentionally NOT gated

| Workflow | Reason |
|----------|--------|
| `agentic-labeler` | Uses `roles: all` for community auto-labeling;
gating would require approval per issue/PR |
| `copilot-evaluate-tests` | Comment-only (max 1), already role-gated to
`[admin, maintain, write]` |
| `copilot-review-tests` | Same as above |

## Environment setup (already done ✅)

The `gh-aw-agents` environment has been created with:
- ✅ `COPILOT_GITHUB_TOKEN` — Copilot inference auth
- ✅ `AZDO_TRIGGER_TENANT_ID` — AzDO Workload Identity Federation
- ✅ `AZDO_TRIGGER_CLIENT_ID` — AzDO Workload Identity Federation
- No required reviewers (scheduled automation would stall)
- No branch restrictions yet (can be added post-merge for `main` +
`net11.0`)

---------

Co-authored-by: Copilot <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](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!

## Problem

The milestone-drift automation (`Fix-MilestoneDrift.ps1`) maps a commit
found on an SR release branch to a milestone using **only the branch
name** — `release/10.0.1xx-sr7` → `.NET 10 SR7`. But a single SR release
branch ships **many servicing drops** over its lifetime (`10.0.70` =
SR7, `10.0.71` = SR7.1, `10.0.72` = SR7.2, …), so the base SR is too
coarse.

A revert/hotfix that lands **after** the base SR shipped actually goes
out in a later sub-patch. Milestoning it as the base SR is wrong — and
can even **downgrade** an already-correct SR7.1 issue back to SR7.

### Concrete case that motivated this

- PR #35694 is a backport to `release/10.0.1xx-sr7`; its commit is
contained only in tag `10.0.71` → it ships in **SR7.1**.
- It directly links the issue via `Fixes #35584`.
- Before this change the script resolved #35694 → **SR7** (branch name),
which would *downgrade* issue #35584 from SR7.1 → SR7.

## Root cause

`Find-ReleaseBranchForCommit` resolved the milestone via
`ConvertBranchToMilestone` (branch-name only), which has no notion of
sub-patches.

## Fix (general — no special-casing)

Add `Get-RefinedReleaseMilestone`: for a commit found on an **SR**
branch, resolve the milestone from the **earliest SR-family tag**
(`X.0.{sr}{sub}`) that actually contains the commit (git ancestry).
Rules:

- **Earliest release wins** — return the earliest family tag that
contains the commit.
- If no family tag contains it yet (the drop isn't tagged), use the
**next sub-patch** after the latest shipped family tag — **clamped to
the SR family** so it never crosses into the next SR (an exhausted SR7
at `.79` falls back to base SR7, never predicts SR8).
- **Non-SR** milestones (preview/rc/GA) are returned unchanged.

It's wired into both match paths of `Find-ReleaseBranchForCommit`. The
PR-number grep fallback now captures the **oldest** on-branch SHA
(`--reverse`, so a later revert that re-mentions `(#NNN)` can't hijack
the result) and is hardened against stderr noise (`2>$null` + strict
40-hex SHA filter).

Result: #35694 → **SR7.1**, so its existing `Fixes #35584` link marks
the issue **SR7.1**. The existing earliest-release-wins guard reconciles
the issue regardless of merge order.

Note: PR #35625 (the same revert on `inflight/current`) intentionally
stays **SR9** — a PR's milestone tracks the physical branch it merged
into; the *issue* converges to the earliest customer-facing release
(SR7.1).

## Verification

- ✅ Dry-run #35694 → `.NET 10 SR7.1` (was SR7); issue #35584
already-correct (no downgrade).
- ✅ Regression dry-runs unchanged: #34620 → SR6, #35016 → SR6, #30132 →
preview3.
- ✅ Pester suite: **162 passed, 0 failed** (9 new unit tests for the
helper, incl. the family-boundary clamp).

## Review

Reviewed with three independent models (GPT-5.5, Gemini 3.1 Pro, Claude
Opus 4.8). Consensus on correctness and contained blast radius
(refinement only ever moves *within* one SR family). Their concrete
findings — boundary clamp, stderr hardening, oldest-match grep, and a
dead parameter — were all addressed in this PR.

---------

Co-authored-by: Copilot <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](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!

## Summary

Follow-up to #35951 — extends `environment: gh-aw-agents` gating to the
**remaining 4 workflows** that were not included in the initial PR:

| Workflow | Type | Change |
|----------|------|--------|
| `agentic-labeler.md` | gh-aw | Added `environment: gh-aw-agents` |
| `copilot-evaluate-tests.md` | gh-aw | Added `environment:
gh-aw-agents` |
| `copilot-review-tests.md` | gh-aw | Added `environment: gh-aw-agents`
|
| `review-trigger.yml` | Regular GHA | Added `environment: gh-aw-agents`
to `trigger-review` job |

## Why

After #35951 merged, repo-level secrets (`COPILOT_GITHUB_TOKEN`,
`AZDO_TRIGGER_TENANT_ID`, `AZDO_TRIGGER_CLIENT_ID`) were deleted in
favor of environment-scoped secrets on the `gh-aw-agents` environment.
These 4 workflows need the `environment:` reference to access those
secrets.

## Details

- All 3 gh-aw workflow locks recompiled with `gh aw v0.79.8`
- `environment: gh-aw-agents` verified on agent + safe-outputs +
threat-detection jobs in all lock files
- `review-trigger.yml` is a regular GHA workflow — environment added at
job level, no compile needed
- Pre-existing compile warnings (agentic-labeler `pull_request_target`,
copilot-evaluate-tests `slash_command` + `bots:`) are unchanged and
intentional

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… as resolved (#35895)

<!-- 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!

### Summary

When a maintainer comments `/review`, `/review rerun`, or `/review
tests` on a PR, the command comment lingers and clutters the
conversation. This change **hides the command comment as resolved**
(collapses it) **once the command is recognized and the commenter is
authorized** (i.e. once we accept and act on it). Unauthorized or
invalid attempts are left fully visible.

### Why hide instead of delete?

The rerun scanner reconstructs review/rerun state by replaying the PR's
comment history through the **REST list endpoint**
(`Resolve-RerunEligibility.ps1`, `Query-RerunReadyPRs.ps1`,
`Get-LatestRerunCommentBefore`). **Deleting** the command comments would
erase that durable checkpoint — re-qualifying unchanged commits and
dropping `--branch`/`--platform` options on later reruns.

Minimizing (GraphQL `minimizeComment(classifier: RESOLVED)`) collapses
the comment in the web UI but **keeps it in the REST comment history**,
so the scanner keeps working. This addresses the data-loss findings from
review without losing the decluttering benefit.

### Changes

| Command | Workflow | Where it's hidden |
|---|---|---|
| `/review` | `review-trigger.yml` (`trigger-review`) | after the AzDO
pipeline is triggered (`trigger_azdo == success`) |
| `/review rerun` | `review-trigger.yml` (`mark-rerun-ready`) | after
eligibility resolution, only when `eligible == 'true'` |
| `/review tests` | `copilot-review-tests.md` (gh-aw) | pre-activation
step |

All three call `minimizeComment(input: { subjectId: <comment node_id>,
classifier: RESOLVED })`.

**`review-trigger.yml`**
- `trigger-review`: hides the `/review` comment as the last step, gated
on `steps.trigger_azdo.outcome == 'success'` so a lock-skip or failed
trigger leaves the command (and its `--branch`/`--platform` options)
visible for retry. The job already has `issues: write`.
- `mark-rerun-ready`: hides the `/review rerun` comment **after**
`Resolve-RerunEligibility.ps1` runs and only when a rerun was actually
triggered (`eligible == 'true'`). Ineligible reruns keep the comment
fully visible.

**`copilot-review-tests.md` (gh-aw)**
- Keeps `on.permissions: issues: write` so the deterministic
pre-activation job token can minimize the comment (the AI agent job
stays read-only).
- The `github-script` pre-activation step minimizes the `/review tests`
comment only when the command is exactly `/review tests` (`should_run`),
the event is `created` (not `edited`), **and** the commenter is an
authorized collaborator (`write`/`maintain`/`admin`).
- Recompiled `copilot-review-tests.lock.yml` in the same commit (gh-aw
v0.77.5). `frontmatter_hash` updated; `body_hash`, the concurrency
block, and the read-only agent-job permissions are unchanged.

### Token / permissions

No special token is required. Minimizing uses the same `issues: write`
scope deletion did, via the default `github.token`. (Minimizing is the
same moderation tier as the previous deletion — if the token could
delete the comment, it can minimize it.)

### Safety
- **No double-hide** — each command activates exactly one minimize path
(`/review tests` is gated on the exact-match that `review-trigger.yml`
explicitly skips).
- Minimizing only runs on `issue_comment` `created` events (never
`workflow_dispatch`, never `edited`).
- A failed minimize emits a `::warning::` and never fails the
review/rerun/tests trigger.
- History is preserved: the REST comment list still returns minimized
comments, so the rerun scanner and option/checkpoint recovery are
unaffected.

### Validation
- `gh aw compile` → 0 errors / 0 warnings; lock diff verified (only the
step body + `frontmatter_hash` change; concurrency intact; agent job
still `issues: read`).
- `review-trigger.yml` and the regenerated lock parse as valid YAML.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…anches from the tracker matrix (#35971)

> [!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!

Follow-up to #35807. Two independent release-readiness fixes.

---

## Fix 1 — Surface p/0-labelled PRs as Preview release blockers

### Summary

The Preview readiness engine (`Get-PreviewReadiness.ps1`) only treated
**p/0 issues** as release blockers. **p/0-labelled PRs** targeting a
preview/candidate branch were silently bucketed into the generic
"Release branch PRs" WATCH count and rendered as "Needs review or
triage" rows — never hoisted, never blocking.

The root cause is structural: the p/0 blocker path used `gh issue list
--label p/0`, which **by design never returns PRs**. So p/0 PRs were
invisible to the blocker logic.

This was observed live on the **net11.0 preview6** tracker (#35866),
where #34758, #35626, and #34600 (all `p/0`, base `net11.0`) appeared
only as generic WATCH rows instead of blockers.

### What changed

Carves p/0-labelled PRs out of the generic human-PR bucket — the label
data is **already fetched** by `Get-OpenPullRequests`, so no extra API
call — and:

- adds a **BLOCKED `P/0 release-branch PRs`** check (parallel to the
p/0-issues check) so the overall verdict turns red when one is open;
- itemizes each p/0 PR as a **`🔥 P/0 PR`** row in the hoisted **🔴
High-priority items** section (with base ref + age + per-PR next
action);
- **excludes** the new check from the **🔴 Blocking** summary (its PRs
are already enumerated in the hoist) — exactly matching the p/0-issues
treatment;
- updates the WATCH note + hoist header/intro text from 3 → 4
high-priority categories.

A PR whose base **is** the survey ref is release-relevant by definition,
so — unlike issues — no title/milestone relevance filter is applied.

### Testability

Adds a small **StrictMode-safe `Test-IsP0Pr`** helper plus a
**dot-source guard** on the engine (mirroring
`Find-ReleaseReadinessTrackers.ps1`) so the predicate can be unit-tested
without invoking the full git/gh-backed report flow.

---

## Fix 2 — Drop stale below-watermark SR branches from the tracker
matrix

### Summary

The Lane 1 in-flight detector (`Find-ReleaseReadinessTrackers.ps1`)
treated **tag-absence** as the sole in-flight signal. Abandoned hotfix
leftovers like **SR2** (patch 21) and **SR3** (patch 33) — which never
published their stable tags and sit far below the shipped watermark
(**SR7** patch 71) — were still emitted as trackers. The workflow then
spun up a **no-op matrix job** per branch: the per-job activity gate
skipped issue creation, but the job still ran.

### What changed

Adds a secondary **`Test-IsStaleSrBranch`** disambiguator applied **only
after** `Test-IsBranchInFlight` returns true. A branch is stale when
**both**:

- its patch is **strictly below** the highest shipped patch, **and**
- it has had **no commits** within the activity window (idle).

Tag-existence stays the **primary** signal; the idle requirement
preserves the out-of-order / security-hotfix case — a real reset branch
below the watermark has recent commits and is therefore **not** dropped.
Freshly-cut live SRs sit at/above the watermark and are never affected.

Dropping these at the detector removes them from the workflow matrix
**entirely**. Verified safe: SR2/SR3 have no open tracker issues, so
nothing is stranded (only SR8/SR9/preview6 have open trackers).

---

### Tests

`Test-ReleaseReadiness.ps1`:
- **12** new unit assertions for `Test-IsP0Pr` (predicate: p/0
present/absent, missing/null/empty labels, hashtable-shaped labels, null
PR; carve-out semantics: p/0 subset selected, generic bucket excludes
them).
- **7** new unit assertions for `Test-IsStaleSrBranch` (below-watermark
idle → stale; above/equal watermark → not stale; below-watermark but
active → not stale; no shipped tags → never fires).
- Live-repo E2E expectations updated: net10 now surfaces **2** SR
trackers (SR8 + SR9) instead of 4.

```
Passed: 517   Failed: 0
```

---------

Co-authored-by: Copilot <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](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!

## Summary

Removes `environment: gh-aw-agents` from the `trigger-review` job in
`review-trigger.yml`.

## Why

PR #35974 added `environment: gh-aw-agents` to `review-trigger.yml`,
which changed the GitHub OIDC token subject from
`repo:dotnet/maui:ref:refs/heads/main` to
`repo:dotnet/maui:environment:gh-aw-agents`. The managed identity
(`Testing-MI-gh2zdo`) behind `AZDO_TRIGGER_CLIENT_ID` has no federated
credential for that environment subject, so every `/review` command
fails with:

```
AADSTS700213: No matching federated identity record found for presented
assertion subject 'repo:dotnet/maui:environment:gh-aw-agents'
```

The `AZDO_TRIGGER_*` secrets have been re-added at the repo level. Once
the federated credential is updated (requires Azure access to the
managed identity), the environment gating can be re-added.

## What changed

- Removed `environment: gh-aw-agents` from the `trigger-review` job (1
line)
- All gh-aw workflows remain gated — only `review-trigger.yml` (regular
GHA) is affected

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ive assertions, add deterministic fixture coverage (#36004)

<!-- 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!

### Summary

Follow-up to #35971. De-flakes a pre-existing time-bomb in the
release-readiness skill's **own** test suite
(`.github/skills/release-readiness/tests/Test-ReleaseReadiness.ps1`).
Skills-only change — no framework code touched.

### The time-bomb

The suite had four **live, wall-clock-dependent** assertions that
hardcoded `hasRecentActivity = $true` against real release branches
(SR8, SR9, the active-SR `foreach` loop, and preview6).

The detector (`Find-ReleaseReadinessTrackers.ps1`) computes
`hasRecentActivity` from `Get-RecentCommitCount`, which runs `git log
<branch> --since=7.days` and returns `(count > 0)`. So those assertions
are only true *while the real branch has had a commit in the last 7
days*. The moment a servicing branch goes quiet for 7 days — a
**normal** end-of-cycle state — the assertion flips red.

On **2026-06-18** this actually happened: SR8's last commit (the SR7→SR8
merge #35810) landed 2026-06-11, so `--since=7.days` returned 0 and the
two SR8 assertions went red. They self-heal on the next commit, but
nondeterministic red in a suite whose entire selling point is
determinism is a credibility bug.

### What changed

- **Removed the wall-clock-dependent live assertions.** The end-to-end
detector run now asserts only that `hasRecentActivity` is a real
`[bool]` the detector emitted — never a date-dependent value.
- **Added genuinely deterministic coverage of the recency-window math**
via a synthetic fixture: a throwaway temp git repo with commits at
controlled dates (`GIT_AUTHOR_DATE`/`GIT_COMMITTER_DATE` at now−6d /
now−8d / now−30d), then calls the **real** `Get-RecentCommitCount`
(dot-sourced) against it and asserts exact counts for the 7/10/60/1-day
windows plus the `origin/`-prefixed ref form. Zero network, zero
dependence on "today"; the temp repo is cleaned up in a `finally`.
- **Corrected the misleading comments** that equated "active SR" with
`hasRecentActivity = true`. An active SR can legitimately idle for >7
days; `hasRecentActivity` is a 7-day-window signal, not a synonym for
"active".

### Verification

Full suite is green: `pwsh -NoProfile -File
.github/skills/release-readiness/tests/Test-ReleaseReadiness.ps1` →
**Passed: 552, Failed: 0** (was 545/2 before, with the two SR8 reds).
The new fixture assertions also pass under `-SkipE2E` (offline),
confirming they're network-independent.

> Note: the `maui-pr` framework pipeline intentionally skips for
skills-only PRs — that's expected, not a failure.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…den comment posting (#36003)

<!-- 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!

### The bug

The **Skill Validation** workflow's `Post results comment` job was
failing on its `Post comment` step with:

```
ReferenceError: agentCount is not defined
    at eval (... actions/github-script/v7 ...)
##[error]Unhandled error: ReferenceError: agentCount is not defined
```

The `github-script` step builds a badge array that references
`agentCount`:

```js
badge('Skills', String(skillCount), '8250df'),
badge('Agents', String(agentCount), '0969da'),   // ❌ agentCount never defined/produced
```

…but `agentCount` was **never defined** in the script and **never
produced** as an artifact, whereas `skillCount` is defined (reads
`static-results/skill-count.txt`). The script threw while constructing
the comment body — *before* any API call — so the results comment was
**never posted**.

This was introduced on 2026-06-16 in commit `1c0c0eb200` (#35713, "Style
skill validation results comment") and has silently broken the results
comment on **every** Skill Validation run since.

It is a real, deterministic workflow bug — not a permissions issue (the
job already has `pull-requests: write` + `issues: write`) and not a
merge/close race (the failure is a JS `ReferenceError`, not a 403).

### The fix

Self-contained to `.github/workflows/skill-validation.yml`:

1. **Producer** — In the existing `Save results artifact` step (the one
that writes `skill-count.txt`/`spec-count.txt`), add an agent count
written to `agent-count.txt` in the same `sv-results/` dir, flowing
through the same `static-check-results` upload/download wiring:
   ```bash
agent_count=$(find .github/agents -mindepth 1 -maxdepth 1 -name '*.md'
-type f 2>/dev/null | wc -l | tr -d ' ')
   echo "$agent_count" > sv-results/agent-count.txt
   ```
2. **Definition** — In the `Post comment` github-script step, define
`agentCount` mirroring `skillCount`/`specCount` exactly (read
`static-results/agent-count.txt` inside a try/catch that falls back to
`'?'`), placed alongside the existing definitions so it's in scope
before the badge array.
3. **Hardening** — Wrap the badge + body construction in a `try/catch`.
On any failure it logs via `console.error` and `core.warning` (no silent
swallowing) and posts a minimal **plain-text fallback** body (keeping
the `<!-- skill-validation-results -->` marker so the upsert still
finds/updates the comment). The results comment now **always** posts
even if the badge formatting breaks.

### Validation

- YAML parses (`yaml.safe_load`).
- `agentCount` is defined (script line ~898) before its only use in the
badge array (line ~1178).
- The embedded github-script JS passes `node --check` (balanced braces /
valid scope) when wrapped in an async function (it uses top-level
`await`, which github-script permits).

The embedded JS can't be executed locally (depends on the
`github`/`context`/`core` runtime), so dynamic behavior wasn't run;
logic was reviewed by hand.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…R lane parity with Preview) (#36006)

<!-- 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!

### The gap

The release-readiness skill has two report generators. The **Preview
lane** (`Get-PreviewReadiness.ps1`) already surfaces open PRs carrying
the `p/0` label as release **blockers**. The **SR lane**
(`Get-ReleaseReadiness.ps1`) did **not**: it derived blocking only from
ship-checks (`BLOCKED`) and Tier-1 `regressed-in-*` **issue**
classifications. Open PRs were listed purely informationally.

Consequence: a `p/0`-labelled PR targeting an SR branch was never
flagged as blocking on the SR tracker issue. Concretely, PR #35970
(`Revert PR #33584…`, base `release/10.0.1xx-sr8`, label `p/0`) did
**not** show as blocking on the SR8 tracker issue #35876.

### What changed

- **Ported `Test-IsP0Pr`** from the Preview lane into
`Get-ReleaseReadiness.ps1`. It is StrictMode-safe and accepts both the
production `gh --json` PSCustomObject shape and the
IDictionary/hashtable shape used by test mocks.
- **Added `Get-P0PrChecks`**, which emits a single `P/0 release-branch
PRs` ship-check — `BLOCKED` (naming each offending PR, e.g. `#35970`)
when any open `p/0` PR targets the SR branch, `READY` otherwise.
- **Merged the check into `shipChecks`** in the main flow, reusing the
already-fetched open-PR list so there is **no extra `gh` call**. Because
it's a standard `BLOCKED` ship-check, it is automatically hoisted into
the top-of-issue `🔴 Blocking` summary and escalates the verdict to **Not
Ready** — no verdict or renderer changes were needed.
- **Added deterministic synthetic-fixture unit tests** for both
functions (no network, no wall-clock dependence).

### Notes

- This is a PowerShell-only `.github/skills/**` change; the framework
`maui-pr` pipeline intentionally skips for skills-only PRs.
- Discovered while working on the separate deflake PR #36004.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update rebase action

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ments once authorized (#36021)

<!-- 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!

### Summary

Follow-up to #35895, which switched the `/review`, `/review rerun`, and
`/review tests` command-comment cleanup from delete to GraphQL
`minimizeComment(classifier: RESOLVED)`. In practice the command
comments were **still left visible** in several cases. This PR makes all
three reliably collapse **once the command is recognized and the
commenter is authorized — regardless of the command's result.**

### Root causes (observed on #30311)

1. **`/review tests` — permission bug.** The gh-aw
`copilot-review-tests` pre-activation job that minimizes the comment
only had `issues: write`. The comment lives on a **pull request**, so
`minimizeComment` needs `pull-requests: write`; with only `issues:
write` it failed with `Resource not accessible by integration` (run
`27824496263`) and the comment stayed visible. #35895's assumption that
"minimizing requires the same `issues:write` scope that deletion did" is
incorrect for PR conversation comments.

2. **`/review rerun` — eligibility gate.** The `mark-rerun-ready` hide
step was gated on `eligible == 'true'`, so an **ineligible** rerun (e.g.
`no-new-comments-or-commits`) left the comment fully visible.

3. **`/review` — trigger-outcome gate.** The `trigger-review` hide step
was gated on `trigger_azdo.outcome == 'success'`, so a **lock-skip** or
a **failed AzDO trigger** left the comment visible.

### Changes

**`copilot-review-tests.md` (gh-aw)**
- Add `pull-requests: write` to the pre-activation job
(`on.permissions`) so it can minimize a PR comment. The AI agent job
stays read-only.
- Recompiled `copilot-review-tests.lock.yml` with gh-aw `v0.79.8`; only
`frontmatter_hash` and the pre-activation permission change —
`body_hash` is unchanged.

**`review-trigger.yml`**
- `/review rerun` and `/review` now minimize the command comment
whenever the actor is authorized and the command was recognized, no
matter the downstream outcome.
- Each job's "Check actor permission" step gets an `id: auth`; the hide
step is gated on `!cancelled() && github.event_name == 'issue_comment'
&& steps.auth.outcome == 'success'`. `!cancelled()` lets the hide run
even after an upstream step failed (failed trigger / ineligible rerun /
errored eligibility), while the `steps.auth` gate keeps **unauthorized**
commenters' comments fully visible.

### Safety / scope
- **Unauthorized comments are never hidden** (the `steps.auth.outcome ==
'success'` gate, and the internal collaborator check in the `/review
tests` script).
- **Transparent to the rerun scanner.** `Resolve-RerunEligibility.ps1` /
`Query-RerunReadyPRs.ps1` / `Get-LatestRerunCommentBefore` key on
comment `id`, body and `created_at` — never on `isMinimized` — and
minimized comments are still returned by the REST list endpoint.
Verified there are no `isMinimized` references in those scripts.
- A failed minimize emits a `::warning::` and never fails the
review/rerun/tests trigger.
- The only outcome not covered is a **cancelled/timed-out run**
(`!cancelled()` skips on interruption); every completed outcome is
covered.

### Validation
- `gh aw compile` → 0 errors / 0 warnings; recompiling produced no stray
diff.
- `review-trigger.yml` parses as valid YAML.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ferences and drop invalid authorAssociation gh field (#36029)

<!-- 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!

### Problem

The `[Release Readiness]` tracker issues were rendering `gh` warnings
embedded in their bodies, two distinct root causes:

**1. Cross-repo timeline cross-references (e.g. SR8 tracker #35876).**
`Get-IssueTimelinePrs` walked a regression issue's `cross-referenced`
timeline events and collected the source PR number **without checking
which repository the PR lives in**. Real examples found live:
- regression #35584 ← PR **#102** in `zhollis21/AniSprinkles` (an
unrelated personal app)
- regression #35771 ← PR **#24** in `praveenkumarkarunanithi/maui` (a
fork)

Looked up against `dotnet/maui` these 404 (`gh pr view 24 ... Could not
resolve to a PullRequest`), and the warning gets written into the
tracker issue. Worse, a foreign PR whose number *happens* to exist in
dotnet/maui (e.g. #877 from `DIPSAS/DIPS.Mobile.UI`) silently
mis-matches an unrelated dotnet/maui PR as a "fix candidate" with no
warning at all.

**2. Invalid `gh pr list` field (e.g. SR9 tracker #35867).**
`Get-CandidatePrChecks` requested `gh pr list --json
number,title,author,authorAssociation,updatedAt,url`, but
`authorAssociation` is **not a valid `gh pr list` projection field**
(it's REST-only). The whole query failed (`Unknown JSON field:
"authorAssociation"`), surfacing a warning and silently degrading the
maintainer spoof-gate.

### Fix

- `Get-IssueTimelinePrs`: drop any cross-reference whose
`repository.full_name` ≠ the target repo. The timeline API populates
`repository.full_name` for same-repo references too, so legitimate
dotnet/maui fix PRs are preserved. This fixes both the noisy 404
warnings and the silent wrong-repo mis-matches.
- `Get-CandidatePrChecks`: remove `authorAssociation` from the `gh pr
list` projection and fetch `author_association` per title-matched
candidate via `gh api repos/{repo}/pulls/{n}` (cheap — candidate matches
are almost always 0–1). Fail closed: an unreadable association excludes
the PR so a `Candidate`-titled spoof can't slip through.

### Tests

Adds 8 deterministic, offline (gh-stubbed) regression assertions:
cross-repo timeline filtering (keeps same-repo, drops foreign #24/#102
and non-PR events; foreign-only → 0 candidates) and the REST
author-association spoof-gate (MEMBER accepted, CONTRIBUTOR excluded,
spoof-only reports exclusion). Offline suite: **492 passed / 0 failed**.

Scope check: the sibling Preview engine (`Get-PreviewReadiness.ps1`) has
neither code path, so no change is needed there.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…d newlines + unescaped pipes) (#36031)

<!-- 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!

### Problem

The `[Release Readiness]` tracker issues render markdown tables built
from upstream-controlled content — issue/PR **titles** dropped into
pipe-delimited rows (`| #link | <title> | <…> |`). Several kinds of
malformed (or hostile) title corrupt those rows:

1. **Embedded newlines.** Some `ci-scan` issues have a title containing
a **literal newline** — observed live on issue #35957, whose real GitHub
title spans two physical lines and ends with `(maui-pr-uitest\n[Content
truncated due to length]`. An embedded `\r\n` splits the row across two
physical lines, so the title tail + trailing cells land on a line that
no longer contains the issue link. This was visible on the live
trackers: **net10-sr9 #35867** (2 broken rows) and **net11-preview6
#35866** (3 broken rows).
2. **Unescaped pipes.** A literal `|` in a title (common — e.g. `Fix A |
B`) injects an extra column. The preview engine already escaped pipes
everywhere via its shared `Format-MarkdownCell`, but on the **SR** side
cells were escaped ad-hoc — several embedded titles with **no newline
collapse at all**.
3. **Escape-the-escaper pipe breakout.** A title may legally contain a
literal `\|` (backslash immediately followed by a pipe). Escaping only
the pipe turns that into `\\|`, which GitHub-flavored Markdown renders
as a literal `\` followed by an **active** column delimiter — so the row
still breaks out. Both engines had this latent bug.
4. **Raw `<`/`>` (HTML injection / display loss) on the SR side.** SR
dropped titles in raw, so a title like `Crash <!--` could inject an
HTML-comment opener, and a legitimate `List<T>` (or engine-authored
placeholder text like `Bump <PatchVersion> …`) was silently swallowed by
GitHub as an unknown HTML tag and never displayed.

The ultimate root cause of (1) is upstream (the CI Failure Scanner
producing a multi-line title), but a readiness engine should defensively
sanitize any external content it embeds into its own tables.

### Fix

- **`Get-ReleaseReadiness.ps1`** — introduce a single null-safe
**`Format-MarkdownTableCell`** helper and route **every** SR site that
embeds upstream-controlled text through it. This covers the ci-scan
rows, the **Open PRs Targeting `<srBranch>`**, **regression
classification**, **🔴 Blocking summary**, **🧹 Cleanup**, **📥 Open Fix
PRs Inbound**, and **Ship-readiness checks** tables, **plus the
candidate-PR bulleted list**. The helper:
  - collapses `[\r\n]+` → space (hazard 1),
- escapes each `|` → `\|` and **doubles only the backslash run
immediately preceding that pipe** (via a single `(\\*)\|` regex pass),
so a pre-existing `\|` becomes `\\\|` (renders a literal `\|`, no
breakout — hazards 2 & 3). The doubling is **scoped to pipe-adjacent
runs** rather than every backslash, so a title's other backslash escapes
(`\[link\](url)`, `\*not emphasis\*`) are preserved verbatim and not
de-escaped into active Markdown. No-pipe-adjacent-backslash titles are
unchanged (`a | b` → `a \| b`).
- escapes `<`/`>` → `&lt;`/`&gt;` (hazard 4), **matching the preview
engine** for SR↔Preview parity.
- **`Get-PreviewReadiness.ps1` (`Format-MarkdownCell`)** — the newline
collapse plus the same **pipe-adjacent** backslash handling so the
preview engine is immune to the `\|` breakout too.

**On `<`/`>` escaping (now consistent across both engines):** escaping
angle brackets to entities has **zero visual cost** — `&lt;T&gt;`
renders as `<T>` — so `List<T>` fidelity is preserved while raw HTML
injection is neutralized (`<!--` → `&lt;!--`). It also fixes a latent
display bug: engine-authored `NextAction` text such as `Bump
<PatchVersion> in eng/Versions.props` was previously rendered raw and
**swallowed by GitHub as an unknown HTML tag**, so the Release Captain
saw `Bump in eng/Versions.props`; it now displays correctly. SR is
additionally hash-freeze-immune (it emits its own hash at the **top** of
the body, extracted with `head -n1`) and its human-notes markers are
matched **full-line-anchored**, so escaping `<>` is defense-in-depth
layered on top of those backend invariants rather than the sole
protection. (`.Trim()` only touches leading/trailing whitespace.)

### Tests

Deterministic, offline assertions:

- **`Format-MarkdownTableCell` / `Format-MarkdownCell` unit tests** —
pipe escaping, LF/CRLF-run collapse, newline+pipe together, null/empty →
empty string, whitespace trim; **angle brackets escaped to `&lt;`/`&gt;`
(both engines, parity)**; **literal `\|` does NOT break out** (→ `A \\\|
B`); **non-pipe backslash preserved** (`C:\dir` unchanged) and
**author-escaped non-pipe Markdown not de-escaped** (`\[link\](url)`
unchanged) for both engines; **`<!--` opener neutralized** to `&lt;!--`.
- **SR ci-scan row** — an embedded-newline title renders as a **single**
physical row with its tail + age intact.
- **SR tables (end-to-end `Format-MarkdownReport`)** — a piped+newline
title in the **Open PRs Targeting**, **regression classification**, **🔴
Blocking summary**, **📥 Open Fix PRs Inbound**, and **Ship-readiness
checks** tables each stays on one physical row, pipe escaped, trailing
column intact; the BLOCKED ship-check next-action with `<PatchVersion>`
renders entity-escaped (so GitHub actually displays it).
- **Human-notes marker-forgery regressions (security)** — a title
embedding `…\n<!-- …:human-notes:begin -->\n…` in a **table cell** and
in the **candidate-PR list** must leave **exactly one** anchored
begin-marker in the rendered body (the legitimate one), proving a
hostile title cannot forge a second notes region.
- **Preview engine** — `Format-MarkdownCell` collapses LF/CRLF runs and
preserves the existing pipe / angle-bracket escaping contract.

The discriminating assertions were verified **red on the pre-fix
scripts** and **green after** (and the surgical-scoping assertions were
verified red against the earlier global-doubling commit). Offline suite:
**566 passed / 0 failed**; full E2E: **632 / 0**.

### Scope / follow-ups (intentionally out of this PR)

- Backtick (inline-code) is intentionally **not** escaped: doing so
would degrade the very common legitimate case of code-quoted titles like
`` `CollectionView` ``, and an unescaped backtick is a cosmetic-only,
non-structural concern (it cannot create a new column, inject HTML, or
forge a human-notes marker, all of which require `|`/`<`, which **are**
escaped).
- Null-safety of `.title.Length` under `Set-StrictMode -Version Latest`
is **pre-existing** (titles are non-null by GitHub API contract) and
intentionally deferred to a focused follow-up rather than mixed into
this rendering PR.
- The SR "Reverts" table's "Reverts commit" column shows `?` for every
row (the `This reverts commit <sha>` body-regex never resolves).
Pre-existing and unrelated; noted for a future pass.
- Filing an upstream issue against the CI Failure Scanner for the
malformed (multi-line) titles is worth doing separately so the trackers
receive clean input at the source.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…und guard) (#35955)

<!-- 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!

### Problem

The `/review rerun` scanner never dispatched a single AzDO run in
production. Investigation across recent scheduled runs showed the agent
correctly deciding `decision=trigger` for open PRs, yet **every**
dispatch was aborted. Two independent bugs were responsible:

1. **Only one decision per run was ever processed.** gh-aw custom
safe-output jobs run exactly once per scan. The agent was instructed to
call the `trigger_rerun_review` tool once *per candidate*, so every call
after the first was silently dropped by gh-aw's "max 1 item"
enforcement. Even when several PRs were eligible, at most one reached
the dispatch script.

2. **A false "PR not found" guard cancelled dispatches for open PRs.**
`Test-GhApiPrNotFound` classified any gh error text containing "Not
Found"/"Gone" (e.g. transient proxy/auth bodies) as a deleted PR, so the
script logged `PR #N no longer exists; skipping stale decision` and
bailed — for PRs that are demonstrably open.

### Fix

1. **Batch all decisions into one tool call.** The agent now calls
`trigger_rerun_review` **exactly once per run**, passing a single
`decisions` JSON array with one object per candidate PR.
`Get-AgentItems` expands the array (`Expand-RerunDecisionItems`) into
individual decisions, with back-compat for the legacy scalar shape. This
processes *all* eligible PRs in a scan instead of just one.

> Note: gh-aw v0.77.5 does **not** support `max: N` on a custom
`safe-outputs.jobs.<name>`, so batching into one array-typed input is
the only way to lift the one-per-run cap.

2. **Harden the not-found guard.** `Test-GhApiPrNotFound` now requires
an explicit `HTTP 404`/`410` status. The fetch block logs the raw gh
error and performs a second confirmation probe before skipping — it
fails loud (throws) rather than silently cancelling a dispatch when the
cause is ambiguous.

### Tests

- 32 Pester tests pass (`Invoke-RerunReviewTrigger.Tests.ps1`),
including:
- 5 new tests for batched `decisions` parsing (JSON string array, object
array, multi-item aggregation, legacy scalar pass-through, empty/null
payloads).
- A regression test ensuring bare "Not Found"/"Gone" text without an
HTTP 404/410 status is **not** misclassified.
- `gh aw compile rerun-review-scanner` succeeds (0 errors/warnings); the
regenerated `.lock.yml` is committed and embeds the new `decisions` tool
schema.

### Files

- `.github/scripts/Invoke-RerunReviewTrigger.ps1` — batched decision
expansion + hardened guard + raw-error logging/re-probe.
- `.github/scripts/Invoke-RerunReviewTrigger.Tests.ps1` — new coverage.
- `.github/workflows/rerun-review-scanner.md` — single batched
`decisions` tool schema + updated agent prompt.
- `.github/workflows/rerun-review-scanner.lock.yml` — recompiled.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ts (#36061)

<!-- 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!

### Summary

The SR release-readiness tracker overcounts the regressions header when
**exactly one** regression candidate exists. The live `.NET 10 SR9`
tracker ([#35867](#35867)) renders:

```
## Regression Candidates — 13 issues scanned
```

…even though only **one** issue
([#35615](#35615)) was actually
scanned. The summary table, tiers, and verdict all correctly show 1 —
only the header is wrong.

### Root cause

The header is built from `$regs.Count`:

```powershell
$regs = $Data['regressions']
... "## Regression Candidates — $($regs.Count) issues scanned"
```

Regression results are **hashtables**. `Get-RegressionCandidates`
returns its `$results` accumulator, and when exactly one candidate
matches, PowerShell **unwraps the single-element array on return**, so
`$Data['regressions']` arrives as a lone hashtable rather than a
1-element array. `.Count` on a hashtable returns its **key count** (13 —
`createdAt, confidence, milestone, state, closedAt, evidence,
candidateFixPrs, labels, stateReason, classification, recommendedAction,
issue, title`), not 1.

- **N = 0** → `@()` → `.Count` = 0 ✅ (already correct)
- **N = 1** → scalar hashtable → `.Count` = 13 ❌ (this bug)
- **N ≥ 2** → real array → `.Count` = element count ✅ (already correct)

### Fix

Force array context so `.Count` always reflects the candidate count:

```powershell
$regs = @($Data['regressions'])
```

One line. The sibling SR headers (`$blockingItems`, `$cleanupItems`,
`$openFixRows` are all `List[hashtable]`) and the preview engine
(`Get-PreviewReadiness.ps1`, which uses `List`/`@()`-wrapped
collections) are **not** affected — this is the only header fed the raw
`regressions` value.

### Tests

Added a **discriminating** regression test in
`Test-ReleaseReadiness.ps1` that reproduces the production unwrap by
assigning the regression result as a **scalar hashtable** (not `@(...)`,
which would mask the bug) and asserts the header reports `1 issues
scanned`, plus an N=2 guard for the already-correct path. A precondition
assertion locks in that the value is a scalar hashtable so a future edit
can't silently neuter the test.

- ✅ Verified the new test **fails pre-fix** (renders the key count) and
**passes post-fix**.
- ✅ Offline suite: **569 passed / 0 failed**.
- ℹ️ Full E2E: 627 passed / 3 failed — the 3 failures are
**pre-existing** (live-`gh` E2E tests: `sr-source-prs.txt`, candidate
JSON, `-InheritFromPriorSr` validation), reproduced identically on
pristine `main` (624/3) and unrelated to this change. They pass in CI's
`release-readiness.yml` Validate job, which has a proper `gh` token.

### Scope

Separate, focused follow-up off `main` — unrelated to the table-escaping
fix in #36031 (already merged). No behavior change beyond the header
count.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Updated [Magick.NET-Q8-AnyCPU](https://github.com/dlemstra/Magick.NET)
from 14.12.0 to 14.13.1.

<details>
<summary>Release notes</summary>

_Sourced from [Magick.NET-Q8-AnyCPU's
releases](https://github.com/dlemstra/Magick.NET/releases)._

## 14.13.1

### What's Changed
- Fixed loading of animated AVIF image as MagickImageCollection (#​2000)
- Fixed determining the number of frames when reading animated AVIF
images (#​2005)
- Fixed loaded of indexed color PSD file (#​2007)
- Another fix when reading JPEG compressed TIFF files. (#​2016)

### Related changes in ImageMagick since the last release of Magick.NET:
- Stack overflow in fx operation
(GHSA-rcr6-g7jc-f57g)
- Heap Buffer Over-Write of a single byte in the JP2 encoder
(GHSA-533m-3wf6-c33v)
- Use-After-Free in MSL decoder
(GHSA-5r4x-w6p5-222q)
- Infinite Loop in the MIFF decoder can lead to CPU exhaustion
(GHSA-7gg8-qqx7-92g5)
- Heap Buffer Over-Write in IPL decoder when reading multiple images of
different dimensions
(GHSA-36wm-hprc-mcf5)
- Heap Buffer Over-Write in MIFF encoder when using LZMA compression
(GHSA-jcqp-6r6f-3mfx)

### Library updates:
- ImageMagick 7.1.2-23 (2026-05-17)
- aom 3.14.0 (2026-05-12)
- openexr 3.4.11 (2026-04-30)
- libhwy 1.4.0 (2026-04-23)
- lcms 2.19.1 (2026-05-06)
- openjph 0.27.3 (2026-05-14)

**Full Changelog**:
dlemstra/Magick.NET@14.13.0...14.13.1

## 14.13.0

### What's Changed
- Added `PixelDifferenceCount` to `ErrorMetric`.

### Related changes in ImageMagick since the last release of Magick.NET:
- Corrected the patch that was made earlier to fix reading JPEG
compressed TIFF images (#​1993)
- Call CloseBlob on the correct image to prevent the blob from remaining
open (#​1997)

### Library updates:
- ImageMagick 7.1.2-21 (2026-04-21)
- harfbuzz 14.2.0 (2026-04-20)
- libpng 1.6.58 (2026-04-15)
- libraqm 0.10.5 (2026-04-11)
- libraw 0.22.1 (2026-04-06)
- libxml2 2.15.3 (2026-04-15)
- openexr 3.4.9 (2026-04-17)
- openjph 0.27.0 (2026-04-14)

**Full Changelog**:
dlemstra/Magick.NET@14.12.0...14.13.0

Commits viewable in [compare
view](dlemstra/Magick.NET@14.12.0...14.13.1).
</details>

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Magick.NET-Q8-AnyCPU&package-manager=nuget&previous-version=14.12.0&new-version=14.13.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts page](https://github.com/dotnet/maui/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…#35927)

<!-- 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 this PR adds

Two new agentic workflows that walk open CI-failure tracking issues
filed by the existing scanners and open draft `[ci-fix]` PRs against the
matching branch:

- `.github/workflows/ci-status-fix.md` — processes `[ci-scan]` issues,
opens PRs against **`main`**.
- `.github/workflows/ci-status-fix-net11.md` — processes
`[ci-scan-net11]` issues, opens PRs against **`net11.0`**.

They are the natural counterpart to the existing `ci-status-main.md` /
`ci-status-net11.md` detection workflows: one pair identifies, the other
proposes a fix. **No KBE / Build Analysis integration** — just identify
→ auto-fix PR, as requested.

Also includes two small, surgical prompt edits to both scanner files so
the fixers can trust what they emit.

## Why two workflows instead of one

The fixer logic is identical for both branches; the split is forced by a
gh-aw transport constraint, not by behavior.

gh-aw always generates a "transport patch" for its `create-pull-request`
safe-output **relative to a single static `base-branch`**, and
`max-patch-size` is hard-capped at **10 MB** by the gh-aw schema
(raising it past 10 MB is a compile error). The `main` ↔ `net11.0`
divergence is ~22 MB / ~1,000 files, so a one-file `net11.0` fix built
against a `main` base produces a ~22 MB transport patch and is
unconditionally rejected (file-count guard, then size).

Because `base-branch` is one static value per workflow, each base needs
its own workflow. With `base-branch: net11.0`, gh-aw builds the
transport patch relative to `net11.0`, so the patch is just the fix's
own delta. This was validated live (see below): the net11.0 fixer opened
a clean **1-file** draft PR against `net11.0`.

## Design highlights

**Each workflow is hard-pinned to exactly one base branch, enforced at
three layers.**

1. **gh-aw declarative gate**:
`safe-outputs.create-pull-request.base-branch` pins the base (`main` /
`net11.0`), and `allowed-base-branches` (`[main]` / `[net11.0]`) makes
gh-aw reject any other base.
2. **Prompt rule**: the agent only processes the matching label
(`ci-scan` / `ci-scan-net11`) and checks out `origin/<branch>` (Step
5.2) before authoring, so the transport patch and the downstream push
are both exactly the one-file fix delta.
3. **Self-check before emission**: the agent greps its own PR body for
`Target branch: <branch>` and confirms `base` matches; mismatch aborts.

**Iterative, capped at 5 attempts per tracking issue.** Attempt count
comes from a live GitHub PR search for `"Refs: dotnet/maui#<N>"` in
closed-unmerged `[ci-fix]` PRs (GitHub is the durable store — no per-run
state needed). After the 5th closed-unmerged attempt the workflow stops
and defers to humans (the open tracking issue is the hand-off surface).
A dedicated `[ci-fix][needs-human]` hand-off PR is planned but
**currently deferred** (Step 6 records a skip and emits no PR). Each
attempt reads prior closed PRs' approaches and close comments and must
propose a substantively different approach.

**"Is it actually fixed?" check.** Before any fix attempt, the agent
fetches the latest completed build of the failing pipeline on the target
branch and `grep -F`s the issue's failure signature against the leaf-log
output. Zero hits → silently skips ("appears fixed in latest build").
Tracking issue closure stays a human decision.

**De-flake capability for intermittent test failures.** A flakiness
probe classifies a reproducing failure as (a) infra → skip, (b)
test-quality → de-flake PR, or (c) product-masking → product fix /
hand-off. A de-flake replaces sleeps/races with condition waits and
tightened assertions; it **never** adds `[Ignore]` / `[Retry]`, weakens
assertions, or bumps timeouts.

**Visual-regression filter is the first gate.** Silently skips any issue
whose title, body, error message, or failed task names match
`screenshot` / `snapshot` / `visual diff` / `baseline image` /
`VerifyScreenshot` / etc. gh-aw can't judge visual diffs and must never
modify baseline images.

**Never mutes a test.** Stages with `[ActiveIssue]`, `Skip = "..."`,
`[SkipOnPlatform]`, csproj `<*Incompatible>` / `<ExcludeFromTestRun>`,
or edits to baseline images under `TestAssets` / `Snapshots` /
`Baselines` are detected and rejected. If the only candidate fix is a
mute, the run records a skip and stops.

**MAUI area bounds.** Compile / XAML breaks are in bounds (≤ 20 lines,
single file when possible). Device-test and UI-test failures (past the
visual-regression gate) become `help`-only PRs or de-flakes. Handler
lifecycle, threading, safe-area, perf hot-paths, Gradle/Maven feed, and
infra failures are skipped — too risky for an autonomous fix.

**Outputs only via `safe-outputs`.** Tracking issues are locked so no
comments are possible. `draft: true`, `max: 3` PRs per run,
`environment: gh-aw-agents` gating on the write-capable job,
`allowed-files` restricts to `src/Core/**`, `src/Controls/**`,
`src/Essentials/**`, `src/BlazorWebView/**`, `src/TestUtils/**`,
`src/Templates/**`, `**/PublicAPI.Unshipped.txt` (which already excludes
`.github/**`).

## Validated live

The net11.0 workflow was run end-to-end against a real `[ci-scan-net11]`
issue (#35981, a flaky `DropEventCoordinates` iOS 18.5 drag-and-drop
test). It opened draft PR **#36027** targeting `net11.0` with a clean
**1-file** de-flake (reset-between-retries + a tightened
positive-coordinate assertion; no banned mute/retry patterns) and
correct body markers (`Refs`, `Target branch: net11.0`, `Attempt 1/5`,
`Flake class: test-quality`). #36027 is left open as a genuine candidate
fix for maintainers to review — it proves the `base-branch: net11.0`
split produces a small, in-cap transport patch.

## Two small scanner edits (so the fixers can rely on what they emit)

Both `ci-status-main.md` and `ci-status-net11.md`:

1. **Mandatory `Build ID: <integer>` line** in the issue body template.
The fixer requires it as a field gate (skipping any issue missing it)
and cites it as the *original failing build* in its PR audit trail; the
existing `Build: <URL>` line is opaque to grep. (The reproduce-check
itself re-fetches the *latest* build of the pipeline.)
2. **Match-count gate** requiring the scanner to verify its own primary
error substring actually appears in the fetched failure log before
filing, and embed the result as a second hidden marker:
   ```
   <!-- ci-scan-match-count: N hits in failure.log -->
   ```
Issues with 0 matches are not filed. This blocks hallucinated signatures
from ever entering the fixers' work lists. The substring is treated as
untrusted data — it is written to a pattern file via a fresh per-run
random-delimiter single-quoted heredoc and matched with `grep -F -f`
(never interpolated into a shell command), mirroring the injection-proof
pattern the fixers use.

## Lifecycle and stop conditions

| State | Action |
|---|---|
| Open `[ci-fix]` PR already exists for the issue | Skip — human owns
the PR |
| Merged `[ci-fix]` PR exists | Skip — fix already landed |
| Open human PR (non-`agentic-workflows` label) references the issue |
Skip — human is on it |
| `attempt_count < 5` and signature still reproduces | Open attempt N+1
|
| `attempt_count >= 5` | Stop and defer to humans; never retry
(dedicated `[ci-fix][needs-human]` PR deferred — see Step 6) |
| `attempt_count` search inconclusive (API error / `incomplete_results`)
| Skip — cannot safely confirm the cap |
| Latest build no longer reproduces the signature | Skip — "appears
fixed" |
| Issue body missing required fields (`Build ID`, fingerprint, error
block) | Skip — scanner needs prompt update |
| Only candidate fix is a mute | Skip |
| Only candidate fix modifies visual baselines | Skip |
| No novel approach producible vs prior attempts | Skip — defer to next
tick |

## Files

- **NEW**: `.github/workflows/ci-status-fix.md` — main-branch fixer
(`base-branch: main`)
- **NEW**: `.github/workflows/ci-status-fix.lock.yml` — generated by `gh
aw compile`
- **NEW**: `.github/workflows/ci-status-fix-net11.md` — net11.0-branch
fixer (`base-branch: net11.0`)
- **NEW**: `.github/workflows/ci-status-fix-net11.lock.yml` — generated
by `gh aw compile`
- **EDIT**: `.github/workflows/ci-status-main.md` — `Build ID` line +
match-count gate
- **EDIT**: `.github/workflows/ci-status-net11.md` — `Build ID` line +
match-count gate

`gh aw compile` passes cleanly on both new workflows (0 errors, 0
warnings).

## Things this PR explicitly does NOT do

- Does **not** integrate Build Analysis / KBE in any form.
- Does **not** add a feedback / KPI workflow (could be a follow-up if
maintainers want one — the marker blocks in PR bodies are designed to
make it easy).
- Does **not** read PR review comments as instructions — the integrity
gate filters them and the agent never treats them as authoring input.
- Does **not** close tracking issues — closure stays a human decision.
- Does **not** modify any production source code in this PR;
workflow-only change.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reset patterns:
- global.json
- NuGet.config
- eng/Version.Details.xml
- eng/Versions.props
- eng/common/*
@PureWeen

Copy link
Copy Markdown
Member

/azp run maui-pr-uitests, maui-pr-devicetests

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 2 pipeline(s).

PureWeen added a commit that referenced this pull request Jun 25, 2026
…previews

The preview daily-flow chain is two hops: main → net<N>.0 → previewN. The radar
already hoisted the net<N>.0 → previewN hop (base = survey ref) to high priority,
but the upstream main → net<N>.0 hop (base = net<N>.0) was buried in the inflight
queue on an in-flight preview tracker — even though a stuck merge there starves
the preview branch of upstream fixes just as much. (On a *candidate* tracker the
survey ref IS net<N>.0, so the same PR was already high-priority — an asymmetry.)

Get-CategorizedPullRequests now collects merge-up PRs from BOTH the target and
inflight human sets and removes them from both queues, so a hop-B merge-up (e.g.
#36085 main → net11.0) is hoisted exactly once rather than double-listed. The
check Area, high-priority blurb, and carve-out use a single chain label
(main → net<N>.0 → previewN in-flight; main → net<N>.0 candidate), and each
high-priority row names its own hop. This also fixes a latent candidate-mode
label bug (net<N>.0 → net<N>.0) that the prior literal-main label would produce.

Adds 4 unit asserts for the inflight merge-up hoist (#36085 scenario). 747/0.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@PureWeen PureWeen closed this Jun 25, 2026
@PureWeen
PureWeen deleted the merge/main-to-net11.0 branch June 25, 2026 16:31
PureWeen added a commit that referenced this pull request Jul 6, 2026
#36111)

<!-- 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!

## Problem

The release-readiness SR tracker falsely listed **CLOSED** regression
issues as `no-fix-yet` / "Investigate" even when a merged fix was
**already on the release branch**. Concrete case: the SR8 tracker
(#35876) flagged six closed issues (#35252, #35253, #35254, #35255,
#35291, #35409) as needing investigation, but five of them were closed
with a maintainer comment naming a merged fix PR that already sits on
`release/10.0.1xx-sr8`.

**Root cause:** the fix↔issue link lived *only* in human closing-comment
prose ("This issue was fixed by PR #35028"). The fix PR never used a
closing keyword (`Fixes #NNNNN`) and GitHub recorded no timeline
cross-reference, so the timeline-only classifier
(`Get-IssueTimelinePrs`) found zero candidates and fell through to
`no-fix-yet`.

## Fix

A new **`closed-fix-unlinked`** classification (Tier 3, non-blocking).
For CLOSED issues, a fallback in `Classify-RegressionCandidate` recovers
the fix PR from the closing-comment prose and verifies it before
de-noising the alarm. Two gates keep it safe and always high-confidence:

1. **fix-phrase only** — the comment must pair the PR reference with
fix/resolve/close language. A **bare mention is rejected**, because
regression issues routinely name the *cause* PR for context ("Before PR
#X … After PR #X"), and the cause naturally sits on the branch — it is
not a fix.
2. **merged AND on the SR branch** — presence verified by SHA-ancestry
**OR** the literal `(#<num>)` squash-subject token, which survives the
cross-branch flow where the PR's `mergeCommit` SHA differs from the
SR-side SHA.

### The #35291 guard
#35291 was closed as by-design (the real bug spun off to #35310). Its
comment blames `#32080` ("Before PR #32080… After PR #32080") — `#32080`
is merged and on SR8, so the branch gate alone would have passed it. The
**fix-phrase requirement** is what correctly keeps #35291 as
`no-fix-yet`.

## New helpers
- `Get-IssueCommentPrs` — scans issue comment bodies for PR references,
tags each `fix-phrase` or `mention`.
- `Test-PrNumberOnBranch` — matches the `(#<num>)` subject token via
`git log --fixed-strings --grep`, handling squash / cross-branch flow.

## Also in this PR — shipped-SR tracker refresh-until-closed lifecycle
(folded from #36113)

The most-recently-shipped SR no longer goes dark the moment it ships.
Previously, once an SR's stable tag was published the detector dropped
its tracker entirely — but post-ship there is still follow-up work (e.g.
adding the new SR to the GitHub issue version dropdown). Now
`Find-ReleaseReadinessTrackers.ps1` emits a `mode='shipped'` tracker for
the **highest** shipped SR that **refreshes until a human closes the
issue**, with a distinct `— shipped (release/…)` title that signals the
lifecycle. Lower already-shipped SRs stay retired.
`Get-ReleaseReadiness.ps1` gains a display-only `-Shipped` switch that
surveys the SR's own branch.

*(This was originally the separate stacked PR #36113. Because the live
SR8 ship (`10.0.80`) made the two changes inseparable — #36111's E2E
suite can only be green once it encodes the shipped-tracker behavior —
#36113 was folded in here and closed. No force-push: it was applied as
an additive commit.)*

## Review response (robust preview6 asserts + fix-phrase negation guard)

Addresses the multi-model review on this PR:
- **Time-robust preview6 E2E asserts.** The net11 `preview6` asserts no
longer hard-code a single lifecycle state. They read the detector's own
`branchExists` and assert `mode`/`surveyRef`/`issueTitle` are
**consistent** with it, so the suite stays green across the `candidate →
in-flight` cut (which is what drifted CI when
`release/11.0.1xx-preview6` landed) instead of re-pinning to a snapshot
that drifts again next transition.
- **Negation guard on fix-phrase scoring.** A negated maintainer comment
(`not fixed by #X`, `won't fix #Y`, `isn't resolved by #Z`) was scored
as high-confidence `fix-phrase`. A negative lookbehind now demotes a
*solely-negated* reference to `mention`; `-match` backtracking still
upgrades a PR that has a separate non-negated fix phrase. New unit tests
exercise the real `Get-IssueCommentPrs` (mocking only its `gh` call).

## Also in this PR — preview-lane merge-up hop is now a high-priority
blocker

On an in-flight preview, the **`main → net<N>.0` merge-up hop** (the
codeflow that carries fixes from `main` into the active `net<N>.0`
development branch, which then merges into the preview branch) was
rendered as ordinary informational context. But a preview cannot ship a
fix that hasn't reached its own branch yet, so a *pending* merge-up is a
real gate. `Get-PreviewReadiness.ps1` now hoists that hop to the 🔴
High-priority section with a distinct `🔀 Merge-up PR (main → net<N>.0)`
label, and the label correctly names `net<N>.0` (not `main`) as the
upstream target. Concrete effect: PR #36085 (the live `main → net11.0`
merge) now surfaces as a High-priority blocker on the preview6 tracker
instead of being buried.

## Also in this PR — tracker auto-create label fix + net11 preview7
detection refresh

- **Auto-create label bug.** The `release-readiness.yml` workflow's
create path applied `--label area-release-readiness`, a label that
**does not exist** in the repo. `gh issue create` hard-fails (422) on an
unknown label, so any *scheduled* first-time creation of a brand-new
tracker would have aborted. The create path now uses the real
`area-infrastructure` label and attaches all labels **best-effort** —
each is probed via `gh api repos/.../labels/<name>` and
skipped-with-warning if absent — mirroring how the workflow already
handles a missing milestone. This makes new-tracker creation robust
against label drift permanently. `SKILL.md` updated to match.
- **net11 → preview7 detection snapshot.** With `net11.0` bumped to
`preview7`, the live-detection E2E now legitimately emits **two** net11
preview trackers (`preview6` in-flight + `preview7` candidate). The
hand-maintained detection snapshot was updated to select trackers **by
preview number** (not array index) and assert each one's
`mode`/`surveyRef`/`issueTitle` are **consistent with its own
`branchExists`**, so it stays green across the next `candidate →
in-flight` cut.

## Also in this PR — semantic-hash now folds in tracker `mode` (resolves
the in-flight→shipped blocker)

The shipped-SR lifecycle above had a latent ❌ that the multi-model
review caught (carried over unfixed from #36113):
`Get-ReportSemanticHash` hashed `verdict / srHead / ci / srPrs /
regressions / openSrPrs / shipChecks / nightlyFeed` but **not `mode`**.
Because `-Shipped` is a pure display relabel that surveys the **same**
SR branch as in-flight, every hashed field can be byte-identical at the
exact moment the stable tag publishes — so `hash(shipped) ==
hash(in-flight)`, the daily workflow's idempotent no-op skips `gh issue
edit`, and the tracker **never visually flips to "shipped."** The fix
folds `metadata.mode` into the `$semantic` hash (StrictMode-safe
`ContainsKey` access, defaulting absent → `'in-flight'`). `mode` is
constant within a mode, so it adds **no** daily churn — only the
one-time lifecycle transitions refresh. Regression tests assert
`hash(shipped) != hash(in-flight)` and `hash(candidate) !=
hash(in-flight)` on byte-identical content, plus mode-fold determinism
and absent-mode back-compat.

## Validation
- **Live SR8 data:** 5 of 6 flagged issues reclassify to
`closed-fix-unlinked` with the correct fix PRs (#35252#34928;
#35253/#35254/#35255/#35409#35028); by-design **#35291 correctly stays
`no-fix-yet`**.
- **Offline suite green: 761/0**, validated against live release state
(SR8 shipped, preview6 in-flight, net11.0 bumped to preview7). New
assertions cover the reclassification, the bare-mention/#35291 guard,
the not-on-branch guard, the OPEN-issue guard, the subject-token
cross-branch path, the verdict-tier mapping, the Tier-3 markdown render,
the shipped-tracker lifecycle, the branchExists-conditional
preview6/preview7 invariants, the fix-phrase negation guard, the
preview-lane merge-up hoist, the best-effort tracker-create labels, and
the **`mode`-folded semantic hash** (`hash(shipped) !=
hash(in-flight)`).

Docs updated: `SKILL.md` and `references/methodology.md` classification
tables.

This is a **tooling/skill-only** change — no product code, no public
API.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 26, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants