Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
513d050
Add headless guards for unrepaired terminal restoration evidence
coneilen Nov 6, 2026
1eb2239
Wire opt-in real terminal restoration qualification route
coneilen Nov 6, 2026
41254cd
Expose read-only kernel pipe server identity for capture checks
coneilen Nov 6, 2026
0f309c7
Report explicit skipped count for pipe peer contracts
coneilen Nov 6, 2026
077efcc
Require pipe peer contracts before capture fixture preparation
coneilen Nov 6, 2026
0228843
Post owned pipe reader before awaiting payload completion
coneilen Nov 6, 2026
51c71b9
Bind terminal restoration route to qualified observer source
coneilen Nov 6, 2026
c171ec7
Merge verified Wire closure and preserve terminal restoration qualifi…
coneilen Nov 7, 2026
801511a
Include the known safe-handle pipe peer export in strict native contr…
coneilen Nov 7, 2026
1742b84
Make terminal qualification cleanup owned bounded and evidence-preser…
coneilen Nov 8, 2026
90f7d87
Resolve terminal applications by single command precedence
coneilen Nov 8, 2026
627da4e
Use the admitted runner temp root for terminal evidence
coneilen Nov 8, 2026
ac5a606
Retain terminal CI evidence and report actual seed health errors
coneilen Nov 8, 2026
b02cf35
Normalize checkout line endings in terminal retention contracts
coneilen Nov 8, 2026
8c03d47
Create fresh terminal roots with verified current-user ownership
coneilen Nov 8, 2026
7efb34b
Verify official gobject package before required Swift provider test
coneilen Nov 8, 2026
04cc0e7
Compose verified gobject prefetch into manual Windows releases
coneilen Nov 8, 2026
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
27 changes: 25 additions & 2 deletions .github/workflows/windows-port-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,16 @@ jobs:
timeout-minutes: 35
run: ./Tools/windows/validate.ps1 -Task swift-portable,swift-contracts,swift-production,swift-paths,swift-process,swift-named-pipe,swift-format

- name: Prefetch verified official gobject package
shell: pwsh
timeout-minutes: 5
env:
GH_TOKEN: ${{ github.token }}
run: |
. .\.ci-tools\environment.ps1
$env:GRAPHCODE_VALIDATION_ASSEMBLY = & .\Tools\windows\Build-CompiledValidationRunner.ps1
./Tools/windows/Initialize-GobjectPackageCache.ps1 -PublishJobEnvironment

# The swift-production run above skips the real-zmx test (no zmx.exe). This step
# builds the pinned zmx and fails unless that test executed and passed.
- name: Run real zmx empty-list test (required, not skippable)
Expand All @@ -264,6 +274,17 @@ jobs:
$env:GRAPHCODE_ZMX_ROOT = (Resolve-Path .ci-providers/zmx).Path
$env:GRAPHCODE_ZIG0160 = (Resolve-Path .ci-tools/zig-0.16.0/zig.exe).Path
./Tools/windows/Test-RealZmxEmptyList.ps1

- name: Retain official package verification evidence
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
timeout-minutes: 5
with:
name: gobject-package-verification-${{ github.run_attempt }}
path: ${{ runner.temp }}\gobject-prefetch-*\**
include-hidden-files: true
if-no-files-found: warn
retention-days: 7
# Everything else in -Task all: remote bridge and E2E, visual baseline, TDD
# evidence, privacy, and the pinned terminal gate.
spikes-other:
Expand Down Expand Up @@ -311,12 +332,14 @@ jobs:
run: ./Tools/windows/validate.ps1 -Task all -SkipTrayLive -SkipWslRemoteE2E -SkipTask swift-portable,swift-contracts,swift-production,swift-paths,swift-process,swift-named-pipe,swift-format,windows-shell,packaging,hardening

- name: Retain UIA failure diagnostics
if: failure()
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
timeout-minutes: 5
with:
name: uia-live-gate-diagnostics
path: ${{ runner.temp }}\gu-*\logs\*.json
path: |
${{ runner.temp }}\gu-*\logs\*.json
${{ runner.temp }}\terminal-owned-*\**\*
if-no-files-found: warn
retention-days: 7

Expand Down
21 changes: 21 additions & 0 deletions .github/workflows/windows-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ jobs:
shell: pwsh
run: ./Tools/windows/bootstrap.ps1 -ToolRoot .ci-tools -ProviderRoot .ci-providers

- name: Prefetch verified official gobject package for zmx
shell: pwsh
timeout-minutes: 5
env:
GH_TOKEN: ${{ github.token }}
run: |
. .\.ci-tools\environment.ps1
$env:GRAPHCODE_VALIDATION_ASSEMBLY = & .\Tools\windows\Build-CompiledValidationRunner.ps1
./Tools/windows/Initialize-GobjectPackageCache.ps1 -PublishJobEnvironment

- name: Stage the Swift daemon and CLI for packaging
shell: pwsh
run: ./Tools/windows/stage-swift-products.ps1
Expand Down Expand Up @@ -71,3 +81,14 @@ jobs:
name: graphcode-windows-x86_64
path: .build/windows/release-publish/publish/
if-no-files-found: warn

- name: Retain official package verification evidence
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
timeout-minutes: 5
with:
name: gobject-package-verification-${{ github.run_attempt }}
path: ${{ runner.temp }}\gobject-prefetch-*\**
include-hidden-files: true
if-no-files-found: warn
retention-days: 7
201 changes: 201 additions & 0 deletions Tools/windows/Initialize-GobjectPackageCache.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
[CmdletBinding()]
param(
[string] $Zig0160 = $env:GRAPHCODE_ZIG0160,
[string] $ArtifactParent = $env:RUNNER_TEMP,
[switch] $PublishJobEnvironment
)

$ErrorActionPreference = "Stop"

function Assert-GobjectArchiveProof([long] $Bytes, [string] $Sha256) {
if ($Bytes -ne 1867503 -or
$Sha256 -cne "D572886E8672A46AC6C740E3FE3A1F1C9D9A1A9F09D59FD62328A41FCDB70196") {
throw "Official gobject asset size or SHA-256 mismatch; package fetch was not authorized."
}
}

function Assert-GobjectFetchProof($Result) {
$expected = "gobject-0.3.2-Skun7F6HogCMynX2JqeSHS7xr-8pK4ob-qRFIcEasVi3"
if ($Result.ExitCode -ne 0 -or $Result.StandardOutput -cnotin @("$expected`n", "$expected`r`n") -or
$Result.StandardError -cne "" -or -not $Result.Cleanup.CanRelease -or
-not $Result.Cleanup.ResourcesClosed) {
throw "Local gobject fetch did not return exactly the pinned package hash with complete owned cleanup."
}
}

function New-GobjectFetchStart([string] $Executable, [string] $Project,
[string] $Cache, [string] $Archive, [string] $Temp) {
$start = [Diagnostics.ProcessStartInfo]::new($Executable)
$start.UseShellExecute = $false
$start.CreateNoWindow = $true
$start.RedirectStandardOutput = $true
$start.RedirectStandardError = $true
$start.WorkingDirectory = $Project
$start.Environment["TEMP"] = $Temp
$start.Environment["TMP"] = $Temp
$start.Environment["ZIG_GLOBAL_CACHE_DIR"] = $Cache
$null = $start.Environment.Remove("ZIG_LOCAL_CACHE_DIR")
$null = $start.Environment.Remove("GH_TOKEN")
$null = $start.Environment.Remove("GITHUB_TOKEN")
foreach ($argument in @("fetch", "--global-cache-dir", $Cache, $Archive)) {
$start.ArgumentList.Add($argument)
}
return $start
}

function Save-OfficialGobjectArchive([string] $Path, [string] $Token) {
if ([string]::IsNullOrWhiteSpace($Token) -or $Token -match '\s') {
throw "Official asset API requires the existing job token without whitespace."
}
$client = [Net.Http.HttpClient]::new()
$client.Timeout = [Threading.Timeout]::InfiniteTimeSpan
$deadline = [Threading.CancellationTokenSource]::new([TimeSpan]::FromSeconds(60))
$request = [Net.Http.HttpRequestMessage]::new(
[Net.Http.HttpMethod]::Get,
"https://api.github.com/repos/ghostty-org/zig-gobject/releases/assets/493157198")
$response = $null
$input = $null
$output = $null
$primary = $null
try {
$request.Headers.UserAgent.ParseAdd("GraphCode-pinned-gobject-prefetch")
$request.Headers.Accept.ParseAdd("application/octet-stream")
$request.Headers.Authorization = [Net.Http.Headers.AuthenticationHeaderValue]::new("Bearer", $Token)
$response = $client.SendAsync($request, [Net.Http.HttpCompletionOption]::ResponseHeadersRead,
$deadline.Token).GetAwaiter().GetResult()
[IO.File]::WriteAllText("$Path.http-status.json",
(@{ statusCode = [int]$response.StatusCode; assetId = 493157198 } | ConvertTo-Json),
[Text.UTF8Encoding]::new($false))
$null = $response.EnsureSuccessStatusCode()
$input = $response.Content.ReadAsStreamAsync($deadline.Token).GetAwaiter().GetResult()
$output = [IO.File]::Open($Path, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write, [IO.FileShare]::Read)
$buffer = [byte[]]::new(16384)
$total = 0L
while (($read = $input.ReadAsync($buffer, 0, $buffer.Length, $deadline.Token).
GetAwaiter().GetResult()) -gt 0) {
$output.Write($buffer, 0, $read)
$total += $read
if ($total -gt 1867503) { throw "Official asset exceeds the exact pinned byte count." }
}
$output.Flush($true)
} catch {
$primary = $_.Exception
throw
} finally {
$cleanupErrors = [Collections.Generic.List[Exception]]::new()
foreach ($resource in @($output, $input, $response, $request, $deadline, $client)) {
if ($null -ne $resource) {
try { $resource.Dispose() } catch { $cleanupErrors.Add($_.Exception) }
}
}
if ($cleanupErrors.Count -gt 0) {
$cleanupFailure = [AggregateException]::new("Official asset transport cleanup failed.", $cleanupErrors)
if ($primary) { $primary.Data["GobjectTransportCleanupFailure"] = $cleanupFailure } else { throw $cleanupFailure }
}
}
}

if ($MyInvocation.InvocationName -eq ".") { return }

$assembly = $env:GRAPHCODE_VALIDATION_ASSEMBLY
if ([string]::IsNullOrWhiteSpace($assembly)) {
throw "Official prefetch requires the existing source-bound compiled ownership bindings."
}
& (Join-Path $PSScriptRoot "Import-CompiledValidationAssembly.ps1") `
-AssemblyPath $assembly -RequiredType "GraphCode.Validation.Ownership.OwnedChildSupervisor" | Out-Null
& (Join-Path $PSScriptRoot "Import-CompiledValidationAssembly.ps1") `
-AssemblyPath (Join-Path (Split-Path $assembly) "ValidationFixtureBindings.dll") `
-RequiredType "GraphCode.ValidationFixtureDirectoryNative" | Out-Null
if ([string]::IsNullOrWhiteSpace($ArtifactParent) -or
$ArtifactParent.Contains("`r") -or $ArtifactParent.Contains("`n") -or
-not [IO.Path]::IsPathFullyQualified($ArtifactParent) -or
-not (Test-Path -LiteralPath $ArtifactParent -PathType Container) -or
[string]::IsNullOrWhiteSpace($Zig0160) -or -not [IO.Path]::IsPathFullyQualified($Zig0160) -or
-not (Test-Path -LiteralPath $Zig0160 -PathType Leaf)) {
throw "Official prefetch requires an existing absolute job temp parent and pinned Zig executable."
}
if ($PublishJobEnvironment -and (
[string]::IsNullOrWhiteSpace($env:GITHUB_ENV) -or
-not [IO.Path]::IsPathFullyQualified($env:GITHUB_ENV) -or
-not (Test-Path -LiteralPath $env:GITHUB_ENV -PathType Leaf))) {
throw "Package cache publication requires the existing job environment file."
}
$root = Join-Path $ArtifactParent ("gobject-prefetch-" + [guid]::NewGuid().ToString("N"))
[GraphCode.ValidationFixtureDirectoryNative]::CreateOwnedByCurrentUser($root)
foreach ($name in @("project", "global", "temp", "logs")) {
[GraphCode.ValidationFixtureDirectoryNative]::CreateOwnedByCurrentUser((Join-Path $root $name))
}
Write-Host "GOBJECT_PREFETCH_ARTIFACT_ROOT=$root"
$project = Join-Path $root "project"
$cache = Join-Path $root "global"
$marker = Join-Path $project "build.zig"
$file = [IO.File]::Open($marker, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write, [IO.FileShare]::None)
$file.Dispose()
$archive = Join-Path $root "official-gobject.tar.gz"
$version = $null
$result = $null
$primary = $null
try {
$versionStart = New-GobjectFetchStart $Zig0160 $project $cache $archive (Join-Path $root "temp")
$versionStart.ArgumentList.Clear()
$versionStart.ArgumentList.Add("version")
$version = [GraphCode.Validation.Ownership.OwnedChildSupervisor]::RunAsync(
$versionStart, [TimeSpan]::FromSeconds(10), (Join-Path $root "logs")).GetAwaiter().GetResult()
if ($version.ExitCode -ne 0 -or $version.StandardOutput -cnotin @("0.16.0`n", "0.16.0`r`n") -or
$version.StandardError -cne "" -or -not $version.Cleanup.CanRelease -or
-not $version.Cleanup.ResourcesClosed) { throw "Prefetch requires exactly pinned Zig 0.16.0." }
Save-OfficialGobjectArchive $archive $env:GH_TOKEN
$archiveInfo = Get-Item -LiteralPath $archive
$archiveHash = (Get-FileHash -LiteralPath $archive -Algorithm SHA256).Hash
Assert-GobjectArchiveProof $archiveInfo.Length $archiveHash
$start = New-GobjectFetchStart $Zig0160 $project $cache $archive (Join-Path $root "temp")
$before = [ordered]@{
fileName = $start.FileName; executableSha256 = (Get-FileHash -LiteralPath $Zig0160 -Algorithm SHA256).Hash
argumentList = @($start.ArgumentList); workingDirectory = $start.WorkingDirectory
markerBytes = (Get-Item -LiteralPath $marker).Length
archiveBytes = $archiveInfo.Length; archiveSha256 = $archiveHash
globalCache = $cache; timeoutSeconds = 120; recursive = $false; save = $false
}
if ($before.markerBytes -ne 0) { throw "The private fetch context marker must remain empty." }
$before | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $root "before-fetch.json") -Encoding utf8
$result = [GraphCode.Validation.Ownership.OwnedChildSupervisor]::RunAsync(
$start, [TimeSpan]::FromSeconds(120), (Join-Path $root "logs")).GetAwaiter().GetResult()
Assert-GobjectFetchProof $result
$cachedArchive = Join-Path $cache "p\gobject-0.3.2-Skun7F6HogCMynX2JqeSHS7xr-8pK4ob-qRFIcEasVi3.tar.gz"
if (-not (Test-Path -LiteralPath $cachedArchive -PathType Leaf) -or
(Get-Item -LiteralPath $cachedArchive).Length -le 0 -or
(Get-Item -LiteralPath $marker).Length -ne 0 -or
(Test-Path -LiteralPath (Join-Path $project "build.zig.zon"))) {
throw "Verified fetch did not retain its native global archive or changed the empty project context."
}
if ($PublishJobEnvironment) {
[IO.File]::AppendAllText($env:GITHUB_ENV, "ZIG_GLOBAL_CACHE_DIR=$cache`n", [Text.UTF8Encoding]::new($false))
}
$env:ZIG_GLOBAL_CACHE_DIR = $cache
} catch {
$primary = $_
throw
} finally {
try {
[ordered]@{
version = $version; result = $result
failure = if ($primary) { $primary.Exception.Message } else { $null }
globalCache = $cache; markerBytes = (Get-Item -LiteralPath $marker).Length
files = @(Get-ChildItem -LiteralPath $root -Recurse -File | ForEach-Object {
@{ path = $_.FullName.Substring($root.Length + 1); bytes = $_.Length
sha256 = (Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash }
})
} | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath (Join-Path $root "receipt.json") -Encoding utf8
} catch {
if ($primary) {
$primary.Exception.Data["GobjectPrefetchReceiptFailure"] = $_.Exception
} else { throw }
} finally {
if ([GraphCode.Validation.Ownership.OwnedChildSupervisor]::RequiresLifetimeHold) {
[Console]::Out.WriteLine("GRAPHCODE_VALIDATION_ROOT_RETENTION_REQUIRED=ownership_unproven")
[Console]::Out.Flush()
$null = [GraphCode.Validation.Ownership.OwnedChildSupervisor]::HoldRetainedResourcesAsync().GetAwaiter().GetResult()
}
}
}
Write-Host "GOBJECT_PACKAGE_HASH_VERIFIED=gobject-0.3.2-Skun7F6HogCMynX2JqeSHS7xr-8pK4ob-qRFIcEasVi3"
55 changes: 55 additions & 0 deletions Tools/windows/Tests/Release.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,61 @@ exit 2
if (-not $publishDefault.Success -or $publishDefault.Groups[1].Value -notmatch "^(false|'false'|`"false`")$") {
throw "the publishing input no longer defaults to false"
}
$normalizedWorkflow = $workflow -replace "\r\n", "`n"
$helperVariants = 0
foreach ($variant in @($normalizedWorkflow, ($normalizedWorkflow -replace "`n", "`r`n"))) {
$source = $variant -replace "\r\n", "`n"
$steps = [regex]::Split($source, '(?m)(?=^ - (?:name|uses):)')
$prefetch = @($steps | Where-Object { $_ -match '^ - name: Prefetch verified official gobject package for zmx\n' })
$retention = @($steps | Where-Object { $_ -match '^ - name: Retain official package verification evidence\n' })
if ($prefetch.Count -ne 1 -or $retention.Count -ne 1) {
throw "the release workflow requires exactly one gobject prefetch and evidence step"
}
foreach ($required in @(
'timeout-minutes: 5',
'GH_TOKEN: ${{ github.token }}',
'. .\.ci-tools\environment.ps1',
'$env:GRAPHCODE_VALIDATION_ASSEMBLY = & .\Tools\windows\Build-CompiledValidationRunner.ps1',
'./Tools/windows/Initialize-GobjectPackageCache.ps1 -PublishJobEnvironment')) {
if (-not $prefetch[0].Contains($required)) {
throw "the release prefetch step no longer references $required"
}
}
if ($prefetch[0] -match 'ValidationRunner\.exe|validate\.ps1|setup-dotnet@|permissions:') {
throw "release prefetch must build only the existing guard without new SDK or permissions"
}
foreach ($required in @(
'if: always()',
'actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02',
'timeout-minutes: 5',
'name: gobject-package-verification-${{ github.run_attempt }}',
'path: ${{ runner.temp }}\gobject-prefetch-*\**',
'include-hidden-files: true',
'if-no-files-found: warn',
'retention-days: 7')) {
if (-not $retention[0].Contains($required)) {
throw "the release evidence step no longer references $required"
}
}
$bootstrapPosition = $source.IndexOf(' - name: Bootstrap exact Windows dependencies')
$prefetchPosition = $source.IndexOf($prefetch[0])
$packagePosition = $source.IndexOf(' - name: Package the Windows release artifact')
$retentionPosition = $source.IndexOf($retention[0])
if ($bootstrapPosition -lt 0 -or $prefetchPosition -le $bootstrapPosition -or
$packagePosition -le $prefetchPosition -or $retentionPosition -le $packagePosition) {
throw "release prefetch and retained evidence must surround the existing package build"
}
$jobsSource = ($source -split "(?m)^jobs:\n", 2)[1]
if ([regex]::Matches($jobsSource, '(?m)^ [a-zA-Z][a-zA-Z0-9_-]*:\s*$').Count -ne 1 -or
$source -notmatch '(?m)^permissions:\n contents: read\n' -or
$source -notmatch '(?m)^ permissions:\n contents: write\n' -or
$source -match '(?m)^ (?:push|release|schedule):') {
throw "release helper composition changed the single job, permissions, or manual trigger"
}
$helperVariants++
}
if ($helperVariants -ne 2) { throw "release helper contract did not execute both line-ending variants" }
Write-Output "Release workflow helper variants executed: $helperVariants"
Write-Output "Release workflow contract: PASS"

# 9. The workflow's own invocation actually binds release.ps1's parameters.
Expand Down
Loading
Loading