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
132 changes: 131 additions & 1 deletion scripts/Install-WorkspaceInstallerFromRelease.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,111 @@ function Resolve-Sha256Hex {
return (Get-FileHash -LiteralPath $resolved -Algorithm SHA256).Hash.ToLowerInvariant()
}

function Resolve-HostValidationProfile {
param([Parameter(Mandatory = $true)]$Policy)

if ($null -eq $Policy.PSObject.Properties['host_validation_profile']) {
return $null
}

$profile = $Policy.host_validation_profile
if ($null -eq $profile) {
return $null
}

$executionProfile = [string]$profile.execution_profile
if ([string]::IsNullOrWhiteSpace($executionProfile)) {
Throw-ReleaseClientError -ReasonCode 'source_blocked' -Message 'host_validation_profile.execution_profile is required when host_validation_profile is defined.'
}
if ($executionProfile -notin @('host-release', 'container-parity')) {
Throw-ReleaseClientError -ReasonCode 'source_blocked' -Message "host_validation_profile.execution_profile '$executionProfile' is invalid. Expected 'host-release' or 'container-parity'."
}

$executionYear = [string]$profile.runnercli_execution_labview_year
if (-not [string]::IsNullOrWhiteSpace($executionYear) -and $executionYear -notmatch '^\d{4}$') {
Throw-ReleaseClientError -ReasonCode 'source_blocked' -Message "host_validation_profile.runnercli_execution_labview_year '$executionYear' is invalid. Expected a 4-digit year."
}

$singlePplBitness = [string]$profile.single_ppl_bitness
if (-not [string]::IsNullOrWhiteSpace($singlePplBitness) -and $singlePplBitness -notin @('32', '64')) {
Throw-ReleaseClientError -ReasonCode 'source_blocked' -Message "host_validation_profile.single_ppl_bitness '$singlePplBitness' is invalid. Expected '32' or '64'."
}

$parityWindowsTag = [string]$profile.parity_windows_tag
if ($executionProfile -eq 'container-parity') {
if ($singlePplBitness -notin @('32', '64')) {
Throw-ReleaseClientError -ReasonCode 'source_blocked' -Message 'host_validation_profile.single_ppl_bitness is required for container-parity execution profile.'
}
if ([string]::IsNullOrWhiteSpace($parityWindowsTag)) {
Throw-ReleaseClientError -ReasonCode 'source_blocked' -Message 'host_validation_profile.parity_windows_tag is required for container-parity execution profile.'
}
}

return [pscustomobject]@{
execution_profile = $executionProfile
runnercli_execution_labview_year = $executionYear
single_ppl_bitness = $singlePplBitness
parity_windows_tag = $parityWindowsTag
}
}

function Get-HostValidationEnvironmentOverrides {
param($HostValidationProfile)

$overrides = @{}
if ($null -eq $HostValidationProfile) {
return $overrides
}

$overrides['LVIE_INSTALLER_EXECUTION_PROFILE'] = [string]$HostValidationProfile.execution_profile
if (-not [string]::IsNullOrWhiteSpace([string]$HostValidationProfile.runnercli_execution_labview_year)) {
$overrides['LVIE_RUNNERCLI_EXECUTION_LABVIEW_YEAR'] = [string]$HostValidationProfile.runnercli_execution_labview_year
}
if (-not [string]::IsNullOrWhiteSpace([string]$HostValidationProfile.single_ppl_bitness)) {
$overrides['LVIE_GATE_SINGLE_PPL_BITNESS'] = [string]$HostValidationProfile.single_ppl_bitness
}
if (-not [string]::IsNullOrWhiteSpace([string]$HostValidationProfile.parity_windows_tag)) {
$overrides['LVIE_PARITY_WINDOWS_TAG'] = [string]$HostValidationProfile.parity_windows_tag
}

return $overrides
}

function Set-TemporaryEnvironmentVariables {
param([Parameter(Mandatory = $true)][hashtable]$Variables)

$snapshot = @{}
foreach ($name in $Variables.Keys) {
$entry = Get-Item -Path ("Env:{0}" -f $name) -ErrorAction SilentlyContinue
$snapshot[$name] = [pscustomobject]@{
exists = ($null -ne $entry)
value = if ($null -ne $entry) { [string]$entry.Value } else { '' }
}

$value = [string]$Variables[$name]
if ([string]::IsNullOrWhiteSpace($value)) {
Remove-Item -Path ("Env:{0}" -f $name) -ErrorAction SilentlyContinue
} else {
Set-Item -Path ("Env:{0}" -f $name) -Value $value
}
}

return $snapshot
}

function Restore-TemporaryEnvironmentVariables {
param([Parameter(Mandatory = $true)][hashtable]$Snapshot)

foreach ($name in $Snapshot.Keys) {
$entry = $Snapshot[$name]
if ([bool]$entry.exists) {
Set-Item -Path ("Env:{0}" -f $name) -Value ([string]$entry.value)
} else {
Remove-Item -Path ("Env:{0}" -f $name) -ErrorAction SilentlyContinue
}
}
}

function Get-ReasonCodeFromException {
param([Parameter(Mandatory = $true)][string]$Message)

Expand Down Expand Up @@ -331,6 +436,8 @@ function Assert-ReleaseClientPolicy {
[void][DateTime]::Parse([string]$Policy.signature_policy.dual_mode_start_utc)
[void][DateTime]::Parse([string]$Policy.signature_policy.canary_enforce_utc)
[void][DateTime]::Parse([string]$Policy.signature_policy.grace_end_utc)

[void](Resolve-HostValidationProfile -Policy $Policy)
}

$report = [ordered]@{
Expand Down Expand Up @@ -386,6 +493,7 @@ try {

$policy = Load-EffectivePolicy -ManifestReleaseClient $manifestPolicy -PolicyPath $resolvedPolicyPath
Assert-ReleaseClientPolicy -Policy $policy
$hostValidationProfile = Resolve-HostValidationProfile -Policy $policy

$statePath = [string]$policy.state_path
if ([string]::IsNullOrWhiteSpace($statePath)) {
Expand All @@ -406,6 +514,9 @@ try {
$report.policy_path = $resolvedPolicyPath
$report.state_path = $statePath
$report.install_report_path = $installReportPath
if ($null -ne $hostValidationProfile) {
$report.details.host_validation_profile = $hostValidationProfile
}

if ($Mode -eq 'ValidatePolicy') {
$report.status = 'pass'
Expand Down Expand Up @@ -640,7 +751,26 @@ try {
enforcement = $enforcement
}

$process = Start-Process -FilePath $installerPath -ArgumentList '/S' -Wait -PassThru
$environmentOverrides = Get-HostValidationEnvironmentOverrides -HostValidationProfile $hostValidationProfile
$environmentSnapshot = @{}
try {
if ($environmentOverrides.Count -gt 0) {
$environmentSnapshot = Set-TemporaryEnvironmentVariables -Variables $environmentOverrides
$report.details.host_validation_environment = [ordered]@{}
foreach ($name in @('LVIE_INSTALLER_EXECUTION_PROFILE', 'LVIE_RUNNERCLI_EXECUTION_LABVIEW_YEAR', 'LVIE_GATE_SINGLE_PPL_BITNESS', 'LVIE_PARITY_WINDOWS_TAG')) {
if ($environmentOverrides.ContainsKey($name)) {
$report.details.host_validation_environment[$name] = [string]$environmentOverrides[$name]
}
}
}

$process = Start-Process -FilePath $installerPath -ArgumentList '/S' -Wait -PassThru
} finally {
if ($environmentSnapshot.Count -gt 0) {
Restore-TemporaryEnvironmentVariables -Snapshot $environmentSnapshot
}
}

if ([int]$process.ExitCode -ne 0) {
Throw-ReleaseClientError -ReasonCode 'installer_exit_nonzero' -Message "Installer exited with code $([int]$process.ExitCode)."
}
Expand Down
5 changes: 5 additions & 0 deletions scripts/Test-PolicyContracts.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ if ($installerContractMembers -contains 'release_client') {
Add-Check -Scope 'manifest' -Name 'release_client_policy_path' -Passed ([string]$releaseClient.policy_path -eq 'C:\dev\workspace-governance\release-policy.json') -Detail ([string]$releaseClient.policy_path)
Add-Check -Scope 'manifest' -Name 'release_client_state_path' -Passed ([string]$releaseClient.state_path -eq 'C:\dev\artifacts\workspace-release-state.json') -Detail ([string]$releaseClient.state_path)
Add-Check -Scope 'manifest' -Name 'release_client_latest_report_path' -Passed ([string]$releaseClient.latest_report_path -eq 'C:\dev\artifacts\workspace-release-client-latest.json') -Detail ([string]$releaseClient.latest_report_path)
Add-Check -Scope 'manifest' -Name 'release_client_host_validation_profile_exists' -Passed ($null -ne $releaseClient.host_validation_profile) -Detail 'installer_contract.release_client.host_validation_profile'
Add-Check -Scope 'manifest' -Name 'release_client_host_validation_execution_profile' -Passed ([string]$releaseClient.host_validation_profile.execution_profile -eq 'container-parity') -Detail ([string]$releaseClient.host_validation_profile.execution_profile)
Add-Check -Scope 'manifest' -Name 'release_client_host_validation_runnercli_execution_labview_year' -Passed ([string]$releaseClient.host_validation_profile.runnercli_execution_labview_year -eq '2026') -Detail ([string]$releaseClient.host_validation_profile.runnercli_execution_labview_year)
Add-Check -Scope 'manifest' -Name 'release_client_host_validation_single_ppl_bitness' -Passed ([string]$releaseClient.host_validation_profile.single_ppl_bitness -eq '64') -Detail ([string]$releaseClient.host_validation_profile.single_ppl_bitness)
Add-Check -Scope 'manifest' -Name 'release_client_host_validation_parity_windows_tag' -Passed ([string]$releaseClient.host_validation_profile.parity_windows_tag -eq '2026q1-windows') -Detail ([string]$releaseClient.host_validation_profile.parity_windows_tag)
Add-Check -Scope 'manifest' -Name 'release_client_provenance_required' -Passed ([bool]$releaseClient.provenance_required) -Detail ([string]$releaseClient.provenance_required)
Add-Check -Scope 'manifest' -Name 'release_client_allowed_repo_upstream' -Passed (@($releaseClient.allowed_repositories) -contains 'LabVIEW-Community-CI-CD/labview-cdev-surface') -Detail ([string]::Join(',', @($releaseClient.allowed_repositories)))
Add-Check -Scope 'manifest' -Name 'release_client_allowed_repo_fork' -Passed (@($releaseClient.allowed_repositories) -contains 'svelderrainruiz/labview-cdev-surface') -Detail ([string]::Join(',', @($releaseClient.allowed_repositories)))
Expand Down
5 changes: 5 additions & 0 deletions scripts/Test-ReleaseClientContracts.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ if ($null -ne $releaseClient) {
Add-Check -Name 'state_path' -Passed ([string]$releaseClient.state_path -eq 'C:\dev\artifacts\workspace-release-state.json') -Detail ([string]$releaseClient.state_path)
Add-Check -Name 'latest_report_path' -Passed ([string]$releaseClient.latest_report_path -eq 'C:\dev\artifacts\workspace-release-client-latest.json') -Detail ([string]$releaseClient.latest_report_path)
Add-Check -Name 'policy_path' -Passed ([string]$releaseClient.policy_path -eq 'C:\dev\workspace-governance\release-policy.json') -Detail ([string]$releaseClient.policy_path)
Add-Check -Name 'host_validation_profile_exists' -Passed ($null -ne $releaseClient.host_validation_profile) -Detail 'installer_contract.release_client.host_validation_profile'
Add-Check -Name 'host_validation_profile_execution_profile' -Passed ([string]$releaseClient.host_validation_profile.execution_profile -eq 'container-parity') -Detail ([string]$releaseClient.host_validation_profile.execution_profile)
Add-Check -Name 'host_validation_profile_runnercli_execution_labview_year' -Passed ([string]$releaseClient.host_validation_profile.runnercli_execution_labview_year -eq '2026') -Detail ([string]$releaseClient.host_validation_profile.runnercli_execution_labview_year)
Add-Check -Name 'host_validation_profile_single_ppl_bitness' -Passed ([string]$releaseClient.host_validation_profile.single_ppl_bitness -eq '64') -Detail ([string]$releaseClient.host_validation_profile.single_ppl_bitness)
Add-Check -Name 'host_validation_profile_parity_windows_tag' -Passed ([string]$releaseClient.host_validation_profile.parity_windows_tag -eq '2026q1-windows') -Detail ([string]$releaseClient.host_validation_profile.parity_windows_tag)

Add-Check -Name 'cdev_cli_sync_primary_repo' -Passed ([string]$releaseClient.cdev_cli_sync.primary_repo -eq 'svelderrainruiz/labview-cdev-cli') -Detail ([string]$releaseClient.cdev_cli_sync.primary_repo)
Add-Check -Name 'cdev_cli_sync_mirror_repo' -Passed ([string]$releaseClient.cdev_cli_sync.mirror_repo -eq 'LabVIEW-Community-CI-CD/labview-cdev-cli') -Detail ([string]$releaseClient.cdev_cli_sync.mirror_repo)
Expand Down
9 changes: 9 additions & 0 deletions tests/ReleaseClientPolicyContract.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ Describe 'Release client policy contract' {
$releaseClient.policy_path | Should -Be 'C:\dev\workspace-governance\release-policy.json'
$releaseClient.state_path | Should -Be 'C:\dev\artifacts\workspace-release-state.json'
$releaseClient.latest_report_path | Should -Be 'C:\dev\artifacts\workspace-release-client-latest.json'
$releaseClient.host_validation_profile.execution_profile | Should -Be 'container-parity'
$releaseClient.host_validation_profile.runnercli_execution_labview_year | Should -Be '2026'
$releaseClient.host_validation_profile.single_ppl_bitness | Should -Be '64'
$releaseClient.host_validation_profile.parity_windows_tag | Should -Be '2026q1-windows'
$releaseClient.cdev_cli_sync.primary_repo | Should -Be 'svelderrainruiz/labview-cdev-cli'
$releaseClient.cdev_cli_sync.mirror_repo | Should -Be 'LabVIEW-Community-CI-CD/labview-cdev-cli'
$releaseClient.cdev_cli_sync.strategy | Should -Be 'fork-and-upstream-full-sync'
Expand Down Expand Up @@ -133,6 +137,11 @@ Describe 'Release client policy contract' {
$script:policyScriptContent | Should -Match 'svelderrainruiz/labview-cdev-surface'
$script:policyScriptContent | Should -Match 'cdev_cli_sync_primary_repo'
$script:policyScriptContent | Should -Match 'cdev_cli_sync_mirror_repo'
$script:policyScriptContent | Should -Match 'host_validation_profile_exists'
$script:policyScriptContent | Should -Match 'host_validation_profile_execution_profile'
$script:policyScriptContent | Should -Match 'host_validation_profile_runnercli_execution_labview_year'
$script:policyScriptContent | Should -Match 'host_validation_profile_single_ppl_bitness'
$script:policyScriptContent | Should -Match 'host_validation_profile_parity_windows_tag'
$script:policyScriptContent | Should -Match 'runtime_images_exists'
$script:policyScriptContent | Should -Match 'runtime_images_cdev_cli_runtime_canonical_repository'
$script:policyScriptContent | Should -Match 'runtime_images_ops_runtime_base_digest'
Expand Down
11 changes: 11 additions & 0 deletions tests/ReleaseClientRuntimeContract.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ Describe 'Release client runtime contract' {
$script:scriptContent | Should -Match 'workspace-release-client-latest\.json'
}

It 'supports policy-driven host-validation profile environment overrides' {
$script:scriptContent | Should -Match 'Resolve-HostValidationProfile'
$script:scriptContent | Should -Match 'Get-HostValidationEnvironmentOverrides'
$script:scriptContent | Should -Match 'Set-TemporaryEnvironmentVariables'
$script:scriptContent | Should -Match 'Restore-TemporaryEnvironmentVariables'
$script:scriptContent | Should -Match 'LVIE_INSTALLER_EXECUTION_PROFILE'
$script:scriptContent | Should -Match 'LVIE_RUNNERCLI_EXECUTION_LABVIEW_YEAR'
$script:scriptContent | Should -Match 'LVIE_GATE_SINGLE_PPL_BITNESS'
$script:scriptContent | Should -Match 'LVIE_PARITY_WINDOWS_TAG'
}

It 'defines deterministic failure reason codes' {
foreach ($reason in @('source_blocked', 'asset_missing', 'hash_mismatch', 'signature_missing', 'signature_invalid', 'provenance_invalid', 'installer_exit_nonzero', 'install_report_missing')) {
$script:scriptContent | Should -Match $reason
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,12 @@
"state_path": "C:\\dev\\artifacts\\workspace-release-state.json",
"latest_report_path": "C:\\dev\\artifacts\\workspace-release-client-latest.json",
"policy_path": "C:\\dev\\workspace-governance\\release-policy.json",
"host_validation_profile": {
"execution_profile": "container-parity",
"runnercli_execution_labview_year": "2026",
"single_ppl_bitness": "64",
"parity_windows_tag": "2026q1-windows"
},
"runtime_images": {
"cdev_cli_runtime": {
"canonical_repository": "ghcr.io/labview-community-ci-cd/labview-cdev-cli-runtime",
Expand Down
6 changes: 6 additions & 0 deletions workspace-governance.json
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,12 @@
"state_path": "C:\\dev\\artifacts\\workspace-release-state.json",
"latest_report_path": "C:\\dev\\artifacts\\workspace-release-client-latest.json",
"policy_path": "C:\\dev\\workspace-governance\\release-policy.json",
"host_validation_profile": {
"execution_profile": "container-parity",
"runnercli_execution_labview_year": "2026",
"single_ppl_bitness": "64",
"parity_windows_tag": "2026q1-windows"
},
"runtime_images": {
"cdev_cli_runtime": {
"canonical_repository": "ghcr.io/labview-community-ci-cd/labview-cdev-cli-runtime",
Expand Down