[CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$ReleaseRoot, [Parameter(Mandatory = $true)] [ValidatePattern("^[A-Za-z0-9._-]{1,96}$")] [string]$RunId, [string]$SourcePackPath = "D:\NDC_MISSIONCORE\runtime\derived\e10-lidar-pack-576c994a6c814e2592dd6240ace3902a5db94843312c759a73ba0c9166157d2b\lidar-pack.npz", [string]$OutputRoot = "D:\NDC_MISSIONCORE\runtime\results\m49-tgs-fail-closed" ) $ErrorActionPreference = "Stop" $ProgressPreference = "SilentlyContinue" $TravelImageTag = "ndc/mission-core-m49-t3-travel:20260826" $TravelImageId = "sha256:7b412020f4d8392d1d1ed1b33beadc44140f0ea8f781e62dd69796042334300f" $ParityImageTag = "ndc-mission-core-m48t-upstream-parity:1.9.4-cu130" $ParityImageId = "sha256:ceb13548617e4bd3f619766bfdff00af3fa5160946b367828da6d2233dcdcba0" function Assert-LastExitCode([string]$Operation) { if ($LASTEXITCODE -ne 0) { throw "$Operation failed with exit code $LASTEXITCODE" } } function Resolve-DDirectory([string]$Path, [string]$Label, [bool]$Create) { if ($Create -and -not (Test-Path -LiteralPath $Path)) { $null = New-Item -ItemType Directory -Path $Path } $item = Get-Item -LiteralPath (Resolve-Path -LiteralPath $Path).Path -Force if ( -not $item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -or [IO.Path]::GetPathRoot($item.FullName).TrimEnd("\") -ine "D:" ) { throw "$Label must be a real D: directory" } return $item.FullName } function Resolve-DFile([string]$Path, [string]$Label) { $item = Get-Item -LiteralPath (Resolve-Path -LiteralPath $Path).Path -Force if ( $item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -or [IO.Path]::GetPathRoot($item.FullName).TrimEnd("\") -ine "D:" ) { throw "$Label must be a real D: file" } return $item.FullName } function Convert-ToDockerPath([string]$Path) { return ($Path -replace "\\", "/") } function Get-Container([string]$Name) { $rows = @(((& docker inspect $Name) | ConvertFrom-Json)) Assert-LastExitCode "Docker inspection for $Name" if ($rows.Count -ne 1) { throw "Container identity for $Name is not unique" } return $rows[0] } function Assert-Image([string]$Tag, [string]$ExpectedId) { $rows = @(((& docker image inspect $Tag) | ConvertFrom-Json)) Assert-LastExitCode "Docker image inspection for $Tag" if ($rows.Count -ne 1 -or [string]$rows[0].Id -cne $ExpectedId) { throw "Pinned image identity changed for $Tag" } } function Remove-ExactContainer([string]$Name) { if (& docker ps -a --format "{{.Names}}" --filter "name=^/$Name$") { & docker rm --force $Name *> $null } } if ($env:COMPUTERNAME -cne "DESKTOP-OPJ8J04") { throw "M49 TGS fail-closed evidence is pinned to Worker 006" } $release = Resolve-DDirectory $ReleaseRoot "M49 TGS release" $false $payload = Resolve-DDirectory (Join-Path $release "payload") "M49 TGS payload" $false $sourcePack = Resolve-DFile $SourcePackPath "RAVNOVES00 source pack" $output = Resolve-DDirectory $OutputRoot "M49 TGS output root" $true $runCandidate = Join-Path $output $RunId if (Test-Path -LiteralPath $runCandidate) { throw "M49 TGS output already exists" } $null = New-Item -ItemType Directory -Path $runCandidate $runOutput = Resolve-DDirectory $runCandidate "M49 TGS run output" $false $releaseDocument = Get-Content -LiteralPath (Join-Path $payload "release.json") -Raw | ConvertFrom-Json if ( $releaseDocument.schema_version -cne "missioncore.m49-tgs-worker-release/v1" -or $releaseDocument.worker_id -cne "worker-006" -or $releaseDocument.candidate_id -cne "travel-tgs-only" ) { throw "M49 TGS release contract changed" } foreach ($property in $releaseDocument.files.PSObject.Properties) { $path = Join-Path $payload $property.Name $actual = (Get-FileHash -Algorithm SHA256 -LiteralPath $path).Hash.ToLowerInvariant() if ($actual -cne [string]$property.Value.sha256) { throw "M49 TGS payload digest changed: $($property.Name)" } } $sourcePackSha = (Get-FileHash -Algorithm SHA256 -LiteralPath $sourcePack).Hash.ToLowerInvariant() if ($sourcePackSha -cne "0685d24219d8236caf8b7f1685e93f6d6b59e7fd015a768d88a92bbe8b154944") { throw "RAVNOVES00 source pack digest changed" } $os = Get-CimInstance Win32_OperatingSystem $freeMemoryGiB = [double]$os.FreePhysicalMemory / 1MB if ($freeMemoryGiB -lt 16.0) { throw ("M49 TGS requires 16 GiB free memory; observed {0:N2} GiB" -f $freeMemoryGiB) } $tritonBefore = Get-Container "ndc-mission-core-triton" if (-not $tritonBefore.State.Running -or $tritonBefore.State.Health.Status -cne "healthy") { throw "Canonical Mission Core Triton must remain healthy during M49 TGS" } Assert-Image $TravelImageTag $TravelImageId Assert-Image $ParityImageTag $ParityImageId $prepareName = "ndc-mission-core-m49-tgs-prepare-$RunId" $runName = "ndc-mission-core-m49-tgs-run-$RunId" $analyzeName = "ndc-mission-core-m49-tgs-analyze-$RunId" foreach ($name in @($prepareName, $runName, $analyzeName)) { if (& docker ps -a --format "{{.Names}}" --filter "name=^/$name$") { throw "M49 TGS container name already exists: $name" } } $started = [DateTimeOffset]::UtcNow try { & docker run --rm --name $prepareName --network none --cpus 8 --memory 16g ` --entrypoint python3 ` --volume ((Convert-ToDockerPath $sourcePack) + ":/source/lidar-pack.npz:ro") ` --volume ((Convert-ToDockerPath $payload) + ":/release:ro") ` --volume ((Convert-ToDockerPath $runOutput) + ":/tgs") ` $ParityImageTag /release/prepare_tgs_fail_closed_inputs.py ` --source-pack /source/lidar-pack.npz ` --config /release/m49-tgs-fail-closed-evidence-v1.json ` --output-root /tgs/inputs Assert-LastExitCode "M49 TGS input preparation" & docker run --rm --name $runName --network none --cpus 16 --memory 24g ` --entrypoint /bin/bash ` --volume ((Convert-ToDockerPath $payload) + ":/release:ro") ` --volume ((Convert-ToDockerPath $runOutput) + ":/tgs") ` $TravelImageTag /release/run_tgs_fail_closed.sh Assert-LastExitCode "M49 TGS-only run" & docker run --rm --name $analyzeName --network none --cpus 8 --memory 16g ` --entrypoint python3 ` --volume ((Convert-ToDockerPath $payload) + ":/release:ro") ` --volume ((Convert-ToDockerPath $runOutput) + ":/tgs") ` $ParityImageTag /release/build_tgs_fail_closed_evidence.py ` --run-root /tgs ` --config /release/m49-tgs-fail-closed-evidence-v1.json ` --output-root /tgs/evidence Assert-LastExitCode "M49 TGS fail-closed evidence analysis" } finally { foreach ($name in @($prepareName, $runName, $analyzeName)) { Remove-ExactContainer $name } } $completed = [DateTimeOffset]::UtcNow $resultPath = Join-Path $runOutput "evidence\result.json" if (-not (Test-Path -LiteralPath $resultPath -PathType Leaf)) { throw "M49 TGS evidence result is missing" } $result = Get-Content -LiteralPath $resultPath -Raw | ConvertFrom-Json if ( $result.status -cne "passed" -or -not [bool]$result.summary.all_eligible_points_accounted -or [bool]$result.summary.aos_used ) { throw "M49 TGS fail-closed acceptance failed" } $tritonAfter = Get-Container "ndc-mission-core-triton" if ( -not $tritonAfter.State.Running -or $tritonAfter.State.Health.Status -cne "healthy" -or [string]$tritonAfter.Id -cne [string]$tritonBefore.Id ) { throw "Canonical Mission Core Triton changed during M49 TGS" } $summary = [ordered]@{ schema_version = "missioncore.m49-tgs-worker-summary/v1" worker_id = "worker-006" run_id = $RunId code_revision = [string]$releaseDocument.code_revision source_pack_sha256 = $sourcePackSha travel_image_id = $TravelImageId parity_image_id = $ParityImageId started_utc = $started.ToString("o") wall_seconds = [math]::Round(($completed - $started).TotalSeconds, 6) free_memory_gib_before = [math]::Round($freeMemoryGiB, 6) canonical_triton_id = [string]$tritonAfter.Id canonical_triton_health = [string]$tritonAfter.State.Health.Status all_eligible_points_accounted = [bool]$result.summary.all_eligible_points_accounted aos_used = [bool]$result.summary.aos_used visual_quality_accepted = $false realtime_accepted = $false navigation_or_actuation_allowed = $false } $summary | ConvertTo-Json -Depth 3 | Set-Content -LiteralPath ( Join-Path $runOutput "worker-summary.json" ) -Encoding utf8 $summary | ConvertTo-Json -Depth 3