diff --git a/.github/workflows/h3-g2-midl-compile-only.yml b/.github/workflows/h3-g2-midl-compile-only.yml new file mode 100644 index 00000000..32f4682a --- /dev/null +++ b/.github/workflows/h3-g2-midl-compile-only.yml @@ -0,0 +1,230 @@ +# ContinuityOS H3 G2 compiler-stage PROPOSAL. NOT AUTHORIZED IN GITHUB. +# This single, independent workflow contains source-locked LAB IDL only. +# Do not commit, open a PR, or trigger CI without exact owner approval. +name: H3 G2 LAB-only IDL to C stub generation + +on: + pull_request: + types: [opened, synchronize, reopened] + paths: + - '.github/workflows/h3-g2-midl-compile-only.yml' + +permissions: + contents: read + +concurrency: + group: h3-g2-midl-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + compile-lab-idl: + name: win-2025 / MIDL synthetic IDL compile only + if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'agent/h3-g2-midl-compile-only-r1' && github.event.pull_request.base.ref == 'master' }} + runs-on: windows-2025-vs2026 + timeout-minutes: 6 + defaults: + run: + shell: pwsh + steps: + - name: Verify pinned G1 toolchain and compile exact LAB IDL only + env: + PR_BASE_REF: ${{ github.event.pull_request.base.ref }} + PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} + PR_HEAD_REF: ${{ github.event.pull_request.head.ref }} + PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} + run: | + Set-StrictMode -Version Latest + $ErrorActionPreference = 'Stop' + $ProgressPreference = 'SilentlyContinue' + + # Exact PR/repository context. The merge SHA is not treated as an authoritative source SHA. + if ($env:GITHUB_REPOSITORY -cne 'bitmaster162/continuityos') { throw 'G2_REPOSITORY_DRIFT' } + if ($env:GITHUB_EVENT_NAME -cne 'pull_request') { throw 'G2_UNAPPROVED_TRIGGER' } + if ($env:PR_BASE_REF -cne 'master') { throw 'G2_BASE_REF_DRIFT' } + if ($env:PR_BASE_SHA -cne '719ed32a64301fc8083a5aad0c4ed7bed316040e') { throw 'G2_PROTECTED_BASE_SHA_DRIFT' } + if ($env:PR_HEAD_REPO -cne $env:GITHUB_REPOSITORY) { throw 'G2_EXTERNAL_HEAD_BLOCKED' } + if ($env:PR_HEAD_REF -cne 'agent/h3-g2-midl-compile-only-r1') { throw 'G2_HEAD_BRANCH_DRIFT' } + if ($env:PR_HEAD_SHA -cnotmatch '^[0-9a-f]{40}$') { throw 'G2_HEAD_SHA_INVALID' } + if ($env:RUNNER_OS -cne 'Windows' -or $env:RUNNER_ARCH -cne 'X64') { throw 'G2_RUNNER_PLATFORM_DRIFT' } + if ($env:ImageOS -cne 'win25-vs2026' -or $env:ImageVersion -cne '20260925.250.1') { + throw 'G2_RUNNER_IMAGE_DRIFT_REQUIRES_FRESH_G1' + } + + # All tool binaries/digests copied from *measured* G1-R5 receipt at head 82d72753. + $pf86 = [Environment]::GetEnvironmentVariable('ProgramFiles(x86)') + if ([string]::IsNullOrWhiteSpace($pf86)) { throw 'G2_PROGRAMFILES_X86_NOT_FOUND' } + $sdk = Join-Path $pf86 'Windows Kits\10' + $sdkVersion = '10.0.26100.0' + $um = Join-Path $sdk 'Include\10.0.26100.0\um' + $shared = Join-Path $sdk 'Include\10.0.26100.0\shared' + $ucrt = Join-Path $sdk 'Include\10.0.26100.0\ucrt' + $bin = Join-Path $sdk 'bin\10.0.26100.0\x64' + $vc = 'C:\Program Files\Microsoft Visual Studio\18\Enterprise\VC\Tools\MSVC\14.51.36231' + $vcBin = Join-Path $vc 'bin\Hostx64\x64' + $vcInclude = Join-Path $vc 'include' + $toolEntries = @( + @{ label = 'midl.exe'; path = (Join-Path $bin 'midl.exe'); sha = 'c3e800b8e3555476511df81e3d260409ff92e119e4c4e353ac6f4728ab34c8a0' }, + @{ label = 'midlc.exe'; path = (Join-Path $bin 'midlc.exe'); sha = '209447b250c33b8b458210e6d3d8b0089b596ebf9b4da18c4228af9b025acffa' }, + @{ label = 'rpcdce.h'; path = (Join-Path $shared 'rpcdce.h'); sha = '991883893134c8ecbe6af8335df0781bfb779c11684b3367def514136241b866' }, + @{ label = 'Rpcrt4.lib'; path = (Join-Path $sdk 'Lib\10.0.26100.0\um\x64\Rpcrt4.lib'); sha = 'bfe69184c180bf6ade840ac5279b53aa67b0eda43c82bcd86596db0309faeef3' }, + @{ label = 'cl.exe'; path = (Join-Path $vcBin 'cl.exe'); sha = 'b0a1818ebae6da7f18230a84e76ce839212584ac4c2ab55b97a0d5268085da57' }, + @{ label = 'link.exe'; path = (Join-Path $vcBin 'link.exe'); sha = 'a8c42e25c91ca2453e1110540ac08f0fc12d7539454a11b9af8baeecb469837d' } + ) + foreach ($item in $toolEntries) { + if (-not (Test-Path -LiteralPath $item.path -PathType Leaf)) { throw ('G2_REQUIRED_TOOL_MISSING: ' + $item.label) } + $x = Get-Item -LiteralPath $item.path -ErrorAction Stop + if (($x.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { throw ('G2_REPARSE_TOOL_BLOCKED: ' + $item.label) } + $actual = (Get-FileHash -LiteralPath $item.path -Algorithm SHA256).Hash.ToLowerInvariant() + if ($actual -cne $item.sha) { throw ('G2_TOOLCHAIN_DIGEST_DRIFT: ' + $item.label) } + } + foreach ($dir in @($um, $shared, $ucrt, $vcInclude)) { + if (-not (Test-Path -LiteralPath $dir -PathType Container)) { throw ('G2_INCLUDE_DIR_MISSING: ' + $dir) } + } + + $receipt = [ordered]@{ + schema = 'continuityos.h3.g2.midl-compile-only/v1-DESIGN' + stage = 'G2_COMPILE_ONLY_NOT_RUNTIME' + repo = $env:GITHUB_REPOSITORY + base_sha = $env:PR_BASE_SHA + head_sha = $env:PR_HEAD_SHA + runner_image = $env:ImageOS + runner_image_version = $env:ImageVersion + sdk_version = $sdkVersion + source_file = 'h3_r7b1c_lab.idl' + source_sha256 = '67915aa4c68d7bf8d8dd3a8879f7abed0c12a5262e577f4e4bf9c8c7fe87fe09' + source_bytes = 1555 + toolchain_digest_match = $true + midl_invocations = 0 + midl_exit_code = $null + compiler_output_sha256 = $null + compiler_output_excerpt = $null + generated = @() + compiler_warning_count = $null + temp_cleanup_verified = $false + no_git_checkout = $true + no_network_command = $true + no_sdk_install = $true + no_c_or_cpp_binary_build = $true + no_rpc_server_client_or_endpoint = $true + no_artifact_upload = $true + security_C0 = 'UNVERIFIED_HOLD' + native_T11_T12 = 'UNEXECUTED' + production = 'HOLD' + verdict = 'HOLD' + reason = 'UNEXECUTED' + } + + # Temp creation and cleanup apply ONLY to this ephemeral runner. Never on owner workstation. + if ([string]::IsNullOrWhiteSpace($env:RUNNER_TEMP)) { throw 'G2_RUNNER_TEMP_MISSING' } + $scratch = Join-Path $env:RUNNER_TEMP 'continuityos_h3_g2_midl_compile_only' + if (Test-Path -LiteralPath $scratch) { throw 'G2_TEMP_PATH_PREEXISTS_FAIL_CLOSED' } + $created = $false + $proc = $null + $compileText = '' + try { + $null = New-Item -ItemType Directory -Path $scratch -ErrorAction Stop + $created = $true + $outDir = Join-Path $scratch 'generated' + $tempDir = Join-Path $scratch 'tmp' + $null = New-Item -ItemType Directory -Path $outDir -ErrorAction Stop + $null = New-Item -ItemType Directory -Path $tempDir -ErrorAction Stop + $sourcePath = Join-Path $scratch 'h3_r7b1c_lab.idl' + $sourceBytes = [Convert]::FromBase64String('LyoKICogQ29udGludWl0eU9TIEgzLVI3QjFDIExBQi1PTkxZIElETCBTS0VUQ0gg4oCUIE5PVCBDT01QSUxFRCBPUiBBVVRIT1JJWkVELgogKiBUaGlzIGlzIGEgZnJlc2gsIHNlcGFyYXRlIHN5bnRoZXRpYyBpbnRlcmZhY2U7IE5PVCBIMyB3aXJlIHYzLgogKiBObyBEQiwgZ3JhbnRzLCB3aXRuZXNzLCBtZW1vcnkgcGF5bG9hZCwgY3JlZGVudGlhbHMsIG9yIHByb2R1Y3Rpb24ga2V5cy4KICogVGhlIHB1YmxpYyBwcm9iZSBpcyBOT1Qgc2VydmVyIGF1dGhlbnRpY2F0aW9uOyBDMCByZW1haW5zIEhPTEQuCiAqIEgzTGFiU3VibWl0U3ludGhldGljIE1VU1QgcmV0dXJuIGEgY29udGVudC1mcmVlIExBQl9ESVNBQkxFRCBzdGF0dXMKICogdW50aWwgc2VwYXJhdGVseSBhcHByb3ZlZCwgaW5kZXBlbmRlbnRseSB2ZXJpZmllZCBzYW1lLWJpbmRpbmcgQzAgZXhpc3RzLgogKiBObyBSUEMgcHJvY2VzcywgZW5kcG9pbnQsIG9yIHNlcnZpY2UgaXMgY3JlYXRlZCBieSB0aGlzIGRlc2lnbiBmaWxlLgogKi8KClsKICAgIHV1aWQoMGIwYTA1OGEtZjVkMS01ZTdiLWEyOWMtOGJiYzY3ODc5NDE4KSwKICAgIHZlcnNpb24oMS4wKSwKICAgIHBvaW50ZXJfZGVmYXVsdChyZWYpCl0KaW50ZXJmYWNlIEgzUjdCMUNfTGFiVjEKewogICAgLyogUHVibGljIGRpc2NvdmVyeTogYSBub25jZSBleGNoYW5nZSBXSVRIT1VUIHByb29mIG9mIHNlcnZlciBpZGVudGl0eS4gKi8KICAgIHZvaWQgSDNMYWJQdWJsaWNQcm9iZSgKICAgICAgICBbaW5dIGhhbmRsZV90IGJpbmRpbmcsCiAgICAgICAgW2luXSBieXRlIGNsaWVudF9ub25jZVszMl0sCiAgICAgICAgW291dF0gYnl0ZSBzZXJ2ZXJfbm9uY2VbMzJdLAogICAgICAgIFtvdXQsIHJlZl0gdW5zaWduZWQgbG9uZyAqbGFiX3N0YXR1cwogICAgKTsKCiAgICAvKiBPbmx5IG5vbi1zZW5zaXRpdmUgc3ludGhldGljIGZpeHR1cmUgYnl0ZXMgbWF5IGVudGVyIHRoaXMgbWV0aG9kLgogICAgICogQSBmdXR1cmUgcmVhbCBicm9rZXIgTVVTVCB1c2UgYSBzZXBhcmF0ZWx5IHJldmlld2VkL2F1dGhlbnRpY2F0ZWQKICAgICAqIHByb3RvY29sLCBhbmQgTVVTVCBOT1QgdHJlYXQgdGhpcyBhcyBhIHYzIG1lbW9yeSBtZXRob2QuCiAgICAgKiBGaXhlZCBhcnJheXMgZGVsaWJlcmF0ZWx5IGF2b2lkIGNhbGxlci1zaXplZCBhbGxvY2F0aW9ucyBpbiBJREwuCiAgICAgKiBUaGUgYnl0ZS1sZW5ndGggZmllbGQgaXMgdmFsaWRhdGVkIGJ5IC9yb2J1c3QgQU5EIGluc2lkZSBoYW5kbGVyLgogICAgICovCiAgICB2b2lkIEgzTGFiU3VibWl0U3ludGhldGljKAogICAgICAgIFtpbl0gaGFuZGxlX3QgYmluZGluZywKICAgICAgICBbaW4sIHJhbmdlKDEsIDE2Mzg0KV0gdW5zaWduZWQgbG9uZyByZXF1ZXN0X2xlbiwKICAgICAgICBbaW5dIGJ5dGUgcmVxdWVzdF9zbG90WzE2Mzg0XSwKICAgICAgICBbb3V0LCByZWZdIHVuc2lnbmVkIGxvbmcgKnJlcGx5X2xlbiwKICAgICAgICBbb3V0XSBieXRlIHJlcGx5X3Nsb3RbODE5Ml0sCiAgICAgICAgW291dCwgcmVmXSB1bnNpZ25lZCBsb25nICpsYWJfc3RhdHVzCiAgICApOwp9Cg==') + if ($sourceBytes.Length -ne 1555) { throw 'G2_SOURCE_SIZE_DRIFT' } + if ([Convert]::ToHexString([System.Security.Cryptography.SHA256]::HashData($sourceBytes)).ToLowerInvariant() -cne '67915aa4c68d7bf8d8dd3a8879f7abed0c12a5262e577f4e4bf9c8c7fe87fe09') { + throw 'G2_SOURCE_PREWRITE_SHA_DRIFT' + } + [System.IO.File]::WriteAllBytes($sourcePath, $sourceBytes) + if ((Get-FileHash -LiteralPath $sourcePath -Algorithm SHA256).Hash.ToLowerInvariant() -cne '67915aa4c68d7bf8d8dd3a8879f7abed0c12a5262e577f4e4bf9c8c7fe87fe09') { + throw 'G2_SOURCE_READBACK_SHA_DRIFT' + } + # MIDL may use the Microsoft C preprocessor (cl.exe /E); it must not compile C objects. + $env:INCLUDE = @($um, $shared, $ucrt, $vcInclude) -join ';' + $env:PATH = @($vcBin, $bin, (Join-Path $env:SystemRoot 'System32'), $env:SystemRoot) -join ';' + $env:TMP = $tempDir + $env:TEMP = $tempDir + + $args = @('/nologo', '/robust', '/Oicf', '/env', 'amd64', '/out', $outDir, + '/I', $um, '/I', $shared, '/I', $ucrt, $sourcePath) + $start = [System.Diagnostics.ProcessStartInfo]::new() + $start.FileName = (Join-Path $bin 'midl.exe') + $start.UseShellExecute = $false + $start.CreateNoWindow = $true + $start.RedirectStandardOutput = $true + $start.RedirectStandardError = $true + $start.WorkingDirectory = $scratch + foreach ($arg in $args) { $start.ArgumentList.Add([string]$arg) } + $proc = [System.Diagnostics.Process]::new() + $proc.StartInfo = $start + if (-not $proc.Start()) { throw 'G2_MIDL_PROCESS_START_FAILED' } + $receipt.midl_invocations = 1 + $stdOutTask = $proc.StandardOutput.ReadToEndAsync() + $stdErrTask = $proc.StandardError.ReadToEndAsync() + if (-not $proc.WaitForExit(90000)) { + try { $proc.Kill($true) } catch { } + $null = $proc.WaitForExit(5000) + throw 'G2_MIDL_TIMEOUT_90S' + } + $receipt.midl_exit_code = [int]$proc.ExitCode + $compileText = $stdOutTask.GetAwaiter().GetResult() + $stdErrTask.GetAwaiter().GetResult() + $receipt.compiler_output_excerpt = $compileText.Substring(0, [Math]::Min(1024, $compileText.Length)) + if ($compileText.Length -gt 8192) { throw 'G2_COMPILER_DIAGNOSTIC_TOO_LARGE' } + $receipt.compiler_output_sha256 = [Convert]::ToHexString([System.Security.Cryptography.SHA256]::HashData([System.Text.Encoding]::UTF8.GetBytes($compileText))).ToLowerInvariant() + if ($proc.ExitCode -ne 0) { throw ('G2_MIDL_COMPILE_ERROR_EXIT_' + $proc.ExitCode) } + if ($compileText -match '(?i)\bwarning\b|\bMIDL\d{4}\b') { throw 'G2_MIDL_DIAGNOSTIC_WARNING_OR_ERROR' } + + $expectedOutputs = @('h3_r7b1c_lab.h', 'h3_r7b1c_lab_c.c', 'h3_r7b1c_lab_s.c') + $actualFiles = @(Get-ChildItem -LiteralPath $outDir -File -Force) + if ($actualFiles.Count -ne $expectedOutputs.Count) { throw 'G2_GENERATED_FILE_COUNT_UNEXPECTED' } + $generatedReceipt = @() + $totalBytes = [long]0 + foreach ($file in $actualFiles) { + if ($file.Name -cnotin $expectedOutputs) { throw ('G2_GENERATED_UNAPPROVED_FILENAME: ' + $file.Name) } + if (($file.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { throw 'G2_OUTPUT_REPARSE_BLOCKED' } + if ($file.Length -le 0 -or $file.Length -gt 262144) { throw 'G2_OUTPUT_SINGLE_FILE_BOUND' } + $totalBytes += [long]$file.Length + $generatedReceipt += [ordered]@{ + filename = $file.Name + bytes = [long]$file.Length + sha256 = (Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash.ToLowerInvariant() + } + } + if ($totalBytes -gt 1048576) { throw 'G2_OUTPUT_TOTAL_BYTES_BOUND' } + foreach ($filename in $expectedOutputs) { if ($filename -cnotin @($actualFiles | ForEach-Object Name)) { throw 'G2_OUTPUT_REQUIRED_FILE_ABSENT' } } + $generatedHeader = [System.IO.File]::ReadAllText((Join-Path $outDir 'h3_r7b1c_lab.h')) + if (-not $generatedHeader.Contains('H3LabPublicProbe') -or -not $generatedHeader.Contains('H3LabSubmitSynthetic')) { + throw 'G2_REQUIRED_SYMBOLS_NOT_IN_GENERATED_HEADER' + } + $receipt.generated = @($generatedReceipt | Sort-Object filename) + $receipt.compiler_warning_count = 0 + $receipt.verdict = 'PASS_COMPILE_ONLY' + $receipt.reason = 'MIDL_C_STUBS_GENERATED_NOT_BUILT_OR_RUN' + } catch { + $receipt.verdict = 'HOLD' + $receipt.reason = [string]$_.Exception.Message + } finally { + if ($null -ne $proc) { + try { if (-not $proc.HasExited) { $proc.Kill($true) } } catch { } + try { $proc.Dispose() } catch { } + } + if ($created) { + try { Remove-Item -LiteralPath $scratch -Recurse -Force -ErrorAction Stop } catch { + $receipt.verdict = 'HOLD' + $receipt.reason = 'G2_TEMP_CLEANUP_EXCEPTION' + } + if (Test-Path -LiteralPath $scratch) { + $receipt.verdict = 'HOLD' + $receipt.reason = 'G2_TEMP_CLEANUP_NOT_VERIFIED' + } else { + $receipt.temp_cleanup_verified = $true + } + } + } + + # Exactly one bounded machine-readable receipt. Do not publish generated C or real data. + Write-Output ('H3_G2_COMPILE_RECEIPT=' + ($receipt | ConvertTo-Json -Compress -Depth 8)) + if ($receipt.verdict -cne 'PASS_COMPILE_ONLY' -or -not $receipt.temp_cleanup_verified) { + throw ('H3_G2_COMPILE_HOLD: ' + $receipt.reason) + } + Write-Output 'H3_G2_MIDL_COMPILE_ONLY=PASS_NOT_RPC_SECURITY_PASS' diff --git a/.github/workflows/h3-g3-c-stubs-object-only.yml b/.github/workflows/h3-g3-c-stubs-object-only.yml new file mode 100644 index 00000000..c9462504 --- /dev/null +++ b/.github/workflows/h3-g3-c-stubs-object-only.yml @@ -0,0 +1,612 @@ +# H3 G3: synthetic MIDL C stubs -> two unlinked COFF objects on an ephemeral runner. +# This job grants no RPC, deployment, production, or owner-workstation authority. +name: H3 G3 LAB C stubs to objects only + +on: + pull_request: + types: [opened, synchronize, reopened] + paths: + - '.github/workflows/h3-g3-c-stubs-object-only.yml' + +permissions: + contents: read + +concurrency: + group: h3-g3-objects-${{ github.event.pull_request.number }} + cancel-in-progress: false + +jobs: + compile-lab-objects: + name: win-2025 / synthetic C stubs to objects only + if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'agent/h3-g2-midl-compile-only-r1' && github.event.pull_request.base.ref == 'master' }} + runs-on: windows-2025-vs2026 + timeout-minutes: 6 + defaults: + run: + shell: pwsh + steps: + - name: Verify toolchain, regenerate LAB stubs and compile two objects + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + PR_BASE_REF: ${{ github.event.pull_request.base.ref }} + PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} + PR_HEAD_REF: ${{ github.event.pull_request.head.ref }} + PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} + run: | + Set-StrictMode -Version Latest + $ErrorActionPreference = 'Stop' + $ProgressPreference = 'SilentlyContinue' + + $receipt = [ordered]@{ + schema = 'continuityos.h3.g3.c-stubs-object-only/v1' + stage = 'G3_OBJECT_COMPILE_ONLY' + repo = $env:GITHUB_REPOSITORY + pr_number = $env:PR_NUMBER + base_sha = $env:PR_BASE_SHA + head_sha = $env:PR_HEAD_SHA + run_id = $env:GITHUB_RUN_ID + run_attempt = $env:GITHUB_RUN_ATTEMPT + runner_image = $env:ImageOS + runner_image_version = $env:ImageVersion + toolchain_digest_match = $false + toolchain_unchanged_after_compile = $false + pinned_tools = @() + source_before = @() + source_after = @() + generated_before_compile = @() + generated_after_compile = @() + objects = @() + midl_invocations = 0 + direct_object_compiler_invocations = 0 + midl_preprocessor_may_invoke_cl_E = $true + process_receipts = [Collections.Generic.List[object]]::new() + temp_inventory = [Collections.Generic.List[object]]::new() + temp_empty_directory_normalization = $null + failures = [Collections.Generic.List[string]]::new() + cleanup_attempted = $false + temp_cleanup_verified = $false + cleanup_error = $null + no_git_checkout = $true + no_artifact_upload = $true + no_explicit_network_command = $true + no_link_invocation = $true + no_executable_or_dll_output = $null + no_rpc_endpoint_process_or_service = $true + no_owner_workstation_execution = $true + security_C0 = 'UNVERIFIED_HOLD' + native_T11_T12 = 'UNEXECUTED' + production = 'HOLD' + verdict = 'HOLD' + } + + function Get-Sha256Bytes([byte[]]$Bytes) { + return [Convert]::ToHexString([Security.Cryptography.SHA256]::HashData($Bytes)).ToLowerInvariant() + } + + # Check each existing ancestor; a final regular file can still sit below a junction. + function Assert-NoReparse([string]$Path) { + $cursor = [IO.Path]::GetFullPath($Path) + while (-not [string]::IsNullOrEmpty($cursor)) { + $item = Get-Item -LiteralPath $cursor -Force -ErrorAction Stop + if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { + throw 'G3_REPARSE_PATH_BLOCKED' + } + $parent = [IO.Directory]::GetParent($cursor) + if ($null -eq $parent) { break } + $cursor = $parent.FullName + } + } + + function Get-BoundedManifest( + [string]$Directory, [string[]]$Names, [long]$MaxFileBytes, [long]$MaxTotalBytes + ) { + Assert-NoReparse $Directory + $rows = [Collections.Generic.List[object]]::new() + $total = [long]0 + foreach ($path in [IO.Directory]::EnumerateFileSystemEntries($Directory)) { + if ($rows.Count -ge $Names.Count) { throw 'G3_MANIFEST_COUNT_BOUND' } + $item = Get-Item -LiteralPath $path -Force -ErrorAction Stop + if ($item.PSIsContainer -or $item.Name -cnotin $Names) { throw 'G3_MANIFEST_UNEXPECTED_ENTRY' } + Assert-NoReparse $path + if ($item.Length -le 0 -or $item.Length -gt $MaxFileBytes) { throw 'G3_MANIFEST_FILE_BOUND' } + $total += [long]$item.Length + if ($total -gt $MaxTotalBytes) { throw 'G3_MANIFEST_TOTAL_BOUND' } + $rows.Add([ordered]@{ + filename = $item.Name + bytes = [long]$item.Length + sha256 = (Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash.ToLowerInvariant() + }) + } + if ($rows.Count -ne $Names.Count) { throw 'G3_MANIFEST_REQUIRED_FILE_ABSENT' } + return @($rows | Sort-Object { $_.filename }) + } + + function Assert-SameManifest([object[]]$Before, [object[]]$After) { + if (($Before | ConvertTo-Json -Compress -Depth 4) -cne ($After | ConvertTo-Json -Compress -Depth 4)) { + throw 'G3_COMPILE_INPUT_OR_OUTPUT_DRIFT' + } + } + + # Native byte streams are capped while reading. No unbounded ReadToEnd tasks. + # Hashes cover raw captured bytes; a truncated/failed stream is labelled PREFIX, never FULL. + function Invoke-BoundedNative( + [string]$Label, [string]$Executable, [string[]]$Arguments, + [int]$DeadlineSeconds, [hashtable]$ChildEnvironment, [string]$WorkingDirectory + ) { + $r = [ordered]@{ + label = $Label + executable = $Executable + argv = @($Arguments) + run_id = $env:GITHUB_RUN_ID + run_attempt = $env:GITHUB_RUN_ATTEMPT + child_environment_keys = @($ChildEnvironment.Keys | Sort-Object) + deadline_seconds = $DeadlineSeconds + diagnostic_limit_bytes_per_stream = 16384 + started = $false + exit_code = $null + parent_exit_confirmed = $false + termination_requested = $false + termination_error = $null + descendant_exit_after_termination = 'NOT_APPLICABLE' + stdout_bytes = 0 + stderr_bytes = 0 + stdout_sha256 = $null + stderr_sha256 = $null + stdout_hash_scope = 'PREFIX' + stderr_hash_scope = 'PREFIX' + stdout_excerpt = '' + stderr_excerpt = '' + warning_count = $null + status = 'HOLD' + reason = 'UNEXECUTED' + } + $receipt.process_receipts.Add($r) + $p = [Diagnostics.Process]::new() + $outMemory = [IO.MemoryStream]::new() + $errMemory = [IO.MemoryStream]::new() + $outEof = $false + $errEof = $false + $outTask = $null + $errTask = $null + $outBuffer = [byte[]]::new(4096) + $errBuffer = [byte[]]::new(4096) + $clock = [Diagnostics.Stopwatch]::StartNew() + try { + $start = [Diagnostics.ProcessStartInfo]::new() + $start.FileName = $Executable + $start.WorkingDirectory = $WorkingDirectory + $start.UseShellExecute = $false + $start.CreateNoWindow = $true + $start.RedirectStandardOutput = $true + $start.RedirectStandardError = $true + $start.Environment.Clear() + foreach ($key in $ChildEnvironment.Keys) { $start.Environment[$key] = [string]$ChildEnvironment[$key] } + if ($start.Environment.ContainsKey('CL') -or $start.Environment.ContainsKey('_CL_')) { + throw 'G3_INHERITED_COMPILER_FLAGS_BLOCKED' + } + foreach ($arg in $Arguments) { $start.ArgumentList.Add($arg) } + $p.StartInfo = $start + if (-not $p.Start()) { throw 'G3_NATIVE_START_FAILED' } + $r.started = $true + if ($Label -ceq 'MIDL') { $receipt.midl_invocations++ } + else { $receipt.direct_object_compiler_invocations++ } + $outTask = $p.StandardOutput.BaseStream.ReadAsync($outBuffer, 0, $outBuffer.Length) + $errTask = $p.StandardError.BaseStream.ReadAsync($errBuffer, 0, $errBuffer.Length) + while (-not ($p.HasExited -and $outEof -and $errEof)) { + if ($clock.ElapsedMilliseconds -ge ($DeadlineSeconds * 1000)) { throw 'G3_NATIVE_DEADLINE_EXCEEDED' } + if (-not $outEof -and $outTask.IsCompleted) { + $n = $outTask.GetAwaiter().GetResult() + if ($n -eq 0) { $outEof = $true } + else { + $remaining = 16384 - [int]$outMemory.Length + $outMemory.Write($outBuffer, 0, [Math]::Min($n, $remaining)) + if ($n -gt $remaining) { throw 'G3_STDOUT_BYTE_CAP_EXCEEDED' } + $outTask = $p.StandardOutput.BaseStream.ReadAsync($outBuffer, 0, [Math]::Min(4096, 16385 - [int]$outMemory.Length)) + } + } + if (-not $errEof -and $errTask.IsCompleted) { + $n = $errTask.GetAwaiter().GetResult() + if ($n -eq 0) { $errEof = $true } + else { + $remaining = 16384 - [int]$errMemory.Length + $errMemory.Write($errBuffer, 0, [Math]::Min($n, $remaining)) + if ($n -gt $remaining) { throw 'G3_STDERR_BYTE_CAP_EXCEEDED' } + $errTask = $p.StandardError.BaseStream.ReadAsync($errBuffer, 0, [Math]::Min(4096, 16385 - [int]$errMemory.Length)) + } + } + if (-not ($p.HasExited -and $outEof -and $errEof)) { Start-Sleep -Milliseconds 20 } + } + $r.parent_exit_confirmed = $true + $r.exit_code = [int]$p.ExitCode + $diagnostics = [Text.Encoding]::UTF8.GetString($outMemory.ToArray()) + [Text.Encoding]::UTF8.GetString($errMemory.ToArray()) + $r.warning_count = [regex]::Matches($diagnostics, '(?i)\bwarning\b').Count + if ($r.exit_code -ne 0) { throw ('G3_NATIVE_NONZERO_EXIT_' + $r.exit_code) } + if ($r.warning_count -ne 0 -or $diagnostics -match '(?i)\b(?:fatal\s+)?error\b|\bMIDL\d{4}\b') { + throw 'G3_NATIVE_DIAGNOSTIC_WARNING_OR_ERROR' + } + $r.status = 'PASS' + $r.reason = 'EXIT_ZERO_AND_COMPLETE_BOUNDED_DIAGNOSTICS' + } catch { + $r.reason = [string]$_.Exception.Message + $r.reason = $r.reason.Substring(0, [Math]::Min(512, $r.reason.Length)) + } finally { + if ($r.started) { + try { + if (-not $p.HasExited) { + $r.termination_requested = $true + $r.descendant_exit_after_termination = 'UNVERIFIED' + try { $p.Kill($true) } catch { $r.termination_error = 'KILL_TREE_REQUEST_FAILED' } + $r.parent_exit_confirmed = $p.WaitForExit(5000) + } else { $r.parent_exit_confirmed = $true } + if ($r.parent_exit_confirmed) { $r.exit_code = [int]$p.ExitCode } + } catch { $r.parent_exit_confirmed = $false } + if ($r.termination_requested -or -not $r.parent_exit_confirmed) { $r.status = 'HOLD' } + } + $clock.Stop() + $outBytes = $outMemory.ToArray() + $errBytes = $errMemory.ToArray() + $r.stdout_bytes = $outBytes.Length + $r.stderr_bytes = $errBytes.Length + $r.stdout_sha256 = Get-Sha256Bytes $outBytes + $r.stderr_sha256 = Get-Sha256Bytes $errBytes + if ($outEof) { $r.stdout_hash_scope = 'FULL' } + if ($errEof) { $r.stderr_hash_scope = 'FULL' } + $r.stdout_excerpt = [Text.Encoding]::UTF8.GetString($outBytes, 0, [Math]::Min(1024, $outBytes.Length)) + $r.stderr_excerpt = [Text.Encoding]::UTF8.GetString($errBytes, 0, [Math]::Min(1024, $errBytes.Length)) + $outMemory.Dispose() + $errMemory.Dispose() + $p.Dispose() + } + if ($r.status -cne 'PASS') { throw ($Label + ': ' + $r.reason) } + } + + function Assert-Amd64Coff([string]$Path) { + Assert-NoReparse $Path + $item = Get-Item -LiteralPath $Path -Force -ErrorAction Stop + if ($item.Length -lt 20 -or $item.Length -gt 1048576) { throw 'G3_OBJECT_BYTE_BOUND' } + $b = [IO.File]::ReadAllBytes($Path) + if (-not [BitConverter]::IsLittleEndian) { throw 'G3_HOST_ENDIAN_UNEXPECTED' } + if ($b[0] -eq 0x4d -and $b[1] -eq 0x5a) { throw 'G3_PE_EXECUTABLE_BLOCKED' } + $machine = [BitConverter]::ToUInt16($b, 0) + $sections = [BitConverter]::ToUInt16($b, 2) + $symbolsAt = [long][BitConverter]::ToUInt32($b, 8) + $symbols = [long][BitConverter]::ToUInt32($b, 12) + $optionalHeader = [BitConverter]::ToUInt16($b, 16) + $flags = [BitConverter]::ToUInt16($b, 18) + if ($machine -ne 0x8664 -or $optionalHeader -ne 0 -or ($flags -band 0x2002) -ne 0) { + throw 'G3_NOT_AMD64_UNLINKED_COFF' + } + if ($sections -lt 1 -or $sections -gt 96 -or (20 + 40 * $sections) -gt $b.Length) { throw 'G3_COFF_SECTION_BOUND' } + if ($symbolsAt -le 0 -or $symbols -le 0 -or ($symbolsAt + 18 * $symbols + 4) -gt $b.Length) { throw 'G3_COFF_SYMBOL_BOUND' } + $stringAt = $symbolsAt + 18 * $symbols + $stringBytes = [long][BitConverter]::ToUInt32($b, [int]$stringAt) + if ($stringBytes -lt 4 -or ($stringAt + $stringBytes) -gt $b.Length) { throw 'G3_COFF_STRING_TABLE_BOUND' } + for ($i = 0; $i -lt $sections; $i++) { + $offset = 20 + 40 * $i + $size = [long][BitConverter]::ToUInt32($b, $offset + 16) + $dataAt = [long][BitConverter]::ToUInt32($b, $offset + 20) + $relocationsAt = [long][BitConverter]::ToUInt32($b, $offset + 24) + $relocations = [long][BitConverter]::ToUInt16($b, $offset + 32) + $sectionFlags = [BitConverter]::ToUInt32($b, $offset + 36) + if ($dataAt -eq 0 -and $size -gt 0 -and ($sectionFlags -band 0x80) -eq 0) { throw 'G3_COFF_RAW_DATA_ABSENT' } + if ($dataAt -gt 0 -and ($dataAt + $size) -gt $b.Length) { throw 'G3_COFF_RAW_DATA_BOUND' } + if ($relocations -eq 65535 -or ($relocations -gt 0 -and ($relocationsAt -le 0 -or ($relocationsAt + 10 * $relocations) -gt $b.Length))) { + throw 'G3_COFF_RELOCATION_BOUND' + } + } + return [ordered]@{ machine = 'AMD64_0x8664'; format = 'COFF_OBJECT_NON_PE'; sections = [int]$sections; symbols = $symbols } + } + + # Diagnose residual compiler temp files without changing the final empty-temp gate. + # Attach each snapshot before sampling so failures retain the entries already observed. + function Add-BoundedTempInventory([string]$Boundary, [string]$Directory) { + $snapshot = [ordered]@{ + boundary = $Boundary + max_entries = 16 + max_depth = 4 + max_prefix_bytes_per_file = 128 + entries = [Collections.Generic.List[object]]::new() + status = 'PARTIAL' + reason = $null + } + $receipt.temp_inventory.Add($snapshot) + try { + Assert-NoReparse $Directory + $queue = [Collections.Generic.Queue[object]]::new() + $queue.Enqueue(@{ path = $Directory; depth = 0 }) + while ($queue.Count -gt 0) { + $current = $queue.Dequeue() + Assert-NoReparse $current.path + foreach ($path in [IO.Directory]::EnumerateFileSystemEntries($current.path)) { + if ($snapshot.entries.Count -ge 16) { throw 'G3_TEMP_INVENTORY_ENTRY_BOUND' } + $relative = [IO.Path]::GetRelativePath($Directory, $path) + $row = [ordered]@{ + relative_path = $relative.Substring(0, [Math]::Min(1024, $relative.Length)) + depth = $current.depth + 1 + type = 'UNKNOWN' + bytes = $null + prefix_bytes = 0 + prefix_base64 = '' + capture = 'UNREAD' + } + $snapshot.entries.Add($row) + if ($relative.Length -gt 1024) { throw 'G3_TEMP_INVENTORY_PATH_BOUND' } + $entry = Get-Item -LiteralPath $path -Force -ErrorAction Stop + if (($entry.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { + $row.type = 'REPARSE_POINT' + $row.capture = 'BLOCKED_NO_FOLLOW' + throw 'G3_TEMP_INVENTORY_REPARSE_BLOCKED' + } + if ($entry.PSIsContainer) { + $row.type = 'DIRECTORY' + $row.capture = 'METADATA_ONLY' + if ($row.depth -ge 4) { throw 'G3_TEMP_INVENTORY_DEPTH_BOUND' } + $queue.Enqueue(@{ path = $path; depth = $row.depth }) + continue + } + if ($entry -isnot [IO.FileInfo] -or ($entry.Attributes -band [IO.FileAttributes]::Device) -ne 0) { throw 'G3_TEMP_INVENTORY_UNKNOWN_ENTRY_TYPE' } + $row.type = 'REGULAR_FILE' + $row.bytes = [long]$entry.Length + Assert-NoReparse $path + $stream = $null + try { + $stream = [IO.File]::Open($path, [IO.FileMode]::Open, [IO.FileAccess]::Read, ([IO.FileShare]::ReadWrite -bor [IO.FileShare]::Delete)) + $prefix = [byte[]]::new(128) + $count = 0 + while ($count -lt $prefix.Length) { + $n = $stream.Read($prefix, $count, $prefix.Length - $count) + if ($n -eq 0) { break } + $count += $n + $row.prefix_bytes = $count + $row.prefix_base64 = [Convert]::ToBase64String($prefix, 0, $count) + } + $row.capture = 'PREFIX_ONLY_NO_FULL_FILE_HASH' + } finally { + if ($null -ne $stream) { $stream.Dispose() } + } + } + } + $snapshot.status = 'COMPLETE_BOUNDED_INVENTORY' + } catch { + $message = [string]$_.Exception.Message + $snapshot.reason = $message.Substring(0, [Math]::Min(256, $message.Length)) + throw ('G3_TEMP_INVENTORY_HOLD: ' + $Boundary + ': ' + $snapshot.reason) + } + } + + # Accept only the observed empty directory shape, never a telemetry/content exemption. + # Fresh bounded reads precede leaf-first NONRECURSIVE deletion; late files keep HOLD. + function Remove-ObservedEmptyTempDirectories([string]$Directory, [object]$Snapshot) { + $normalization = [ordered]@{ + policy = 'EMPTY_OR_EXACT_THREE_EMPTY_DIRECTORIES_V1' + allowed_parent_names = @('Microsoft', 'VSApplicationInsights') + allowed_leaf_pattern = '^vstel[0-9a-f]{32}$' + max_entries = 3 + files_allowed = $false + recursive_delete = $false + removed_relative_paths = [Collections.Generic.List[string]]::new() + final_empty_verified = $false + status = 'HOLD' + reason = $null + } + $receipt.temp_empty_directory_normalization = $normalization + try { + Assert-NoReparse $Directory + if ($Snapshot.boundary -cne 'FINAL_BEFORE_EMPTY_TEMP_GATE' -or $Snapshot.status -cne 'COMPLETE_BOUNDED_INVENTORY') { + throw 'G3_TEMP_NORMALIZATION_REQUIRES_COMPLETE_FINAL_SNAPSHOT' + } + if ($Snapshot.entries.Count -ne 0 -and $Snapshot.entries.Count -ne 3) { throw 'G3_TEMP_NORMALIZATION_UNKNOWN_ENTRY_COUNT' } + if ($Snapshot.entries.Count -eq 3) { + $leafName = [IO.Path]::GetFileName($Snapshot.entries[2].relative_path) + if ($leafName -cnotmatch '^vstel[0-9a-f]{32}$') { throw 'G3_TEMP_NORMALIZATION_UNKNOWN_LEAF' } + $parents = Join-Path 'Microsoft' 'VSApplicationInsights' + $expected = @('Microsoft', $parents, (Join-Path $parents $leafName)) + for ($i = 0; $i -lt 3; $i++) { + $row = $Snapshot.entries[$i] + if ($row.type -cne 'DIRECTORY' -or $row.capture -cne 'METADATA_ONLY' -or $row.depth -ne ($i + 1) -or $row.relative_path -cne $expected[$i]) { + throw 'G3_TEMP_NORMALIZATION_UNKNOWN_DIRECTORY_SHAPE' + } + } + $parent = $Directory + for ($i = 0; $i -lt 3; $i++) { + Assert-NoReparse $parent + $count = 0 + foreach ($path in [IO.Directory]::EnumerateFileSystemEntries($parent)) { + $count++ + if ($count -gt 1) { throw 'G3_TEMP_NORMALIZATION_EXTRA_ENTRY' } + $entry = Get-Item -LiteralPath $path -Force -ErrorAction Stop + if (-not $entry.PSIsContainer -or $entry.Name -cne [IO.Path]::GetFileName($expected[$i])) { throw 'G3_TEMP_NORMALIZATION_LIVE_SHAPE_DRIFT' } + Assert-NoReparse $path + } + if ($count -ne 1) { throw 'G3_TEMP_NORMALIZATION_DIRECTORY_MISSING' } + $parent = Join-Path $Directory $expected[$i] + } + Assert-NoReparse $parent + foreach ($path in [IO.Directory]::EnumerateFileSystemEntries($parent)) { throw 'G3_TEMP_NORMALIZATION_LEAF_NOT_EMPTY' } + for ($i = 2; $i -ge 0; $i--) { + $path = Join-Path $Directory $expected[$i] + Assert-NoReparse $path + [IO.Directory]::Delete($path, $false) + if (Test-Path -LiteralPath $path) { throw 'G3_TEMP_NORMALIZATION_DELETE_NOT_VERIFIED' } + $normalization.removed_relative_paths.Add($expected[$i]) + } + } + Assert-NoReparse $Directory + foreach ($path in [IO.Directory]::EnumerateFileSystemEntries($Directory)) { throw 'G3_TEMP_NORMALIZATION_RESIDUAL_ENTRY' } + $normalization.final_empty_verified = $true + $normalization.status = 'PASS_EMPTY_TEMP' + } catch { + $message = [string]$_.Exception.Message + $normalization.reason = $message.Substring(0, [Math]::Min(256, $message.Length)) + throw ('G3_TEMP_NORMALIZATION_HOLD: ' + $normalization.reason) + } + } + + # Bound traversal before recursive deletion; never traverse a junction or symlink. + function Assert-CleanupTree([string]$Root) { + Assert-NoReparse $Root + $queue = [Collections.Generic.Queue[string]]::new() + $queue.Enqueue($Root) + $count = 0 + while ($queue.Count -gt 0) { + foreach ($path in [IO.Directory]::EnumerateFileSystemEntries($queue.Dequeue())) { + $count++ + if ($count -gt 64) { throw 'G3_CLEANUP_ENTRY_BOUND' } + $entry = Get-Item -LiteralPath $path -Force -ErrorAction Stop + if (($entry.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { throw 'G3_CLEANUP_REPARSE_BLOCKED' } + if ($entry.PSIsContainer) { $queue.Enqueue($entry.FullName) } + } + } + } + + $scratch = $null + $created = $false + $completed = $false + try { + if ($env:GITHUB_REPOSITORY -cne 'bitmaster162/continuityos') { throw 'G3_REPOSITORY_DRIFT' } + if ($env:GITHUB_EVENT_NAME -cne 'pull_request' -or $env:PR_NUMBER -cne '210') { throw 'G3_TRIGGER_OR_PR_DRIFT' } + if ($env:PR_BASE_REF -cne 'master' -or $env:PR_BASE_SHA -cne '719ed32a64301fc8083a5aad0c4ed7bed316040e') { throw 'G3_BASE_DRIFT' } + if ($env:PR_HEAD_REPO -cne $env:GITHUB_REPOSITORY -or $env:PR_HEAD_REF -cne 'agent/h3-g2-midl-compile-only-r1') { throw 'G3_HEAD_CONTEXT_DRIFT' } + if ($env:PR_HEAD_SHA -cnotmatch '^[0-9a-f]{40}$') { throw 'G3_HEAD_SHA_INVALID' } + if ($env:GITHUB_RUN_ID -cnotmatch '^[0-9]{1,20}$' -or $env:GITHUB_RUN_ATTEMPT -cnotmatch '^[0-9]{1,6}$') { throw 'G3_RUN_ID_INVALID' } + if ($env:RUNNER_OS -cne 'Windows' -or $env:RUNNER_ARCH -cne 'X64') { throw 'G3_RUNNER_PLATFORM_DRIFT' } + if ($env:ImageOS -cne 'win25-vs2026' -or $env:ImageVersion -cne '20260925.250.1') { throw 'G3_IMAGE_DRIFT_REQUIRES_FRESH_TOOLCHAIN_REVIEW' } + + $pf86 = [Environment]::GetEnvironmentVariable('ProgramFiles(x86)') + if ([string]::IsNullOrWhiteSpace($pf86)) { throw 'G3_PROGRAMFILES_X86_MISSING' } + $sdk = Join-Path $pf86 'Windows Kits\10' + $um = Join-Path $sdk 'Include\10.0.26100.0\um' + $shared = Join-Path $sdk 'Include\10.0.26100.0\shared' + $ucrt = Join-Path $sdk 'Include\10.0.26100.0\ucrt' + $bin = Join-Path $sdk 'bin\10.0.26100.0\x64' + $vc = 'C:\Program Files\Microsoft Visual Studio\18\Enterprise\VC\Tools\MSVC\14.51.36231' + $vcBin = Join-Path $vc 'bin\Hostx64\x64' + $vcInclude = Join-Path $vc 'include' + $tools = @( + @{ label = 'midl.exe'; path = (Join-Path $bin 'midl.exe'); sha = 'c3e800b8e3555476511df81e3d260409ff92e119e4c4e353ac6f4728ab34c8a0' }, + @{ label = 'midlc.exe'; path = (Join-Path $bin 'midlc.exe'); sha = '209447b250c33b8b458210e6d3d8b0089b596ebf9b4da18c4228af9b025acffa' }, + @{ label = 'rpcdce.h'; path = (Join-Path $shared 'rpcdce.h'); sha = '991883893134c8ecbe6af8335df0781bfb779c11684b3367def514136241b866' }, + @{ label = 'Rpcrt4.lib'; path = (Join-Path $sdk 'Lib\10.0.26100.0\um\x64\Rpcrt4.lib'); sha = 'bfe69184c180bf6ade840ac5279b53aa67b0eda43c82bcd86596db0309faeef3' }, + @{ label = 'cl.exe'; path = (Join-Path $vcBin 'cl.exe'); sha = 'b0a1818ebae6da7f18230a84e76ce839212584ac4c2ab55b97a0d5268085da57' }, + @{ label = 'link.exe'; path = (Join-Path $vcBin 'link.exe'); sha = 'a8c42e25c91ca2453e1110540ac08f0fc12d7539454a11b9af8baeecb469837d' } + ) + foreach ($tool in $tools) { + Assert-NoReparse $tool.path + if (-not (Test-Path -LiteralPath $tool.path -PathType Leaf)) { throw 'G3_PINNED_TOOL_ABSENT' } + if ((Get-FileHash -LiteralPath $tool.path -Algorithm SHA256).Hash.ToLowerInvariant() -cne $tool.sha) { throw ('G3_TOOL_DIGEST_DRIFT: ' + $tool.label) } + $receipt.pinned_tools += [ordered]@{ filename = $tool.label; sha256 = $tool.sha } + } + # link.exe and Rpcrt4.lib are inventory pins only; neither is used to link. + $receipt.toolchain_digest_match = $true + foreach ($directory in @($um, $shared, $ucrt, $vcInclude, $env:RUNNER_TEMP, $env:SystemRoot)) { + if ([string]::IsNullOrWhiteSpace($directory) -or -not (Test-Path -LiteralPath $directory -PathType Container)) { throw 'G3_REQUIRED_DIRECTORY_MISSING' } + Assert-NoReparse $directory + } + $scratch = Join-Path ([IO.Path]::GetFullPath($env:RUNNER_TEMP)) ('continuityos_h3_g3_' + $env:GITHUB_RUN_ID + '_' + $env:GITHUB_RUN_ATTEMPT + '_pr210') + if (Test-Path -LiteralPath $scratch) { throw 'G3_TEMP_PREEXISTS_BLOCKED' } + $null = New-Item -ItemType Directory -Path $scratch -ErrorAction Stop + $created = $true + Assert-NoReparse $scratch + $sourceDir = Join-Path $scratch 'source' + $outDir = Join-Path $scratch 'generated' + $objDir = Join-Path $scratch 'objects' + $tempDir = Join-Path $scratch 'tmp' + foreach ($directory in @($sourceDir, $outDir, $objDir, $tempDir)) { $null = New-Item -ItemType Directory -Path $directory -ErrorAction Stop } + Add-BoundedTempInventory 'INITIAL_AFTER_DIRECTORY_CREATION' $tempDir + $sourcePath = Join-Path $sourceDir 'h3_r7b1c_lab.idl' + $sourceBytes = [Convert]::FromBase64String('LyoKICogQ29udGludWl0eU9TIEgzLVI3QjFDIExBQi1PTkxZIElETCBTS0VUQ0gg4oCUIE5PVCBDT01QSUxFRCBPUiBBVVRIT1JJWkVELgogKiBUaGlzIGlzIGEgZnJlc2gsIHNlcGFyYXRlIHN5bnRoZXRpYyBpbnRlcmZhY2U7IE5PVCBIMyB3aXJlIHYzLgogKiBObyBEQiwgZ3JhbnRzLCB3aXRuZXNzLCBtZW1vcnkgcGF5bG9hZCwgY3JlZGVudGlhbHMsIG9yIHByb2R1Y3Rpb24ga2V5cy4KICogVGhlIHB1YmxpYyBwcm9iZSBpcyBOT1Qgc2VydmVyIGF1dGhlbnRpY2F0aW9uOyBDMCByZW1haW5zIEhPTEQuCiAqIEgzTGFiU3VibWl0U3ludGhldGljIE1VU1QgcmV0dXJuIGEgY29udGVudC1mcmVlIExBQl9ESVNBQkxFRCBzdGF0dXMKICogdW50aWwgc2VwYXJhdGVseSBhcHByb3ZlZCwgaW5kZXBlbmRlbnRseSB2ZXJpZmllZCBzYW1lLWJpbmRpbmcgQzAgZXhpc3RzLgogKiBObyBSUEMgcHJvY2VzcywgZW5kcG9pbnQsIG9yIHNlcnZpY2UgaXMgY3JlYXRlZCBieSB0aGlzIGRlc2lnbiBmaWxlLgogKi8KClsKICAgIHV1aWQoMGIwYTA1OGEtZjVkMS01ZTdiLWEyOWMtOGJiYzY3ODc5NDE4KSwKICAgIHZlcnNpb24oMS4wKSwKICAgIHBvaW50ZXJfZGVmYXVsdChyZWYpCl0KaW50ZXJmYWNlIEgzUjdCMUNfTGFiVjEKewogICAgLyogUHVibGljIGRpc2NvdmVyeTogYSBub25jZSBleGNoYW5nZSBXSVRIT1VUIHByb29mIG9mIHNlcnZlciBpZGVudGl0eS4gKi8KICAgIHZvaWQgSDNMYWJQdWJsaWNQcm9iZSgKICAgICAgICBbaW5dIGhhbmRsZV90IGJpbmRpbmcsCiAgICAgICAgW2luXSBieXRlIGNsaWVudF9ub25jZVszMl0sCiAgICAgICAgW291dF0gYnl0ZSBzZXJ2ZXJfbm9uY2VbMzJdLAogICAgICAgIFtvdXQsIHJlZl0gdW5zaWduZWQgbG9uZyAqbGFiX3N0YXR1cwogICAgKTsKCiAgICAvKiBPbmx5IG5vbi1zZW5zaXRpdmUgc3ludGhldGljIGZpeHR1cmUgYnl0ZXMgbWF5IGVudGVyIHRoaXMgbWV0aG9kLgogICAgICogQSBmdXR1cmUgcmVhbCBicm9rZXIgTVVTVCB1c2UgYSBzZXBhcmF0ZWx5IHJldmlld2VkL2F1dGhlbnRpY2F0ZWQKICAgICAqIHByb3RvY29sLCBhbmQgTVVTVCBOT1QgdHJlYXQgdGhpcyBhcyBhIHYzIG1lbW9yeSBtZXRob2QuCiAgICAgKiBGaXhlZCBhcnJheXMgZGVsaWJlcmF0ZWx5IGF2b2lkIGNhbGxlci1zaXplZCBhbGxvY2F0aW9ucyBpbiBJREwuCiAgICAgKiBUaGUgYnl0ZS1sZW5ndGggZmllbGQgaXMgdmFsaWRhdGVkIGJ5IC9yb2J1c3QgQU5EIGluc2lkZSBoYW5kbGVyLgogICAgICovCiAgICB2b2lkIEgzTGFiU3VibWl0U3ludGhldGljKAogICAgICAgIFtpbl0gaGFuZGxlX3QgYmluZGluZywKICAgICAgICBbaW4sIHJhbmdlKDEsIDE2Mzg0KV0gdW5zaWduZWQgbG9uZyByZXF1ZXN0X2xlbiwKICAgICAgICBbaW5dIGJ5dGUgcmVxdWVzdF9zbG90WzE2Mzg0XSwKICAgICAgICBbb3V0LCByZWZdIHVuc2lnbmVkIGxvbmcgKnJlcGx5X2xlbiwKICAgICAgICBbb3V0XSBieXRlIHJlcGx5X3Nsb3RbODE5Ml0sCiAgICAgICAgW291dCwgcmVmXSB1bnNpZ25lZCBsb25nICpsYWJfc3RhdHVzCiAgICApOwp9Cg==') + $sourceSha = '67915aa4c68d7bf8d8dd3a8879f7abed0c12a5262e577f4e4bf9c8c7fe87fe09' + if ($sourceBytes.Length -ne 1555 -or (Get-Sha256Bytes $sourceBytes) -cne $sourceSha) { throw 'G3_EMBEDDED_IDL_DRIFT' } + [IO.File]::WriteAllBytes($sourcePath, $sourceBytes) + $sourceNames = @('h3_r7b1c_lab.idl') + $receipt.source_before = @(Get-BoundedManifest $sourceDir $sourceNames 1555 1555) + if ($receipt.source_before[0].sha256 -cne $sourceSha) { throw 'G3_IDL_READBACK_DRIFT' } + + # Closed environment for MIDL, its C preprocessor, and both object compilations. + $childEnv = @{ + SystemRoot = $env:SystemRoot + WINDIR = $env:SystemRoot + ComSpec = (Join-Path $env:SystemRoot 'System32\cmd.exe') + PATH = (@($vcBin, $bin, (Join-Path $env:SystemRoot 'System32'), $env:SystemRoot) -join ';') + INCLUDE = (@($outDir, $um, $shared, $ucrt, $vcInclude) -join ';') + TMP = $tempDir + TEMP = $tempDir + VSLANG = '1033' + } + $midlArgs = @('/nologo', '/robust', '/Oicf', '/env', 'amd64', '/out', $outDir, '/I', $um, '/I', $shared, '/I', $ucrt, $sourcePath) + Invoke-BoundedNative 'MIDL' (Join-Path $bin 'midl.exe') $midlArgs 90 $childEnv $scratch + Add-BoundedTempInventory 'AFTER_MIDL' $tempDir + $generatedNames = @('h3_r7b1c_lab.h', 'h3_r7b1c_lab_c.c', 'h3_r7b1c_lab_s.c') + $receipt.generated_before_compile = @(Get-BoundedManifest $outDir $generatedNames 262144 786432) + $header = [IO.File]::ReadAllText((Join-Path $outDir 'h3_r7b1c_lab.h')) + if (-not $header.Contains('H3LabPublicProbe') -or -not $header.Contains('H3LabSubmitSynthetic')) { throw 'G3_GENERATED_INTERFACE_SYMBOLS_MISSING' } + + # /W3 covers actionable generated-C warnings; /WX rejects every warning at that level. + # No warning suppression, /link, /LD, or executable output. Each /Fo is an absolute .obj. + $clientObject = Join-Path $objDir 'h3_r7b1c_lab_c.obj' + $serverObject = Join-Path $objDir 'h3_r7b1c_lab_s.obj' + $clientArgs = @('/nologo', '/TC', '/c', '/W3', '/WX', '/Od', '/I', $outDir, ('/Fo' + $clientObject), (Join-Path $outDir 'h3_r7b1c_lab_c.c')) + Invoke-BoundedNative 'CL_CLIENT_OBJECT' (Join-Path $vcBin 'cl.exe') $clientArgs 60 $childEnv $scratch + Add-BoundedTempInventory 'AFTER_CL_CLIENT_OBJECT' $tempDir + $clientBefore = @(Get-BoundedManifest $objDir @('h3_r7b1c_lab_c.obj') 1048576 1048576) + $clientCoff = Assert-Amd64Coff $clientObject + $receipt.objects = @([ordered]@{ file = $clientBefore[0]; coff = $clientCoff }) + Assert-SameManifest $receipt.generated_before_compile @(Get-BoundedManifest $outDir $generatedNames 262144 786432) + Assert-SameManifest $receipt.source_before @(Get-BoundedManifest $sourceDir $sourceNames 1555 1555) + + $serverArgs = @('/nologo', '/TC', '/c', '/W3', '/WX', '/Od', '/I', $outDir, ('/Fo' + $serverObject), (Join-Path $outDir 'h3_r7b1c_lab_s.c')) + Invoke-BoundedNative 'CL_SERVER_OBJECT' (Join-Path $vcBin 'cl.exe') $serverArgs 60 $childEnv $scratch + Add-BoundedTempInventory 'AFTER_CL_SERVER_OBJECT' $tempDir + $objectManifest = @(Get-BoundedManifest $objDir @('h3_r7b1c_lab_c.obj', 'h3_r7b1c_lab_s.obj') 1048576 2097152) + $serverCoff = Assert-Amd64Coff $serverObject + Assert-SameManifest $clientBefore @($objectManifest | Where-Object { $_.filename -ceq 'h3_r7b1c_lab_c.obj' }) + $receipt.objects += [ordered]@{ file = $objectManifest[1]; coff = $serverCoff } + $receipt.generated_after_compile = @(Get-BoundedManifest $outDir $generatedNames 262144 786432) + $receipt.source_after = @(Get-BoundedManifest $sourceDir $sourceNames 1555 1555) + Assert-SameManifest $receipt.generated_before_compile $receipt.generated_after_compile + Assert-SameManifest $receipt.source_before $receipt.source_after + foreach ($tool in $tools) { + Assert-NoReparse $tool.path + if ((Get-FileHash -LiteralPath $tool.path -Algorithm SHA256).Hash.ToLowerInvariant() -cne $tool.sha) { throw 'G3_TOOL_CHANGED_DURING_COMPILE' } + } + $receipt.toolchain_unchanged_after_compile = $true + $rootEntries = 0 + foreach ($path in [IO.Directory]::EnumerateFileSystemEntries($scratch)) { + $rootEntries++ + $entry = Get-Item -LiteralPath $path -Force -ErrorAction Stop + if ($rootEntries -gt 4 -or -not $entry.PSIsContainer -or $entry.Name -cnotin @('source', 'generated', 'objects', 'tmp')) { throw 'G3_UNEXPECTED_SCRATCH_ENTRY' } + Assert-NoReparse $path + } + if ($rootEntries -ne 4) { throw 'G3_SCRATCH_DIRECTORY_MISSING' } + Add-BoundedTempInventory 'FINAL_BEFORE_EMPTY_TEMP_GATE' $tempDir + Remove-ObservedEmptyTempDirectories $tempDir $receipt.temp_inventory[$receipt.temp_inventory.Count - 1] + foreach ($path in [IO.Directory]::EnumerateFileSystemEntries($tempDir)) { throw 'G3_RESIDUAL_TEMP_OUTPUT' } + if ($receipt.midl_invocations -ne 1 -or $receipt.direct_object_compiler_invocations -ne 2 -or $receipt.process_receipts.Count -ne 3) { throw 'G3_INVOCATION_COUNT_DRIFT' } + $receipt.no_executable_or_dll_output = $true + $completed = $true + } catch { + $message = [string]$_.Exception.Message + $receipt.failures.Add($message.Substring(0, [Math]::Min(512, $message.Length))) + } finally { + if ($created) { + $receipt.cleanup_attempted = $true + try { + Assert-CleanupTree $scratch + Remove-Item -LiteralPath $scratch -Recurse -Force -ErrorAction Stop + # A failed deletion never sets success, even if a later existence check is false. + if (Test-Path -LiteralPath $scratch) { throw 'G3_TEMP_CLEANUP_NOT_VERIFIED' } + $receipt.temp_cleanup_verified = $true + } catch { + $receipt.cleanup_error = 'G3_TEMP_CLEANUP_FAILED_OR_UNVERIFIED' + $receipt.failures.Add($receipt.cleanup_error) + } + } + } + + if ($completed -and $receipt.failures.Count -eq 0 -and $receipt.temp_cleanup_verified) { + $receipt.verdict = 'PASS_OBJECT_COMPILE_ONLY' + } + Write-Output ('H3_G3_OBJECT_COMPILE_RECEIPT=' + ($receipt | ConvertTo-Json -Compress -Depth 10)) + if ($receipt.verdict -cne 'PASS_OBJECT_COMPILE_ONLY') { throw 'H3_G3_OBJECT_COMPILE_HOLD_SEE_RECEIPT' } + Write-Output 'H3_G3_OBJECT_COMPILE_ONLY=PASS_NOT_RPC_SECURITY_PASS'