Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
93 changes: 84 additions & 9 deletions .github/workflows/self-hosted-machine-certification.yml
Original file line number Diff line number Diff line change
Expand Up @@ -295,23 +295,32 @@ jobs:
trace_jq matrix_routing_diagnostics
jq -c '
map(
(.runner_labels_json | fromjson | map(tostring | gsub("^\\s+|\\s+$";""))) as $labels
(.runner_labels_json | fromjson | map(tostring | gsub("^\\s+|\\s+$";"") | ascii_downcase)) as $labels
| (.setup_name // "") as $setup_name
| ($labels | map(select(startswith("cert-setup-")))) as $setup_labels
| ($labels | map(select(startswith("cert-actor-")))) as $actor_labels
| {
setup_name: $setup_name,
expected_labview_year: .expected_labview_year,
contract_labview_year: .contract_labview_year,
required_setup_label: ("cert-setup-" + $setup_name),
requires_actor_label: ((.expected_labview_year == "2025") or (.expected_labview_year == "2026")),
labels: $labels,
setup_labels: $setup_labels,
setup_label_count: ($setup_labels | length),
has_setup_label: ($labels | index("cert-setup-" + $setup_name) != null),
actor_labels: ($labels | map(select(startswith("cert-actor-")))),
has_actor_label: (($labels | map(select(startswith("cert-actor-"))) | length) > 0),
labels: $labels
has_single_setup_label: (($setup_labels | length) == 1),
has_only_required_setup_label: (($setup_labels | length) == 1 and $setup_labels[0] == ("cert-setup-" + $setup_name)),
actor_labels: $actor_labels,
actor_label_count: ($actor_labels | length),
has_actor_label: (($actor_labels | length) > 0),
has_single_actor_label: (($actor_labels | length) == 1),
has_actor_label_format: (($actor_labels | all(test("^cert-actor-[a-z0-9-]+-[a-z0-9-]+$"))))
}
)' matrix.json > matrix-routing-diagnostics.json

trace_jq routing_failure_count
ROUTING_FAILURE_COUNT="$(jq '[ .[] | select((.has_setup_label | not) or (.requires_actor_label and (.has_actor_label | not))) ] | length' matrix-routing-diagnostics.json)"
ROUTING_FAILURE_COUNT="$(jq '[ .[] | select((.has_setup_label | not) or (.has_single_setup_label | not) or (.has_only_required_setup_label | not) or (.requires_actor_label and ((.has_actor_label | not) or (.has_single_actor_label | not) or (.has_actor_label_format | not)))) ] | length' matrix-routing-diagnostics.json)"
{
echo "### Routing matrix contract"
trace_jq matrix_count
Expand All @@ -323,12 +332,12 @@ jobs:
if jq -e '.[] | select(.requires_actor_label == true)' matrix-routing-diagnostics.json > /dev/null; then
echo "#### 2025/2026 actor-label diagnostics" >> "$GITHUB_STEP_SUMMARY"
trace_jq matrix_actor_diagnostics
jq -r '.[] | select(.requires_actor_label == true) | "- \(.setup_name): setup_label=\(.has_setup_label) actor_label=\(.has_actor_label) actor_tokens=\(.actor_labels | join(","))"' matrix-routing-diagnostics.json >> "$GITHUB_STEP_SUMMARY"
jq -r '.[] | select(.requires_actor_label == true) | "- \(.setup_name): setup_labels=\(.setup_labels | join(",")) setup_count=\(.setup_label_count) actor_labels=\(.actor_labels | join(",")) actor_count=\(.actor_label_count) actor_format=\(.has_actor_label_format)"' matrix-routing-diagnostics.json >> "$GITHUB_STEP_SUMMARY"
fi
if [[ "$ROUTING_FAILURE_COUNT" != "0" ]]; then
echo "::error::routing_matrix_contract_failed: setup and actor label contract violations detected."
echo "::error::routing_matrix_contract_failed: setup/actor label cardinality or format contract violations detected."
trace_jq matrix_routing_failures_dump
jq '.[] | select((.has_setup_label | not) or (.requires_actor_label and (.has_actor_label | not)))' matrix-routing-diagnostics.json
jq '.[] | select((.has_setup_label | not) or (.has_single_setup_label | not) or (.has_only_required_setup_label | not) or (.requires_actor_label and ((.has_actor_label | not) or (.has_single_actor_label | not) or (.has_actor_label_format | not))))' matrix-routing-diagnostics.json
exit 1
fi
trace_jq matrix_emit_setups_json
Expand Down Expand Up @@ -530,18 +539,63 @@ jobs:
}

$requiredSetupLabel = ("cert-setup-{0}" -f $setupName).ToLowerInvariant()
$setupLabels = @($labels | Where-Object { [string]$_ -like 'cert-setup-*' } | Select-Object -Unique)
$setupLabelCount = @($setupLabels).Count
$hasSetupLabel = @($labels | Where-Object { [string]$_ -eq $requiredSetupLabel }).Count -gt 0
$hasSingleSetupLabel = $setupLabelCount -eq 1
$hasOnlyRequiredSetupLabel = $hasSingleSetupLabel -and [string]::Equals([string]$setupLabels[0], $requiredSetupLabel, [System.StringComparison]::Ordinal)

$actorLabels = @($labels | Where-Object { [string]$_ -like 'cert-actor-*' } | Select-Object -Unique)
$actorLabelCount = @($actorLabels).Count
$requiresActorLabel = $is2025Or2026
$hasActorLabel = @($actorLabels).Count -gt 0
$hasActorLabel = $actorLabelCount -gt 0
$hasSingleActorLabel = $actorLabelCount -eq 1
$hasActorLabelFormat = @($actorLabels | Where-Object { [string]$_ -notmatch '^cert-actor-[a-z0-9-]+-[a-z0-9-]+$' }).Count -eq 0
$expectedActorLabel = if ($requiresActorLabel) {
$machineToken = ([string]$currentMachine).Trim().ToLowerInvariant()
$machineToken = [Regex]::Replace($machineToken, '[^a-z0-9\-]', '-')
$machineToken = [Regex]::Replace($machineToken, '-{2,}', '-')
$machineToken = $machineToken.Trim('-')
if ([string]::IsNullOrWhiteSpace($machineToken)) { $machineToken = 'unknown' }

$setupToken = ([string]$setupName).Trim().ToLowerInvariant()
$setupToken = [Regex]::Replace($setupToken, '[^a-z0-9\-]', '-')
$setupToken = [Regex]::Replace($setupToken, '-{2,}', '-')
$setupToken = $setupToken.Trim('-')
if ([string]::IsNullOrWhiteSpace($setupToken)) { $setupToken = 'unknown' }

$label = ("cert-actor-{0}-{1}" -f $machineToken, $setupToken)
if ($label.Length -gt 63) {
$label = $label.Substring(0, 63).TrimEnd('-')
}
$label
} else {
''
}
$hasExpectedActorLabel = if ($requiresActorLabel) { $actorLabels -contains $expectedActorLabel } else { $true }

$failures = @()
if (-not $hasSetupLabel) {
$failures += ("missing setup route label '{0}'." -f $requiredSetupLabel)
}
if (-not $hasSingleSetupLabel) {
$failures += ("setup route label cardinality invalid; expected exactly 1 cert-setup-* label, found {0}." -f $setupLabelCount)
}
if (-not $hasOnlyRequiredSetupLabel) {
$failures += ("setup route label mismatch; expected '{0}', resolved '{1}'." -f $requiredSetupLabel, ($setupLabels -join ','))
}
if ($requiresActorLabel -and -not $hasActorLabel) {
$failures += "missing required actor route label token 'cert-actor-*'."
}
if ($requiresActorLabel -and -not $hasSingleActorLabel) {
$failures += ("actor route label cardinality invalid; expected exactly 1 cert-actor-* label, found {0}." -f $actorLabelCount)
}
if ($requiresActorLabel -and -not $hasActorLabelFormat) {
$failures += "actor route label format invalid; expected pattern '^cert-actor-[a-z0-9-]+-[a-z0-9-]+$'."
}
if ($requiresActorLabel -and $hasActorLabel -and -not $hasExpectedActorLabel) {
$failures += ("actor route label mismatch; expected '{0}', resolved '{1}'." -f $expectedActorLabel, ($actorLabels -join ','))
}

$reportPath = Join-Path '${{ steps.runner-metadata.outputs.metadata_root }}' 'routing-contract-report.json'
[ordered]@{
Expand All @@ -555,9 +609,18 @@ jobs:
labels_parse_mode = $labelsParseMode
labels_raw_count = [int]$labelsRawCount
labels_flattened_count = [int]$labelsFlattenedCount
setup_labels = @($setupLabels)
setup_label_count = $setupLabelCount
has_setup_label = $hasSetupLabel
has_single_setup_label = $hasSingleSetupLabel
has_only_required_setup_label = $hasOnlyRequiredSetupLabel
expected_actor_label = $expectedActorLabel
has_expected_actor_label = $hasExpectedActorLabel
has_actor_label = $hasActorLabel
has_single_actor_label = $hasSingleActorLabel
has_actor_label_format = $hasActorLabelFormat
actor_labels = @($actorLabels)
actor_label_count = $actorLabelCount
labels = @($labels)
failures = @($failures)
pass = (@($failures).Count -eq 0)
Expand All @@ -576,10 +639,19 @@ jobs:
"- labels raw count: $labelsRawCount",
"- labels flattened count: $labelsFlattenedCount",
"- required setup label: $requiredSetupLabel",
"- setup labels: $(if (@($setupLabels).Count -gt 0) { $setupLabels -join ', ' } else { '(none)' })",
"- setup label count: $setupLabelCount",
"- setup label present: $hasSetupLabel",
"- setup label cardinality valid: $hasSingleSetupLabel",
"- setup label exact match: $hasOnlyRequiredSetupLabel",
"- actor labels: $(if (@($actorLabels).Count -gt 0) { $actorLabels -join ', ' } else { '(none)' })",
"- actor label required: $requiresActorLabel",
"- expected actor label: $(if ($requiresActorLabel) { $expectedActorLabel } else { '(n/a)' })",
"- actor label count: $actorLabelCount",
"- actor label present: $hasActorLabel",
"- actor label cardinality valid: $hasSingleActorLabel",
"- actor label format valid: $hasActorLabelFormat",
"- actor label exact match: $hasExpectedActorLabel",
"- pass: $(@($failures).Count -eq 0)"
) | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
}
Expand Down Expand Up @@ -2039,3 +2111,6 @@ jobs:
if-no-files-found: error





34 changes: 22 additions & 12 deletions scripts/Start-SelfHostedMachineCertification.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ function Assert-DispatchRoutingContract {

$labels = @(
Split-LabelCsv -Csv $RunnerLabelsCsv |
ForEach-Object { ([string]$_).Trim() } |
ForEach-Object { ([string]$_).Trim().ToLowerInvariant() } |
Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
Select-Object -Unique
)
Expand All @@ -145,41 +145,51 @@ function Assert-DispatchRoutingContract {
}

$requiredSetupLabel = Get-SetupRouteLabel -Setup $Setup
$setupLabels = @($labels | Where-Object { [string]$_ -like 'cert-setup-*' })
if ($labels -notcontains $requiredSetupLabel) {
throw ("dispatch_routing_setup_label_missing: setup '{0}' requires setup label '{1}' in runner labels '{2}'." -f [string]$Setup.name, $requiredSetupLabel, ($labels -join ','))
$setupLabels = @($labels | Where-Object { [string]$_ -like 'cert-setup-*' } | Select-Object -Unique)
$setupLabelCount = @($setupLabels).Count
if ($setupLabelCount -ne 1) {
throw ("dispatch_routing_setup_label_count_invalid: setup '{0}' requires exactly one setup route label '{1}', found count={2} labels='{3}'." -f [string]$Setup.name, $requiredSetupLabel, $setupLabelCount, ($setupLabels -join ','))
}
if (@($setupLabels | Where-Object { [string]$_ -ne $requiredSetupLabel }).Count -gt 0) {
throw ("dispatch_routing_setup_label_ambiguous: setup '{0}' resolved multiple setup route labels '{1}'." -f [string]$Setup.name, ($setupLabels -join ','))
if (-not [string]::Equals([string]$setupLabels[0], $requiredSetupLabel, [System.StringComparison]::Ordinal)) {
throw ("dispatch_routing_setup_label_mismatch: setup '{0}' requires setup label '{1}' but resolved '{2}'." -f [string]$Setup.name, $requiredSetupLabel, [string]$setupLabels[0])
}

$expectedLabviewYear = Get-SetupExpectedLabviewYear -Setup $Setup
$requiresActorLabelByYear = Test-SetupRequiresActorLabel -ExpectedLabviewYear $expectedLabviewYear
$owner = Get-RepositoryOwner -Repository $Repository
$requiresActorLabel = $requiresActorLabelByYear -and [string]::Equals($owner, 'LabVIEW-Community-CI-CD', [System.StringComparison]::OrdinalIgnoreCase)

$actorLabels = @($labels | Where-Object { [string]$_ -like 'cert-actor-*' })
$actorLabelPresent = @($actorLabels).Count -gt 0
$expectedActorLabel = ([string]$ActorRunnerLabel).Trim().ToLowerInvariant()
$actorLabels = @($labels | Where-Object { [string]$_ -like 'cert-actor-*' } | Select-Object -Unique)
$actorLabelCount = @($actorLabels).Count
$actorLabelPresent = $actorLabelCount -gt 0
if ($requiresActorLabel -and -not $actorLabelPresent) {
throw ("dispatch_routing_actor_label_missing: setup '{0}' (LabVIEW {1}) requires actor label for upstream dispatch; labels='{2}'." -f [string]$Setup.name, $expectedLabviewYear, ($labels -join ','))
}
if ($requiresActorLabel -and $labels -notcontains $ActorRunnerLabel) {
throw ("dispatch_routing_actor_label_mismatch: setup '{0}' expected actor label '{1}' but resolved labels were '{2}'." -f [string]$Setup.name, $ActorRunnerLabel, ($labels -join ','))
if ($requiresActorLabel -and $actorLabelCount -ne 1) {
throw ("dispatch_routing_actor_label_count_invalid: setup '{0}' expected exactly one actor label '{1}', found count={2} labels='{3}'." -f [string]$Setup.name, $expectedActorLabel, $actorLabelCount, ($actorLabels -join ','))
}
if ($requiresActorLabel -and $actorLabelCount -gt 0 -and $actorLabels[0] -notmatch '^cert-actor-[a-z0-9-]+-[a-z0-9-]+$') {
throw ("dispatch_routing_actor_label_format_invalid: setup '{0}' resolved actor label '{1}' with invalid format." -f [string]$Setup.name, [string]$actorLabels[0])
}
if ($requiresActorLabel -and $actorLabelCount -eq 1 -and -not [string]::Equals([string]$actorLabels[0], $expectedActorLabel, [System.StringComparison]::Ordinal)) {
throw ("dispatch_routing_actor_label_mismatch: setup '{0}' expected actor label '{1}' but resolved '{2}'." -f [string]$Setup.name, $expectedActorLabel, [string]$actorLabels[0])
}

return [pscustomobject]@{
pass = $true
expected_labview_year = $expectedLabviewYear
required_setup_label = $requiredSetupLabel
setup_label_count = $setupLabelCount
setup_labels_csv = ($setupLabels -join ',')
requires_actor_label = $requiresActorLabel
required_actor_label = $ActorRunnerLabel
required_actor_label = $expectedActorLabel
actor_label_count = $actorLabelCount
actor_label_present = $actorLabelPresent
actor_labels_csv = ($actorLabels -join ',')
resolved_labels_csv = ($labels -join ',')
}
}

function Get-ActorMachineName {
param([string]$PreferredName)

Expand Down
13 changes: 13 additions & 0 deletions tests/SelfHostedMachineCertificationWorkflowContract.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,26 @@ Describe 'Self-hosted machine certification workflow contract' {
$script:workflowContent | Should -Match 'routing_matrix_contract_failed'
$script:workflowContent | Should -Match 'required_setup_label'
$script:workflowContent | Should -Match 'requires_actor_label'
$script:workflowContent | Should -Match 'has_single_setup_label'
$script:workflowContent | Should -Match 'has_only_required_setup_label'
$script:workflowContent | Should -Match 'actor_label_count'
$script:workflowContent | Should -Match 'has_single_actor_label'
$script:workflowContent | Should -Match 'has_actor_label_format'
$script:workflowContent | Should -Match '\.expected_labview_year == "2025"\) or \(\.expected_labview_year == "2026"'
}

It 'fails fast per lane when setup or actor routing labels are missing' {
$script:workflowContent | Should -Match 'id:\s*routing-contract'
$script:workflowContent | Should -Match 'Assert setup/actor routing labels'
$script:workflowContent | Should -Match 'routing_label_contract_failed'
$script:workflowContent | Should -Match 'routing-contract-report\.json'
$script:workflowContent | Should -Match 'labels_parse_mode'
$script:workflowContent | Should -Match 'labels_raw_count'
$script:workflowContent | Should -Match 'labels_flattened_count'
$script:workflowContent | Should -Match 'has_only_required_setup_label'
$script:workflowContent | Should -Match 'has_single_actor_label'
$script:workflowContent | Should -Match 'has_actor_label_format'
$script:workflowContent | Should -Match 'has_expected_actor_label'
$script:workflowContent | Should -Match '2025/2026 routing diagnostics'
$script:workflowContent | Should -Match '\$parsedLabels\s*=\s*\$labelsJson\s*\|\s*ConvertFrom-Json'
$script:workflowContent | Should -Match 'labels_parse_mode'
Expand Down
6 changes: 5 additions & 1 deletion tests/StartSelfHostedMachineCertificationContract.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,15 @@ Describe 'Start self-hosted machine certification dispatch contract' {
$script:startScriptContent | Should -Match 'function Get-SetupExpectedLabviewYear'
$script:startScriptContent | Should -Match 'function Test-SetupRequiresActorLabel'
$script:startScriptContent | Should -Match 'function Assert-DispatchRoutingContract'
$script:startScriptContent | Should -Match 'dispatch_routing_setup_label_missing'
$script:startScriptContent | Should -Match 'dispatch_routing_setup_label_count_invalid'
$script:startScriptContent | Should -Match 'dispatch_routing_setup_label_mismatch'
$script:startScriptContent | Should -Match 'dispatch_routing_actor_label_missing'
$script:startScriptContent | Should -Match 'dispatch_routing_actor_label_count_invalid'
$script:startScriptContent | Should -Match 'dispatch_routing_actor_label_format_invalid'
$script:startScriptContent | Should -Match 'dispatch_routing_actor_label_mismatch'
$script:startScriptContent | Should -Match '\$routingContract = Assert-DispatchRoutingContract'
$script:startScriptContent | Should -Match 'routing_contract_requires_actor_label = \[bool\]\$routingContract\.requires_actor_label'
$script:startScriptContent | Should -Match 'routing_contract_required_actor_label = \[string\]\$routingContract\.required_actor_label'
}
}

Loading