diff --git a/.github/workflows/h3-r7b1b1-toolchain-preflight.yml b/.github/workflows/h3-r7b1b1-toolchain-preflight.yml new file mode 100644 index 00000000..f952cb9c --- /dev/null +++ b/.github/workflows/h3-r7b1b1-toolchain-preflight.yml @@ -0,0 +1,342 @@ +# ContinuityOS H3-G1 R5: SDK shared-header path correction for read-only inventory. +# INVENTORY ONLY: no H3 C0/T11/T12 broker-security or production qualification. +# No checkout, installation, build, native RPC registration, or artifacts. +name: H3 G1 Windows SDK component inventory R5 + +on: + pull_request: + types: [opened, synchronize, reopened] + paths: + - '.github/workflows/h3-r7b1b1-toolchain-preflight.yml' + +permissions: + contents: read + +concurrency: + group: h3-r7b1b1-toolchain-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + inventory: + name: win-2025 / independent SDK shared RPC header + if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.base.ref == 'master' }} + runs-on: windows-2025-vs2026 + timeout-minutes: 8 + defaults: + run: + shell: pwsh + steps: + - name: Verify exact context and inventory preinstalled toolchain + env: + EXPECTED_REPOSITORY: bitmaster162/continuityos + EXPECTED_BASE_SHA: 719ed32a64301fc8083a5aad0c4ed7bed316040e + PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} + PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + Set-StrictMode -Version Latest + $ErrorActionPreference = 'Stop' + $ProgressPreference = 'SilentlyContinue' + + if ($env:GITHUB_REPOSITORY -cne $env:EXPECTED_REPOSITORY) { + throw 'H3_REPOSITORY_DRIFT' + } + if ($env:GITHUB_EVENT_NAME -cne 'pull_request') { + throw 'H3_UNAPPROVED_EVENT' + } + if ($env:PR_BASE_SHA -cne $env:EXPECTED_BASE_SHA) { + throw 'H3_PROTECTED_MASTER_BASE_SHA_DRIFT' + } + if ($env:PR_HEAD_SHA -notmatch '^[0-9a-f]{40}$') { + throw 'H3_HEAD_SHA_INVALID' + } + if ($env:RUNNER_OS -cne 'Windows' -or $env:RUNNER_ARCH -cne 'X64') { + throw 'H3_UNEXPECTED_RUNNER_PLATFORM' + } + + function Get-FileEvidence { + param( + [Parameter(Mandatory = $true)][string] $Label, + [AllowNull()][string] $Path, + [bool] $Required = $true + ) + $record = [ordered]@{ + label = $Label + required = $Required + present = $false + path = $null + bytes = $null + file_version = $null + sha256 = $null + } + if (-not [string]::IsNullOrWhiteSpace($Path) -and + (Test-Path -LiteralPath $Path -PathType Leaf)) { + $file = Get-Item -LiteralPath $Path -ErrorAction Stop + if (($file.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { + throw "H3_REPARSE_TOOLCHAIN_FILE_BLOCKED: $Label" + } + $record.present = $true + $record.path = $file.FullName + $record.bytes = [long]$file.Length + $record.file_version = [string]([Diagnostics.FileVersionInfo]::GetVersionInfo($file.FullName).FileVersion) + $record.sha256 = (Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash.ToLowerInvariant() + } + [pscustomobject]$record + } + + $pf86 = [Environment]::GetEnvironmentVariable('ProgramFiles(x86)') + if ([string]::IsNullOrWhiteSpace($pf86)) { + throw 'H3_PROGRAMFILES_X86_MISSING' + } + + $vswhere = Join-Path $pf86 'Microsoft Visual Studio\Installer\vswhere.exe' + $vsRoot = $null + $vcVersion = $null + $cl = $null + $link = $null + if (Test-Path -LiteralPath $vswhere -PathType Leaf) { + $installations = @(& $vswhere -latest -products '*' -requires 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64' -property installationPath) + if ($LASTEXITCODE -ne 0) { + throw 'H3_VSWHERE_QUERY_FAILED' + } + $installations = @($installations | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) + if ($installations.Count -eq 1) { + $vsRoot = [string]$installations[0].Trim() + $msvcRoot = Join-Path $vsRoot 'VC\Tools\MSVC' + if (Test-Path -LiteralPath $msvcRoot -PathType Container) { + $versions = @(Get-ChildItem -LiteralPath $msvcRoot -Directory | Sort-Object -Property @{ Expression = { [version]$_.Name }; Descending = $true }) + if ($versions.Count -gt 0) { + $vcVersion = $versions[0].Name + $vcBin = Join-Path $versions[0].FullName 'bin\Hostx64\x64' + $cl = Join-Path $vcBin 'cl.exe' + $link = Join-Path $vcBin 'link.exe' + } + } + } + } + + # H3 G1 R5: bounded, independent component discovery; no toolchain execution. + # R3 had a composite early-exit that left four individual statuses null. + $sdkRoots = [System.Collections.Generic.List[object]]::new() + $seenRoots = [System.Collections.Generic.HashSet[string]]::new( + [System.StringComparer]::OrdinalIgnoreCase + ) + function Add-SdkRoot { + param([string] $Origin, [AllowNull()][string] $Value) + if ([string]::IsNullOrWhiteSpace($Value)) { return } + $path = [IO.Path]::GetFullPath($Value.Trim().TrimEnd([char[]]@('\','/'))) + if ($seenRoots.Add($path)) { + if ($sdkRoots.Count -ge 8) { throw 'H3_SDK_ROOT_LIMIT_EXCEEDED' } + $sdkRoots.Add([pscustomobject]@{ + source = $Origin + path = $path + present = [bool](Test-Path -LiteralPath $path -PathType Container) + }) + } + } + + Add-SdkRoot -Origin 'ProgramFiles(x86)' -Value (Join-Path $pf86 'Windows Kits\10') + $pf = [Environment]::GetEnvironmentVariable('ProgramFiles') + if (-not [string]::IsNullOrWhiteSpace($pf)) { + Add-SdkRoot -Origin 'ProgramFiles' -Value (Join-Path $pf 'Windows Kits\10') + } + Add-SdkRoot -Origin 'WindowsSdkDir' -Value $env:WindowsSdkDir + $regKeys = @( + 'HKLM:\SOFTWARE\Microsoft\Windows Kits\Installed Roots', + 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows Kits\Installed Roots' + ) + $registryReads = [System.Collections.Generic.List[object]]::new() + foreach ($regKey in $regKeys) { + $regValue = $null + $regStatus = 'MISSING_KEY_OR_VALUE' + try { + $regData = Get-ItemProperty -LiteralPath $regKey -ErrorAction Stop + $regValue = [string]$regData.KitsRoot10 + if (-not [string]::IsNullOrWhiteSpace($regValue)) { + $regStatus = 'FOUND' + Add-SdkRoot -Origin $regKey -Value $regValue + } + } catch { + $regStatus = 'UNAVAILABLE' + } + $registryReads.Add([pscustomobject]@{ key=$regKey; status=$regStatus }) + } + + $sdkProbes = [System.Collections.Generic.List[object]]::new() + foreach ($rootEntry in $sdkRoots) { + if (-not $rootEntry.present) { continue } + $versions = [System.Collections.Generic.HashSet[string]]::new( + [System.StringComparer]::OrdinalIgnoreCase + ) + # A bounded union of Bin/Include/Lib versions, not only Bin versions. + [void]$versions.Add('10.0.26100.0') + $envSDKVersion = [string]$env:WindowsSDKVersion + $envSDKVersion = $envSDKVersion.Trim().TrimEnd([char[]]@('\','/')) + if ($envSDKVersion -match '^10\.\d+\.\d+\.\d+$') { + [void]$versions.Add($envSDKVersion) + } + foreach ($directory in @('bin','Include','Lib')) { + $dir = Join-Path $rootEntry.path $directory + if (Test-Path -LiteralPath $dir -PathType Container) { + $kids = @(Get-ChildItem -LiteralPath $dir -Directory -ErrorAction Stop) + if ($kids.Count -gt 64) { throw 'H3_SDK_DIRECTORY_ENTRY_LIMIT_EXCEEDED' } + foreach ($child in $kids) { + if ($child.Name -match '^10\.\d+\.\d+\.\d+$') { + [void]$versions.Add($child.Name) + } + } + } + } + if ($versions.Count -gt 16) { throw 'H3_SDK_VERSION_LIMIT_EXCEEDED' } + $sorted = @($versions | Sort-Object -Property @{Expression={ [version]$_ };Descending=$true}) + foreach ($ver in $sorted) { + if ($sdkProbes.Count -ge 128) { throw 'H3_SDK_PROBE_LIMIT_EXCEEDED' } + $dirVersion = Join-Path $rootEntry.path ("bin\$ver\x64") + $dirLegacy = Join-Path $rootEntry.path 'bin\x64' + $midlPath = Join-Path $dirVersion 'midl.exe' + $midlcPath = Join-Path $dirVersion 'midlc.exe' + $layout = 'versioned' + if (-not (Test-Path -LiteralPath $midlPath -PathType Leaf)) { + $legacyMidl = Join-Path $dirLegacy 'midl.exe' + if (Test-Path -LiteralPath $legacyMidl -PathType Leaf) { + $midlPath = $legacyMidl + $layout = 'legacy' + } + } + if (-not (Test-Path -LiteralPath $midlcPath -PathType Leaf)) { + $legacyMidlc = Join-Path $dirLegacy 'midlc.exe' + if (Test-Path -LiteralPath $legacyMidlc -PathType Leaf) { + $midlcPath = $legacyMidlc + $layout = 'mixed_or_legacy' + } + } + $headerPath = Join-Path $rootEntry.path ("Include\$ver\shared\rpcdce.h") + $libPath = Join-Path $rootEntry.path ("Lib\$ver\um\x64\Rpcrt4.lib") + $probe = [pscustomobject]@{ + root = $rootEntry.path + version = $ver + layout = $layout + midl_path = $midlPath + midlc_path = $midlcPath + header_path = $headerPath + lib_path = $libPath + midl_present = [bool](Test-Path -LiteralPath $midlPath -PathType Leaf) + midlc_present = [bool](Test-Path -LiteralPath $midlcPath -PathType Leaf) + header_present = [bool](Test-Path -LiteralPath $headerPath -PathType Leaf) + lib_present = [bool](Test-Path -LiteralPath $libPath -PathType Leaf) + } + $sdkProbes.Add($probe) + } + } + + # Independent presence per component; never infer absence from composite failure. + $componentFound = [ordered]@{ + 'midl.exe' = [bool](@($sdkProbes | Where-Object { $_.midl_present }).Count -gt 0) + 'midlc.exe' = [bool](@($sdkProbes | Where-Object { $_.midlc_present }).Count -gt 0) + 'rpcdce.h' = [bool](@($sdkProbes | Where-Object { $_.header_present }).Count -gt 0) + 'Rpcrt4.lib' = [bool](@($sdkProbes | Where-Object { $_.lib_present }).Count -gt 0) + } + $coherentSets = @($sdkProbes | Where-Object { + $_.midl_present -and $_.midlc_present -and $_.header_present -and $_.lib_present + }) + $coherentSet = if ($coherentSets.Count -gt 0) { $coherentSets[0] } else { $null } + $sdkVersion = if ($null -ne $coherentSet) { $coherentSet.version } else { $null } + $midl = $null + $midlc = $null + $header = $null + $importLib = $null + if ($null -ne $coherentSet) { + $midl = $coherentSet.midl_path + $midlc = $coherentSet.midlc_path + $header = $coherentSet.header_path + $importLib = $coherentSet.lib_path + } else { + $p = @($sdkProbes | Where-Object { $_.midl_present } | Select-Object -First 1) + if ($p.Count -gt 0) { $midl = $p[0].midl_path } + $p = @($sdkProbes | Where-Object { $_.midlc_present } | Select-Object -First 1) + if ($p.Count -gt 0) { $midlc = $p[0].midlc_path } + $p = @($sdkProbes | Where-Object { $_.header_present } | Select-Object -First 1) + if ($p.Count -gt 0) { $header = $p[0].header_path } + $p = @($sdkProbes | Where-Object { $_.lib_present } | Select-Object -First 1) + if ($p.Count -gt 0) { $importLib = $p[0].lib_path } + } + + $files = @( + (Get-FileEvidence -Label 'vswhere.exe' -Path $vswhere) + (Get-FileEvidence -Label 'cl.exe' -Path $cl) + (Get-FileEvidence -Label 'link.exe' -Path $link) + (Get-FileEvidence -Label 'midl.exe' -Path $midl) + (Get-FileEvidence -Label 'rpcdce.h' -Path $header) + (Get-FileEvidence -Label 'Rpcrt4.lib' -Path $importLib) + (Get-FileEvidence -Label 'midlc.exe' -Path $midlc -Required $true) + ) + $midlDriver = @($files | Where-Object { $_.label -ceq 'midl.exe' })[0] + $midlEngine = @($files | Where-Object { $_.label -ceq 'midlc.exe' })[0] + $midlPairVersionMatch = $false + if ($midlDriver.present -and $midlEngine.present) { + $midlPairVersionMatch = ( + -not [string]::IsNullOrWhiteSpace($midlDriver.file_version) -and + -not [string]::IsNullOrWhiteSpace($midlEngine.file_version) -and + $midlDriver.file_version -ceq $midlEngine.file_version + ) + } + + $missing = @($files | Where-Object { $_.required -and -not $_.present } | + ForEach-Object { $_.label }) + + $missingIndependent = @($componentFound.Keys | Where-Object { + -not $componentFound[$_] + }) + $coherentSdkFound = [bool]($null -ne $coherentSet) + $sdkRootRecords = @($sdkRoots | ForEach-Object { + [pscustomobject]@{ source=$_.source; path=$_.path; present=$_.present } + }) + $sdkProbeRecords = @($sdkProbes | ForEach-Object { + [pscustomobject]@{ + root=$_.root; version=$_.version; layout=$_.layout + midl_present=$_.midl_present; midlc_present=$_.midlc_present + header_present=$_.header_present; lib_present=$_.lib_present + } + }) + $receipt = [ordered]@{ + schema = 'continuityos.h3.g1.sdk-independent-inventory/v4' + expected_runner_label = 'windows-2025-vs2026' + scope = 'preinstalled local toolchain inventory only' + github_repository = $env:GITHUB_REPOSITORY + pr_base_sha = $env:PR_BASE_SHA + pr_head_sha = $env:PR_HEAD_SHA + runner_os = $env:RUNNER_OS + runner_arch = $env:RUNNER_ARCH + image_os = $env:ImageOS + image_version = $env:ImageVersion + windows_version = [Environment]::OSVersion.Version.ToString() + vs_installation_path = $vsRoot + msvc_version = $vcVersion + windows_sdk_version = $sdkVersion + files = $files + sdk_roots = $sdkRootRecords + sdk_registry_reads = @($registryReads) + sdk_probes = $sdkProbeRecords + independent_presence = $componentFound + missing_independent = $missingIndependent + coherent_sdk_found = $coherentSdkFound + missing_required = $missing + midl_driver_engine_versions_match = $midlPairVersionMatch + no_source_checkout = $true + no_compiler_execution = $true + no_network_command = $true + no_artifact_upload = $true + no_rpc_registration = $true + production = 'HOLD' + } + Write-Output ('H3_R7B1B1_READONLY_RECEIPT=' + ($receipt | ConvertTo-Json -Compress -Depth 8)) + if (-not $coherentSdkFound) { + throw ('H3_G1_COHERENT_SDK_NOT_FOUND: absent_individually=' + + ($missingIndependent -join ',') + '; no same-root same-version quartet') + } + if ($missing.Count -ne 0) { + throw ('H3_G1_TOOLCHAIN_INCOMPLETE: ' + ($missing -join ',')) + } + if (-not $midlPairVersionMatch) { + throw 'H3_MIDL_DRIVER_ENGINE_VERSION_MISMATCH_OR_ABSENT' + } + Write-Output 'H3_G1_R5_TOOLCHAIN_INVENTORY=PASS_ONLY_NOT_NATIVE_BROKER_SECURITY_PASS'