Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions .github/skills/release-readiness/scripts/Get-PreviewReadiness.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,19 @@ param(
$ErrorActionPreference = "Stop"
Set-StrictMode -Version Latest

# Shared nightly-feed freshness helpers (Get-NightlyFeedFreshness / Format-NightlyFeedBanner).
# Defensive load: the banner is auxiliary signal, not part of the verdict, so a missing
# helper degrades to "no banner" rather than crashing the unattended preview tracker job.
# Loaded above the dot-source guard so the pure renderer is reachable from the test harness.
$Script:NightlyFeedHelperLoaded = $false
$nightlyFeedHelperPath = Join-Path $PSScriptRoot 'NightlyFeed.ps1'
if (Test-Path $nightlyFeedHelperPath) {
. $nightlyFeedHelperPath
$Script:NightlyFeedHelperLoaded = $true
} else {
Write-Warning "NightlyFeed.ps1 helper not found at $nightlyFeedHelperPath — nightly-feed banner disabled." -WarningAction Continue
}

# ===================================================================
# BRANCH PARSING
# ===================================================================
Expand Down Expand Up @@ -1346,6 +1359,49 @@ $report = [PSCustomObject]@{
PriorityIssues = $priorityIssues
KnownBuildErrorIssues = $kbeIssues
CiScanIssues = $ciScanIssues
NightlyFeed = $null
}

# Nightly dogfood feed freshness (preview lane). Tracks the inflight/current dogfood stream
# (ci.inflight builds) on the dotnet<major> feed; falls back to this preview's preview.N
# version band when the feed has no inflight builds yet (the common case while a major is
# still in preview — its newest bits ARE the preview.N builds). Fail-open: any gap (helper
# unloaded, version unreadable, network error) degrades to "no banner".
$nightlyFeedBanner = $null
if ($Script:NightlyFeedHelperLoaded -and
(Get-Command Resolve-NightlyDogfoodFreshness -ErrorAction SilentlyContinue) -and
(Get-Command Format-NightlyFeedBanner -ErrorAction SilentlyContinue)) {
try {
$nfFeed = "dotnet$majorVersion"
$nfFeedUrl = "https://dev.azure.com/dnceng/public/_artifacts/feed/$nfFeed"
$nfIteration = Get-PreReleaseVersionIteration -BranchName $SurveyRef
if ([string]::IsNullOrWhiteSpace($nfIteration)) { $nfIteration = "$previewNumber" }
$nfBand = "$majorVersion.0.0-preview.$nfIteration"
$nfBandPrefix = '^' + [regex]::Escape("$nfBand.")

$nfFresh = Resolve-NightlyDogfoodFreshness -Feed $nfFeed -BandPrefixRegex $nfBandPrefix
if ($null -eq $nfFresh) { $nfFresh = @{ unknown = $true } }

$nfBuildType = [string](Get-NightlyFeedProp $nfFresh 'buildType')
# Label honestly, mirroring the SR lane: 'inflight' -> ci.inflight; a definitive band
# fallback -> the band; anything else (unknown / transient inflight failure) -> ci.inflight,
# the stream we were measuring -- never imply the band carries the signal when unknown.
$nfTypeNote = if ($nfBuildType -eq 'inflight') { 'ci.inflight' }
Comment thread
kubaflo marked this conversation as resolved.
Resolved
elseif ($nfBuildType -eq 'band') { "``$nfBand`` (preview.$nfIteration)" }
else { 'ci.inflight' }
$nfLaneLabel = "[``$nfFeed``]($nfFeedUrl) · $nfTypeNote"
$nfFresh['laneLabel'] = $nfLaneLabel
$nfFresh['feedUrl'] = $nfFeedUrl
$nfFresh['versionPrefix'] = $nfBandPrefix

$report.NightlyFeed = $nfFresh
$nightlyFeedBanner = Format-NightlyFeedBanner -Freshness $nfFresh -Now ([DateTime]::UtcNow)
} catch {
# -WarningAction Continue: keep this fail-open even under an ambient
# $WarningPreference='Stop', where a bare Write-Warning would be promoted to a
# terminating error inside the catch and escape, crashing the unattended job.
Write-Warning "Nightly-feed freshness check failed (non-fatal): $($_.Exception.Message)" -WarningAction Continue
}
}

$md = [System.Text.StringBuilder]::new()
Expand All @@ -1360,6 +1416,10 @@ if ($Mode -eq 'candidate') {
[void]$md.AppendLine("")
[void]$md.AppendLine("**Overall status:** **$overallStatus**")
[void]$md.AppendLine("")
if ($nightlyFeedBanner) {
[void]$md.AppendLine($nightlyFeedBanner)
[void]$md.AppendLine("")
}

# === HIGH-PRIORITY ITEMS (hoisted to the very top) ===
# Four categories the release captain must see BEFORE anything else:
Expand Down
119 changes: 119 additions & 0 deletions .github/skills/release-readiness/scripts/Get-ReleaseReadiness.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,18 @@ param(
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest

# Shared nightly-feed freshness helpers (Get-NightlyFeedFreshness / Format-NightlyFeedBanner).
# Defensive load: the banner is auxiliary signal, not part of the verdict, so a missing
# helper must degrade to "no banner" rather than crash the unattended nightly tracker job.
$Script:NightlyFeedHelperLoaded = $false
$nightlyFeedHelperPath = Join-Path $PSScriptRoot 'NightlyFeed.ps1'
if (Test-Path $nightlyFeedHelperPath) {
. $nightlyFeedHelperPath
$Script:NightlyFeedHelperLoaded = $true
} else {
Write-Warning "NightlyFeed.ps1 helper not found at $nightlyFeedHelperPath — nightly-feed banner disabled." -WarningAction Continue
}

# DETERMINISTIC RULE — SR branches in dotnet/maui ALWAYS cut from `main`.
# Refuse to operate on any `inflight/*` or `staging/*` ref — those are
# integration branches, not SR sources. This guard exists because conflating
Expand Down Expand Up @@ -2999,6 +3011,27 @@ function Get-ReportSemanticHash {
"$($_.Area):$($_.Status)"
}) -join '|'
} else { '' }
# Nightly dogfood feed banner state. Folded in so a feed going stale (or a
# fresh build landing) refreshes the tracker even on an otherwise-quiet branch
# — the banner is the whole point of the feature and must not be frozen out by
# the idempotent no-op. We hash the non-drifting tier + resolved version (NOT the
# "N days" count) so threshold crossings and new builds flip the hash but a daily
# day-count tick within the same tier does not (no watcher spam). Fail-open: if the
# NightlyFeed helper isn't loaded, contributes '' (hash behaves as before).
nightlyFeed = if ($Data.ContainsKey('nightlyFeed') -and $Data['nightlyFeed'] -and
(Get-Command Get-NightlyFeedTier -ErrorAction SilentlyContinue)) {
$nf = $Data['nightlyFeed']
# Reuse the SAME instant the banner was rendered with (stored by
# Add-SrNightlyFeedFreshness) so the hashed tier can never disagree with
# the displayed banner tier and freeze a stale banner via the no-op gate.
# Fall back to UtcNow when unset (e.g. unit tests that inject nightlyFeed directly).
$nfNow = if ($Data.ContainsKey('nightlyFeedNow') -and $Data['nightlyFeedNow']) {
[datetime]$Data['nightlyFeedNow']
} else { [datetime]::UtcNow }
$tier = Get-NightlyFeedTier -Freshness $nf -Now $nfNow
$ver = [string](Get-NightlyFeedProp $nf 'version')
if ($ver) { "$tier|$ver" } else { $tier }
} else { '' }
}

$json = $semantic | ConvertTo-Json -Depth 5 -Compress
Expand Down Expand Up @@ -3065,6 +3098,15 @@ function Format-MarkdownReport {
$shaLinked = ConvertTo-LinkedSha -Sha $ctx.srHeadSha -RepoUrl $RepoUrl
[void]$sb.AppendLine("**HEAD**: $shaLinked — $($ctx.srHeadSubject)")
[void]$sb.AppendLine("**Generated**: $($ctx.fetchedAt)")
# Nightly dogfood feed freshness — surfaces when the feed testers point at has gone
# stale (no new build), so a captain sees at a glance whether dogfood feedback is being
# collected against current bits. The banner string is rendered upstream in Invoke-Main
# (where "now" is natural), keeping this renderer clock-free and deterministic. Absent in
# phase-scoped runs / when the helper isn't loaded → nothing is appended.
if ($Data.ContainsKey('nightlyFeedBanner') -and $Data['nightlyFeedBanner']) {
[void]$sb.AppendLine()
[void]$sb.AppendLine($Data['nightlyFeedBanner'])
}
# Expected ship date — cadence depends on PatchVersion:
# - x0 patches (80, 90…) + previews → 2nd Tuesday of the month
# - hotfix patches (81, 82…) → ASAP, no cadence
Expand Down Expand Up @@ -3590,6 +3632,76 @@ function Format-MarkdownReport {

# region ────────────────────── 8. ORCHESTRATOR ────────────────────────────

function Add-SrNightlyFeedFreshness {
<#
.SYNOPSIS
Maps this SR lane to its nightly Azure Artifacts dogfood feed + version band,
queries the freshest matching build, and stores both the structured result
($Data['nightlyFeed']) and a pre-rendered banner string ($Data['nightlyFeedBanner']).
.DESCRIPTION
Lane → feed/band mapping (verified against the live feeds):
- feed = dotnet<Major> (e.g. dotnet10, dotnet11)
- signal = the inflight/current dogfood stream (ci.inflight builds) on that feed —
the "shipping next" bits dogfooders validate against. Resolved feed-wide
(not band-pinned) so it auto-follows when inflight/current advances bands
(e.g. 10.0.80 → 10.0.90). Ordinary main CI (ci.main) is deliberately NOT
tracked: it publishes daily and would paint an inflight stall green.
- band = <Major>.0.<Patch> (PatchVersion from eng/Versions.props at srRef)
used only as a FALLBACK when the feed has no inflight builds at all
(e.g. a preview feed not yet in the inflight phase).
Fail-open throughout: any gap (helper not loaded, version unreadable, network error)
degrades to "no banner"/"unknown" rather than disturbing the verdict.
#>
param([hashtable]$Data)

if (-not $Script:NightlyFeedHelperLoaded) { return }
if (-not (Get-Command Resolve-NightlyDogfoodFreshness -ErrorAction SilentlyContinue)) { return }
if (-not (Get-Command Format-NightlyFeedBanner -ErrorAction SilentlyContinue)) { return }

try {
$ctx = $Data.metadata
$surveyRef = $ctx.srRef
$vp = Get-VersionsPropsState -Ref $surveyRef
if (-not $vp) { return } # can't map a band → skip silently (no banner)

$major = [int]$vp.Major
$patch = [int]$vp.Patch
$band = "$major.0.$patch"
$feed = "dotnet$major"
$feedUrl = "https://dev.azure.com/dnceng/public/_artifacts/feed/$feed"
$bandPrefix = '^' + [regex]::Escape($band) + '-'

$fresh = Resolve-NightlyDogfoodFreshness -Feed $feed -BandPrefixRegex $bandPrefix
if ($null -eq $fresh) { $fresh = @{ unknown = $true } }

$buildType = [string](Get-NightlyFeedProp $fresh 'buildType')
# Label honestly: 'inflight' → ci.inflight; a definitive band fallback → the band;
# anything else (unknown / transient inflight failure) → ci.inflight, the stream we
# were measuring — never imply the band carries the signal when freshness is unknown.
$typeNote = if ($buildType -eq 'inflight') { 'ci.inflight' }
elseif ($buildType -eq 'band') { "``$band``" }
else { 'ci.inflight' }
$laneLabel = "[``$feed``]($feedUrl) · $typeNote"
$fresh['laneLabel'] = $laneLabel
$fresh['feedUrl'] = $feedUrl
$fresh['versionPrefix'] = $bandPrefix

# Capture ONE timestamp and reuse it for both the banner render and the semantic-hash
# tier (Get-ReportSemanticHash reads $Data['nightlyFeedNow']) so the two can never
# sample different sides of a tier boundary within a single run.
$nfNow = [DateTime]::UtcNow
$Data['nightlyFeed'] = $fresh
$Data['nightlyFeedNow'] = $nfNow
$banner = Format-NightlyFeedBanner -Freshness $fresh -Now $nfNow
if ($banner) { $Data['nightlyFeedBanner'] = $banner }
} catch {
# -WarningAction Continue: keep this fail-open even under an ambient
# $WarningPreference='Stop', where a bare Write-Warning would be promoted to a
# terminating error inside the catch and escape, crashing the unattended job.
Write-Warning "Nightly-feed freshness check failed (non-fatal): $($_.Exception.Message)" -WarningAction Continue
}
}

function Invoke-Main {
$excludes = $ExcludeBranches -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ }
$ctx = Resolve-Context -SrBranch $SrBranch -Repo $Repo -MainBranch $MainBranch `
Expand Down Expand Up @@ -3624,6 +3736,13 @@ function Invoke-Main {
warnings = @()
}

# Nightly dogfood feed freshness (full runs only). Maps this SR lane to its Azure
# Artifacts feed + version band and records how fresh the newest matching build is, so
# the tracker can flag when dogfooders are testing stale bits. Fail-open inside.
if ($Phase -eq 'all') {
Add-SrNightlyFeedFreshness -Data $data
}

if ($Phase -in 'all', 'commits', 'regressions') {
$srContents = Get-SrCommits -Ctx $ctx
$data['srContents'] = $srContents
Expand Down
Loading
Loading