Skip to content

Add closed-fix-unlinked regression classification to release-readiness - #36111

Merged
PureWeen merged 10 commits into
mainfrom
pureween/rr-closed-fix-unlinked
Jul 6, 2026
Merged

Add closed-fix-unlinked regression classification to release-readiness#36111
PureWeen merged 10 commits into
mainfrom
pureween/rr-closed-fix-unlinked

Conversation

@PureWeen

@PureWeen PureWeen commented Jun 24, 2026

Copy link
Copy Markdown
Member

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!

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

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

The SR tracker falsely listed CLOSED regression issues as no-fix-yet/Investigate
even when a merged fix was already on the release branch, because the fix<->issue
link lived only in a human closing comment (no closing keyword, no GitHub timeline
cross-reference). The timeline-only classifier saw zero candidates and fell through
to no-fix-yet.

This adds a fallback in Classify-RegressionCandidate that, for CLOSED issues,
recovers the fix PR from the closing-comment prose and verifies it before
de-noising the alarm. Two gates keep it safe and high-confidence:

  1. fix-phrase only - the comment must pair the PR 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. (This is the #35291
     guard: its comment blames #32080, which is merged and on SR8, but names no
     fix - it correctly stays no-fix-yet.)
  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.

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

The new class is Tier 3 (non-blocking); its recommended action is to add a
closing reference for traceability. Validated against live SR8 data: 5 of 6
flagged issues reclassify to closed-fix-unlinked (correct fix PRs), and the
by-design #35291 correctly stays no-fix-yet. Offline suite green (654/0).

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

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 36111

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 36111"

@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Skill Validation Results

@PureWeen — new skill validation results are available based on this last commit: 9d114d3.
To request a fresh validation after new comments or commits, comment /evaluate-skills.

Overall Passed Static Passed LLM Skipped Skills 20 Agents 6

Skill Validation Results9d114d3 · Add closed-fix-unlinked regression classification to release-readiness · 2026-07-06T06:24:06Z

✅ Static Checks Passed

Skills: 20 | Eval specs linted: 7

Full lint output
── .github/skills/agentic-labeler/tests/eval.vally.yaml
npm warn deprecated prebuild-install@7.1.3: No longer maintained. Please contact the author of the relevant native addon; alternatives are available.
✔ .github/skills/agentic-labeler/tests/eval.vally.yaml is valid
── .github/skills/code-review/tests/eval.capability.vally.yaml
✔ .github/skills/code-review/tests/eval.capability.vally.yaml is valid
── .github/skills/code-review/tests/eval.vally.yaml
✔ .github/skills/code-review/tests/eval.vally.yaml is valid
── .github/skills/code-review/tests/hermeticity.vally.yaml
✔ .github/skills/code-review/tests/hermeticity.vally.yaml is valid
── .github/skills/evaluate-pr-tests/tests/eval.vally.yaml
✔ .github/skills/evaluate-pr-tests/tests/eval.vally.yaml is valid
── .github/skills/try-fix/tests/eval.vally.yaml
✔ .github/skills/try-fix/tests/eval.vally.yaml is valid
── .github/skills/verify-tests-fail-without-fix/tests/eval.vally.yaml
✔ .github/skills/verify-tests-fail-without-fix/tests/eval.vally.yaml is valid

⏭️ LLM Evaluation: Skipped

No changed skills with eval specs found.

🔍 Full results and investigation steps

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

🤖 This review was generated by AI — an automated multi-model code review (Claude Opus 4.8 · GPT-5.5 · Gemini 3.1 Pro), independent reviews reconciled via cross-pollination.


🔍 AI Multi-Model Code Review — PR #36111

"Add closed-fix-unlinked regression classification to release-readiness" (base main, +279/−1).

🟡 Verdict: NEEDS_DISCUSSION (the code is sound; red CI is environmental)

Confidence: Medium — unanimous (all 3 models). CI classification: environmental (not PR-caused).

🔬 The red Validate (PR) is environmental — NOT caused by this PR

Validate (PR) reports Passed 716 / Failed 4. All 4 failures are pre-existing, time-sensitive "preview6" E2E asserts that this PR does not touch:

  • preview6 mode = candidate, preview6 surveyRef = net11.0, preview6 issue title format, preview6 branchExists = false (no branch yet).

These assume no release/11.0.1xx-preview6 branch exists yet — but that branch has since been cut, so the "no branch yet" assumption is now stale and the candidate-mode asserts drift. The closed-fix-unlinked change is unrelated to preview detection, and the sibling stacked PR #36113 (same branch + more) passes the same suite. Recommendation: make these preview6 E2E asserts time-robust (don't hard-code "no branch yet") so CI goes green — ideally as a small follow-up, since they're pre-existing.

Per the code-review skill Rule #6, a red required check caps the verdict below LGTM even when the failures are environmental — hence NEEDS_DISCUSSION rather than LGTM. The code in this PR is sound.

✅ The closed-fix-unlinked classification is well-built

  • Correctly identifies a closed regression whose fix PR is present on the SR branch but wasn't auto-linked, and routes it to an informational tier rather than a blocker.
  • Has test coverage and clear docs (SKILL.md / methodology.md) matching the code.

💡 Non-blocking suggestions

  1. Negated fix-phrases (inline @ Get-ReleaseReadiness.ps1:1838): not fixed by #X is scored as fix evidence. Bounded to Tier-3 (no blocker hidden), but worth a negation guard.
  2. Test gaps: no direct unit test for the Test-PrNumberOnBranch squash-token grep, or the Test-PrIsToolingOnly rejection path in the closed-fix-unlinked fallback.
  3. Get-IssueCommentPrs extraction regex also matches issue refs, so Get-PrInfo may be invoked on an issue number — harmless today, worth narrowing.

🏁 Recommendation

NEEDS_DISCUSSION → the code is approve-adjacent. No code changes are required by the classification itself; the only thing keeping it from green is the environmental preview6 CI drift (a pre-existing, time-sensitive assert that should be made robust). A maintainer can decide whether to fix those asserts here or in a follow-up.


Reviewed with the code-review skill: independence-first, full-file reading, local reproduction of the Validate (PR) failure to classify it as environmental, and cross-model reconciliation.

Comment thread .github/skills/release-readiness/scripts/Get-ReleaseReadiness.ps1 Outdated
PureWeen and others added 3 commits June 25, 2026 08:38
… manually closed

After an SR ships (its stable tag exists), its [Release Readiness] tracker
issue still has post-ship follow-up worth surfacing — adding the new build to
the GitHub issue version dropdown, publishing release notes, closing out the
milestone. Previously the detector hard-retired every shipped SR the moment its
tag appeared, so the just-shipped tracker stopped refreshing immediately.

Add a 'shipped' tracker mode for the single most-recently-shipped SR (highest
shipped patch):

- Find-ReleaseReadinessTrackers.ps1: Lane 1 emits mode='shipped' for the
  highest shipped SR (guarded by $branchPatch -eq $highestShippedPatch); older
  shipped SRs stay retired. New-Tracker gains a 'shipped' title variant.
- release-readiness.yml: the report step passes -Shipped for shipped trackers;
  the issue step treats 'shipped' as REFRESH-ONLY — it updates an open tracker
  but never (re)creates one, so once a human closes it, it stays closed.
- Get-ReleaseReadiness.ps1: new display-only -Shipped switch. It surveys the SR
  branch identically to in-flight (mode only ever gates 'candidate' logic) and
  solely relabels the rendered header mode=shipped, mutually exclusive with
  -Candidate, so a post-ship tracker no longer misreports as in-flight.
- Tests: +6 render assertions (in-flight/shipped/candidate header + H1) and the
  detector E2E updated for the SR8 ship and preview6 branch-cut drift. Full
  suite 738/0.
- SKILL.md: documents the post-ship 'shipped' refresh-only lifecycle.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…fix-phrase negation guard

Two changes from the PR #36111 multi-model review (NEEDS_DISCUSSION):

1. Make the net11 preview6 E2E asserts lifecycle-robust. Instead of hard-coding
   either "candidate, no branch" or "in-flight, branch cut", read the detector's
   own branchExists and assert mode/surveyRef/issueTitle are CONSISTENT with it.
   This stays green across the candidate->in-flight cut (which is what drifted the
   suite when release/11.0.1xx-preview6 landed) rather than re-pinning to a snapshot
   that drifts again at the next transition. Same invariant-over-snapshot philosophy
   as the existing hasRecentActivity asserts.

2. Add a negation guard to Get-IssueCommentPrs 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' because the regex only needed a fix
   word within 60 chars before the PR ref. 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) to keep the regex under test.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The preview release radar labelled the merge-up check and high-priority blurb
as 'main → preview6', but the actual daily-flow upstream for the preview lane
is net11.0 → preview6 (main flows to net11.0, net11.0 flows to the preview
branch). The filter and bucketing were already correct; only the human-readable
label hardcoded 'main'. Replace with $mainBranch (net11.0 on the preview lane,
main on the SR lane) so the captain-facing label names the real flow that the
net11.0 → preview6 merge-up PR will populate.

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

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

🤖 This review was generated by AI — an automated multi-model code review (Claude Opus 4.8 · GPT-5.5 · Gemini 3.1 Pro).


🔍 AI Multi-Model Code Review — PR #36111 · Round 2

HEAD: 0d292b43 — now a combined PR: the closed-fix-unlinked classification + the refresh-most-recently-shipped-SR-tracker feature (folded in from the former #36113) + the R1 review fixes.

🔴 Verdict: NEEDS CHANGES

Confidence: High — unanimous (all 3 models).

✅ Two R1 items are resolved — nice

  • Environmental CI fixed: Validate (PR) now passes — the preview6 E2E asserts were made robust (no longer hard-coding "no branch yet" after the preview6 branch was cut). 👍
  • Negation guard added & correct: not fixed by #X is no longer scored as positive fix evidence (all 3 models confirmed negation_guard_correct: true). The R1 thread is resolved.

❌ Blocker — the #36113 semantic-hash bug came along unfixed (see inline @ Get-ReleaseReadiness.ps1:1352)

By folding the refresh-shipped-tracker feature into this PR, it inherits the confirmed ❌ error from the #36113 review, and all 3 models re-verified it is still unresolved: Get-ReportSemanticHash excludes mode, the -Shipped path surveys identically to in-flight, and the function isn't touched by this PR — so the daily workflow's hash-compare skips gh issue edit at the in-flight→shipped transition and the tracker never flips to "shipped." The headline behavior silently no-ops.

Fix: fold mode/the shipped tag into Get-ReportSemanticHash (or force the edit on the mode flip) + a regression test (hash(shipped) != hash(inflight) on identical content). Details inline.

💡 Non-blocking

  • Negation guard residual (Opus): the guard catches not fixed by #X but misses an intervening adverb like not yet fixed by #X / still not resolved by #X. Minor — worth extending the negation lookbehind to span a short adverb.

🏁 Recommendation

REQUEST_CHANGES — one blocker: persist the in-flight→shipped flip (the semantic-hash fix + a guard test). The closed-fix-unlinked classification, the preview6 robustness, and the negation guard are all good; this is the same one-spot fix called out on #36113, which just needs to land here since the feature now lives in this PR.


Reviewed with the code-review skill: independence-first, full-file reading, semantic-hash control-flow re-verification (the prior confirmed error was re-checked against the combined branch by all 3 models).

Comment thread .github/skills/release-readiness/scripts/Get-ReleaseReadiness.ps1
PureWeen and others added 3 commits June 25, 2026 11:18
…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>
…pshot

The release-readiness workflow's issue-create path attached a non-existent
`area-release-readiness` label, which would 422 `gh issue create` and abort
every scheduled auto-create of a new tracker. Switch to the actual
`area-infrastructure` label the live trackers use, and make label attach
best-effort (probe each label, skip with a warning if missing) mirroring the
milestone handling so a future label rename can't fail the job again.

Update the live E2E detection snapshot: net11.0 was bumped to
PreReleaseVersionIteration=7, so the detector now emits two preview trackers
(preview6 in-flight + preview7 candidate from net11.0) instead of one. Select
each preview by number and add a preview7 candidate assertion block.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Addresses the Round-2 review blocker on #36111 (re-confirmed from #36113 by all
3 models): 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 moment the stable tag publishes, so
hash(shipped) == hash(in-flight). The daily workflow's idempotent no-op then
skips 'gh issue edit' and the tracker never flips to 'shipped' — the shipped
lifecycle silently no-ops.

Fold metadata.mode into the semantic hash (StrictMode-safe ContainsKey access,
defaulting absent → 'in-flight' to match Format-MarkdownReport). mode is
constant within a mode, so this adds no daily churn — only the one-time
candidate→in-flight and in-flight→shipped transitions refresh.

Regression tests: hash(shipped) != hash(in-flight) and candidate != in-flight on
byte-identical content; mode-fold determinism; absent-mode defaults to in-flight.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
kubaflo
kubaflo previously approved these changes Jun 25, 2026

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

🤖 AI-generated approval (multi-model review automation, approving on @kubaflo's behalf).

Approving — 3-model LGTM (Round 3). The last blocker is fixed.

This combined PR (closed-fix-unlinked classification + refresh-most-recently-shipped-SR-tracker) is now clean:

  • The semantic-hash bug is fixedmode is folded into Get-ReportSemanticHash, so an in-flight→shipped flip now changes the hash and the tracker actually refreshes to "shipped." All 3 models verified (semantic_hash_bug_fixed: true), including the one that originally found the bug. The default-when-absent is 'in-flight', so existing in-flight trackers don't mass re-churn on first run.
  • A regression guard test was addedhash(in-flight) != hash(shipped) on identical content, plus a deterministic-recompute check (guard_test_present: true).
  • ✅ Earlier rounds' items all resolved: preview6 E2E asserts made robust (Validate (PR) green), fix-phrase negation guard correct, closed-fix-unlinked classification sound.

One cosmetic non-blocking nit (Opus): Get-ReportSemanticHash's mode guard is -and $Data.metadata['mode'] (truthy) while Format-MarkdownReport uses ContainsKey only — a present-but-empty mode would hash as 'in-flight' but render as empty. Harmless in practice (mode is always set); align the two guards if you touch it.

Nicely iterated — from a real false-green class of bugs down to a clean, test-guarded gate.

Resolves the release-readiness E2E conflict in Test-ReleaseReadiness.ps1.
Only one file conflicted; all scripts auto-merged cleanly.

Resolution: took this PR's 2-tracker net10 model (SR8 shipped+refresh-until-closed,
SR9 candidate) — ground-truth-verified by running the merged detection engine, which
retains #36111's shipped-SR-tracker feature. Preserved #36172's drift-proof derived
highestShippedTag assertions (Get-ExpectedHighestShippedTag) and dropped main's
now-incompatible shipped-exclusion invariant (SR8 is a shipped tracker that legitimately
carries tag 10.0.80). Full suite green: 809 passed / 0 failed.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 6, 2026 05:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances the release-readiness tooling to better classify regressions and manage tracker lifecycles, primarily by (1) de-noising closed regressions whose fixes are only mentioned in human closing comments and (2) keeping the most-recently-shipped SR tracker refreshing until manually closed. It also improves preview readiness signaling around merge-up PRs, and hardens the workflow’s tracker creation behavior.

Changes:

  • Add closed-fix-unlinked regression classification by extracting fix PR references from issue comment prose and verifying the fix is merged and present on the SR branch.
  • Introduce/propagate a shipped tracker mode (refresh-only) for the most-recently-shipped SR, including workflow behavior and semantic-hash updates so the UI flips modes reliably.
  • Improve preview readiness by hoisting main → net<N>.0 merge-up PRs into high-priority blockers and making detection/tests more time-robust.
Show a summary per file
File Description
.github/workflows/release-readiness.yml Adds refresh-only handling for mode=shipped and makes label attachment best-effort to avoid failing auto-create on missing labels.
.github/skills/release-readiness/tests/Test-ReleaseReadiness.ps1 Expands unit/E2E coverage for shipped tracker mode, mode-aware hashing/rendering, comment-based fix recovery, and preview merge-up hoisting.
.github/skills/release-readiness/SKILL.md Documents -Shipped behavior, shipped tracker lifecycle, new classification, and updated label behavior.
.github/skills/release-readiness/scripts/Get-ReleaseReadiness.ps1 Implements -Shipped, adds comment-based PR extraction + branch-presence checks, adds closed-fix-unlinked, and folds mode into semantic hash.
.github/skills/release-readiness/scripts/Get-PreviewReadiness.ps1 Treats merge-up PRs in the full chain as high-priority blockers and improves labeling/text consistency for the merge-up chain.
.github/skills/release-readiness/scripts/Find-ReleaseReadinessTrackers.ps1 Emits mode='shipped' tracker for the highest shipped SR (refresh-until-closed), while retiring older shipped SRs.
.github/skills/release-readiness/references/methodology.md Updates methodology to include closed-fix-unlinked classification semantics.

Copilot's findings

  • Files reviewed: 7/7 changed files
  • Comments generated: 1

# 'mention', not high-confidence 'fix-phrase'. Because -match backtracks,
# a separate non-negated fix phrase for the same PR still matches; only a
# SOLELY-negated reference is demoted.
$isFix = $body -match "(?i)(?<!\b(?:not|never|no|cannot|can't|cant|isn't|isnt|wasn't|wasnt|aren't|arent|weren't|werent|won't|wont|don't|dont|doesn't|doesnt|didn't|didnt)\s{0,3})(?:fix(?:e[ds])?|resolv(?:e[ds]|ing)?|close[ds]?)\b[\s\S]{0,60}?(?:pull/|#)$num\b"
Copilot AI added 2 commits July 6, 2026 00:56
…oke-run

Adversarial review of the closed-fix-unlinked classifier surfaced three
correctness gaps in the release-readiness tooling:

- Revert guard (fallback): the closed-fix-unlinked path accepted a
  comment-cited MERGED fix without checking whether the SR later reverted
  it, nor whether the cited PR was itself a 'Revert ...' rollback. Because
  Test-PrNumberOnBranch matches '(#num)' — which also appears inside a
  revert commit's own subject 'Revert "... (#num)" (#N)' — a rolled-back
  fix passed the on-branch gate and was reported as 'No ship risk'. Now
  mirrors the main SR-contents/candidate paths (revertedPrSet + Revert-title
  skip).

- Cross-repo references (Get-IssueCommentPrs): the extraction regex stripped
  the owner/repo qualifier, so 'dotnet/runtime#123' or a cross-repo
  github.com/.../pull/123 URL was mistaken for maui#123. Extraction is now
  repo-aware and rejects foreign qualifiers while preserving recall for
  same-repo shorthand, same-repo pull URLs, bare #N and PR#N.

- Validate smoke-run (workflow): the PR-time validate job lacked the
  '-Shipped' branch the production job has, so the shipped code path was
  never exercised on PRs. Added the mirroring elif.

Also clarifies the fix-phrase negation comment (adjacent vs non-adjacent).
Adds 9 regression assertions; suite 818/0.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ss-repo refs

Round-2 adversarial review surfaced two verified false-green paths in the
closed-fix-unlinked Tier-3 classifier:

1. Manual/hand-authored reverts (e.g. #36152 reverting #35372) carry neither
   GitHub's quoted 'Revert "..."' subject nor a 'This reverts commit <sha>'
   body line, so Get-RevertedPrFromSubject returned null. revertedPrSet then
   recorded only the revert PR; the reverted fix's original commit still
   satisfied the on-branch gate, and a 'fixed by #35372' comment on a CLOSED
   issue was falsely de-noised. Add a third, anchored pattern for the
   'Revert - <title> #N (#M)' form that recovers the reverted PR (#N) while
   never returning the revert's own trailing (#M).

2. Scheme-less cross-repo paths ('dotnet/runtime/pull/N', no github.com host,
   no #) matched the bare 'pull/' alternative and were mistaken for maui#N.
   Add a 'pathrepo' owner/repo group before the bare 'pull/' alternative and
   skip when it names a foreign repo; same-repo scheme-less paths are still
   accepted.

Adds 6 regression tests (4 revert-subject, 2 scheme-less cross-repo). Suite 824/0.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 6, 2026 06:23

@PureWeen PureWeen left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔬 Adversarial multi-model review — 3 reviewers × 2 rounds

Re-reviewed after the last push using independent reviewers (opus-4.8, gpt-5.5, opus-4.6) with adversarial consensus (verify factual claims against source; don't discard a finding on vote count alone). Any fixes were pushed to the branch; this is a COMMENT review only — approval is a human call.

Round 1 → fixed in 95fdff5077

# Finding Consensus Action
A closed-fix-unlinked fallback didn't skip reverted fix PRs agree (2/3) Fallback now skips $revertedPrSet members + Revert-titled PRs
C Workflow validate job didn't cover -Shipped dispatch verified Added -Shipped branch to validate job
D Cross-repo owner/repo#N / github.com/.../pull/N refs treated as maui#N verified Repo-aware extraction regex in Get-IssueCommentPrs
B Non-adjacent negation window (fix-phrase guard) disputed (1/3) Doc-only reword; regex left as-is (bounded, documented)

Round 2 → fixed in 9d114d35af

gpt-5.5 returned NEEDS_CHANGES with two findings; both verified against live maui PRs and fixed:

  • ❌ Manual reverts leaked a false green. #36152 ("Revert - Fix Android stale ContainerView root leak #35372") reverts #35372 but carries neither GitHub's quoted Revert "..." subject nor a This reverts commit <sha> body line, so Get-RevertedPrFromSubject returned null. revertedPrSet then held only the revert PR, the reverted fix's original commit still satisfied the on-branch gate, and a "fixed by #35372" comment on a closed issue was falsely de-noised. Added an anchored third pattern for the Revert - <title> #N (#M) form that recovers #N and never returns the revert's own (#M).
  • ⚠️ Scheme-less cross-repo paths. dotnet/runtime/pull/N (no github.com host, no #) matched the bare pull/ alternative → mistaken for maui#N. Added a pathrepo owner/repo group before the bare pull/ alt and skip on a foreign repo; same-repo scheme-less paths still accepted.

opus-4.8 and opus-4.6 both returned LGTM in Round 2 (ran the suite 818/0, traced the Round-1 fixes, verified regex group numbering empirically).

Residuals (not fixed — bounded/known)

  • 💡 Contrived cross-repo number-collision "fix-phrase bleed" — requires a same-number PR in another repo cited with fix language; very low likelihood, safe direction.
  • 💡 Non-adjacent negation window — documented; bounded proximity window.

Verification

  • Suite 824/0 locally (818 + 6 new regression tests: 4 revert-subject, 2 scheme-less cross-repo).
  • Both edited files AST-parse clean.
  • CI on 9d114d35af: Validate (PR) pass, Static validation pass, PR gate pass. maui-pr correctly skipping (tooling-only PR).

Verdict: all verified findings resolved. No outstanding ❌/⚠️. Deferring to a human for the actual approval.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 7/7 changed files
  • Comments generated: 1

Comment on lines +1917 to +1937
$refs = [regex]::Matches($body, '(?:(?<qual>[A-Za-z0-9._-]+/[A-Za-z0-9._-]+)#|github\.com/(?<urlrepo>[A-Za-z0-9._-]+/[A-Za-z0-9._-]+)/pull/|(?<pathrepo>[A-Za-z0-9._-]+/[A-Za-z0-9._-]+)/pull/|pull/|#)(\d+)')
foreach ($m in $refs) {
$qual = $m.Groups['qual'].Value
$urlRepo = $m.Groups['urlrepo'].Value
$pathRepo = $m.Groups['pathrepo'].Value
if ($qual -and $qual -ne $Repo) { continue } # cross-repo owner/repo#N shorthand
if ($urlRepo -and $urlRepo -ne $Repo) { continue } # cross-repo github.com/.../pull/N URL
if ($pathRepo -and $pathRepo -ne $Repo) { continue } # cross-repo scheme-less owner/repo/pull/N
$num = [int]$m.Groups[1].Value
# Does THIS comment pair the reference with fix/resolve/close language
# within a short window (tolerates the long ".../pull/" URL prefix)?
# The negative lookbehind drops ADJACENTLY-negated fix phrases ("not
# fixed by #X", "won't fix #Y", "isn't resolved by #Z") so they score as
# a bare 'mention', not high-confidence 'fix-phrase'. Because -match
# backtracks, a separate non-negated fix phrase for the same PR still
# matches; only a SOLELY-(adjacently-)negated reference is demoted. A
# non-adjacent negation ("won't be fixed by #X") is not caught here, but
# the caller's merged-AND-on-branch gates still bound the blast radius.
$isFix = $body -match "(?i)(?<!\b(?:not|never|no|cannot|can't|cant|isn't|isnt|wasn't|wasnt|aren't|arent|weren't|werent|won't|wont|don't|dont|doesn't|doesnt|didn't|didnt)\s{0,3})(?:fix(?:e[ds])?|resolv(?:e[ds]|ing)?|close[ds]?)\b[\s\S]{0,60}?(?:pull/|#)$num\b"
$ev = if ($isFix) { 'fix-phrase' } else { 'mention' }
if (-not $byNum.ContainsKey($num) -or $ev -eq 'fix-phrase') { $byNum[$num] = $ev }

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

🔍 AI-generated review (automated multi-model orchestrator, on behalf of @kubaflo) — Opus 4.8 · GPT-5.5 · Gemini 3.1 Pro.

✅ Round 4 — APPROVED (new commit 9d114d35 reviewed)

New commit 9d114d35 ("Harden closed-fix-unlinked against manual reverts and scheme-less cross-repo refs") is a well-reasoned adversarial-hardening follow-up to the Tier-3 classifier. Two verified false-green paths closed:

  1. Manual/hand-authored reverts (Revert - <title> #N (#M), no GitHub-quoted subject, no This reverts commit <sha> body) — Get-RevertedPrFromSubject returned null, so the reverted fix's original commit still satisfied the on-branch gate and a fixed by #N comment on a CLOSED issue was falsely de-noised. New anchored regex (?i)^(?:\[[^\]]+\]\s+)?Revert\b.*#(\d+)\s+\(#\d+\)\s*$ recovers #N while never returning the trailing (#M).
  2. Scheme-less cross-repo paths (dotnet/runtime/pull/N, no host, no #) matched the bare pull/ alternative and were mistaken for maui#N. New pathrepo group ordered before the bare pull/ alternative, skipped when it names a foreign repo; same-repo scheme-less paths still accepted.

Verification

  • Regex correctness confirmed. Greedy .* + end-anchor picks the trailing #N (#M) correctly; the char class excludes / so qual can't swallow a path; the pathrepo alternative precedes bare pull/ so the specific match wins. Both changes are safe-direction — a rare misfire only ever marks a PR reverted, never falsely shipped.
  • 6 regression tests land exactly on the new paths: manual-revert recovery, [branch-prefix] Revert form, two safety negatives (non-revert #N (#M) → null; revert with only (#M) → null), scheme-less cross-repo reject + same-repo accept. Suite 824/0.
  • CI green: Validate (PR) (Pester) ✅ 2m37s, PR gate ✅, Static validation ✅, Skill Validation ✅. evaluate jobs skipping (no eval entry for this skill). maui-pr skipping (by-design .github/**).

Verdict: Approve. Correct, safe-direction, and precisely tested. Re-approving on the refreshed head.

@PureWeen
PureWeen merged commit 1582034 into main Jul 6, 2026
18 of 19 checks passed
@PureWeen
PureWeen deleted the pureween/rr-closed-fix-unlinked branch July 6, 2026 12:12
@github-actions github-actions Bot added this to the .NET 10 SR9 milestone Jul 6, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 6, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-infrastructure CI, Maestro / Coherency, upstream dependencies/versions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants