494 lines
19 KiB
PowerShell
494 lines
19 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ReleaseRoot,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ArtifactPath,
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidatePattern("^[a-f0-9]{64}$")]
|
|
[string]$ExpectedArtifactSha256,
|
|
[string]$OutputRoot = "D:\NDC_MISSIONCORE\runtime\results\m47-reference-graph",
|
|
[ValidateRange(1, 1000)]
|
|
[int]$FreeGiBFloor = 300,
|
|
[switch]$PreflightOnly
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
$ProgressPreference = "SilentlyContinue"
|
|
|
|
function Assert-LastExitCode([string]$Operation) {
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "$Operation failed with exit code $LASTEXITCODE"
|
|
}
|
|
}
|
|
|
|
function Get-Sha256([string]$Path) {
|
|
return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
}
|
|
|
|
function Assert-FileSha256([string]$Path, [string]$Expected, [string]$Label) {
|
|
$item = Get-Item -LiteralPath (Resolve-Path -LiteralPath $Path).Path -Force
|
|
if ($item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
|
|
throw "$Label must be a regular file"
|
|
}
|
|
$observed = Get-Sha256 $item.FullName
|
|
if ($observed -cne $Expected) {
|
|
throw "$Label SHA-256 changed: expected $Expected, observed $observed"
|
|
}
|
|
return $item.FullName
|
|
}
|
|
|
|
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
|
|
$root = [IO.Path]::GetPathRoot($item.FullName).TrimEnd("\")
|
|
if (
|
|
-not $item.PSIsContainer -or
|
|
($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -or
|
|
$root -ine "D:"
|
|
) {
|
|
throw "$Label must be a real D: directory"
|
|
}
|
|
return $item.FullName
|
|
}
|
|
|
|
function Convert-ToDockerPath([string]$Path) {
|
|
return $Path.Replace("\", "/")
|
|
}
|
|
|
|
function Assert-FreeSpace([string]$Phase) {
|
|
$free = [int64](Get-PSDrive -Name D).Free
|
|
$floor = [int64]$FreeGiBFloor * 1GB
|
|
Write-Host (
|
|
"DISK_GUARD PHASE={0} DRIVE=D FREE_BYTES={1} FREE_GIB={2} FLOOR_GIB={3}" -f
|
|
$Phase, $free, [math]::Round($free / 1GB, 3), $FreeGiBFloor
|
|
)
|
|
if ($free -lt ($floor + 1GB)) {
|
|
throw "D: lacks the guarded M4.7 reserve during $Phase"
|
|
}
|
|
return $free
|
|
}
|
|
|
|
function Get-ContainerIdentity([string]$Name) {
|
|
$json = & docker inspect $Name
|
|
Assert-LastExitCode "Docker inspection for $Name"
|
|
$rows = @($json | ConvertFrom-Json)
|
|
if ($rows.Count -ne 1) {
|
|
throw "Docker identity for $Name is not unique"
|
|
}
|
|
return $rows[0]
|
|
}
|
|
|
|
function Assert-ContainerIdentity(
|
|
[string]$Name,
|
|
[string]$ExpectedId,
|
|
[string]$ExpectedImageId,
|
|
[bool]$RequireHealthy
|
|
) {
|
|
$container = Get-ContainerIdentity $Name
|
|
if (
|
|
$container.Id -cne $ExpectedId -or
|
|
$container.Image -cne $ExpectedImageId -or
|
|
-not $container.State.Running
|
|
) {
|
|
throw "$Name identity or running state changed"
|
|
}
|
|
if ($RequireHealthy -and $container.State.Health.Status -cne "healthy") {
|
|
throw "$Name is not healthy"
|
|
}
|
|
return $container
|
|
}
|
|
|
|
function Get-DirectoryTreeSha256([string]$Root, [object[]]$Files) {
|
|
$rootPrefix = $Root.TrimEnd("\") + "\"
|
|
$rows = @(
|
|
foreach ($file in $Files) {
|
|
if (-not $file.FullName.StartsWith($rootPrefix, [StringComparison]::OrdinalIgnoreCase)) {
|
|
throw "M4.7 dependency file escaped its declared root"
|
|
}
|
|
[pscustomobject]@{
|
|
Relative = $file.FullName.Substring($rootPrefix.Length).Replace("\", "/")
|
|
File = $file
|
|
}
|
|
}
|
|
)
|
|
$rows = @($rows | Sort-Object -Property Relative -CaseSensitive)
|
|
$digest = [Security.Cryptography.IncrementalHash]::CreateHash(
|
|
[Security.Cryptography.HashAlgorithmName]::SHA256
|
|
)
|
|
$encoding = New-Object System.Text.UTF8Encoding($false)
|
|
try {
|
|
foreach ($row in $rows) {
|
|
$record = "{0}`t{1}`t{2}`n" -f `
|
|
$row.Relative, $row.File.Length, (Get-Sha256 $row.File.FullName)
|
|
$digest.AppendData($encoding.GetBytes($record))
|
|
}
|
|
return ([BitConverter]::ToString($digest.GetHashAndReset())).Replace("-", "").ToLowerInvariant()
|
|
} finally {
|
|
$digest.Dispose()
|
|
}
|
|
}
|
|
|
|
function Write-Utf8NoBom([string]$Path, [string]$Value) {
|
|
$encoding = New-Object System.Text.UTF8Encoding($false)
|
|
[IO.File]::WriteAllText($Path, $Value, $encoding)
|
|
}
|
|
|
|
function Test-NoPublishedPorts([object]$Container) {
|
|
return @($Container.HostConfig.PortBindings.PSObject.Properties).Count -eq 0
|
|
}
|
|
|
|
if ($env:COMPUTERNAME -cne "DESKTOP-OPJ8J04") {
|
|
throw "M4.7 shadow release is pinned to DESKTOP-OPJ8J04"
|
|
}
|
|
|
|
$release = Resolve-DDirectory $ReleaseRoot "M4.7 release root" $false
|
|
$payload = Resolve-DDirectory (Join-Path $release "payload") "M4.7 payload" $false
|
|
$artifact = Assert-FileSha256 $ArtifactPath $ExpectedArtifactSha256 "M4.7 release artifact"
|
|
$descriptorPath = Join-Path $payload "mission-core-worker-m47-graph-shadow-v3.json"
|
|
$descriptor = Get-Content -LiteralPath $descriptorPath -Raw | ConvertFrom-Json
|
|
if (
|
|
$descriptor.schema_version -cne "nodedc.mission-core-worker.shadow-release/v3" -or
|
|
$descriptor.transition -cne "m47-canonical-graph-isolated-shadow-v1" -or
|
|
$descriptor.component -cne "mission-core-worker" -or
|
|
$descriptor.host.node -cne $env:COMPUTERNAME -or
|
|
$descriptor.host.worker_id -cne "worker-006" -or
|
|
$descriptor.boundary.repository -cne "NODEDC_MISSION_CORE" -or
|
|
$descriptor.boundary.external_deploy_registry -ne $false -or
|
|
$descriptor.acceptance.run_mode -cne "lossless-replay" -or
|
|
$descriptor.acceptance.expected_frames -ne 4489 -or
|
|
$descriptor.acceptance.delivered_frames -ne 4489 -or
|
|
$descriptor.acceptance.failed_frames -ne 0 -or
|
|
$descriptor.acceptance.stale_frames -ne 0 -or
|
|
$descriptor.acceptance.superseded_frames -ne 0 -or
|
|
$descriptor.acceptance.accepted_parity -ne $true -or
|
|
$descriptor.readiness.graph.graph_id -cne "reference-perception-graph/v2" -or
|
|
$descriptor.readiness.graph.actuation_allowed -ne $false
|
|
) {
|
|
throw "M4.7 shadow descriptor contract changed"
|
|
}
|
|
|
|
$null = Assert-FileSha256 $PSCommandPath $descriptor.release.runner.sha256 "M4.7 runner"
|
|
$wheelPath = Assert-FileSha256 (
|
|
Join-Path $payload $descriptor.release.wheel.name
|
|
) $descriptor.release.wheel.sha256 "M4.7 wheel"
|
|
foreach ($entry in $descriptor.release.configs.PSObject.Properties) {
|
|
$null = Assert-FileSha256 (
|
|
Join-Path $payload $entry.Value.name
|
|
) $entry.Value.sha256 ("M4.7 config {0}" -f $entry.Name)
|
|
}
|
|
foreach ($entry in $descriptor.inputs.PSObject.Properties) {
|
|
$null = Assert-FileSha256 $entry.Value.host_path $entry.Value.sha256 (
|
|
"M4.7 input {0}" -f $entry.Name
|
|
)
|
|
}
|
|
foreach ($dependency in $descriptor.dependencies) {
|
|
$root = Resolve-DDirectory $dependency.host_path ("M4.7 dependency {0}" -f $dependency.id) $false
|
|
$files = @()
|
|
foreach ($include in $dependency.includes) {
|
|
$candidate = if ($include -eq ".") { $root } else { Join-Path $root $include }
|
|
$item = Get-Item -LiteralPath (Resolve-Path -LiteralPath $candidate).Path -Force
|
|
if ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) {
|
|
throw "M4.7 dependency $($dependency.id) contains a reparse point"
|
|
}
|
|
if ($item.PSIsContainer) {
|
|
$files += @(Get-ChildItem -LiteralPath $item.FullName -File -Recurse -Force)
|
|
} else {
|
|
$files += @($item)
|
|
}
|
|
}
|
|
$files = @($files | Sort-Object -Property FullName -Unique)
|
|
$bytes = [int64]0
|
|
foreach ($file in $files) {
|
|
if ($file.Attributes -band [IO.FileAttributes]::ReparsePoint) {
|
|
throw "M4.7 dependency $($dependency.id) contains a reparse-point file"
|
|
}
|
|
$bytes += [int64]$file.Length
|
|
}
|
|
if (
|
|
$files.Count -ne [int]$dependency.file_count -or
|
|
$bytes -ne [int64]$dependency.bytes -or
|
|
(Get-DirectoryTreeSha256 $root $files) -cne [string]$dependency.tree_sha256
|
|
) {
|
|
throw "M4.7 dependency $($dependency.id) inventory changed"
|
|
}
|
|
}
|
|
|
|
$imageRef = [string]$descriptor.container.image_ref
|
|
& docker image inspect $imageRef *> $null
|
|
Assert-LastExitCode "Pinned M4.7 image inspection"
|
|
|
|
function Get-PreservedContainerSnapshot([object]$Expected, [string]$Label) {
|
|
$container = Get-ContainerIdentity $Expected.name
|
|
if (
|
|
$container.Id -cne $Expected.container_id -or
|
|
$container.Image -cne $Expected.image_id
|
|
) {
|
|
throw "$Label identity changed"
|
|
}
|
|
return [pscustomobject]@{
|
|
Id = [string]$container.Id
|
|
Image = [string]$container.Image
|
|
Running = [bool]$container.State.Running
|
|
}
|
|
}
|
|
|
|
function Assert-PreservedContainerSnapshot(
|
|
[object]$Expected,
|
|
[object]$Before,
|
|
[string]$Label
|
|
) {
|
|
$after = Get-PreservedContainerSnapshot $Expected $Label
|
|
if ($after.Running -ne $Before.Running) {
|
|
throw "$Label running state changed during isolated shadow"
|
|
}
|
|
return $after
|
|
}
|
|
|
|
$durableExpected = $descriptor.predecessor.durable_worker
|
|
$historicalTritonExpected = $descriptor.predecessor.triton
|
|
$durableBefore = Get-PreservedContainerSnapshot $durableExpected "Historical durable worker"
|
|
$historicalTritonBefore = Get-PreservedContainerSnapshot (
|
|
$historicalTritonExpected
|
|
) "Historical Triton"
|
|
$modelRepository = Resolve-DDirectory (
|
|
[string]$descriptor.container.model_repository_host_path
|
|
) "M4.7 model repository" $false
|
|
$output = Resolve-DDirectory $OutputRoot "M4.7 output root" $true
|
|
$freeBefore = Assert-FreeSpace "preflight"
|
|
|
|
$candidateName = [string]$descriptor.container.name
|
|
$tritonCandidateName = [string]$descriptor.container.triton_name
|
|
foreach ($name in @($candidateName, $tritonCandidateName)) {
|
|
if (& docker ps -a --format "{{.Names}}" --filter "name=^/$name$") {
|
|
throw "M4.7 isolated candidate $name already exists"
|
|
}
|
|
}
|
|
$scratch = Join-Path $output (".runtime-{0}" -f $descriptor.patch_id)
|
|
if (Test-Path -LiteralPath $scratch) {
|
|
throw "M4.7 runtime scratch already exists"
|
|
}
|
|
$null = New-Item -ItemType Directory -Path $scratch
|
|
$scratch = Resolve-DDirectory $scratch "M4.7 runtime scratch" $false
|
|
$runtimeIdentityPath = Join-Path $scratch "runtime-identity.json"
|
|
$dockerPayload = Convert-ToDockerPath $payload
|
|
$dockerOutput = Convert-ToDockerPath $output
|
|
$dockerScratch = Convert-ToDockerPath $scratch
|
|
$dockerModelRepository = Convert-ToDockerPath $modelRepository
|
|
$tritonArguments = @(
|
|
"create",
|
|
"--name", $tritonCandidateName,
|
|
"--read-only",
|
|
"--security-opt", "no-new-privileges:true",
|
|
"--cap-drop", "ALL",
|
|
"--pids-limit", "512",
|
|
"--shm-size", "1g",
|
|
"--gpus", "all",
|
|
"--tmpfs", "/tmp:rw,noexec,nosuid,size=2g",
|
|
"--health-cmd", "curl --fail --silent http://127.0.0.1:8000/v2/health/ready",
|
|
"--health-interval", "5s",
|
|
"--health-timeout", "3s",
|
|
"--health-start-period", "20s",
|
|
"--health-retries", "24",
|
|
"-v", ("{0}:/models:ro" -f $dockerModelRepository),
|
|
$imageRef,
|
|
"tritonserver",
|
|
"--model-repository=/models",
|
|
"--model-control-mode=explicit",
|
|
"--load-model=yolox_s",
|
|
"--disable-auto-complete-config",
|
|
"--strict-readiness=true",
|
|
"--exit-on-error=true",
|
|
"--allow-http=true",
|
|
"--allow-grpc=false",
|
|
"--allow-metrics=false"
|
|
)
|
|
|
|
$graphArguments = @(
|
|
"create",
|
|
"--name", $candidateName,
|
|
"--network", ("container:{0}" -f $tritonCandidateName),
|
|
"--read-only",
|
|
"--security-opt", "no-new-privileges:true",
|
|
"--cap-drop", "ALL",
|
|
"--pids-limit", "256",
|
|
"--tmpfs", "/tmp:rw,noexec,nosuid,size=2g",
|
|
"-e", "PYTHONDONTWRITEBYTECODE=1",
|
|
"-e", ("PYTHONPATH=/release/{0}:/opt/media:/opt/opencv:/opt/pillow" -f $descriptor.release.wheel.name),
|
|
"-v", ("{0}:/release:ro" -f $dockerPayload),
|
|
"-v", ("{0}:/output:rw" -f $dockerOutput),
|
|
"-v", ("{0}:/run/mission-core:ro" -f $dockerScratch)
|
|
)
|
|
foreach ($entry in $descriptor.inputs.PSObject.Properties) {
|
|
if ($null -ne $entry.Value.container_path) {
|
|
$graphArguments += @(
|
|
"-v", ("{0}:{1}:ro" -f (
|
|
Convert-ToDockerPath $entry.Value.host_path
|
|
), $entry.Value.container_path)
|
|
)
|
|
}
|
|
}
|
|
foreach ($dependency in $descriptor.dependencies) {
|
|
$graphArguments += @(
|
|
"-v", ("{0}:{1}:ro" -f (
|
|
Convert-ToDockerPath $dependency.host_path
|
|
), $dependency.container_path)
|
|
)
|
|
}
|
|
$graphArguments += @(
|
|
"--entrypoint", "python3",
|
|
$imageRef,
|
|
"-m", "k1link.perception.reference_graph_cli",
|
|
"--graph-config", "/release/m4-reference-graph-v2.json",
|
|
"--baseline-profile", "/release/m4-recorded-realtime-baseline-v1.json",
|
|
"--geometry-profile", "/release/m4-geometry-association-v1.json",
|
|
"--temporal-motion-profile", "/release/m4-temporal-motion-v1.json",
|
|
"--rolling-map-profile", "/release/m4-rolling-local-map-v1.json",
|
|
"--threat-profile", "/release/m4-replay-threat-v3.json",
|
|
"--camera-index", $descriptor.inputs.camera_index.container_path,
|
|
"--source-pack", $descriptor.inputs.source_pack.container_path,
|
|
"--local-surface", $descriptor.inputs.local_surface.container_path,
|
|
"--video", $descriptor.inputs.video.container_path,
|
|
"--valid-fov-mask", $descriptor.inputs.valid_fov_mask.container_path,
|
|
"--temporal-parity-frames", $descriptor.inputs.accepted_temporal_frames.container_path,
|
|
"--threat-parity-frames", $descriptor.inputs.accepted_threat_frames.container_path,
|
|
"--triton-origin", $descriptor.container.triton_origin,
|
|
"--mode", $descriptor.acceptance.run_mode,
|
|
"--expected-frames", ([string]$descriptor.acceptance.expected_frames),
|
|
"--runtime-identity", "/run/mission-core/runtime-identity.json",
|
|
"--output-root", "/output"
|
|
)
|
|
|
|
$tritonCreated = $false
|
|
$graphCreated = $false
|
|
$providerAccepted = $false
|
|
$graphAccepted = $false
|
|
$runFailure = $null
|
|
try {
|
|
$tritonCandidateId = (& docker @tritonArguments).Trim()
|
|
Assert-LastExitCode "M4.7 isolated Triton creation"
|
|
if ($tritonCandidateId -notmatch "^[a-f0-9]{64}$") {
|
|
throw "M4.7 isolated Triton id is invalid"
|
|
}
|
|
$tritonCreated = $true
|
|
& docker start $tritonCandidateName *> $null
|
|
Assert-LastExitCode "M4.7 isolated Triton start"
|
|
$tritonCandidate = $null
|
|
foreach ($attempt in 1..120) {
|
|
$tritonCandidate = Get-ContainerIdentity $tritonCandidateName
|
|
if (-not $tritonCandidate.State.Running) {
|
|
throw "M4.7 isolated Triton stopped before readiness"
|
|
}
|
|
if ($tritonCandidate.State.Health.Status -ceq "healthy") {
|
|
break
|
|
}
|
|
if ($attempt -eq 120) {
|
|
throw "M4.7 isolated Triton readiness timed out"
|
|
}
|
|
Start-Sleep -Seconds 2
|
|
}
|
|
if (
|
|
$tritonCandidate.Id -cne $tritonCandidateId -or
|
|
$tritonCandidate.Image -cne $descriptor.container.image_id -or
|
|
-not $tritonCandidate.HostConfig.ReadonlyRootfs -or
|
|
-not (Test-NoPublishedPorts $tritonCandidate)
|
|
) {
|
|
throw "M4.7 isolated Triton contract changed"
|
|
}
|
|
$providerAccepted = $true
|
|
|
|
if (-not $PreflightOnly) {
|
|
$candidateId = (& docker @graphArguments).Trim()
|
|
Assert-LastExitCode "M4.7 graph candidate creation"
|
|
if ($candidateId -notmatch "^[a-f0-9]{64}$") {
|
|
throw "M4.7 graph candidate id is invalid"
|
|
}
|
|
$graphCreated = $true
|
|
$candidate = Get-ContainerIdentity $candidateName
|
|
if (
|
|
$candidate.Id -cne $candidateId -or
|
|
$candidate.Image -cne $descriptor.container.image_id -or
|
|
$candidate.HostConfig.NetworkMode -cne ("container:{0}" -f $tritonCandidate.Id) -or
|
|
-not $candidate.HostConfig.ReadonlyRootfs -or
|
|
-not (Test-NoPublishedPorts $candidate)
|
|
) {
|
|
throw "M4.7 graph candidate isolation contract changed"
|
|
}
|
|
$runtimeIdentity = [ordered]@{
|
|
schema_version = "missioncore.reference-graph-runtime-identity/v3"
|
|
worker_id = "worker-006"
|
|
worker_node = $env:COMPUTERNAME
|
|
worker_container_id = $candidate.Id
|
|
worker_image_id = $candidate.Image
|
|
isolated_triton_container_id = $tritonCandidate.Id
|
|
isolated_triton_image_id = $tritonCandidate.Image
|
|
historical_worker_container_id = $durableBefore.Id
|
|
historical_worker_running = $durableBefore.Running
|
|
historical_triton_container_id = $historicalTritonBefore.Id
|
|
historical_triton_running = $historicalTritonBefore.Running
|
|
artifact_sha256 = $ExpectedArtifactSha256
|
|
patch_id = $descriptor.patch_id
|
|
code_revision = $descriptor.code_revision
|
|
graph_id = $descriptor.readiness.graph.graph_id
|
|
started_at_utc = [DateTime]::UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
|
|
source_mount_read_only = $true
|
|
isolated_model_service = $true
|
|
public_worker_port_added = $false
|
|
commands_enabled = $false
|
|
actuation_allowed = $false
|
|
}
|
|
Write-Utf8NoBom $runtimeIdentityPath ($runtimeIdentity | ConvertTo-Json -Depth 4)
|
|
& docker start --attach $candidateName
|
|
Assert-LastExitCode "M4.7 canonical graph isolated shadow"
|
|
$graphAccepted = $true
|
|
}
|
|
} catch {
|
|
$runFailure = $_
|
|
} finally {
|
|
if ($graphCreated) {
|
|
& docker rm --force $candidateName *> $null
|
|
if ($LASTEXITCODE -ne 0 -and $null -eq $runFailure) {
|
|
$runFailure = "M4.7 graph candidate cleanup failed"
|
|
}
|
|
}
|
|
if ($tritonCreated) {
|
|
& docker rm --force $tritonCandidateName *> $null
|
|
if ($LASTEXITCODE -ne 0 -and $null -eq $runFailure) {
|
|
$runFailure = "M4.7 isolated Triton cleanup failed"
|
|
}
|
|
}
|
|
Remove-Item -LiteralPath $scratch -Force -Recurse -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
$durableAfter = Assert-PreservedContainerSnapshot (
|
|
$durableExpected
|
|
) $durableBefore "Historical durable worker"
|
|
$historicalTritonAfter = Assert-PreservedContainerSnapshot (
|
|
$historicalTritonExpected
|
|
) $historicalTritonBefore "Historical Triton"
|
|
$freeAfter = Assert-FreeSpace "completed"
|
|
Write-Output ("PATCH_ID={0}" -f $descriptor.patch_id)
|
|
Write-Output ("ARTIFACT_SHA256={0}" -f $ExpectedArtifactSha256)
|
|
Write-Output ("DISK_FREE_BYTES_BEFORE={0}" -f $freeBefore)
|
|
Write-Output ("DISK_FREE_BYTES_AFTER={0}" -f $freeAfter)
|
|
Write-Output ("HISTORICAL_DURABLE_WORKER_RUNNING={0}" -f $durableAfter.Running)
|
|
Write-Output ("HISTORICAL_TRITON_RUNNING={0}" -f $historicalTritonAfter.Running)
|
|
Write-Output "DURABLE_WORKER_ACTION=none"
|
|
Write-Output "HISTORICAL_TRITON_ACTION=none"
|
|
Write-Output "ISOLATED_TRITON_ACTION=removed"
|
|
if ($null -ne $runFailure) {
|
|
throw $runFailure
|
|
}
|
|
if (-not $providerAccepted) {
|
|
throw "M4.7 provider readiness was not accepted"
|
|
}
|
|
Write-Output "PROVIDER_READINESS=accepted"
|
|
if ($PreflightOnly) {
|
|
Write-Output "GRAPH_READINESS=not-run"
|
|
Write-Output "PREFLIGHT=accepted"
|
|
} elseif ($graphAccepted) {
|
|
Write-Output "GRAPH_READINESS=accepted"
|
|
}
|