build: reject dirty Rayforge source trees

This commit is contained in:
Codex 2026-10-21 18:52:53 +02:00
parent a5a8e4086d
commit 7b817f6339
8 changed files with 54 additions and 6 deletions

View file

@ -15,7 +15,8 @@ negative cases, cancellation/recovery and synthetic LightBurn imports.
The repository now provides:
- an on-demand source/build entry point that never installs the Windows
toolchain;
toolchain, rejects modified upstream source and replaces only the validated
addon staging directory;
- a non-executing verifier for externally produced Windows artifacts;
it requires an exact repository-to-manifest-to-ZIP addon file set;
- a checksummed acceptance-kit generator containing only allowlisted public

View file

@ -39,8 +39,12 @@ toolchain preparation remains a separate, explicitly invoked upstream action
or should happen on a dedicated build runner.
Before staging, the script rejects transport imports in the addon. The build
manifest records the exact upstream commit and hashes every addon source file,
the portable archive and installer. It does not claim the GUI has been tested.
entry point also rejects tracked Rayforge modifications and any unexpected
untracked source. It removes and recreates only the validated addon staging
directory, preventing stale files from a previous run from entering the
bundle. The build manifest records the exact upstream commit and hashes every
addon source file, the portable archive and installer. It does not claim the
GUI has been tested.
Copy the ZIP, installer and JSON manifest together, then verify an externally
produced checkpoint without launching either binary:

View file

@ -45,6 +45,12 @@ revision into the ignored `.build/upstreams/` cache only when explicitly
invoked. Its default action prepares source only; it neither installs the
Windows toolchain nor starts a build.
Source preparation also verifies the Git worktree state. Tracked changes and
unexpected untracked source fail closed. The Windows checkpoint wrapper allows
only its known addon staging subtree, validates that path, removes it before
copying, and then rechecks the worktree before invoking upstream's build.
Ignored upstream build outputs remain outside this source-integrity decision.
The repository scripts do not install Windows dependencies. Bundle creation
requires the explicit `-BuildWindowsBundle` switch and an already reviewed,
prepared runtime. This keeps a multi-gigabyte MSYS2/UCRT64 toolchain out of

View file

@ -321,7 +321,9 @@ The transport-free portions are now consolidated by the
[M1 no-device acceptance checkpoint](research/m1-no-device-acceptance.md).
It reruns the public suite and leak guard and hashes the validated regression
reports. The on-demand build entry point fetches pinned source and stages the
addon but deliberately refuses to install a missing Windows toolchain.
addon but deliberately refuses to install a missing Windows toolchain. It
rejects modified upstream source and replaces the exact addon staging tree so
stale files cannot survive between builds.
An artifact produced elsewhere can be checked locally without execution: the
verifier rejects unsafe paths or changed bytes and confirms the pin plus every
packaged addon source before the manual GUI checklist may begin. It also

View file

@ -20,7 +20,8 @@ if (-not $ArtifactRoot) {
}
& (Join-Path $PSScriptRoot "prepare_rayforge_build.ps1") `
-CacheRoot $CacheRoot
-CacheRoot $CacheRoot `
-AllowStagedPirateRayAddon
if ($LASTEXITCODE -ne 0) {
throw "Pinned Rayforge source preparation failed"
}
@ -48,6 +49,17 @@ $addonDestination = Join-Path $checkout (
"rayforge\builtin_addons\rayforge-addon-the-pirate-ray-jcz"
)
if ($PSCmdlet.ShouldProcess($addonDestination, "Stage no-device addon")) {
$resolvedCheckout = [IO.Path]::GetFullPath($checkout).TrimEnd("\")
$resolvedAddonDestination = [IO.Path]::GetFullPath($addonDestination)
$expectedAddonDestination = Join-Path $resolvedCheckout (
"rayforge\builtin_addons\rayforge-addon-the-pirate-ray-jcz"
)
if ($resolvedAddonDestination -ne $expectedAddonDestination) {
throw "Refusing to replace an unexpected addon staging path"
}
if (Test-Path -LiteralPath $resolvedAddonDestination) {
Remove-Item -LiteralPath $resolvedAddonDestination -Recurse -Force
}
New-Item -ItemType Directory -Force -Path $addonDestination | Out-Null
Copy-Item -Path (Join-Path $addonSource "*") `
-Destination $addonDestination -Recurse -Force
@ -56,6 +68,7 @@ if ($PSCmdlet.ShouldProcess($addonDestination, "Stage no-device addon")) {
$prepareArguments = @{
CacheRoot = $CacheRoot
BuildWindowsBundle = $true
AllowStagedPirateRayAddon = $true
}
& (Join-Path $PSScriptRoot "prepare_rayforge_build.ps1") @prepareArguments
if ($LASTEXITCODE -ne 0) {

View file

@ -1,7 +1,8 @@
[CmdletBinding(SupportsShouldProcess)]
param(
[string]$CacheRoot,
[switch]$BuildWindowsBundle
[switch]$BuildWindowsBundle,
[switch]$AllowStagedPirateRayAddon
)
$ErrorActionPreference = "Stop"
@ -54,8 +55,23 @@ if ($verifiedCommit -ne $manifest.commit) {
throw "Rayforge pin mismatch: expected $($manifest.commit), got $verifiedCommit"
}
$statusLines = @(& git -C $checkout status --porcelain=v1 --untracked-files=all)
if ($LASTEXITCODE -ne 0) {
throw "Could not inspect the Rayforge checkout state"
}
$allowedAddonPrefix = "?? rayforge/builtin_addons/rayforge-addon-the-pirate-ray-jcz/"
$unexpectedChanges = @(
$statusLines | Where-Object {
$_ -and (-not $AllowStagedPirateRayAddon -or -not $_.StartsWith($allowedAddonPrefix))
}
)
if ($unexpectedChanges.Count -gt 0) {
throw "Rayforge checkout contains unreviewed tracked or untracked changes"
}
Write-Output "Rayforge source ready at $checkout"
Write-Output "Verified commit: $verifiedCommit"
Write-Output "Verified checkout state: clean except explicitly staged PirateRay addon"
if (-not $BuildWindowsBundle) {
Write-Output "No dependencies were installed and no build was started."

View file

@ -47,6 +47,9 @@ class M1AcceptancePipelineTests(unittest.TestCase):
self.assertIn("schema_version = 2", source)
self.assertIn("Rayforge-LICENSE.txt", source)
self.assertIn("license_files", source)
self.assertIn("GetFullPath", source)
self.assertIn("Remove-Item -LiteralPath $resolvedAddonDestination", source)
self.assertIn("AllowStagedPirateRayAddon", source)
def test_acceptance_has_no_hardware_commands(self):
source = ACCEPTANCE.read_text(encoding="utf-8").lower()

View file

@ -24,6 +24,9 @@ class UpstreamBuildReferenceTests(unittest.TestCase):
self.assertNotIn("InstallWindowsDependencies", script)
self.assertIn("BuildWindowsBundle", script)
self.assertIn("No dependencies were installed", script)
self.assertIn("status --porcelain=v1 --untracked-files=all", script)
self.assertIn("unreviewed tracked or untracked changes", script)
self.assertIn("AllowStagedPirateRayAddon", script)
def test_prepare_script_has_no_hardware_path(self):
script = PREPARE.read_text(encoding="utf-8").lower()