feat(worker): install combined observatory profiles
This commit is contained in:
@@ -0,0 +1,698 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[ValidatePattern("^[a-f0-9]{40}$")]
|
||||
[string]$SourceRevision,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[ValidatePattern("^[a-f0-9]{64}$")]
|
||||
[string]$ExpectedGitArchiveSha256,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[ValidatePattern("^[a-f0-9]{64}$")]
|
||||
[string]$ExpectedStagedSnapshotSha256
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ProgressPreference = "SilentlyContinue"
|
||||
|
||||
$RuntimeRoot = [IO.Path]::GetFullPath("D:\NDC_MISSIONCORE\runtime").TrimEnd("\")
|
||||
$StagedSnapshotRoot = Join-Path (
|
||||
$RuntimeRoot
|
||||
) "staging\observatory-worker-agent-$SourceRevision"
|
||||
$ContextManifestRelativePath = (
|
||||
"experiments/perception/worker/observatory_portable/" +
|
||||
"worker-006-agent-build-context.json"
|
||||
)
|
||||
$DockerfileRelativePath = (
|
||||
"experiments/perception/worker/observatory_portable/" +
|
||||
"Dockerfile.worker-006-agent"
|
||||
)
|
||||
$SourceTreeRelativePath = "src/k1link"
|
||||
$BaseImageSha256 = (
|
||||
"58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794"
|
||||
)
|
||||
$BaseImageReference = "sha256:$BaseImageSha256"
|
||||
$BuildMethod = "docker-commit-exact-layer-v1"
|
||||
$MaximumLayerBytes = [int64](32MB)
|
||||
$ImageWorkdir = "/opt/nodedc/mission-core"
|
||||
$ImageEntrypoint = '["python3","-m","k1link.observatory.m49_worker_container_main"]'
|
||||
$ImageCommand = '[]'
|
||||
$FixedEnvironment = @(
|
||||
"PYTHONPATH=/opt/nodedc/mission-core/src",
|
||||
"PYTHONNOUSERSITE=1",
|
||||
"PYTHONDONTWRITEBYTECODE=1",
|
||||
"PYTHONUNBUFFERED=1"
|
||||
)
|
||||
|
||||
function Get-FileSha256 {
|
||||
param([string]$Path)
|
||||
|
||||
$item = Get-Item -LiteralPath $Path -Force
|
||||
if (
|
||||
-not ($item -is [IO.FileInfo]) -or
|
||||
($item.Attributes -band [IO.FileAttributes]::ReparsePoint)
|
||||
) {
|
||||
throw "Worker 006 staged source is not a regular file"
|
||||
}
|
||||
return (Get-FileHash -LiteralPath $item.FullName -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
}
|
||||
|
||||
function Assert-ExactDirectoryChildren {
|
||||
param(
|
||||
[string]$Path,
|
||||
[string[]]$ExpectedNames
|
||||
)
|
||||
|
||||
$directory = Get-Item -LiteralPath $Path -Force
|
||||
if (
|
||||
-not ($directory -is [IO.DirectoryInfo]) -or
|
||||
($directory.Attributes -band [IO.FileAttributes]::ReparsePoint)
|
||||
) {
|
||||
throw "Worker 006 staged snapshot directory is invalid"
|
||||
}
|
||||
$actualNames = @(
|
||||
Get-ChildItem -LiteralPath $directory.FullName -Force |
|
||||
ForEach-Object { $_.Name }
|
||||
)
|
||||
[Array]::Sort($actualNames, [StringComparer]::Ordinal)
|
||||
$expected = @($ExpectedNames)
|
||||
[Array]::Sort($expected, [StringComparer]::Ordinal)
|
||||
if (
|
||||
$actualNames.Count -ne $expected.Count -or
|
||||
([string]::Join("`n", $actualNames) -cne [string]::Join("`n", $expected))
|
||||
) {
|
||||
throw "Worker 006 staged snapshot contains unexpected entries"
|
||||
}
|
||||
}
|
||||
|
||||
function Get-StagedSnapshotInspection {
|
||||
param([string]$Root)
|
||||
|
||||
Assert-ExactDirectoryChildren $Root @("experiments", "src")
|
||||
$srcRoot = Join-Path $Root "src"
|
||||
Assert-ExactDirectoryChildren $srcRoot @("k1link")
|
||||
$experimentsRoot = Join-Path $Root "experiments"
|
||||
Assert-ExactDirectoryChildren $experimentsRoot @("perception")
|
||||
$perceptionRoot = Join-Path $experimentsRoot "perception"
|
||||
Assert-ExactDirectoryChildren $perceptionRoot @("worker")
|
||||
$workerRoot = Join-Path $perceptionRoot "worker"
|
||||
Assert-ExactDirectoryChildren $workerRoot @("observatory_portable")
|
||||
$portableRoot = Join-Path $workerRoot "observatory_portable"
|
||||
Assert-ExactDirectoryChildren $portableRoot @(
|
||||
"Dockerfile.worker-006-agent",
|
||||
"worker-006-agent-build-context.json"
|
||||
)
|
||||
|
||||
$sourceRoot = Join-Path $Root ($SourceTreeRelativePath.Replace("/", "\"))
|
||||
$sourceItems = @(Get-ChildItem -LiteralPath $sourceRoot -Recurse -Force)
|
||||
foreach ($item in $sourceItems) {
|
||||
if ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) {
|
||||
throw "Worker 006 staged snapshot contains a reparse point"
|
||||
}
|
||||
if (-not ($item -is [IO.FileInfo]) -and -not ($item -is [IO.DirectoryInfo])) {
|
||||
throw "Worker 006 staged snapshot contains an unsupported entry"
|
||||
}
|
||||
}
|
||||
|
||||
$manifestPath = Join-Path $Root ($ContextManifestRelativePath.Replace("/", "\"))
|
||||
$dockerfilePath = Join-Path $Root ($DockerfileRelativePath.Replace("/", "\"))
|
||||
$requiredSourceFiles = @(
|
||||
Join-Path $sourceRoot "__init__.py",
|
||||
Join-Path $sourceRoot "observatory\m49_worker_container_main.py",
|
||||
Join-Path $sourceRoot "observatory\m49_worker_service.py"
|
||||
)
|
||||
foreach ($requiredPath in $requiredSourceFiles + @($manifestPath, $dockerfilePath)) {
|
||||
if (-not (Test-Path -LiteralPath $requiredPath -PathType Leaf)) {
|
||||
throw "Worker 006 staged snapshot is incomplete"
|
||||
}
|
||||
[void](Get-FileSha256 $requiredPath)
|
||||
}
|
||||
|
||||
$context = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json
|
||||
if (
|
||||
[string]$context.schema_version -cne
|
||||
"missioncore.observatory-worker-agent-build-context/v1" -or
|
||||
[string]$context.worker_id -cne "worker-006" -or
|
||||
[string]$context.base_image.sha256 -cne $BaseImageSha256 -or
|
||||
[bool]$context.base_image.pull_allowed
|
||||
) {
|
||||
throw "Worker 006 staged build-context contract is invalid"
|
||||
}
|
||||
$declaredPaths = @($context.context_entries | ForEach-Object { [string]$_.path })
|
||||
$expectedDeclaredPaths = @(
|
||||
$SourceTreeRelativePath,
|
||||
$DockerfileRelativePath,
|
||||
$ContextManifestRelativePath
|
||||
)
|
||||
if (
|
||||
$declaredPaths.Count -ne $expectedDeclaredPaths.Count -or
|
||||
([string]::Join("`n", $declaredPaths) -cne
|
||||
[string]::Join("`n", $expectedDeclaredPaths))
|
||||
) {
|
||||
throw "Worker 006 staged build-context entries changed"
|
||||
}
|
||||
|
||||
$files = New-Object "Collections.Generic.List[object]"
|
||||
foreach ($item in $sourceItems) {
|
||||
if ($item -is [IO.FileInfo]) {
|
||||
$files.Add($item)
|
||||
}
|
||||
}
|
||||
$files.Add((Get-Item -LiteralPath $dockerfilePath -Force))
|
||||
$files.Add((Get-Item -LiteralPath $manifestPath -Force))
|
||||
|
||||
$rows = New-Object "Collections.Generic.List[object]"
|
||||
foreach ($file in $files) {
|
||||
$relative = $file.FullName.Substring($Root.Length).TrimStart("\")
|
||||
$relative = $relative.Replace("\", "/")
|
||||
if (-not $relative -or $relative -match "[\x00\r\n]") {
|
||||
throw "Worker 006 staged snapshot path is invalid"
|
||||
}
|
||||
$rows.Add([pscustomobject]@{
|
||||
path = $relative
|
||||
byte_length = [int64]$file.Length
|
||||
sha256 = Get-FileSha256 $file.FullName
|
||||
})
|
||||
}
|
||||
$orderedRows = @($rows | Sort-Object -Property path -CaseSensitive)
|
||||
$canonical = New-Object Text.StringBuilder
|
||||
[int64]$totalBytes = 0
|
||||
foreach ($row in $orderedRows) {
|
||||
[void]$canonical.Append([string]$row.path)
|
||||
[void]$canonical.Append([char]0)
|
||||
[void]$canonical.Append(([int64]$row.byte_length).ToString([Globalization.CultureInfo]::InvariantCulture))
|
||||
[void]$canonical.Append([char]0)
|
||||
[void]$canonical.Append([string]$row.sha256)
|
||||
[void]$canonical.Append("`n")
|
||||
$totalBytes += [int64]$row.byte_length
|
||||
}
|
||||
$utf8 = New-Object Text.UTF8Encoding($false)
|
||||
$canonicalBytes = $utf8.GetBytes($canonical.ToString())
|
||||
$hasher = [Security.Cryptography.SHA256]::Create()
|
||||
try {
|
||||
$digestBytes = $hasher.ComputeHash($canonicalBytes)
|
||||
}
|
||||
finally {
|
||||
$hasher.Dispose()
|
||||
}
|
||||
$digest = ([BitConverter]::ToString($digestBytes)).Replace("-", "").ToLowerInvariant()
|
||||
return [pscustomobject]@{
|
||||
sha256 = $digest
|
||||
file_count = [int64]$orderedRows.Count
|
||||
byte_length = $totalBytes
|
||||
canonicalization = "utf8-path-nul-length-nul-sha256-lf-v1"
|
||||
files = @($orderedRows)
|
||||
}
|
||||
}
|
||||
|
||||
function New-StagedSnapshotManifest {
|
||||
param([object]$SnapshotInspection)
|
||||
|
||||
$stagingRoot = Split-Path -Parent $StagedSnapshotRoot
|
||||
$manifestPath = Join-Path (
|
||||
$stagingRoot
|
||||
) ("observatory-worker-agent-embedded-manifest-{0}.json" -f [Guid]::NewGuid().ToString("N"))
|
||||
$payload = [ordered]@{
|
||||
schema_version = "missioncore.observatory-worker-agent-embedded-snapshot/v1"
|
||||
snapshot_sha256 = [string]$SnapshotInspection.sha256
|
||||
canonicalization = [string]$SnapshotInspection.canonicalization
|
||||
file_count = [int64]$SnapshotInspection.file_count
|
||||
byte_length = [int64]$SnapshotInspection.byte_length
|
||||
files = @($SnapshotInspection.files)
|
||||
}
|
||||
$json = $payload | ConvertTo-Json -Compress -Depth 5
|
||||
$utf8 = New-Object Text.UTF8Encoding($false)
|
||||
try {
|
||||
[IO.File]::WriteAllText($manifestPath, $json, $utf8)
|
||||
return [pscustomobject]@{
|
||||
path = $manifestPath
|
||||
sha256 = Get-FileSha256 $manifestPath
|
||||
byte_length = [int64](Get-Item -LiteralPath $manifestPath -Force).Length
|
||||
}
|
||||
}
|
||||
catch {
|
||||
if (Test-Path -LiteralPath $manifestPath -PathType Leaf) {
|
||||
Remove-Item -LiteralPath $manifestPath -Force
|
||||
}
|
||||
throw
|
||||
}
|
||||
}
|
||||
|
||||
function Get-ImageInspection {
|
||||
param([string]$Reference)
|
||||
|
||||
$payload = docker image inspect $Reference
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Worker 006 image is unavailable: $Reference"
|
||||
}
|
||||
$rows = @($payload | ConvertFrom-Json)
|
||||
if ($rows.Count -ne 1 -or [string]$rows[0].Id -notmatch "^sha256:[a-f0-9]{64}$") {
|
||||
throw "Worker 006 image inspection is invalid"
|
||||
}
|
||||
return $rows[0]
|
||||
}
|
||||
|
||||
function Assert-ImageContract {
|
||||
param(
|
||||
[object]$Image,
|
||||
[object]$BaseImage,
|
||||
[string]$EmbeddedManifestSha256
|
||||
)
|
||||
|
||||
$labels = $Image.Config.Labels
|
||||
$expectedLabels = [ordered]@{
|
||||
"org.opencontainers.image.title" = "NODE.DC Observatory Worker 006 agent"
|
||||
"org.opencontainers.image.source" = "NODEDC_MISSION_CORE"
|
||||
"org.opencontainers.image.revision" = $SourceRevision
|
||||
"com.nodedc.product" = "mission-core"
|
||||
"com.nodedc.stack" = "ndc-mission-core-observatory"
|
||||
"com.nodedc.role" = "observatory-worker-agent"
|
||||
"com.nodedc.managed-by" = "mission-core-worker-release"
|
||||
"com.nodedc.worker-contour" = "worker-006"
|
||||
"com.nodedc.authority" = "observation-only"
|
||||
"com.nodedc.models" = "external"
|
||||
"com.nodedc.runtime-registries" = "external-read-only"
|
||||
"com.nodedc.base-image.sha256" = $BaseImageSha256
|
||||
"com.nodedc.build-context.sha256" = $ExpectedGitArchiveSha256
|
||||
"com.nodedc.staged-snapshot.sha256" = $ExpectedStagedSnapshotSha256
|
||||
"com.nodedc.embedded-snapshot-manifest.sha256" = $EmbeddedManifestSha256
|
||||
"com.nodedc.build-method" = $BuildMethod
|
||||
}
|
||||
foreach ($key in $expectedLabels.Keys) {
|
||||
if ([string]$labels.$key -cne [string]$expectedLabels[$key]) {
|
||||
throw "Worker 006 installed image label identity changed: $key"
|
||||
}
|
||||
}
|
||||
if ([string]$Image.Config.WorkingDir -cne $ImageWorkdir) {
|
||||
throw "Worker 006 installed image workdir changed"
|
||||
}
|
||||
if ([string]::Join([char]0, @($Image.Config.Entrypoint)) -cne
|
||||
[string]::Join([char]0, @("python3", "-m", "k1link.observatory.m49_worker_container_main"))) {
|
||||
throw "Worker 006 installed image entrypoint changed"
|
||||
}
|
||||
if (@($Image.Config.Cmd).Count -ne 0) {
|
||||
throw "Worker 006 installed image command changed"
|
||||
}
|
||||
if ([string]$Image.Config.User -cne "0:0") {
|
||||
throw "Worker 006 installed image user changed"
|
||||
}
|
||||
$actualEnvironment = @($Image.Config.Env)
|
||||
foreach ($value in $FixedEnvironment) {
|
||||
if ($actualEnvironment -cnotcontains $value) {
|
||||
throw "Worker 006 installed image environment changed"
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
[string]$BaseImage.RootFS.Type -cne "layers" -or
|
||||
[string]$Image.RootFS.Type -cne "layers"
|
||||
) {
|
||||
throw "Worker 006 image RootFS type changed"
|
||||
}
|
||||
$baseLayers = @($BaseImage.RootFS.Layers)
|
||||
$imageLayers = @($Image.RootFS.Layers)
|
||||
if ($baseLayers.Count -eq 0 -or $imageLayers.Count -ne ($baseLayers.Count + 1)) {
|
||||
throw "Worker 006 image is not exactly one layer above the pinned base"
|
||||
}
|
||||
for ($index = 0; $index -lt $baseLayers.Count; $index += 1) {
|
||||
if ([string]$imageLayers[$index] -cne [string]$baseLayers[$index]) {
|
||||
throw "Worker 006 image RootFS does not extend the pinned base layer chain"
|
||||
}
|
||||
}
|
||||
$derivedLayerDiffId = [string]$imageLayers[$baseLayers.Count]
|
||||
if ($derivedLayerDiffId -notmatch "^sha256:[a-f0-9]{64}$") {
|
||||
throw "Worker 006 derived layer identity is invalid"
|
||||
}
|
||||
|
||||
$layerBytes = [int64]$Image.Size - [int64]$BaseImage.Size
|
||||
if ($layerBytes -lt 0 -or $layerBytes -gt $MaximumLayerBytes) {
|
||||
throw "Worker 006 image is not within the thin-layer bound"
|
||||
}
|
||||
return [pscustomobject]@{
|
||||
layer_bytes = $layerBytes
|
||||
base_layer_count = [int64]$baseLayers.Count
|
||||
derived_layer_count = [int64]1
|
||||
derived_layer_diff_id = $derivedLayerDiffId
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-InstalledImageSmoke {
|
||||
param(
|
||||
[string]$Tag,
|
||||
[string]$SnapshotRoot,
|
||||
[object]$EmbeddedManifest
|
||||
)
|
||||
|
||||
$smokeScript = (
|
||||
"set -eu; " +
|
||||
"test ! -e /opt/nodedc/mission-core/experiments; " +
|
||||
"test -z `"`$(find /opt/nodedc/mission-core -perm /022 -print -quit)`"; " +
|
||||
"test -z `"`$(find /opt/nodedc/mission-core/src/k1link " +
|
||||
"! -type d ! -type f -print -quit)`"; " +
|
||||
"test -z `"`$(find /nodedc-verify-source/src/k1link " +
|
||||
"! -type d ! -type f -print -quit)`"; " +
|
||||
"cmp -s /nodedc-verify-snapshot.json " +
|
||||
"/opt/nodedc/mission-core/release/worker-006-agent-staged-snapshot.json; " +
|
||||
"test `"`$(sha256sum " +
|
||||
"/opt/nodedc/mission-core/release/worker-006-agent-staged-snapshot.json " +
|
||||
"| cut -d' ' -f1)`" = $($EmbeddedManifest.sha256); " +
|
||||
"cmp -s /nodedc-verify-source/$ContextManifestRelativePath " +
|
||||
"/opt/nodedc/mission-core/release/worker-006-agent-build-context.json; " +
|
||||
"find /nodedc-verify-source/src/k1link -type d -exec sh -c '" +
|
||||
"for source_path do relative=`${source_path#/nodedc-verify-source/}; " +
|
||||
"test -d `"/opt/nodedc/mission-core/`$relative`" || exit 70; " +
|
||||
"done' sh {} +; " +
|
||||
"find /opt/nodedc/mission-core/src/k1link -type d -exec sh -c '" +
|
||||
"for image_path do relative=`${image_path#/opt/nodedc/mission-core/}; " +
|
||||
"test -d `"/nodedc-verify-source/`$relative`" || exit 71; " +
|
||||
"done' sh {} +; " +
|
||||
"find /nodedc-verify-source/src/k1link -type f -exec sh -c '" +
|
||||
"for source_path do relative=`${source_path#/nodedc-verify-source/}; " +
|
||||
"cmp -s `"`$source_path`" `"/opt/nodedc/mission-core/`$relative`" || exit 72; " +
|
||||
"done' sh {} +; " +
|
||||
"find /opt/nodedc/mission-core/src/k1link -type f -exec sh -c '" +
|
||||
"for image_path do relative=`${image_path#/opt/nodedc/mission-core/}; " +
|
||||
"cmp -s `"`$image_path`" `"/nodedc-verify-source/`$relative`" || exit 73; " +
|
||||
"done' sh {} +; " +
|
||||
"cd /opt/nodedc/mission-core; " +
|
||||
"python3 -B -c 'import k1link.observatory.m49_worker_container_main as entrypoint; " +
|
||||
"import k1link.observatory.m49_worker_service as composition; " +
|
||||
"assert callable(entrypoint.main); " +
|
||||
"assert callable(composition.compose_installed_m49_worker_service)'"
|
||||
)
|
||||
$sourceMount = (
|
||||
"type=bind,source=$SnapshotRoot," +
|
||||
"target=/nodedc-verify-source,readonly"
|
||||
)
|
||||
$manifestMount = (
|
||||
"type=bind,source=$($EmbeddedManifest.path)," +
|
||||
"target=/nodedc-verify-snapshot.json,readonly"
|
||||
)
|
||||
docker run `
|
||||
--rm `
|
||||
--network none `
|
||||
--read-only `
|
||||
--tmpfs "/tmp:rw,noexec,nosuid,size=16m" `
|
||||
--cap-drop ALL `
|
||||
--security-opt no-new-privileges `
|
||||
--mount $sourceMount `
|
||||
--mount $manifestMount `
|
||||
--entrypoint /bin/sh `
|
||||
$Tag `
|
||||
-c $smokeScript
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Installed Worker 006 image smoke failed"
|
||||
}
|
||||
}
|
||||
|
||||
function New-OutputContract {
|
||||
param(
|
||||
[string]$Status,
|
||||
[string]$Tag,
|
||||
[object]$Image,
|
||||
[object]$LayerInspection,
|
||||
[object]$SnapshotInspection,
|
||||
[object]$EmbeddedManifest
|
||||
)
|
||||
|
||||
return [ordered]@{
|
||||
schema_version = "missioncore.observatory-worker-agent-image-installation/v1"
|
||||
status = $Status
|
||||
worker_id = "worker-006"
|
||||
build_method = $BuildMethod
|
||||
source_revision = $SourceRevision
|
||||
provenance = [ordered]@{
|
||||
git_archive_sha256 = $ExpectedGitArchiveSha256
|
||||
git_archive_verification = "external-before-extract"
|
||||
staged_snapshot_sha256 = [string]$SnapshotInspection.sha256
|
||||
staged_snapshot_file_count = [int64]$SnapshotInspection.file_count
|
||||
staged_snapshot_byte_length = [int64]$SnapshotInspection.byte_length
|
||||
staged_snapshot_canonicalization = [string]$SnapshotInspection.canonicalization
|
||||
embedded_snapshot_manifest_sha256 = [string]$EmbeddedManifest.sha256
|
||||
embedded_snapshot_manifest_byte_length = [int64]$EmbeddedManifest.byte_length
|
||||
}
|
||||
base_image_sha256 = $BaseImageSha256
|
||||
derived_image_sha256 = ([string]$Image.Id).Substring(7)
|
||||
image = [ordered]@{
|
||||
tag = $Tag
|
||||
id = [string]$Image.Id
|
||||
size_bytes = [int64]$Image.Size
|
||||
thin_layer_bytes = [int64]$LayerInspection.layer_bytes
|
||||
maximum_thin_layer_bytes = $MaximumLayerBytes
|
||||
rootfs = [ordered]@{
|
||||
base_layer_count = [int64]$LayerInspection.base_layer_count
|
||||
derived_layer_count = [int64]$LayerInspection.derived_layer_count
|
||||
derived_layer_diff_id = [string]$LayerInspection.derived_layer_diff_id
|
||||
pinned_base_is_exact_prefix = $true
|
||||
}
|
||||
}
|
||||
runtime_contract = [ordered]@{
|
||||
workdir = $ImageWorkdir
|
||||
entrypoint = @("python3", "-m", "k1link.observatory.m49_worker_container_main")
|
||||
command = @()
|
||||
authority = "observation-only"
|
||||
models = "external"
|
||||
runtime_registries = "external-read-only"
|
||||
}
|
||||
smoke = [ordered]@{
|
||||
network = "none"
|
||||
read_only_rootfs = $true
|
||||
staged_source_bytes = "matched"
|
||||
embedded_context_bytes = "matched"
|
||||
embedded_snapshot_manifest = "matched"
|
||||
result = "passed"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $StagedSnapshotRoot -PathType Container)) {
|
||||
throw "Worker 006 staged snapshot root is unavailable"
|
||||
}
|
||||
$snapshotBefore = Get-StagedSnapshotInspection $StagedSnapshotRoot
|
||||
if ([string]$snapshotBefore.sha256 -cne $ExpectedStagedSnapshotSha256) {
|
||||
throw "Worker 006 staged snapshot identity changed"
|
||||
}
|
||||
$embeddedManifest = New-StagedSnapshotManifest $snapshotBefore
|
||||
try {
|
||||
$base = Get-ImageInspection $BaseImageReference
|
||||
if ([string]$base.Id -cne $BaseImageReference) {
|
||||
throw "Worker 006 base image identity changed"
|
||||
}
|
||||
|
||||
$shortRevision = $SourceRevision.Substring(0, 12)
|
||||
$tag = "ndc/mission-core-observatory-worker-agent:$shortRevision"
|
||||
$existingIds = @(
|
||||
docker image ls `
|
||||
--quiet `
|
||||
--no-trunc `
|
||||
--filter "reference=$tag"
|
||||
)
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Worker 006 image lookup failed"
|
||||
}
|
||||
if ($existingIds.Count -gt 1) {
|
||||
throw "Worker 006 image tag resolves ambiguously"
|
||||
}
|
||||
if ($existingIds.Count -eq 1) {
|
||||
$image = Get-ImageInspection $tag
|
||||
if ([string]$image.Id -cne [string]$existingIds[0]) {
|
||||
throw "Worker 006 existing image lookup changed"
|
||||
}
|
||||
$layerInspection = Assert-ImageContract `
|
||||
$image `
|
||||
$base `
|
||||
([string]$embeddedManifest.sha256)
|
||||
Invoke-InstalledImageSmoke $tag $StagedSnapshotRoot $embeddedManifest
|
||||
New-OutputContract `
|
||||
"already-installed" `
|
||||
$tag `
|
||||
$image `
|
||||
$layerInspection `
|
||||
$snapshotBefore `
|
||||
$embeddedManifest |
|
||||
ConvertTo-Json -Compress -Depth 9
|
||||
return
|
||||
}
|
||||
|
||||
$containerName = "ndc-observatory-worker-agent-image-build-$shortRevision"
|
||||
$occupiedContainerIds = @(
|
||||
docker ps -a --filter "name=^/$containerName$" --format "{{.ID}}"
|
||||
)
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Worker 006 temporary build container lookup failed"
|
||||
}
|
||||
if ($occupiedContainerIds.Count -ne 0) {
|
||||
throw "Worker 006 temporary build container name is already occupied"
|
||||
}
|
||||
|
||||
$copyScript = (
|
||||
"set -eu; " +
|
||||
"test ! -e /opt/nodedc/mission-core; " +
|
||||
"mkdir -p /opt/nodedc/mission-core/src /opt/nodedc/mission-core/release; " +
|
||||
"mkdir -p /run/nodedc/registries; " +
|
||||
"cp -a /nodedc-build-source/src/k1link /opt/nodedc/mission-core/src/k1link; " +
|
||||
"cp /nodedc-build-source/$ContextManifestRelativePath " +
|
||||
"/opt/nodedc/mission-core/release/worker-006-agent-build-context.json; " +
|
||||
"cp /nodedc-build-snapshot.json " +
|
||||
"/opt/nodedc/mission-core/release/worker-006-agent-staged-snapshot.json; " +
|
||||
"find /nodedc-build-source/src/k1link -type f -exec sh -c '" +
|
||||
"for source_path do relative=`${source_path#/nodedc-build-source/}; " +
|
||||
"cmp -s `"`$source_path`" `"/opt/nodedc/mission-core/`$relative`" || exit 70; " +
|
||||
"done' sh {} +; " +
|
||||
"cmp -s /nodedc-build-source/$ContextManifestRelativePath " +
|
||||
"/opt/nodedc/mission-core/release/worker-006-agent-build-context.json; " +
|
||||
"cmp -s /nodedc-build-snapshot.json " +
|
||||
"/opt/nodedc/mission-core/release/worker-006-agent-staged-snapshot.json; " +
|
||||
"test ! -e /opt/nodedc/mission-core/experiments; " +
|
||||
"find /opt/nodedc/mission-core -type d -exec chmod 0555 {} +; " +
|
||||
"find /opt/nodedc/mission-core -type f -exec chmod 0444 {} +; " +
|
||||
"chmod 0555 /run/nodedc /run/nodedc/registries; " +
|
||||
"cd /opt/nodedc/mission-core; " +
|
||||
"PYTHONPATH=/opt/nodedc/mission-core/src PYTHONNOUSERSITE=1 " +
|
||||
"PYTHONDONTWRITEBYTECODE=1 python3 -B -c '" +
|
||||
"import k1link.observatory.m49_worker_container_main as entrypoint; " +
|
||||
"import k1link.observatory.m49_worker_service as composition; " +
|
||||
"assert callable(entrypoint.main); " +
|
||||
"assert callable(composition.compose_installed_m49_worker_service)'"
|
||||
)
|
||||
$sourceMount = (
|
||||
"type=bind,source=$StagedSnapshotRoot," +
|
||||
"target=/nodedc-build-source,readonly"
|
||||
)
|
||||
$manifestMount = (
|
||||
"type=bind,source=$($embeddedManifest.path)," +
|
||||
"target=/nodedc-build-snapshot.json,readonly"
|
||||
)
|
||||
$containerId = $null
|
||||
$imageCommitted = $false
|
||||
$committedImageId = $null
|
||||
try {
|
||||
try {
|
||||
$createOutput = @(
|
||||
docker create `
|
||||
--name $containerName `
|
||||
--network none `
|
||||
--cap-drop ALL `
|
||||
--security-opt no-new-privileges `
|
||||
--mount $sourceMount `
|
||||
--mount $manifestMount `
|
||||
--entrypoint /bin/sh `
|
||||
$BaseImageReference `
|
||||
-c $copyScript
|
||||
)
|
||||
$createdIds = @(
|
||||
$createOutput | Where-Object { $_ -match "^[a-f0-9]{64}$" }
|
||||
)
|
||||
if ($createdIds.Count -eq 1) {
|
||||
$containerId = [string]$createdIds[0]
|
||||
}
|
||||
if ($LASTEXITCODE -ne 0 -or $null -eq $containerId) {
|
||||
throw "Worker 006 temporary build container creation failed"
|
||||
}
|
||||
docker start --attach $containerId
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Worker 006 source installation command failed"
|
||||
}
|
||||
$snapshotAfter = Get-StagedSnapshotInspection $StagedSnapshotRoot
|
||||
if (
|
||||
[string]$snapshotAfter.sha256 -cne $ExpectedStagedSnapshotSha256 -or
|
||||
[int64]$snapshotAfter.file_count -ne [int64]$snapshotBefore.file_count -or
|
||||
[int64]$snapshotAfter.byte_length -ne [int64]$snapshotBefore.byte_length
|
||||
) {
|
||||
throw "Worker 006 staged snapshot changed during installation"
|
||||
}
|
||||
if ((Get-FileSha256 $embeddedManifest.path) -cne [string]$embeddedManifest.sha256) {
|
||||
throw "Worker 006 embedded snapshot manifest changed during installation"
|
||||
}
|
||||
|
||||
$changes = @(
|
||||
"--change", "WORKDIR $ImageWorkdir",
|
||||
"--change", "USER 0:0",
|
||||
"--change", "ENTRYPOINT $ImageEntrypoint",
|
||||
"--change", "CMD $ImageCommand",
|
||||
"--change", 'LABEL org.opencontainers.image.title="NODE.DC Observatory Worker 006 agent"',
|
||||
"--change", "LABEL org.opencontainers.image.source=NODEDC_MISSION_CORE",
|
||||
"--change", "LABEL org.opencontainers.image.revision=$SourceRevision",
|
||||
"--change", "LABEL com.nodedc.product=mission-core",
|
||||
"--change", "LABEL com.nodedc.stack=ndc-mission-core-observatory",
|
||||
"--change", "LABEL com.nodedc.role=observatory-worker-agent",
|
||||
"--change", "LABEL com.nodedc.managed-by=mission-core-worker-release",
|
||||
"--change", "LABEL com.nodedc.worker-contour=worker-006",
|
||||
"--change", "LABEL com.nodedc.authority=observation-only",
|
||||
"--change", "LABEL com.nodedc.models=external",
|
||||
"--change", "LABEL com.nodedc.runtime-registries=external-read-only",
|
||||
"--change", "LABEL com.nodedc.base-image.sha256=$BaseImageSha256",
|
||||
"--change", "LABEL com.nodedc.build-context.sha256=$ExpectedGitArchiveSha256",
|
||||
"--change", "LABEL com.nodedc.staged-snapshot.sha256=$ExpectedStagedSnapshotSha256",
|
||||
"--change", (
|
||||
"LABEL com.nodedc.embedded-snapshot-manifest.sha256=" +
|
||||
[string]$embeddedManifest.sha256
|
||||
),
|
||||
"--change", "LABEL com.nodedc.build-method=$BuildMethod"
|
||||
)
|
||||
foreach ($value in $FixedEnvironment) {
|
||||
$changes += @("--change", "ENV $value")
|
||||
}
|
||||
$commitOutput = docker commit --pause=true @changes $containerId $tag
|
||||
$committedIds = @(
|
||||
$commitOutput |
|
||||
ForEach-Object { ([string]$_).Trim() } |
|
||||
Where-Object { $_ -match "^sha256:[a-f0-9]{64}$" }
|
||||
)
|
||||
if ($LASTEXITCODE -ne 0 -or $committedIds.Count -ne 1) {
|
||||
throw "Worker 006 image commit failed"
|
||||
}
|
||||
$committedImageId = [string]$committedIds[0]
|
||||
$imageCommitted = $true
|
||||
}
|
||||
finally {
|
||||
if ($null -ne $containerId -and $containerId -match "^[a-f0-9]{64}$") {
|
||||
docker rm -f $containerId | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Worker 006 temporary build container cleanup failed"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$image = Get-ImageInspection $tag
|
||||
if ([string]$image.Id -cne $committedImageId) {
|
||||
throw "Worker 006 committed image tag identity changed"
|
||||
}
|
||||
$layerInspection = Assert-ImageContract `
|
||||
$image `
|
||||
$base `
|
||||
([string]$embeddedManifest.sha256)
|
||||
Invoke-InstalledImageSmoke $tag $StagedSnapshotRoot $embeddedManifest
|
||||
New-OutputContract `
|
||||
"installed" `
|
||||
$tag `
|
||||
$image `
|
||||
$layerInspection `
|
||||
$snapshotBefore `
|
||||
$embeddedManifest |
|
||||
ConvertTo-Json -Compress -Depth 9
|
||||
}
|
||||
catch {
|
||||
$installationFailure = $_
|
||||
if ($imageCommitted -and $committedImageId -match "^sha256:[a-f0-9]{64}$") {
|
||||
docker image rm --force $committedImageId | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw (
|
||||
"Worker 006 image verification failed and committed image cleanup failed: " +
|
||||
[string]$installationFailure
|
||||
)
|
||||
}
|
||||
}
|
||||
throw $installationFailure
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (
|
||||
$null -ne $embeddedManifest -and
|
||||
(Test-Path -LiteralPath $embeddedManifest.path -PathType Leaf)
|
||||
) {
|
||||
Remove-Item -LiteralPath $embeddedManifest.path -Force
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -149,10 +149,10 @@
|
||||
},
|
||||
{
|
||||
"asset_id": "lab-v1-portable-worker",
|
||||
"byte_length": 47008,
|
||||
"byte_length": 47392,
|
||||
"kind": "repository-file",
|
||||
"repository_path": "src/k1link/observatory/portable_lab_v1_worker.py",
|
||||
"sha256": "0a954b5d4d2f3cdd588d58ba4234141c32992b4b46380a03b621060c25c4ac23"
|
||||
"sha256": "56aa40390b8413fdf7f7293329e89420305914abd478821c08bfe8b0a2ec3df8"
|
||||
},
|
||||
{
|
||||
"asset_id": "portable-result-contracts",
|
||||
@@ -189,7 +189,7 @@
|
||||
"navigation_or_safety_accepted": false,
|
||||
"production_accepted": false
|
||||
},
|
||||
"candidate_sha256": "546c74fb88b9571996cecc41fa915044e3907365b8891b7fceb297fb74a837c7",
|
||||
"candidate_sha256": "9d40369e393aea4b1a09a5784336442a082b1b6126afaac379f1ecece39b54f9",
|
||||
"declared_blockers": [
|
||||
"combined-executor-entrypoint-uninstalled",
|
||||
"combined-executor-image-unsealed",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+93
-27
@@ -6,17 +6,31 @@
|
||||
"build": {
|
||||
"source_revision": null,
|
||||
"source_revision_requirement": "full clean committed Git revision",
|
||||
"source_date_epoch": null,
|
||||
"build_context_sha256": null,
|
||||
"git_archive_sha256": null,
|
||||
"staged_snapshot_sha256": null,
|
||||
"installer": "experiments/perception/worker/observatory_portable/Install-Worker006AgentImage.ps1",
|
||||
"context_manifest": "experiments/perception/worker/observatory_portable/worker-006-agent-build-context.json",
|
||||
"dockerfile": "experiments/perception/worker/observatory_portable/Dockerfile.worker-006-agent",
|
||||
"dockerfile_role": "source-contract evidence included in the staged snapshot; not executed by the exact-layer installer",
|
||||
"materialization": {
|
||||
"method": "git archive",
|
||||
"include_only_context_manifest_entries": true,
|
||||
"reject_dirty_worktree": true,
|
||||
"archive_format": "tar",
|
||||
"archive_paths_source": "context manifest context_entries in declared order",
|
||||
"build_context_sha256_subject": "exact git-archive tar bytes"
|
||||
"git_archive_sha256_subject": "exact git-archive tar bytes",
|
||||
"extraction_target": "D:\\NDC_MISSIONCORE\\runtime\\staging\\observatory-worker-agent-<source-revision>"
|
||||
},
|
||||
"staged_snapshot": {
|
||||
"sha256_subject": "sorted regular-file rows for src/k1link, Dockerfile.worker-006-agent and worker-006-agent-build-context.json",
|
||||
"canonicalization": "utf8-path-nul-length-nul-sha256-lf-v1",
|
||||
"reject_reparse_points": true,
|
||||
"reject_unexpected_entries": true,
|
||||
"verify_timing": [
|
||||
"before temporary container creation",
|
||||
"after source installation and before image commit"
|
||||
],
|
||||
"embedded_manifest": "/opt/nodedc/mission-core/release/worker-006-agent-staged-snapshot.json"
|
||||
},
|
||||
"base_image": {
|
||||
"reference": "nvcr.io/nvidia/tritonserver:26.06-py3@sha256:58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794",
|
||||
@@ -24,17 +38,20 @@
|
||||
"must_exist_locally": true,
|
||||
"pull_allowed": false
|
||||
},
|
||||
"docker_build": {
|
||||
"image_materialization": {
|
||||
"method": "docker-commit-exact-layer-v1",
|
||||
"dockerfile_executed": false,
|
||||
"network": "none",
|
||||
"pull": false,
|
||||
"no_cache": true,
|
||||
"provenance": false,
|
||||
"platform": "linux/amd64",
|
||||
"context_input": "exact git-archive tar bytes",
|
||||
"required_build_args": [
|
||||
"NODEDC_SOURCE_REVISION",
|
||||
"NODEDC_BUILD_CONTEXT_SHA256",
|
||||
"SOURCE_DATE_EPOCH"
|
||||
"source_input": "exact read-only staged snapshot",
|
||||
"base_container": "docker create by exact pinned base image ID",
|
||||
"commit": "docker commit --pause=true with fixed config changes",
|
||||
"maximum_thin_layer_bytes": 33554432,
|
||||
"embedded_payload": [
|
||||
"/opt/nodedc/mission-core/src/k1link",
|
||||
"/opt/nodedc/mission-core/release/worker-006-agent-build-context.json",
|
||||
"/opt/nodedc/mission-core/release/worker-006-agent-staged-snapshot.json"
|
||||
]
|
||||
},
|
||||
"required_preflight": [
|
||||
@@ -42,7 +59,9 @@
|
||||
"selected revision resolves to exactly one commit",
|
||||
"selected revision equals HEAD",
|
||||
"base image inspect ID equals the pinned SHA-256",
|
||||
"context archive contains exactly the context manifest entries"
|
||||
"context archive contains exactly the context manifest entries",
|
||||
"git archive SHA-256 was verified externally before extraction",
|
||||
"staged snapshot SHA-256 matches the exact canonical regular-file inventory"
|
||||
],
|
||||
"image_tag_template": "ndc/mission-core-observatory-worker-agent:<source-revision-12>"
|
||||
},
|
||||
@@ -57,7 +76,21 @@
|
||||
"com.nodedc.models": "external",
|
||||
"com.nodedc.runtime-registries": "external-read-only",
|
||||
"com.nodedc.base-image.sha256": "58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794",
|
||||
"com.nodedc.build-context.sha256": "<build-context-sha256>"
|
||||
"com.nodedc.build-context.sha256": "<git-archive-sha256>",
|
||||
"com.nodedc.staged-snapshot.sha256": "<staged-snapshot-sha256>",
|
||||
"com.nodedc.embedded-snapshot-manifest.sha256": "<embedded-snapshot-manifest-sha256>",
|
||||
"com.nodedc.build-method": "docker-commit-exact-layer-v1"
|
||||
},
|
||||
"rootfs": {
|
||||
"base_layer_chain": "exact prefix of the pinned base image RootFS.Layers",
|
||||
"derived_layer_count": 1,
|
||||
"derived_layer_diff_id_required": true,
|
||||
"maximum_thin_layer_bytes": 33554432
|
||||
},
|
||||
"embedded_identity": {
|
||||
"source_tree": "byte-for-byte and bidirectional path match against staged src/k1link",
|
||||
"context_manifest": "byte-for-byte match against the staged context manifest",
|
||||
"snapshot_manifest": "byte-for-byte match against the generated manifest covering every staged file, including the Dockerfile"
|
||||
},
|
||||
"smoke": {
|
||||
"network": "none",
|
||||
@@ -68,6 +101,9 @@
|
||||
"-c",
|
||||
"import k1link.observatory.m49_worker_container_main as e; import k1link.observatory.m49_worker_service as c; assert callable(e.main); assert callable(c.compose_installed_m49_worker_service)"
|
||||
],
|
||||
"staged_source_bytes": "matched",
|
||||
"embedded_context_bytes": "matched",
|
||||
"embedded_snapshot_manifest": "matched",
|
||||
"expected_result": "exit-0"
|
||||
}
|
||||
},
|
||||
@@ -80,7 +116,8 @@
|
||||
"MISSIONCORE_OBSERVATORY_WORKER_DEFINITIONS_FILE": "/run/nodedc/registries/observatory-portable-run-definitions.json",
|
||||
"MISSIONCORE_OBSERVATORY_WORKER_RUNTIME_REGISTRY_FILE": "/run/nodedc/registries/observatory-worker-runtime-candidates.json",
|
||||
"MISSIONCORE_OBSERVATORY_M49_INSTALLATION_RECEIPT_FILE": "/release/worker-installation-receipt.json",
|
||||
"MISSIONCORE_OBSERVATORY_LAB_V1_INSTALLATION_RECEIPT_FILE": "/release/lab-v1-worker-installation-receipt.json"
|
||||
"MISSIONCORE_OBSERVATORY_LAB_V1_INSTALLATION_RECEIPT_FILE": "/release/lab-v1-worker-installation-receipt.json",
|
||||
"MISSIONCORE_OBSERVATORY_LAB_V1_RELEASE_CANDIDATE_FILE": "/release/lab-v1-executor-release.json"
|
||||
},
|
||||
"runtime_registry_files": {
|
||||
"binding": "individual read-only bind files",
|
||||
@@ -238,6 +275,9 @@
|
||||
"state": "external-installed-receipt-required",
|
||||
"environment_variable": "MISSIONCORE_OBSERVATORY_LAB_V1_INSTALLATION_RECEIPT_FILE",
|
||||
"container_path": "/release/lab-v1-worker-installation-receipt.json",
|
||||
"release_candidate_environment_variable": "MISSIONCORE_OBSERVATORY_LAB_V1_RELEASE_CANDIDATE_FILE",
|
||||
"release_candidate_container_path": "/release/lab-v1-executor-release.json",
|
||||
"release_repository_root": "/release",
|
||||
"mode": "read-only",
|
||||
"owns_component_image_identities": true,
|
||||
"queued_jobs_may_override_component_images": false
|
||||
@@ -249,29 +289,55 @@
|
||||
]
|
||||
},
|
||||
"receipt_skeleton": {
|
||||
"schema_version": "missioncore.observatory-worker-agent-image-receipt/v1",
|
||||
"receipt_state": "not-built",
|
||||
"schema_version": "missioncore.observatory-worker-agent-image-installation/v1",
|
||||
"status": null,
|
||||
"worker_id": "worker-006",
|
||||
"build_method": "docker-commit-exact-layer-v1",
|
||||
"source_revision": null,
|
||||
"source_date_epoch": null,
|
||||
"build_context_sha256": null,
|
||||
"provenance": {
|
||||
"git_archive_sha256": null,
|
||||
"git_archive_verification": "external-before-extract",
|
||||
"staged_snapshot_sha256": null,
|
||||
"staged_snapshot_file_count": null,
|
||||
"staged_snapshot_byte_length": null,
|
||||
"staged_snapshot_canonicalization": "utf8-path-nul-length-nul-sha256-lf-v1",
|
||||
"embedded_snapshot_manifest_sha256": null,
|
||||
"embedded_snapshot_manifest_byte_length": null
|
||||
},
|
||||
"base_image_sha256": "58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794",
|
||||
"derived_image_sha256": null,
|
||||
"image": {
|
||||
"tag": null,
|
||||
"id": null,
|
||||
"size_bytes": null
|
||||
"size_bytes": null,
|
||||
"thin_layer_bytes": null,
|
||||
"maximum_thin_layer_bytes": 33554432,
|
||||
"rootfs": {
|
||||
"base_layer_count": null,
|
||||
"derived_layer_count": 1,
|
||||
"derived_layer_diff_id": null,
|
||||
"pinned_base_is_exact_prefix": true
|
||||
}
|
||||
},
|
||||
"runtime_contract": {
|
||||
"workdir": "/opt/nodedc/mission-core",
|
||||
"entrypoint": [
|
||||
"python3",
|
||||
"-m",
|
||||
"k1link.observatory.m49_worker_container_main"
|
||||
],
|
||||
"command": [],
|
||||
"authority": "observation-only",
|
||||
"models": "external",
|
||||
"runtime_registries": "external-read-only"
|
||||
},
|
||||
"smoke": {
|
||||
"network": "none",
|
||||
"read_only_rootfs": true,
|
||||
"staged_source_bytes": null,
|
||||
"embedded_context_bytes": null,
|
||||
"embedded_snapshot_manifest": null,
|
||||
"result": "not-run"
|
||||
},
|
||||
"models_baked_into_image": false,
|
||||
"runtime_registries_baked_into_image": false,
|
||||
"authority": {
|
||||
"commands_enabled": false,
|
||||
"actuation_allowed": false,
|
||||
"navigation_or_safety_accepted": false,
|
||||
"production_accepted": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Fixed POSIX service composition for the sealed portable M4.9 executor.
|
||||
"""Fixed POSIX service composition for installed M4.9 and LAB V1 executors.
|
||||
|
||||
The entrypoint reads only immutable registries, one installation receipt and
|
||||
path-only Worker service settings. Jobs cannot select commands, providers,
|
||||
The entrypoint reads only immutable registries, exact external release files
|
||||
and path-only Worker service settings. Jobs cannot select commands, providers,
|
||||
modules, images or filesystem locations. Every runtime asset is resolved from
|
||||
the fixed release layout and re-verified against the ready runtime candidate
|
||||
before the first queue claim.
|
||||
@@ -15,6 +15,7 @@ import json
|
||||
import os
|
||||
import re
|
||||
import signal
|
||||
import socket
|
||||
import stat
|
||||
from collections.abc import Iterator, Mapping, Sequence
|
||||
from contextlib import contextmanager
|
||||
@@ -35,6 +36,22 @@ from k1link.observatory.m49_portable_executor import (
|
||||
M49PortableRunnerInstallation,
|
||||
compose_m49_portable_executor_adapter,
|
||||
)
|
||||
from k1link.observatory.portable_lab_v1_executor import (
|
||||
PortableLabV1ReleaseAsset,
|
||||
PortableLabV1ReleaseCandidate,
|
||||
PortableLabV1ReleaseInspection,
|
||||
)
|
||||
from k1link.observatory.portable_lab_v1_worker import (
|
||||
PortableLabV1RunnerInstallation as PortableLabV1ReleaseInstallation,
|
||||
)
|
||||
from k1link.observatory.portable_lab_v1_worker_service import (
|
||||
PORTABLE_LAB_V1_ADAPTER_ID,
|
||||
PORTABLE_LAB_V1_SETUP_ID,
|
||||
PORTABLE_LAB_V1_WORKER_INSTALLATION_RECEIPT_ASSET_ID,
|
||||
PortableLabV1WorkerInstallationReceipt,
|
||||
compose_installed_lab_v1_executor_builder,
|
||||
load_portable_lab_v1_worker_installation_receipt,
|
||||
)
|
||||
from k1link.observatory.portable_result_contract import OBSERVATION_ONLY_AUTHORITY
|
||||
from k1link.observatory.portable_run_definitions import (
|
||||
PortableRunDefinition,
|
||||
@@ -64,6 +81,12 @@ M49_WORKER_RUNTIME_REGISTRY_FILE_ENV: Final = "MISSIONCORE_OBSERVATORY_WORKER_RU
|
||||
M49_WORKER_INSTALLATION_RECEIPT_FILE_ENV: Final = (
|
||||
"MISSIONCORE_OBSERVATORY_M49_INSTALLATION_RECEIPT_FILE"
|
||||
)
|
||||
LAB_V1_WORKER_INSTALLATION_RECEIPT_FILE_ENV: Final = (
|
||||
"MISSIONCORE_OBSERVATORY_LAB_V1_INSTALLATION_RECEIPT_FILE"
|
||||
)
|
||||
LAB_V1_WORKER_RELEASE_CANDIDATE_FILE_ENV: Final = (
|
||||
"MISSIONCORE_OBSERVATORY_LAB_V1_RELEASE_CANDIDATE_FILE"
|
||||
)
|
||||
|
||||
M49_WORKER_INSTALLATION_RECEIPT_SCHEMA: Final = (
|
||||
"missioncore.m49-tgs-portable-worker-installation-ready-receipt/v1"
|
||||
@@ -74,12 +97,24 @@ M49_WORKER_RELEASE_ID: Final = "m49-tgs-portable-executor-v1"
|
||||
|
||||
M49_EXECUTOR_RELEASE_ASSET_ID: Final = "m49-portable-executor-release"
|
||||
M49_WORKER_INSTALLATION_RECEIPT_ASSET_ID: Final = "m49-portable-worker-installation-receipt"
|
||||
LAB_V1_PORTABLE_CONFIG_ASSET_ID: Final = "ddrnet-portable-config"
|
||||
LAB_V1_WORKER_AGENT_IMAGE_ASSET_ID: Final = "worker-006-agent-image"
|
||||
|
||||
_MAX_RECEIPT_BYTES: Final = 256 * 1024
|
||||
_MAX_DOCKER_INSPECTION_BYTES: Final = 1024 * 1024
|
||||
_SHA256: Final = re.compile(r"^[a-f0-9]{64}$")
|
||||
_CONTAINER_HOSTNAME: Final = re.compile(r"^[a-f0-9]{12,64}$")
|
||||
_SOURCE_REVISION: Final = re.compile(r"^[a-f0-9]{40}$")
|
||||
_WORKER_COMPUTER_NAME: Final = "DESKTOP-OPJ8J04"
|
||||
_DOCKER_SOCKET: Final = Path("/var/run/docker.sock")
|
||||
_DOCKER_API_VERSION: Final = "v1.47"
|
||||
_FIXED_RUNTIME_RELEASE_ROOT: Final = Path("/release")
|
||||
_FIXED_LAB_V1_INSTALLATION_RECEIPT_FILE: Final = (
|
||||
_FIXED_RUNTIME_RELEASE_ROOT / "lab-v1-worker-installation-receipt.json"
|
||||
)
|
||||
_FIXED_LAB_V1_RELEASE_CANDIDATE_FILE: Final = (
|
||||
_FIXED_RUNTIME_RELEASE_ROOT / "lab-v1-executor-release.json"
|
||||
)
|
||||
_PROTECTED_RUNTIME_NAMES: Final = (
|
||||
"ndc-mission-core-triton",
|
||||
"ndc-mission-core-perception-worker",
|
||||
@@ -119,6 +154,8 @@ class M49WorkerEntrypointConfiguration:
|
||||
definitions_file: Path
|
||||
runtime_registry_file: Path
|
||||
installation_receipt_file: Path
|
||||
lab_v1_installation_receipt_file: Path | None = None
|
||||
lab_v1_release_candidate_file: Path | None = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
for path, label in (
|
||||
@@ -127,6 +164,33 @@ class M49WorkerEntrypointConfiguration:
|
||||
(self.installation_receipt_file, "M4.9 installation receipt"),
|
||||
):
|
||||
_absolute_path(path, label)
|
||||
if (self.lab_v1_installation_receipt_file is None) != (
|
||||
self.lab_v1_release_candidate_file is None
|
||||
):
|
||||
raise M49WorkerCompositionError(
|
||||
"LAB V1 installation receipt and release candidate must be configured together"
|
||||
)
|
||||
for optional_path, label in (
|
||||
(
|
||||
self.lab_v1_installation_receipt_file,
|
||||
"LAB V1 installation receipt",
|
||||
),
|
||||
(self.lab_v1_release_candidate_file, "LAB V1 release candidate"),
|
||||
):
|
||||
if optional_path is not None:
|
||||
_absolute_path(optional_path, label)
|
||||
if (
|
||||
self.lab_v1_installation_receipt_file is not None
|
||||
and (
|
||||
self.lab_v1_installation_receipt_file
|
||||
!= _FIXED_LAB_V1_INSTALLATION_RECEIPT_FILE
|
||||
or self.lab_v1_release_candidate_file
|
||||
!= _FIXED_LAB_V1_RELEASE_CANDIDATE_FILE
|
||||
)
|
||||
):
|
||||
raise M49WorkerCompositionError(
|
||||
"LAB V1 production inputs must use the fixed /release files"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_environment(
|
||||
@@ -148,6 +212,14 @@ class M49WorkerEntrypointConfiguration:
|
||||
values,
|
||||
M49_WORKER_INSTALLATION_RECEIPT_FILE_ENV,
|
||||
),
|
||||
lab_v1_installation_receipt_file=_required_environment_path(
|
||||
values,
|
||||
LAB_V1_WORKER_INSTALLATION_RECEIPT_FILE_ENV,
|
||||
),
|
||||
lab_v1_release_candidate_file=_required_environment_path(
|
||||
values,
|
||||
LAB_V1_WORKER_RELEASE_CANDIDATE_FILE_ENV,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -483,15 +555,374 @@ def compose_installed_m49_worker_service(
|
||||
installation=installation,
|
||||
),
|
||||
)
|
||||
installed_builders = list(executor_builders)
|
||||
if configuration.lab_v1_installation_receipt_file is not None:
|
||||
installed_builders.insert(
|
||||
0,
|
||||
_compose_installed_lab_v1_builder(
|
||||
configuration=configuration,
|
||||
definitions=definitions,
|
||||
runtime_registry=runtime_registry,
|
||||
),
|
||||
)
|
||||
return compose_installed_observatory_worker_service_from_builders(
|
||||
configuration=configuration.worker,
|
||||
definitions=definitions,
|
||||
runtime_registry=runtime_registry,
|
||||
builders=(m49_builder, *executor_builders),
|
||||
builders=(m49_builder, *installed_builders),
|
||||
http_transport=http_transport,
|
||||
)
|
||||
|
||||
|
||||
def _compose_installed_lab_v1_builder(
|
||||
*,
|
||||
configuration: M49WorkerEntrypointConfiguration,
|
||||
definitions: PortableRunDefinitionRegistry,
|
||||
runtime_registry: PortableWorkerRuntimeRegistry,
|
||||
) -> ObservatoryWorkerExecutorBuilderRegistration:
|
||||
receipt_path = configuration.lab_v1_installation_receipt_file
|
||||
release_path = configuration.lab_v1_release_candidate_file
|
||||
if receipt_path is None or release_path is None:
|
||||
raise M49WorkerCompositionError("LAB V1 production inputs are incomplete")
|
||||
definition = definitions.resolve_setup(PORTABLE_LAB_V1_SETUP_ID)
|
||||
candidate = runtime_registry.resolve(
|
||||
definition.setup_id,
|
||||
definition.definition_sha256,
|
||||
)
|
||||
_verify_ready_lab_v1_identity(definitions, definition, candidate)
|
||||
receipt = load_portable_lab_v1_worker_installation_receipt(receipt_path)
|
||||
release_file = _regular_file(release_path, "LAB V1 release candidate")
|
||||
release = PortableLabV1ReleaseCandidate.from_file(
|
||||
release_file,
|
||||
repository_root=_real_directory(
|
||||
release_file.parent,
|
||||
"LAB V1 release repository root",
|
||||
),
|
||||
)
|
||||
if receipt.release_candidate_sha256 != release.candidate_sha256:
|
||||
raise M49WorkerCompositionError(
|
||||
"LAB V1 installation receipt belongs to another release candidate"
|
||||
)
|
||||
release.bind_definition(definition)
|
||||
inspection = _inspect_installed_lab_v1_release(
|
||||
release=release,
|
||||
receipt=receipt,
|
||||
definition=definition,
|
||||
running_worker_image_sha256=(
|
||||
_inspect_running_worker_container_image_sha256()
|
||||
),
|
||||
)
|
||||
bindings = _lab_v1_runtime_asset_bindings(
|
||||
candidate=candidate,
|
||||
release=release,
|
||||
receipt=receipt,
|
||||
)
|
||||
admission = inspect_runtime_candidate(candidate, bindings)
|
||||
if not admission.ready:
|
||||
raise M49WorkerCompositionError(
|
||||
"portable LAB V1 local asset admission is not ready"
|
||||
)
|
||||
portable_config_path = _lab_v1_release_repository_file(
|
||||
release,
|
||||
LAB_V1_PORTABLE_CONFIG_ASSET_ID,
|
||||
)
|
||||
release_installation = PortableLabV1ReleaseInstallation(
|
||||
release=release,
|
||||
inspection=inspection,
|
||||
portable_ddrnet_config_path=portable_config_path,
|
||||
output_parent=(
|
||||
configuration.worker.work_root / "lab-v1-portable" / "runner-output"
|
||||
),
|
||||
)
|
||||
return compose_installed_lab_v1_executor_builder(
|
||||
receipt=receipt,
|
||||
definition=definition,
|
||||
candidate=candidate,
|
||||
admission=admission,
|
||||
release_installation=release_installation,
|
||||
)
|
||||
|
||||
|
||||
def _verify_ready_lab_v1_identity(
|
||||
definitions: PortableRunDefinitionRegistry,
|
||||
definition: PortableRunDefinition,
|
||||
candidate: PortableWorkerRuntimeCandidate,
|
||||
) -> None:
|
||||
ready_identities = {
|
||||
(item.setup_id, item.definition_sha256)
|
||||
for item in definitions.ready_recorded_definitions()
|
||||
}
|
||||
if (
|
||||
(definition.setup_id, definition.definition_sha256) not in ready_identities
|
||||
or definition.setup_id != PORTABLE_LAB_V1_SETUP_ID
|
||||
or definition.executor.contour_id != WORKER_006_CONTOUR_ID
|
||||
or candidate.adapter_id != PORTABLE_LAB_V1_ADAPTER_ID
|
||||
or not definition.executor.ready
|
||||
or not candidate.ready
|
||||
):
|
||||
raise M49WorkerCompositionError(
|
||||
"fixed Worker requires the exact ready LAB V1 definition"
|
||||
)
|
||||
|
||||
|
||||
def _lab_v1_runtime_asset_bindings(
|
||||
*,
|
||||
candidate: PortableWorkerRuntimeCandidate,
|
||||
release: PortableLabV1ReleaseCandidate,
|
||||
receipt: PortableLabV1WorkerInstallationReceipt,
|
||||
) -> dict[str, PortableWorkerLocalAssetBinding]:
|
||||
release_assets = {asset.asset_id: asset for asset in release.assets}
|
||||
bindings: dict[str, PortableWorkerLocalAssetBinding] = {}
|
||||
for requirement in candidate.reusable_assets:
|
||||
if (
|
||||
requirement.asset_id
|
||||
== PORTABLE_LAB_V1_WORKER_INSTALLATION_RECEIPT_ASSET_ID
|
||||
):
|
||||
if (
|
||||
requirement.kind != "local-file"
|
||||
or requirement.sha256 != receipt.file_sha256
|
||||
or requirement.byte_length != receipt.file_byte_length
|
||||
):
|
||||
raise M49WorkerCompositionError(
|
||||
"LAB V1 runtime installation receipt anchor changed"
|
||||
)
|
||||
bindings[requirement.asset_id] = PortableWorkerLocalAssetBinding(
|
||||
asset_id=requirement.asset_id,
|
||||
file_path=receipt.path,
|
||||
)
|
||||
continue
|
||||
asset = release_assets.get(requirement.asset_id)
|
||||
if (
|
||||
asset is None
|
||||
or asset.sha256 != requirement.sha256
|
||||
or asset.byte_length != requirement.byte_length
|
||||
):
|
||||
raise M49WorkerCompositionError(
|
||||
"LAB V1 runtime asset differs from its exact release candidate"
|
||||
)
|
||||
if requirement.kind == "container-image" and asset.kind == "container-image":
|
||||
bindings[requirement.asset_id] = PortableWorkerLocalAssetBinding(
|
||||
asset_id=requirement.asset_id,
|
||||
image_sha256=asset.sha256,
|
||||
)
|
||||
continue
|
||||
if asset.repository_path is None:
|
||||
raise M49WorkerCompositionError(
|
||||
"LAB V1 non-image runtime asset has no exact release file"
|
||||
)
|
||||
bindings[requirement.asset_id] = PortableWorkerLocalAssetBinding(
|
||||
asset_id=requirement.asset_id,
|
||||
file_path=_lab_v1_release_repository_file(
|
||||
release,
|
||||
requirement.asset_id,
|
||||
),
|
||||
)
|
||||
return bindings
|
||||
|
||||
|
||||
def _inspect_installed_lab_v1_release(
|
||||
*,
|
||||
release: PortableLabV1ReleaseCandidate,
|
||||
receipt: PortableLabV1WorkerInstallationReceipt,
|
||||
definition: PortableRunDefinition,
|
||||
running_worker_image_sha256: str,
|
||||
) -> PortableLabV1ReleaseInspection:
|
||||
if release.declared_blockers or release.executor_image_sha256 is None:
|
||||
raise M49WorkerCompositionError("LAB V1 release candidate is not ready")
|
||||
_digest(
|
||||
running_worker_image_sha256,
|
||||
"running Worker container image SHA-256",
|
||||
)
|
||||
worker_image_assets = tuple(
|
||||
asset
|
||||
for asset in release.assets
|
||||
if asset.asset_id == LAB_V1_WORKER_AGENT_IMAGE_ASSET_ID
|
||||
)
|
||||
if (
|
||||
len(worker_image_assets) != 1
|
||||
or worker_image_assets[0].kind != "container-image"
|
||||
or worker_image_assets[0].sha256 != running_worker_image_sha256
|
||||
or worker_image_assets[0].byte_length is not None
|
||||
or worker_image_assets[0].repository_path is not None
|
||||
or release.executor_image_sha256 != running_worker_image_sha256
|
||||
):
|
||||
raise M49WorkerCompositionError(
|
||||
"LAB V1 executor image differs from the running Worker container"
|
||||
)
|
||||
installed_evidence = _lab_v1_installed_release_evidence(receipt)
|
||||
installed_images = {
|
||||
receipt.eomt_image_build.base_image_sha256,
|
||||
receipt.eomt_image_build.derived_image_sha256,
|
||||
receipt.ddrnet_image_build.base_image_sha256,
|
||||
receipt.ddrnet_image_build.derived_image_sha256,
|
||||
running_worker_image_sha256,
|
||||
}
|
||||
definition_components = {component.sha256 for component in definition.components}
|
||||
matched: list[str] = []
|
||||
for asset in release.assets:
|
||||
if (
|
||||
asset.asset_id
|
||||
== PORTABLE_LAB_V1_WORKER_INSTALLATION_RECEIPT_ASSET_ID
|
||||
):
|
||||
raise M49WorkerCompositionError(
|
||||
"LAB V1 installation receipt must remain a runtime-only anchor"
|
||||
)
|
||||
if asset.kind == "repository-file":
|
||||
_lab_v1_release_repository_file(release, asset.asset_id)
|
||||
elif asset.kind == "container-image":
|
||||
if asset.byte_length is not None or asset.sha256 not in installed_images:
|
||||
raise M49WorkerCompositionError(
|
||||
"LAB V1 release contains an uninstalled container image"
|
||||
)
|
||||
elif asset.kind == "definition-component":
|
||||
if asset.byte_length is not None or asset.sha256 not in definition_components:
|
||||
raise M49WorkerCompositionError(
|
||||
"LAB V1 release contains an unknown definition component"
|
||||
)
|
||||
elif not _lab_v1_release_evidence_matches(asset, installed_evidence):
|
||||
raise M49WorkerCompositionError(
|
||||
"LAB V1 release contains an asset absent from its installation receipt"
|
||||
)
|
||||
matched.append(asset.asset_id)
|
||||
return PortableLabV1ReleaseInspection(
|
||||
candidate_sha256=release.candidate_sha256,
|
||||
matched_assets=tuple(matched),
|
||||
blockers=(),
|
||||
ready=True,
|
||||
)
|
||||
|
||||
|
||||
def _inspect_running_worker_container_image_sha256(
|
||||
*,
|
||||
container_hostname: str | None = None,
|
||||
transport: httpx.BaseTransport | None = None,
|
||||
docker_socket: Path = _DOCKER_SOCKET,
|
||||
) -> str:
|
||||
"""Return the exact image ID of this running Worker container."""
|
||||
|
||||
hostname = socket.gethostname() if container_hostname is None else container_hostname
|
||||
if _CONTAINER_HOSTNAME.fullmatch(hostname) is None:
|
||||
raise M49WorkerCompositionError(
|
||||
"running Worker container hostname is not an exact container ID"
|
||||
)
|
||||
selected_transport = transport
|
||||
if selected_transport is None:
|
||||
candidate = docker_socket.expanduser().absolute()
|
||||
try:
|
||||
metadata = candidate.lstat()
|
||||
except OSError as exc:
|
||||
raise M49WorkerCompositionError(
|
||||
"running Worker Docker Engine socket is unavailable"
|
||||
) from exc
|
||||
if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISSOCK(metadata.st_mode):
|
||||
raise M49WorkerCompositionError(
|
||||
"running Worker Docker Engine socket is unsafe"
|
||||
)
|
||||
selected_transport = httpx.HTTPTransport(uds=str(candidate))
|
||||
try:
|
||||
with httpx.Client(
|
||||
base_url="http://docker",
|
||||
transport=selected_transport,
|
||||
timeout=5.0,
|
||||
) as client:
|
||||
response = client.get(
|
||||
f"/{_DOCKER_API_VERSION}/containers/{hostname}/json"
|
||||
)
|
||||
payload = response.content
|
||||
except (httpx.HTTPError, OSError) as exc:
|
||||
raise M49WorkerCompositionError(
|
||||
"running Worker container inspection failed"
|
||||
) from exc
|
||||
if response.status_code != 200 or not 0 < len(payload) <= _MAX_DOCKER_INSPECTION_BYTES:
|
||||
raise M49WorkerCompositionError(
|
||||
"running Worker container inspection is unavailable"
|
||||
)
|
||||
try:
|
||||
document = json.loads(payload.decode("utf-8"))
|
||||
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
|
||||
raise M49WorkerCompositionError(
|
||||
"running Worker container inspection is invalid"
|
||||
) from exc
|
||||
row = _object(document, "running Worker container inspection")
|
||||
container_id = _string(row.get("Id"), "running Worker container ID")
|
||||
image_reference = _string(
|
||||
row.get("Image"),
|
||||
"running Worker container image ID",
|
||||
)
|
||||
state = _object(row.get("State"), "running Worker container state")
|
||||
image_sha256 = image_reference.removeprefix("sha256:")
|
||||
if (
|
||||
_SHA256.fullmatch(container_id) is None
|
||||
or not container_id.startswith(hostname)
|
||||
or image_reference != f"sha256:{image_sha256}"
|
||||
or _SHA256.fullmatch(image_sha256) is None
|
||||
or state.get("Running") is not True
|
||||
):
|
||||
raise M49WorkerCompositionError(
|
||||
"running Worker container identity changed"
|
||||
)
|
||||
return image_sha256
|
||||
|
||||
|
||||
def _lab_v1_installed_release_evidence(
|
||||
receipt: PortableLabV1WorkerInstallationReceipt,
|
||||
) -> dict[str, set[int | None]]:
|
||||
evidence: dict[str, set[int | None]] = {}
|
||||
|
||||
def add(digest: str, byte_length: int | None) -> None:
|
||||
evidence.setdefault(digest, set()).add(byte_length)
|
||||
|
||||
for component in (
|
||||
receipt.runner_installation.eomt,
|
||||
receipt.runner_installation.ddrnet,
|
||||
):
|
||||
add(component.image_sha256, None)
|
||||
add(component.installation_sha256, None)
|
||||
for asset in component.assets:
|
||||
add(asset.identity_sha256, asset.byte_length)
|
||||
add(receipt.runner_installation.receipt_sha256, None)
|
||||
for build in (receipt.eomt_image_build, receipt.ddrnet_image_build):
|
||||
for key, value in build.identity_document().items():
|
||||
if key.endswith("_sha256") and isinstance(value, str):
|
||||
add(value, None)
|
||||
add(build.seal_sha256, None)
|
||||
return evidence
|
||||
|
||||
|
||||
def _lab_v1_release_evidence_matches(
|
||||
asset: PortableLabV1ReleaseAsset,
|
||||
evidence: Mapping[str, set[int | None]],
|
||||
) -> bool:
|
||||
lengths = evidence.get(asset.sha256)
|
||||
if lengths is None:
|
||||
return False
|
||||
return asset.byte_length is None or asset.byte_length in lengths
|
||||
|
||||
|
||||
def _lab_v1_release_repository_file(
|
||||
release: PortableLabV1ReleaseCandidate,
|
||||
asset_id: str,
|
||||
) -> Path:
|
||||
asset = next((row for row in release.assets if row.asset_id == asset_id), None)
|
||||
if asset is None or asset.kind != "repository-file" or asset.repository_path is None:
|
||||
raise M49WorkerCompositionError("LAB V1 exact release file is unavailable")
|
||||
relative = _safe_relative_path(asset.repository_path)
|
||||
path = _regular_file(
|
||||
release.repository_root.joinpath(*relative.parts),
|
||||
"LAB V1 release file",
|
||||
)
|
||||
if not path.is_relative_to(release.repository_root):
|
||||
raise M49WorkerCompositionError("LAB V1 release file escapes its release root")
|
||||
if (
|
||||
(asset.byte_length is not None and path.stat().st_size != asset.byte_length)
|
||||
or _sha256_file(path) != asset.sha256
|
||||
):
|
||||
raise M49WorkerCompositionError(
|
||||
"LAB V1 release file differs from its release candidate"
|
||||
)
|
||||
return path
|
||||
|
||||
|
||||
def run_installed_m49_worker(
|
||||
service: InstalledObservatoryWorkerService,
|
||||
*,
|
||||
|
||||
@@ -94,6 +94,9 @@ _EXPECTED_RESULT_CONTRACT_SHA256: Final = (
|
||||
"b3dfaa8e20a0f22fc510d062ac469f010a3281c650059d9ea134f0b3ccb38d9a"
|
||||
)
|
||||
_PORTABLE_CONFIG_ASSET_ID: Final = "ddrnet-portable-config"
|
||||
_WORKER_INSTALLATION_RECEIPT_ASSET_ID: Final = (
|
||||
"lab-v1-worker-installation-receipt"
|
||||
)
|
||||
_MAX_SOURCE_DOCUMENT_BYTES: Final = 8 * 1024 * 1024
|
||||
_MAX_MATERIALIZATION_MANIFEST_BYTES: Final = 64 * 1024 * 1024
|
||||
_MAX_SOURCE_MEMBERS: Final = 100_000
|
||||
@@ -582,6 +585,12 @@ def _verify_candidate_release(
|
||||
):
|
||||
raise PortableLabV1WorkerError("portable LAB V1 runtime and release candidates disagree")
|
||||
for requirement in candidate.reusable_assets:
|
||||
if requirement.asset_id == _WORKER_INSTALLATION_RECEIPT_ASSET_ID:
|
||||
if requirement.kind != "local-file":
|
||||
raise PortableLabV1WorkerError(
|
||||
"portable LAB V1 installation receipt runtime anchor changed"
|
||||
)
|
||||
continue
|
||||
asset = release_assets.get(requirement.asset_id)
|
||||
if (
|
||||
asset is None
|
||||
|
||||
@@ -63,10 +63,13 @@ from k1link.observatory.worker_service import (
|
||||
)
|
||||
|
||||
PORTABLE_LAB_V1_WORKER_INSTALLATION_RECEIPT_SCHEMA: Final = (
|
||||
"missioncore.observatory-portable-lab-v1-worker-installation-ready-receipt/v1"
|
||||
"missioncore.observatory-portable-lab-v1-worker-installation-ready-receipt/v2"
|
||||
)
|
||||
PORTABLE_LAB_V1_COMPONENT_IMAGE_BUILD_SEAL_SCHEMA: Final = (
|
||||
"missioncore.observatory-portable-lab-v1-component-image-build-seal/v1"
|
||||
"missioncore.observatory-portable-lab-v1-component-image-build-seal/v2"
|
||||
)
|
||||
PORTABLE_LAB_V1_COMPONENT_IMAGE_BUILD_METHOD: Final = (
|
||||
"docker-commit-exact-layer-v1"
|
||||
)
|
||||
PORTABLE_LAB_V1_WORKER_INSTALLATION_RECEIPT_ASSET_ID: Final = (
|
||||
"lab-v1-worker-installation-receipt"
|
||||
@@ -98,6 +101,8 @@ class PortableLabV1ComponentImageBuildSeal:
|
||||
base_image_sha256: str
|
||||
derived_image_sha256: str
|
||||
dockerfile_sha256: str
|
||||
build_method: str
|
||||
installer_sha256: str
|
||||
shared_adapter_sha256: str
|
||||
component_adapter_sha256: str
|
||||
network: str
|
||||
@@ -112,6 +117,7 @@ class PortableLabV1ComponentImageBuildSeal:
|
||||
(self.base_image_sha256, "LAB V1 component base image SHA-256"),
|
||||
(self.derived_image_sha256, "LAB V1 derived component image SHA-256"),
|
||||
(self.dockerfile_sha256, "LAB V1 component Dockerfile SHA-256"),
|
||||
(self.installer_sha256, "LAB V1 component image installer SHA-256"),
|
||||
(self.shared_adapter_sha256, "LAB V1 shared adapter SHA-256"),
|
||||
(self.component_adapter_sha256, "LAB V1 component adapter SHA-256"),
|
||||
(self.seal_sha256, "LAB V1 component image build seal SHA-256"),
|
||||
@@ -120,6 +126,8 @@ class PortableLabV1ComponentImageBuildSeal:
|
||||
if (
|
||||
self.base_image_sha256
|
||||
!= _COMPONENT_BASE_IMAGE_SHA256S[self.component]
|
||||
or self.build_method
|
||||
!= PORTABLE_LAB_V1_COMPONENT_IMAGE_BUILD_METHOD
|
||||
or self.network != "none"
|
||||
or self.seal_sha256
|
||||
!= hashlib.sha256(canonical_json(self.identity_document())).hexdigest()
|
||||
@@ -136,6 +144,8 @@ class PortableLabV1ComponentImageBuildSeal:
|
||||
base_image_sha256: str,
|
||||
derived_image_sha256: str,
|
||||
dockerfile_sha256: str,
|
||||
build_method: str,
|
||||
installer_sha256: str,
|
||||
shared_adapter_sha256: str,
|
||||
component_adapter_sha256: str,
|
||||
) -> PortableLabV1ComponentImageBuildSeal:
|
||||
@@ -144,6 +154,8 @@ class PortableLabV1ComponentImageBuildSeal:
|
||||
base_image_sha256=base_image_sha256,
|
||||
derived_image_sha256=derived_image_sha256,
|
||||
dockerfile_sha256=dockerfile_sha256,
|
||||
build_method=build_method,
|
||||
installer_sha256=installer_sha256,
|
||||
shared_adapter_sha256=shared_adapter_sha256,
|
||||
component_adapter_sha256=component_adapter_sha256,
|
||||
)
|
||||
@@ -152,6 +164,8 @@ class PortableLabV1ComponentImageBuildSeal:
|
||||
base_image_sha256=base_image_sha256,
|
||||
derived_image_sha256=derived_image_sha256,
|
||||
dockerfile_sha256=dockerfile_sha256,
|
||||
build_method=build_method,
|
||||
installer_sha256=installer_sha256,
|
||||
shared_adapter_sha256=shared_adapter_sha256,
|
||||
component_adapter_sha256=component_adapter_sha256,
|
||||
network="none",
|
||||
@@ -164,6 +178,8 @@ class PortableLabV1ComponentImageBuildSeal:
|
||||
base_image_sha256=self.base_image_sha256,
|
||||
derived_image_sha256=self.derived_image_sha256,
|
||||
dockerfile_sha256=self.dockerfile_sha256,
|
||||
build_method=self.build_method,
|
||||
installer_sha256=self.installer_sha256,
|
||||
shared_adapter_sha256=self.shared_adapter_sha256,
|
||||
component_adapter_sha256=self.component_adapter_sha256,
|
||||
)
|
||||
@@ -181,6 +197,7 @@ class PortableLabV1WorkerInstallationReceipt:
|
||||
runner_installation: PortableLabV1ComponentRunnerInstallation
|
||||
eomt_image_build: PortableLabV1ComponentImageBuildSeal
|
||||
ddrnet_image_build: PortableLabV1ComponentImageBuildSeal
|
||||
installation_evidence_sha256: str
|
||||
receipt_sha256: str
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
@@ -193,6 +210,10 @@ class PortableLabV1WorkerInstallationReceipt:
|
||||
"LAB V1 installation receipt size is invalid"
|
||||
)
|
||||
_digest(self.file_sha256, "LAB V1 installation receipt file SHA-256")
|
||||
_digest(
|
||||
self.installation_evidence_sha256,
|
||||
"LAB V1 external installation evidence SHA-256",
|
||||
)
|
||||
if _SOURCE_REVISION.fullmatch(self.source_revision) is None:
|
||||
raise PortableLabV1WorkerCompositionError(
|
||||
"LAB V1 installation receipt source revision is invalid"
|
||||
@@ -223,6 +244,7 @@ class PortableLabV1WorkerInstallationReceipt:
|
||||
runner_installation=self.runner_installation,
|
||||
eomt_image_build=self.eomt_image_build,
|
||||
ddrnet_image_build=self.ddrnet_image_build,
|
||||
installation_evidence_sha256=self.installation_evidence_sha256,
|
||||
):
|
||||
raise PortableLabV1WorkerCompositionError(
|
||||
"LAB V1 installation receipt identity changed"
|
||||
@@ -309,7 +331,8 @@ def load_portable_lab_v1_worker_installation_receipt(
|
||||
"runner_installation",
|
||||
"component_installations",
|
||||
"component_image_build_seals",
|
||||
"fixture_smoke",
|
||||
"installation_evidence_sha256",
|
||||
"installation_smoke",
|
||||
"blockers",
|
||||
"authority",
|
||||
"receipt_sha256",
|
||||
@@ -322,7 +345,7 @@ def load_portable_lab_v1_worker_installation_receipt(
|
||||
!= PORTABLE_LAB_V1_WORKER_INSTALLATION_RECEIPT_SCHEMA
|
||||
or row["receipt_state"] != "installed-ready"
|
||||
or row["worker_id"] != _WORKER_ID
|
||||
or row["fixture_smoke"] != "passed"
|
||||
or row["installation_smoke"] != "offline-import-passed"
|
||||
or blockers
|
||||
or row["authority"] != OBSERVATION_ONLY_AUTHORITY
|
||||
):
|
||||
@@ -385,6 +408,10 @@ def load_portable_lab_v1_worker_installation_receipt(
|
||||
runner_installation=runner,
|
||||
eomt_image_build=eomt_image_build,
|
||||
ddrnet_image_build=ddrnet_image_build,
|
||||
installation_evidence_sha256=_string(
|
||||
row["installation_evidence_sha256"],
|
||||
"LAB V1 external installation evidence SHA-256",
|
||||
),
|
||||
receipt_sha256=_string(
|
||||
row["receipt_sha256"],
|
||||
"LAB V1 installation receipt SHA-256",
|
||||
@@ -501,6 +528,7 @@ def portable_lab_v1_worker_receipt_document(
|
||||
runner_installation: PortableLabV1ComponentRunnerInstallation,
|
||||
eomt_image_build: PortableLabV1ComponentImageBuildSeal,
|
||||
ddrnet_image_build: PortableLabV1ComponentImageBuildSeal,
|
||||
installation_evidence_sha256: str,
|
||||
) -> dict[str, object]:
|
||||
"""Return the canonical serializable receipt document for an installer."""
|
||||
|
||||
@@ -510,6 +538,7 @@ def portable_lab_v1_worker_receipt_document(
|
||||
runner_installation=runner_installation,
|
||||
eomt_image_build=eomt_image_build,
|
||||
ddrnet_image_build=ddrnet_image_build,
|
||||
installation_evidence_sha256=installation_evidence_sha256,
|
||||
)
|
||||
return {**identity, "receipt_sha256": hashlib.sha256(canonical_json(identity)).hexdigest()}
|
||||
|
||||
@@ -521,6 +550,7 @@ def _receipt_identity_sha256(
|
||||
runner_installation: PortableLabV1ComponentRunnerInstallation,
|
||||
eomt_image_build: PortableLabV1ComponentImageBuildSeal,
|
||||
ddrnet_image_build: PortableLabV1ComponentImageBuildSeal,
|
||||
installation_evidence_sha256: str,
|
||||
) -> str:
|
||||
return hashlib.sha256(
|
||||
canonical_json(
|
||||
@@ -530,6 +560,7 @@ def _receipt_identity_sha256(
|
||||
runner_installation=runner_installation,
|
||||
eomt_image_build=eomt_image_build,
|
||||
ddrnet_image_build=ddrnet_image_build,
|
||||
installation_evidence_sha256=installation_evidence_sha256,
|
||||
)
|
||||
)
|
||||
).hexdigest()
|
||||
@@ -542,7 +573,12 @@ def _receipt_identity_document(
|
||||
runner_installation: PortableLabV1ComponentRunnerInstallation,
|
||||
eomt_image_build: PortableLabV1ComponentImageBuildSeal,
|
||||
ddrnet_image_build: PortableLabV1ComponentImageBuildSeal,
|
||||
installation_evidence_sha256: str,
|
||||
) -> dict[str, object]:
|
||||
_digest(
|
||||
installation_evidence_sha256,
|
||||
"LAB V1 external installation evidence SHA-256",
|
||||
)
|
||||
return {
|
||||
"schema_version": PORTABLE_LAB_V1_WORKER_INSTALLATION_RECEIPT_SCHEMA,
|
||||
"receipt_state": "installed-ready",
|
||||
@@ -561,7 +597,8 @@ def _receipt_identity_document(
|
||||
"eomt": _component_image_build_document(eomt_image_build),
|
||||
"ddrnet": _component_image_build_document(ddrnet_image_build),
|
||||
},
|
||||
"fixture_smoke": "passed",
|
||||
"installation_evidence_sha256": installation_evidence_sha256,
|
||||
"installation_smoke": "offline-import-passed",
|
||||
"blockers": [],
|
||||
"authority": dict(OBSERVATION_ONLY_AUTHORITY),
|
||||
}
|
||||
@@ -582,6 +619,8 @@ def _component_image_build_identity(
|
||||
base_image_sha256: str,
|
||||
derived_image_sha256: str,
|
||||
dockerfile_sha256: str,
|
||||
build_method: str,
|
||||
installer_sha256: str,
|
||||
shared_adapter_sha256: str,
|
||||
component_adapter_sha256: str,
|
||||
) -> dict[str, object]:
|
||||
@@ -591,6 +630,8 @@ def _component_image_build_identity(
|
||||
"base_image_sha256": base_image_sha256,
|
||||
"derived_image_sha256": derived_image_sha256,
|
||||
"dockerfile_sha256": dockerfile_sha256,
|
||||
"build_method": build_method,
|
||||
"installer_sha256": installer_sha256,
|
||||
"shared_adapter_sha256": shared_adapter_sha256,
|
||||
"component_adapter_sha256": component_adapter_sha256,
|
||||
"network": "none",
|
||||
@@ -618,6 +659,8 @@ def _component_image_build_seal(
|
||||
"base_image_sha256",
|
||||
"derived_image_sha256",
|
||||
"dockerfile_sha256",
|
||||
"build_method",
|
||||
"installer_sha256",
|
||||
"shared_adapter_sha256",
|
||||
"component_adapter_sha256",
|
||||
"network",
|
||||
@@ -649,6 +692,14 @@ def _component_image_build_seal(
|
||||
row["dockerfile_sha256"],
|
||||
"LAB V1 component Dockerfile SHA-256",
|
||||
),
|
||||
build_method=_string(
|
||||
row["build_method"],
|
||||
"LAB V1 component image build method",
|
||||
),
|
||||
installer_sha256=_string(
|
||||
row["installer_sha256"],
|
||||
"LAB V1 component image installer SHA-256",
|
||||
),
|
||||
shared_adapter_sha256=_string(
|
||||
row["shared_adapter_sha256"],
|
||||
"LAB V1 shared adapter SHA-256",
|
||||
@@ -694,6 +745,7 @@ def _verify_release_covers_runner_installation(
|
||||
for build in image_builds
|
||||
for digest in (
|
||||
build.dockerfile_sha256,
|
||||
build.installer_sha256,
|
||||
build.shared_adapter_sha256,
|
||||
build.component_adapter_sha256,
|
||||
build.seal_sha256,
|
||||
@@ -969,6 +1021,7 @@ def _utc_now() -> str:
|
||||
|
||||
|
||||
__all__ = [
|
||||
"PORTABLE_LAB_V1_COMPONENT_IMAGE_BUILD_METHOD",
|
||||
"PORTABLE_LAB_V1_COMPONENT_IMAGE_BUILD_SEAL_SCHEMA",
|
||||
"PORTABLE_LAB_V1_ADAPTER_ID",
|
||||
"PORTABLE_LAB_V1_SETUP_ID",
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from typing import cast
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from k1link.observatory import m49_worker_service as service_module
|
||||
from k1link.observatory.portable_lab_v1_executor import (
|
||||
PortableLabV1ReleaseAsset,
|
||||
PortableLabV1ReleaseCandidate,
|
||||
)
|
||||
from k1link.observatory.portable_lab_v1_worker_service import (
|
||||
PortableLabV1WorkerInstallationReceipt,
|
||||
)
|
||||
from k1link.observatory.portable_run_definitions import PortableRunDefinition
|
||||
|
||||
|
||||
def _sha256(payload: bytes) -> str:
|
||||
return hashlib.sha256(payload).hexdigest()
|
||||
|
||||
|
||||
def _receipt(asset_sha256: str, byte_length: int) -> object:
|
||||
host_asset = SimpleNamespace(
|
||||
identity_sha256=asset_sha256,
|
||||
byte_length=byte_length,
|
||||
)
|
||||
eomt = SimpleNamespace(
|
||||
image_sha256="1" * 64,
|
||||
installation_sha256="c" * 64,
|
||||
assets=(host_asset,),
|
||||
)
|
||||
ddrnet = SimpleNamespace(
|
||||
image_sha256="2" * 64,
|
||||
installation_sha256="d" * 64,
|
||||
assets=(),
|
||||
)
|
||||
eomt_build = SimpleNamespace(
|
||||
base_image_sha256="3" * 64,
|
||||
derived_image_sha256=eomt.image_sha256,
|
||||
seal_sha256="4" * 64,
|
||||
identity_document=lambda: {
|
||||
"base_image_sha256": "3" * 64,
|
||||
"derived_image_sha256": eomt.image_sha256,
|
||||
"adapter_sha256": "5" * 64,
|
||||
},
|
||||
)
|
||||
ddrnet_build = SimpleNamespace(
|
||||
base_image_sha256="6" * 64,
|
||||
derived_image_sha256=ddrnet.image_sha256,
|
||||
seal_sha256="7" * 64,
|
||||
identity_document=lambda: {
|
||||
"base_image_sha256": "6" * 64,
|
||||
"derived_image_sha256": ddrnet.image_sha256,
|
||||
"adapter_sha256": "8" * 64,
|
||||
},
|
||||
)
|
||||
return SimpleNamespace(
|
||||
runner_installation=SimpleNamespace(
|
||||
eomt=eomt,
|
||||
ddrnet=ddrnet,
|
||||
receipt_sha256="e" * 64,
|
||||
),
|
||||
eomt_image_build=eomt_build,
|
||||
ddrnet_image_build=ddrnet_build,
|
||||
)
|
||||
|
||||
|
||||
def _release(
|
||||
tmp_path: Path,
|
||||
*,
|
||||
installed_asset_sha256: str,
|
||||
installed_asset_length: int,
|
||||
include_unknown: bool = False,
|
||||
include_worker_image: bool = True,
|
||||
executor_image_sha256: str = "b" * 64,
|
||||
) -> object:
|
||||
config_payload = b'{"profile":"lab-v1"}'
|
||||
config_path = tmp_path / "config.json"
|
||||
config_path.write_bytes(config_payload)
|
||||
assets = [
|
||||
PortableLabV1ReleaseAsset(
|
||||
asset_id="ddrnet-portable-config",
|
||||
kind="repository-file",
|
||||
sha256=_sha256(config_payload),
|
||||
byte_length=len(config_payload),
|
||||
repository_path="config.json",
|
||||
),
|
||||
PortableLabV1ReleaseAsset(
|
||||
asset_id="eomt-installed-tree",
|
||||
kind="runtime-artifact",
|
||||
sha256=installed_asset_sha256,
|
||||
byte_length=installed_asset_length,
|
||||
repository_path=None,
|
||||
),
|
||||
]
|
||||
if include_worker_image:
|
||||
assets.append(
|
||||
PortableLabV1ReleaseAsset(
|
||||
asset_id=service_module.LAB_V1_WORKER_AGENT_IMAGE_ASSET_ID,
|
||||
kind="container-image",
|
||||
sha256=executor_image_sha256,
|
||||
byte_length=None,
|
||||
repository_path=None,
|
||||
)
|
||||
)
|
||||
if include_unknown:
|
||||
assets.append(
|
||||
PortableLabV1ReleaseAsset(
|
||||
asset_id="unknown-runtime-artifact",
|
||||
kind="runtime-artifact",
|
||||
sha256="9" * 64,
|
||||
byte_length=None,
|
||||
repository_path=None,
|
||||
)
|
||||
)
|
||||
assets.sort(key=lambda asset: asset.asset_id)
|
||||
return SimpleNamespace(
|
||||
assets=tuple(assets),
|
||||
candidate_sha256="a" * 64,
|
||||
declared_blockers=(),
|
||||
executor_image_sha256=executor_image_sha256,
|
||||
repository_root=tmp_path,
|
||||
)
|
||||
|
||||
|
||||
def test_release_admission_requires_exact_files_or_installation_evidence(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
installed_payload = b"sealed EoMT tree identity"
|
||||
installed_sha256 = _sha256(installed_payload)
|
||||
receipt = _receipt(installed_sha256, len(installed_payload))
|
||||
release = _release(
|
||||
tmp_path,
|
||||
installed_asset_sha256=installed_sha256,
|
||||
installed_asset_length=len(installed_payload),
|
||||
)
|
||||
definition = SimpleNamespace(components=())
|
||||
|
||||
inspection = service_module._inspect_installed_lab_v1_release( # noqa: SLF001
|
||||
release=cast(PortableLabV1ReleaseCandidate, release),
|
||||
receipt=cast(PortableLabV1WorkerInstallationReceipt, receipt),
|
||||
definition=cast(PortableRunDefinition, definition),
|
||||
running_worker_image_sha256="b" * 64,
|
||||
)
|
||||
|
||||
assert inspection.ready
|
||||
assert inspection.matched_assets == (
|
||||
"ddrnet-portable-config",
|
||||
"eomt-installed-tree",
|
||||
service_module.LAB_V1_WORKER_AGENT_IMAGE_ASSET_ID,
|
||||
)
|
||||
|
||||
|
||||
def test_release_admission_rejects_uncovered_runtime_artifact(tmp_path: Path) -> None:
|
||||
installed_payload = b"sealed EoMT tree identity"
|
||||
installed_sha256 = _sha256(installed_payload)
|
||||
receipt = _receipt(installed_sha256, len(installed_payload))
|
||||
release = _release(
|
||||
tmp_path,
|
||||
installed_asset_sha256=installed_sha256,
|
||||
installed_asset_length=len(installed_payload),
|
||||
include_unknown=True,
|
||||
)
|
||||
|
||||
with pytest.raises(
|
||||
service_module.M49WorkerCompositionError,
|
||||
match="absent from its installation receipt",
|
||||
):
|
||||
service_module._inspect_installed_lab_v1_release( # noqa: SLF001
|
||||
release=cast(PortableLabV1ReleaseCandidate, release),
|
||||
receipt=cast(PortableLabV1WorkerInstallationReceipt, receipt),
|
||||
definition=cast(
|
||||
PortableRunDefinition,
|
||||
SimpleNamespace(components=()),
|
||||
),
|
||||
running_worker_image_sha256="b" * 64,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("include_worker_image", [False, True])
|
||||
def test_release_admission_rejects_missing_or_changed_running_worker_image(
|
||||
tmp_path: Path,
|
||||
include_worker_image: bool,
|
||||
) -> None:
|
||||
receipt = _receipt("1" * 64, 1)
|
||||
release = _release(
|
||||
tmp_path,
|
||||
installed_asset_sha256="1" * 64,
|
||||
installed_asset_length=1,
|
||||
include_worker_image=include_worker_image,
|
||||
executor_image_sha256="b" * 64,
|
||||
)
|
||||
|
||||
with pytest.raises(
|
||||
service_module.M49WorkerCompositionError,
|
||||
match="differs from the running Worker container",
|
||||
):
|
||||
service_module._inspect_installed_lab_v1_release( # noqa: SLF001
|
||||
release=cast(PortableLabV1ReleaseCandidate, release),
|
||||
receipt=cast(PortableLabV1WorkerInstallationReceipt, receipt),
|
||||
definition=cast(
|
||||
PortableRunDefinition,
|
||||
SimpleNamespace(components=()),
|
||||
),
|
||||
running_worker_image_sha256="c" * 64,
|
||||
)
|
||||
|
||||
|
||||
def test_running_worker_image_uses_exact_docker_container_inspection() -> None:
|
||||
container_id = "a" * 64
|
||||
image_sha256 = "b" * 64
|
||||
requested_paths: list[str] = []
|
||||
|
||||
def inspect(request: httpx.Request) -> httpx.Response:
|
||||
requested_paths.append(request.url.path)
|
||||
return httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"Id": container_id,
|
||||
"Image": f"sha256:{image_sha256}",
|
||||
"State": {"Running": True},
|
||||
},
|
||||
)
|
||||
|
||||
result = service_module._inspect_running_worker_container_image_sha256( # noqa: SLF001
|
||||
container_hostname=container_id[:12],
|
||||
transport=httpx.MockTransport(inspect),
|
||||
)
|
||||
|
||||
assert result == image_sha256
|
||||
assert requested_paths == [
|
||||
f"/v1.47/containers/{container_id[:12]}/json"
|
||||
]
|
||||
@@ -38,7 +38,10 @@ from k1link.observatory.portable_worker_runtime import (
|
||||
PortableWorkerRuntimeRegistry,
|
||||
)
|
||||
from k1link.observatory.worker_http_transport import ObservatoryWorkerHttpGateway
|
||||
from k1link.observatory.worker_service import ObservatoryWorkerServiceConfiguration
|
||||
from k1link.observatory.worker_service import (
|
||||
ObservatoryWorkerExecutorBuilderRegistration,
|
||||
ObservatoryWorkerServiceConfiguration,
|
||||
)
|
||||
|
||||
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
|
||||
DEFINITIONS_PATH = REPOSITORY_ROOT / "config" / "observatory-portable-run-definitions.json"
|
||||
@@ -424,6 +427,62 @@ def test_fixed_m49_identity_allows_an_additional_ready_worker_profile(
|
||||
)
|
||||
|
||||
|
||||
def test_configured_lab_v1_builder_is_added_to_the_shared_worker_registry(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
configuration, _receipt = _fixture(tmp_path, monkeypatch)
|
||||
configuration = replace(
|
||||
configuration,
|
||||
lab_v1_installation_receipt_file=(
|
||||
service_module._FIXED_LAB_V1_INSTALLATION_RECEIPT_FILE # noqa: SLF001
|
||||
),
|
||||
lab_v1_release_candidate_file=(
|
||||
service_module._FIXED_LAB_V1_RELEASE_CANDIDATE_FILE # noqa: SLF001
|
||||
),
|
||||
)
|
||||
lab_builder = ObservatoryWorkerExecutorBuilderRegistration(
|
||||
setup_id="lab-v1-eomt-ddrnet-portable-v1",
|
||||
builder=lambda _context: pytest.fail("builder must not be called here"),
|
||||
)
|
||||
captured: dict[str, object] = {}
|
||||
sentinel = object()
|
||||
|
||||
def compose_lab_v1(**kwargs: object) -> ObservatoryWorkerExecutorBuilderRegistration:
|
||||
captured["lab_inputs"] = kwargs
|
||||
return lab_builder
|
||||
|
||||
def compose_worker(**kwargs: object) -> object:
|
||||
captured["worker_inputs"] = kwargs
|
||||
return sentinel
|
||||
|
||||
monkeypatch.setattr(
|
||||
service_module,
|
||||
"_compose_installed_lab_v1_builder",
|
||||
compose_lab_v1,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
service_module,
|
||||
"compose_installed_observatory_worker_service_from_builders",
|
||||
compose_worker,
|
||||
)
|
||||
|
||||
composed = service_module.compose_installed_m49_worker_service(configuration)
|
||||
|
||||
assert composed is sentinel
|
||||
lab_inputs = cast(dict[str, object], captured["lab_inputs"])
|
||||
assert lab_inputs["configuration"] is configuration
|
||||
worker_inputs = cast(dict[str, object], captured["worker_inputs"])
|
||||
builders = cast(
|
||||
tuple[ObservatoryWorkerExecutorBuilderRegistration, ...],
|
||||
worker_inputs["builders"],
|
||||
)
|
||||
assert tuple(builder.setup_id for builder in builders) == (
|
||||
service_module.M49_WORKER_SETUP_ID,
|
||||
"lab-v1-eomt-ddrnet-portable-v1",
|
||||
)
|
||||
|
||||
|
||||
def test_fixed_m49_composition_rejects_receipt_asset_drift_before_gateway(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
@@ -467,12 +526,35 @@ def test_entrypoint_environment_requires_all_absolute_fixed_files(tmp_path: Path
|
||||
service_module.M49_WORKER_DEFINITIONS_FILE_ENV: str(tmp_path / "definitions.json"),
|
||||
service_module.M49_WORKER_RUNTIME_REGISTRY_FILE_ENV: str(tmp_path / "runtime.json"),
|
||||
service_module.M49_WORKER_INSTALLATION_RECEIPT_FILE_ENV: str(tmp_path / "receipt.json"),
|
||||
service_module.LAB_V1_WORKER_INSTALLATION_RECEIPT_FILE_ENV: str(
|
||||
service_module._FIXED_LAB_V1_INSTALLATION_RECEIPT_FILE # noqa: SLF001
|
||||
),
|
||||
service_module.LAB_V1_WORKER_RELEASE_CANDIDATE_FILE_ENV: str(
|
||||
service_module._FIXED_LAB_V1_RELEASE_CANDIDATE_FILE # noqa: SLF001
|
||||
),
|
||||
}
|
||||
|
||||
configuration = service_module.M49WorkerEntrypointConfiguration.from_environment(environment)
|
||||
|
||||
assert configuration.worker.base_url == "http://127.0.0.1:18080"
|
||||
assert configuration.installation_receipt_file == tmp_path / "receipt.json"
|
||||
assert configuration.lab_v1_installation_receipt_file == (
|
||||
service_module._FIXED_LAB_V1_INSTALLATION_RECEIPT_FILE # noqa: SLF001
|
||||
)
|
||||
assert configuration.lab_v1_release_candidate_file == (
|
||||
service_module._FIXED_LAB_V1_RELEASE_CANDIDATE_FILE # noqa: SLF001
|
||||
)
|
||||
environment[service_module.LAB_V1_WORKER_RELEASE_CANDIDATE_FILE_ENV] = str(
|
||||
tmp_path / "lab-v1-release.json"
|
||||
)
|
||||
with pytest.raises(
|
||||
service_module.M49WorkerCompositionError,
|
||||
match="fixed /release files",
|
||||
):
|
||||
service_module.M49WorkerEntrypointConfiguration.from_environment(environment)
|
||||
environment[service_module.LAB_V1_WORKER_RELEASE_CANDIDATE_FILE_ENV] = str(
|
||||
service_module._FIXED_LAB_V1_RELEASE_CANDIDATE_FILE # noqa: SLF001
|
||||
)
|
||||
with pytest.raises(service_module.M49WorkerCompositionError, match="is required"):
|
||||
service_module.M49WorkerEntrypointConfiguration.from_environment(
|
||||
{
|
||||
|
||||
@@ -0,0 +1,435 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
|
||||
import pytest
|
||||
|
||||
from k1link.observatory.portable_lab_v1_executor import (
|
||||
PortableLabV1ReleaseCandidate,
|
||||
)
|
||||
from k1link.observatory.portable_run_definitions import (
|
||||
PortableRunDefinitionRegistry,
|
||||
)
|
||||
from k1link.observatory.portable_worker_runtime import (
|
||||
PortableWorkerRuntimeRegistry,
|
||||
)
|
||||
|
||||
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
|
||||
DEFINITIONS = REPOSITORY_ROOT / "config" / "observatory-portable-run-definitions.json"
|
||||
RUNTIME = REPOSITORY_ROOT / "config" / "observatory-worker-runtime-candidates.json"
|
||||
PORTABLE_CONFIG = (
|
||||
REPOSITORY_ROOT / "config" / "perception" / "lab-v1-eomt-ddrnet-portable-v2.json"
|
||||
)
|
||||
PROMOTION_SCRIPT = (
|
||||
REPOSITORY_ROOT
|
||||
/ "experiments"
|
||||
/ "perception"
|
||||
/ "worker"
|
||||
/ "observatory_portable"
|
||||
/ "promote_portable_lab_v1_ready.py"
|
||||
)
|
||||
_SPEC = importlib.util.spec_from_file_location(
|
||||
"observatory_portable_lab_v1_promotion_test",
|
||||
PROMOTION_SCRIPT,
|
||||
)
|
||||
assert _SPEC is not None and _SPEC.loader is not None
|
||||
promotion = importlib.util.module_from_spec(_SPEC)
|
||||
sys.modules[_SPEC.name] = promotion
|
||||
_SPEC.loader.exec_module(promotion)
|
||||
|
||||
|
||||
def _asset(
|
||||
asset_id: str,
|
||||
container_path: str,
|
||||
identity_sha256: str,
|
||||
byte_length: int | None,
|
||||
*,
|
||||
tree: bool,
|
||||
) -> dict[str, object]:
|
||||
return {
|
||||
"asset_id": asset_id,
|
||||
"host_path": (
|
||||
"D:\\NDC_MISSIONCORE\\runtime\\assets\\observatory-portable\\"
|
||||
+ asset_id
|
||||
),
|
||||
"container_path": container_path,
|
||||
"kind": "tree" if tree else "file",
|
||||
"verification": "identity-sha256" if tree else "sha256",
|
||||
"identity_sha256": identity_sha256,
|
||||
"byte_length": byte_length,
|
||||
}
|
||||
|
||||
|
||||
def _input_document(
|
||||
work_root: Path,
|
||||
*,
|
||||
component_receipt: Path,
|
||||
coordinator_receipt: Path,
|
||||
) -> dict[str, object]:
|
||||
eomt_assets = [
|
||||
_asset(
|
||||
"eomt-environment",
|
||||
"/environment",
|
||||
"8c8f343a5368ff17edbb58defa1669f6eccfba767aab897a23693872070ab9e0",
|
||||
211_776_082,
|
||||
tree=True,
|
||||
),
|
||||
_asset(
|
||||
"eomt-ffmpeg-runtime",
|
||||
"/opt/ffmpeg",
|
||||
"03651449fdcccec847a0f1241e1663a82cf374bd94e7470b4ddb0c0e46d88c69",
|
||||
256_208_352,
|
||||
tree=True,
|
||||
),
|
||||
_asset(
|
||||
"eomt-model-cache",
|
||||
"/cache",
|
||||
"064870e58814b97027d6a7ccd553bf51f5b8e6a8ad82a1cc703584d2dca5690c",
|
||||
2_552_355_458,
|
||||
tree=True,
|
||||
),
|
||||
_asset(
|
||||
"eomt-python-environment",
|
||||
"/opt/env",
|
||||
"b3f4efc53af491f174b1cff74b3ba03016e67c9c5c74257b49c6e7dd7d853f20",
|
||||
5_120_848_705,
|
||||
tree=True,
|
||||
),
|
||||
_asset(
|
||||
"eomt-runner-bundle",
|
||||
"/runner",
|
||||
"3bcfb73db5079deffe51173198f7a02e9e4c49f5fc5439d7976757a430fe91d3",
|
||||
144_128,
|
||||
tree=True,
|
||||
),
|
||||
_asset(
|
||||
"eomt-transformers-environment",
|
||||
"/opt/transformers",
|
||||
"f365de01426a33be51a310923c743655634d0868941bbf3f1aae1647fdeadfc9",
|
||||
225_272_284,
|
||||
tree=True,
|
||||
),
|
||||
_asset(
|
||||
"k1-valid-fov-root",
|
||||
"/valid-fov",
|
||||
"f4fc2053e4e6213bb364c8773979b755d5682a81b3946c25ff86274bc5f0031e",
|
||||
6_019,
|
||||
tree=True,
|
||||
),
|
||||
]
|
||||
ddrnet_assets = [
|
||||
_asset(
|
||||
"ddrnet-checkpoint",
|
||||
"/opt/nodedc/assets/ddrnet-checkpoint",
|
||||
"b99c2838051bcd7b092fd3970aa62a77d5c0bbb809c9b9afb2ff4b0ebdaa4ee6",
|
||||
259_419_077,
|
||||
tree=False,
|
||||
),
|
||||
_asset(
|
||||
"ddrnet-goose-mapping",
|
||||
"/opt/nodedc/assets/ddrnet-goose-mapping",
|
||||
"88ae319ba5a3877dd3ae0773f693a6a5fdc283934140de9dfaff029108aefd7f",
|
||||
1_427,
|
||||
tree=False,
|
||||
),
|
||||
_asset(
|
||||
"ddrnet-goose-runner",
|
||||
"/opt/nodedc/assets/ddrnet-goose-runner",
|
||||
"b18ad60f277eea69a240a28f290611b94627fb9707faf1bb3e6e22102dad67c1",
|
||||
32_877,
|
||||
tree=False,
|
||||
),
|
||||
_asset(
|
||||
"vegetation-policy",
|
||||
"/opt/nodedc/assets/vegetation-policy",
|
||||
"b75c4ac841d7b4bcc57f7a9c8417ca2317d8ecfa499e72a9af8a8591a2ec0d35",
|
||||
3_022,
|
||||
tree=False,
|
||||
),
|
||||
_asset(
|
||||
"vegetation-provider-map",
|
||||
"/opt/nodedc/assets/vegetation-provider-map",
|
||||
"f2b69046b6a740fd9532d2d88e7fabae7c20fb662f783c9502adc9026406f352",
|
||||
2_756,
|
||||
tree=False,
|
||||
),
|
||||
]
|
||||
return {
|
||||
"schema_version": promotion.PROMOTION_INPUT_SCHEMA,
|
||||
"source_revision": "1" * 40,
|
||||
"component_source_revision": "2" * 40,
|
||||
"coordinator_image_sha256": "c" * 64,
|
||||
"work_root": {
|
||||
"controller_root": str(work_root),
|
||||
"engine_host_root": "D:\\NDC_MISSIONCORE\\runtime\\work",
|
||||
},
|
||||
"components": {
|
||||
"eomt": {
|
||||
"base_image_sha256": (
|
||||
"58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794"
|
||||
),
|
||||
"derived_image_sha256": "d" * 64,
|
||||
"dockerfile_sha256": "2" * 64,
|
||||
"installer_sha256": "3" * 64,
|
||||
"shared_adapter_sha256": "4" * 64,
|
||||
"component_adapter_sha256": "5" * 64,
|
||||
"assets": eomt_assets,
|
||||
},
|
||||
"ddrnet": {
|
||||
"base_image_sha256": (
|
||||
"591cb382c099eeb05e7ec16e2371e0b2da54d2bb5c49ec0f4ac88dbf72b0f0cd"
|
||||
),
|
||||
"derived_image_sha256": "e" * 64,
|
||||
"dockerfile_sha256": "6" * 64,
|
||||
"installer_sha256": "3" * 64,
|
||||
"shared_adapter_sha256": "4" * 64,
|
||||
"component_adapter_sha256": "7" * 64,
|
||||
"assets": ddrnet_assets,
|
||||
},
|
||||
},
|
||||
"installation_evidence": {
|
||||
"component_image_installer_receipt_file": str(component_receipt),
|
||||
"coordinator_image_installer_receipt_file": str(coordinator_receipt),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _write_installation_evidence(tmp_path: Path) -> tuple[Path, Path]:
|
||||
source_revision = "1" * 40
|
||||
component_source_revision = "2" * 40
|
||||
component = tmp_path / "component-image-installation.json"
|
||||
component.write_text(
|
||||
json.dumps(
|
||||
[
|
||||
{
|
||||
"component": "eomt",
|
||||
"status": "already-installed",
|
||||
"tag": (
|
||||
"ndc/mission-core-lab-v1-eomt-adapter:"
|
||||
f"{component_source_revision[:12]}"
|
||||
),
|
||||
"base_image_sha256": (
|
||||
"58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794"
|
||||
),
|
||||
"derived_image_sha256": "d" * 64,
|
||||
"build_method": "docker-commit-exact-layer-v1",
|
||||
},
|
||||
{
|
||||
"component": "ddrnet",
|
||||
"status": "already-installed",
|
||||
"tag": (
|
||||
"ndc/mission-core-lab-v1-ddrnet-adapter:"
|
||||
f"{component_source_revision[:12]}"
|
||||
),
|
||||
"base_image_sha256": (
|
||||
"591cb382c099eeb05e7ec16e2371e0b2da54d2bb5c49ec0f4ac88dbf72b0f0cd"
|
||||
),
|
||||
"derived_image_sha256": "e" * 64,
|
||||
"build_method": "docker-commit-exact-layer-v1",
|
||||
},
|
||||
]
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
coordinator = tmp_path / "coordinator-image-installation.json"
|
||||
coordinator.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"schema_version": promotion.WORKER_AGENT_IMAGE_INSTALLATION_SCHEMA,
|
||||
"status": "installed",
|
||||
"worker_id": "worker-006",
|
||||
"build_method": "docker-commit-exact-layer-v1",
|
||||
"source_revision": source_revision,
|
||||
"provenance": {
|
||||
"git_archive_sha256": "8" * 64,
|
||||
"git_archive_verification": "external-before-extract",
|
||||
"staged_snapshot_sha256": "9" * 64,
|
||||
"staged_snapshot_file_count": 42,
|
||||
"staged_snapshot_byte_length": 4096,
|
||||
"staged_snapshot_canonicalization": (
|
||||
"utf8-path-nul-length-nul-sha256-lf-v1"
|
||||
),
|
||||
"embedded_snapshot_manifest_sha256": "a" * 64,
|
||||
"embedded_snapshot_manifest_byte_length": 8192,
|
||||
},
|
||||
"base_image_sha256": (
|
||||
"58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794"
|
||||
),
|
||||
"derived_image_sha256": "c" * 64,
|
||||
"image": {
|
||||
"tag": (
|
||||
"ndc/mission-core-observatory-worker-agent:"
|
||||
f"{source_revision[:12]}"
|
||||
),
|
||||
"id": f"sha256:{'c' * 64}",
|
||||
"size_bytes": 1_000_000,
|
||||
"thin_layer_bytes": 10_000,
|
||||
"maximum_thin_layer_bytes": 33_554_432,
|
||||
"rootfs": {
|
||||
"base_layer_count": 42,
|
||||
"derived_layer_count": 1,
|
||||
"derived_layer_diff_id": f"sha256:{'b' * 64}",
|
||||
"pinned_base_is_exact_prefix": True,
|
||||
},
|
||||
},
|
||||
"runtime_contract": {
|
||||
"workdir": "/opt/nodedc/mission-core",
|
||||
"entrypoint": [
|
||||
"python3",
|
||||
"-m",
|
||||
"k1link.observatory.m49_worker_container_main",
|
||||
],
|
||||
"command": [],
|
||||
"authority": "observation-only",
|
||||
"models": "external",
|
||||
"runtime_registries": "external-read-only",
|
||||
},
|
||||
"smoke": {
|
||||
"network": "none",
|
||||
"read_only_rootfs": True,
|
||||
"staged_source_bytes": "matched",
|
||||
"embedded_context_bytes": "matched",
|
||||
"embedded_snapshot_manifest": "matched",
|
||||
"result": "passed",
|
||||
},
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
return component, coordinator
|
||||
|
||||
|
||||
def _files(root: Path) -> dict[str, bytes]:
|
||||
return {
|
||||
path.relative_to(root).as_posix(): path.read_bytes()
|
||||
for path in root.rglob("*")
|
||||
if path.is_file()
|
||||
}
|
||||
|
||||
|
||||
def _row(document: dict[str, object], collection: str, setup_id: str) -> object:
|
||||
rows = cast(list[dict[str, object]], document[collection])
|
||||
return next(row for row in rows if row["setup_id"] == setup_id)
|
||||
|
||||
|
||||
def test_ready_promotion_is_deterministic_additive_and_round_trips(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
work_root = tmp_path / "work"
|
||||
work_root.mkdir()
|
||||
component_receipt, coordinator_receipt = _write_installation_evidence(tmp_path)
|
||||
input_path = tmp_path / "promotion-input.json"
|
||||
input_path.write_text(
|
||||
json.dumps(
|
||||
_input_document(
|
||||
work_root,
|
||||
component_receipt=component_receipt,
|
||||
coordinator_receipt=coordinator_receipt,
|
||||
)
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
inputs = promotion.load_promotion_input(input_path)
|
||||
|
||||
first = promotion.generate_ready_lab_v1_artifacts(
|
||||
promotion=inputs,
|
||||
source_definition_registry=DEFINITIONS,
|
||||
source_runtime_registry=RUNTIME,
|
||||
ddrnet_portable_config=PORTABLE_CONFIG,
|
||||
output_root=tmp_path / "ready-a",
|
||||
)
|
||||
second = promotion.generate_ready_lab_v1_artifacts(
|
||||
promotion=inputs,
|
||||
source_definition_registry=DEFINITIONS,
|
||||
source_runtime_registry=RUNTIME,
|
||||
ddrnet_portable_config=PORTABLE_CONFIG,
|
||||
output_root=tmp_path / "ready-b",
|
||||
)
|
||||
|
||||
assert _files(first.root) == _files(second.root)
|
||||
source_definitions = json.loads(DEFINITIONS.read_text(encoding="utf-8"))
|
||||
ready_definitions_document = json.loads(
|
||||
first.definition_registry_path.read_text(encoding="utf-8")
|
||||
)
|
||||
assert _row(
|
||||
ready_definitions_document,
|
||||
"definitions",
|
||||
"m49-tgs-portable-v2",
|
||||
) == _row(source_definitions, "definitions", "m49-tgs-portable-v2")
|
||||
|
||||
definitions = PortableRunDefinitionRegistry.from_file(
|
||||
first.definition_registry_path
|
||||
)
|
||||
definition = definitions.resolve_setup(promotion.PORTABLE_LAB_V1_SETUP_ID)
|
||||
runtime = PortableWorkerRuntimeRegistry.from_file(
|
||||
first.runtime_registry_path,
|
||||
definitions=definitions,
|
||||
).resolve(definition.setup_id, definition.definition_sha256)
|
||||
release = PortableLabV1ReleaseCandidate.from_file(
|
||||
first.release_candidate_path,
|
||||
repository_root=first.root,
|
||||
)
|
||||
release.bind_definition(definition)
|
||||
|
||||
assert first.release_candidate_path.name == "lab-v1-executor-release.json"
|
||||
assert definition.executor.ready
|
||||
assert runtime.ready
|
||||
assert [
|
||||
asset.asset_id for asset in release.assets if asset.kind == "repository-file"
|
||||
] == [promotion.DDRNET_PORTABLE_CONFIG_ASSET_ID]
|
||||
assert promotion.PORTABLE_LAB_V1_WORKER_INSTALLATION_RECEIPT_ASSET_ID not in {
|
||||
asset.asset_id for asset in release.assets
|
||||
}
|
||||
receipt_requirement = next(
|
||||
asset
|
||||
for asset in runtime.reusable_assets
|
||||
if asset.asset_id
|
||||
== promotion.PORTABLE_LAB_V1_WORKER_INSTALLATION_RECEIPT_ASSET_ID
|
||||
)
|
||||
assert receipt_requirement.sha256 == first.installation_receipt_file_sha256
|
||||
assert first.release_candidate_sha256 == second.release_candidate_sha256
|
||||
assert first.release_sha256 == second.release_sha256
|
||||
assert first.definition_sha256 == second.definition_sha256
|
||||
assert first.runtime_candidate_sha256 == second.runtime_candidate_sha256
|
||||
|
||||
|
||||
def test_promotion_rejects_bare_or_mismatched_installer_claims(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
work_root = tmp_path / "work"
|
||||
work_root.mkdir()
|
||||
component_receipt, coordinator_receipt = _write_installation_evidence(tmp_path)
|
||||
bare = _input_document(
|
||||
work_root,
|
||||
component_receipt=component_receipt,
|
||||
coordinator_receipt=coordinator_receipt,
|
||||
)
|
||||
del bare["installation_evidence"]
|
||||
bare_path = tmp_path / "bare.json"
|
||||
bare_path.write_text(json.dumps(bare), encoding="utf-8")
|
||||
with pytest.raises(promotion.PortableLabV1PromotionError, match="fields are invalid"):
|
||||
promotion.load_promotion_input(bare_path)
|
||||
|
||||
rows = json.loads(component_receipt.read_text(encoding="utf-8"))
|
||||
rows[0]["derived_image_sha256"] = "f" * 64
|
||||
component_receipt.write_text(json.dumps(rows), encoding="utf-8")
|
||||
mismatched = tmp_path / "mismatched.json"
|
||||
mismatched.write_text(
|
||||
json.dumps(
|
||||
_input_document(
|
||||
work_root,
|
||||
component_receipt=component_receipt,
|
||||
coordinator_receipt=coordinator_receipt,
|
||||
)
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
with pytest.raises(
|
||||
promotion.PortableLabV1PromotionError,
|
||||
match="does not bind the promotion",
|
||||
):
|
||||
promotion.load_promotion_input(mismatched)
|
||||
@@ -0,0 +1,95 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from types import SimpleNamespace
|
||||
from typing import cast
|
||||
|
||||
import pytest
|
||||
|
||||
from k1link.observatory import portable_lab_v1_worker as worker_module
|
||||
from k1link.observatory.portable_lab_v1_worker import PortableLabV1WorkerError
|
||||
from k1link.observatory.portable_run_definitions import PortableRunDefinition
|
||||
from k1link.observatory.portable_worker_runtime import PortableWorkerRuntimeCandidate
|
||||
|
||||
|
||||
def _candidate(*, asset_id: str, kind: str = "local-file") -> object:
|
||||
return SimpleNamespace(
|
||||
setup_id="lab-v1-eomt-ddrnet-portable-v1",
|
||||
definition_id="lab-v1-eomt-ddrnet-portable",
|
||||
definition_version=2,
|
||||
definition_sha256="a" * 64,
|
||||
result_contract_sha256="b" * 64,
|
||||
phases=tuple(
|
||||
SimpleNamespace(phase_id=phase_id)
|
||||
for phase_id in worker_module.PORTABLE_LAB_V1_RUNTIME_PHASES
|
||||
),
|
||||
executor=SimpleNamespace(
|
||||
release_id="lab-v1-eomt-ddrnet-v1",
|
||||
release_sha256="c" * 64,
|
||||
image_sha256="d" * 64,
|
||||
),
|
||||
reusable_assets=(
|
||||
SimpleNamespace(
|
||||
asset_id=asset_id,
|
||||
kind=kind,
|
||||
sha256="e" * 64,
|
||||
byte_length=100,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _definition() -> object:
|
||||
return SimpleNamespace(
|
||||
definition_sha256="a" * 64,
|
||||
executable_contract_sha256="f" * 64,
|
||||
)
|
||||
|
||||
|
||||
def _installation() -> object:
|
||||
seal = SimpleNamespace(
|
||||
release_id="lab-v1-eomt-ddrnet-v1",
|
||||
release_sha256="c" * 64,
|
||||
executor_image_sha256="d" * 64,
|
||||
)
|
||||
release = SimpleNamespace(
|
||||
setup_id="lab-v1-eomt-ddrnet-portable-v1",
|
||||
definition_id="lab-v1-eomt-ddrnet-portable",
|
||||
definition_version=2,
|
||||
definition_contract_sha256="f" * 64,
|
||||
result_contract_sha256="b" * 64,
|
||||
assets=(),
|
||||
seal=lambda _inspection: seal,
|
||||
)
|
||||
return SimpleNamespace(release=release, inspection=object())
|
||||
|
||||
|
||||
def test_runtime_only_installation_receipt_anchor_is_not_required_in_release() -> None:
|
||||
worker_module._verify_candidate_release( # noqa: SLF001
|
||||
cast(PortableWorkerRuntimeCandidate, _candidate(
|
||||
asset_id="lab-v1-worker-installation-receipt"
|
||||
)),
|
||||
cast(worker_module.PortableLabV1RunnerInstallation, _installation()),
|
||||
cast(PortableRunDefinition, _definition()),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("asset_id", "kind"),
|
||||
[
|
||||
("unreviewed-runtime-anchor", "local-file"),
|
||||
("lab-v1-worker-installation-receipt", "container-image"),
|
||||
],
|
||||
)
|
||||
def test_no_other_runtime_asset_bypasses_release_coverage(
|
||||
asset_id: str,
|
||||
kind: str,
|
||||
) -> None:
|
||||
with pytest.raises(PortableLabV1WorkerError):
|
||||
worker_module._verify_candidate_release( # noqa: SLF001
|
||||
cast(
|
||||
PortableWorkerRuntimeCandidate,
|
||||
_candidate(asset_id=asset_id, kind=kind),
|
||||
),
|
||||
cast(worker_module.PortableLabV1RunnerInstallation, _installation()),
|
||||
cast(PortableRunDefinition, _definition()),
|
||||
)
|
||||
@@ -49,6 +49,7 @@ RELEASE_SHA256 = "7" * 64
|
||||
IMAGE_SHA256 = "8" * 64
|
||||
RELEASE_CANDIDATE_SHA256 = "9" * 64
|
||||
SOURCE_REVISION = "a" * 40
|
||||
INSTALLER_SHA256 = hashlib.sha256(b"component-image-installer").hexdigest()
|
||||
BASE_IMAGE_SHA256S = {
|
||||
"eomt": "58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794",
|
||||
"ddrnet": "591cb382c099eeb05e7ec16e2371e0b2da54d2bb5c49ec0f4ac88dbf72b0f0cd",
|
||||
@@ -244,6 +245,10 @@ def _image_build(
|
||||
dockerfile_sha256=hashlib.sha256(
|
||||
f"{component}:dockerfile".encode()
|
||||
).hexdigest(),
|
||||
build_method=(
|
||||
service_module.PORTABLE_LAB_V1_COMPONENT_IMAGE_BUILD_METHOD
|
||||
),
|
||||
installer_sha256=INSTALLER_SHA256,
|
||||
shared_adapter_sha256=hashlib.sha256(b"shared-adapter").hexdigest(),
|
||||
component_adapter_sha256=hashlib.sha256(
|
||||
f"{component}:adapter".encode()
|
||||
@@ -293,6 +298,7 @@ def _write_receipt(
|
||||
"ddrnet",
|
||||
derived_image_sha256=runner.ddrnet.image_sha256,
|
||||
),
|
||||
installation_evidence_sha256="d" * 64,
|
||||
)
|
||||
path = tmp_path / path_name
|
||||
path.write_bytes(canonical_json(document))
|
||||
@@ -365,6 +371,7 @@ def _composition_inputs(
|
||||
)
|
||||
for digest in (
|
||||
build.dockerfile_sha256,
|
||||
build.installer_sha256,
|
||||
build.shared_adapter_sha256,
|
||||
build.component_adapter_sha256,
|
||||
build.seal_sha256,
|
||||
@@ -402,6 +409,11 @@ def test_receipt_loader_round_trips_full_runner_identity(tmp_path: Path) -> None
|
||||
receipt.runner_installation.eomt.image_sha256
|
||||
)
|
||||
assert receipt.ddrnet_image_build.network == "none"
|
||||
assert receipt.ddrnet_image_build.build_method == (
|
||||
"docker-commit-exact-layer-v1"
|
||||
)
|
||||
assert receipt.ddrnet_image_build.installer_sha256 == INSTALLER_SHA256
|
||||
assert receipt.installation_evidence_sha256 == "d" * 64
|
||||
assert receipt.runner_installation.definition_sha256 == (
|
||||
definition.definition_sha256
|
||||
)
|
||||
@@ -411,6 +423,37 @@ def test_receipt_loader_round_trips_full_runner_identity(tmp_path: Path) -> None
|
||||
assert receipt.file_sha256 == hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def test_component_image_build_seal_binds_actual_installer_method() -> None:
|
||||
seal = _image_build("eomt", derived_image_sha256="b" * 64)
|
||||
|
||||
with pytest.raises(
|
||||
service_module.PortableLabV1WorkerCompositionError,
|
||||
match="build provenance changed",
|
||||
):
|
||||
service_module.PortableLabV1ComponentImageBuildSeal.seal(
|
||||
component="eomt",
|
||||
base_image_sha256=BASE_IMAGE_SHA256S["eomt"],
|
||||
derived_image_sha256="b" * 64,
|
||||
dockerfile_sha256=seal.dockerfile_sha256,
|
||||
build_method="dockerfile-build-v1",
|
||||
installer_sha256=INSTALLER_SHA256,
|
||||
shared_adapter_sha256=seal.shared_adapter_sha256,
|
||||
component_adapter_sha256=seal.component_adapter_sha256,
|
||||
)
|
||||
|
||||
with pytest.raises(
|
||||
service_module.PortableLabV1WorkerCompositionError,
|
||||
match="image installer SHA-256 is invalid",
|
||||
):
|
||||
replace(seal, installer_sha256="invalid")
|
||||
|
||||
with pytest.raises(
|
||||
service_module.PortableLabV1WorkerCompositionError,
|
||||
match="build provenance changed",
|
||||
):
|
||||
replace(seal, installer_sha256="f" * 64)
|
||||
|
||||
|
||||
def test_receipt_loader_rejects_noncanonical_links_and_oversized_files(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
|
||||
@@ -29,6 +29,7 @@ RUNTIME_REGISTRY_CONTAINER_PATH = (
|
||||
"/run/nodedc/registries/observatory-worker-runtime-candidates.json"
|
||||
)
|
||||
LAB_V1_RECEIPT_CONTAINER_PATH = "/release/lab-v1-worker-installation-receipt.json"
|
||||
LAB_V1_RELEASE_CANDIDATE_CONTAINER_PATH = "/release/lab-v1-executor-release.json"
|
||||
|
||||
|
||||
def _document(path: Path) -> dict[str, object]:
|
||||
@@ -149,8 +150,13 @@ def test_install_plan_requires_offline_build_hardening_smoke_and_unfilled_receip
|
||||
assert document["state"] == "planned-not-built"
|
||||
build = cast(dict[str, object], document["build"])
|
||||
assert build["source_revision"] is None
|
||||
assert build["source_date_epoch"] is None
|
||||
assert build["build_context_sha256"] is None
|
||||
assert build["git_archive_sha256"] is None
|
||||
assert build["staged_snapshot_sha256"] is None
|
||||
assert build["installer"] == (
|
||||
"experiments/perception/worker/observatory_portable/"
|
||||
"Install-Worker006AgentImage.ps1"
|
||||
)
|
||||
assert "not executed" in cast(str, build["dockerfile_role"])
|
||||
materialization = cast(dict[str, object], build["materialization"])
|
||||
assert materialization == {
|
||||
"method": "git archive",
|
||||
@@ -158,25 +164,43 @@ def test_install_plan_requires_offline_build_hardening_smoke_and_unfilled_receip
|
||||
"reject_dirty_worktree": True,
|
||||
"archive_format": "tar",
|
||||
"archive_paths_source": "context manifest context_entries in declared order",
|
||||
"build_context_sha256_subject": "exact git-archive tar bytes",
|
||||
"git_archive_sha256_subject": "exact git-archive tar bytes",
|
||||
"extraction_target": (
|
||||
"D:\\NDC_MISSIONCORE\\runtime\\staging\\"
|
||||
"observatory-worker-agent-<source-revision>"
|
||||
),
|
||||
}
|
||||
staged = cast(dict[str, object], build["staged_snapshot"])
|
||||
assert staged["canonicalization"] == "utf8-path-nul-length-nul-sha256-lf-v1"
|
||||
assert staged["reject_reparse_points"] is True
|
||||
assert staged["reject_unexpected_entries"] is True
|
||||
assert staged["verify_timing"] == [
|
||||
"before temporary container creation",
|
||||
"after source installation and before image commit",
|
||||
]
|
||||
assert staged["embedded_manifest"] == (
|
||||
"/opt/nodedc/mission-core/release/worker-006-agent-staged-snapshot.json"
|
||||
)
|
||||
base = cast(dict[str, object], build["base_image"])
|
||||
assert base["reference"] == BASE_REFERENCE
|
||||
assert base["sha256"] == BASE_SHA256
|
||||
assert base["must_exist_locally"] is True
|
||||
assert base["pull_allowed"] is False
|
||||
docker_build = cast(dict[str, object], build["docker_build"])
|
||||
assert docker_build == {
|
||||
image_materialization = cast(dict[str, object], build["image_materialization"])
|
||||
assert image_materialization == {
|
||||
"method": "docker-commit-exact-layer-v1",
|
||||
"dockerfile_executed": False,
|
||||
"network": "none",
|
||||
"pull": False,
|
||||
"no_cache": True,
|
||||
"provenance": False,
|
||||
"platform": "linux/amd64",
|
||||
"context_input": "exact git-archive tar bytes",
|
||||
"required_build_args": [
|
||||
"NODEDC_SOURCE_REVISION",
|
||||
"NODEDC_BUILD_CONTEXT_SHA256",
|
||||
"SOURCE_DATE_EPOCH",
|
||||
"source_input": "exact read-only staged snapshot",
|
||||
"base_container": "docker create by exact pinned base image ID",
|
||||
"commit": "docker commit --pause=true with fixed config changes",
|
||||
"maximum_thin_layer_bytes": 33554432,
|
||||
"embedded_payload": [
|
||||
"/opt/nodedc/mission-core/src/k1link",
|
||||
"/opt/nodedc/mission-core/release/worker-006-agent-build-context.json",
|
||||
"/opt/nodedc/mission-core/release/worker-006-agent-staged-snapshot.json",
|
||||
],
|
||||
}
|
||||
assert build["required_preflight"] == [
|
||||
@@ -185,6 +209,8 @@ def test_install_plan_requires_offline_build_hardening_smoke_and_unfilled_receip
|
||||
"selected revision equals HEAD",
|
||||
"base image inspect ID equals the pinned SHA-256",
|
||||
"context archive contains exactly the context manifest entries",
|
||||
"git archive SHA-256 was verified externally before extraction",
|
||||
"staged snapshot SHA-256 matches the exact canonical regular-file inventory",
|
||||
]
|
||||
|
||||
acceptance = cast(dict[str, object], document["build_acceptance"])
|
||||
@@ -193,10 +219,31 @@ def test_install_plan_requires_offline_build_hardening_smoke_and_unfilled_receip
|
||||
assert labels["com.nodedc.base-image.sha256"] == BASE_SHA256
|
||||
assert labels["com.nodedc.models"] == "external"
|
||||
assert labels["com.nodedc.runtime-registries"] == "external-read-only"
|
||||
assert labels["com.nodedc.staged-snapshot.sha256"] == (
|
||||
"<staged-snapshot-sha256>"
|
||||
)
|
||||
assert labels["com.nodedc.embedded-snapshot-manifest.sha256"] == (
|
||||
"<embedded-snapshot-manifest-sha256>"
|
||||
)
|
||||
assert labels["com.nodedc.build-method"] == "docker-commit-exact-layer-v1"
|
||||
rootfs = cast(dict[str, object], acceptance["rootfs"])
|
||||
assert rootfs == {
|
||||
"base_layer_chain": "exact prefix of the pinned base image RootFS.Layers",
|
||||
"derived_layer_count": 1,
|
||||
"derived_layer_diff_id_required": True,
|
||||
"maximum_thin_layer_bytes": 33554432,
|
||||
}
|
||||
embedded = cast(dict[str, object], acceptance["embedded_identity"])
|
||||
assert "byte-for-byte" in cast(str, embedded["source_tree"])
|
||||
assert "byte-for-byte" in cast(str, embedded["context_manifest"])
|
||||
assert "every staged file" in cast(str, embedded["snapshot_manifest"])
|
||||
smoke = cast(dict[str, object], acceptance["smoke"])
|
||||
assert smoke["network"] == "none"
|
||||
assert smoke["read_only_rootfs"] is True
|
||||
assert smoke["platform"] == "linux/amd64"
|
||||
assert smoke["staged_source_bytes"] == "matched"
|
||||
assert smoke["embedded_context_bytes"] == "matched"
|
||||
assert smoke["embedded_snapshot_manifest"] == "matched"
|
||||
assert smoke["expected_result"] == "exit-0"
|
||||
|
||||
runtime = cast(dict[str, object], document["runtime"])
|
||||
@@ -215,6 +262,9 @@ def test_install_plan_requires_offline_build_hardening_smoke_and_unfilled_receip
|
||||
"MISSIONCORE_OBSERVATORY_LAB_V1_INSTALLATION_RECEIPT_FILE": (
|
||||
LAB_V1_RECEIPT_CONTAINER_PATH
|
||||
),
|
||||
"MISSIONCORE_OBSERVATORY_LAB_V1_RELEASE_CANDIDATE_FILE": (
|
||||
LAB_V1_RELEASE_CANDIDATE_CONTAINER_PATH
|
||||
),
|
||||
}
|
||||
registry_files = cast(dict[str, object], runtime["runtime_registry_files"])
|
||||
assert registry_files["binding"] == "individual read-only bind files"
|
||||
@@ -339,19 +389,69 @@ def test_install_plan_requires_offline_build_hardening_smoke_and_unfilled_receip
|
||||
"state": "external-installed-receipt-required",
|
||||
"environment_variable": "MISSIONCORE_OBSERVATORY_LAB_V1_INSTALLATION_RECEIPT_FILE",
|
||||
"container_path": LAB_V1_RECEIPT_CONTAINER_PATH,
|
||||
"release_candidate_environment_variable": (
|
||||
"MISSIONCORE_OBSERVATORY_LAB_V1_RELEASE_CANDIDATE_FILE"
|
||||
),
|
||||
"release_candidate_container_path": (
|
||||
LAB_V1_RELEASE_CANDIDATE_CONTAINER_PATH
|
||||
),
|
||||
"release_repository_root": "/release",
|
||||
"mode": "read-only",
|
||||
"owns_component_image_identities": True,
|
||||
"queued_jobs_may_override_component_images": False,
|
||||
}
|
||||
|
||||
receipt = cast(dict[str, object], document["receipt_skeleton"])
|
||||
assert receipt["receipt_state"] == "not-built"
|
||||
assert receipt["schema_version"] == (
|
||||
"missioncore.observatory-worker-agent-image-installation/v1"
|
||||
)
|
||||
assert receipt["status"] is None
|
||||
assert receipt["build_method"] == "docker-commit-exact-layer-v1"
|
||||
assert receipt["source_revision"] is None
|
||||
assert receipt["source_date_epoch"] is None
|
||||
assert receipt["build_context_sha256"] is None
|
||||
provenance = cast(dict[str, object], receipt["provenance"])
|
||||
assert provenance == {
|
||||
"git_archive_sha256": None,
|
||||
"git_archive_verification": "external-before-extract",
|
||||
"staged_snapshot_sha256": None,
|
||||
"staged_snapshot_file_count": None,
|
||||
"staged_snapshot_byte_length": None,
|
||||
"staged_snapshot_canonicalization": (
|
||||
"utf8-path-nul-length-nul-sha256-lf-v1"
|
||||
),
|
||||
"embedded_snapshot_manifest_sha256": None,
|
||||
"embedded_snapshot_manifest_byte_length": None,
|
||||
}
|
||||
assert receipt["base_image_sha256"] == BASE_SHA256
|
||||
assert receipt["derived_image_sha256"] is None
|
||||
image = cast(dict[str, object], receipt["image"])
|
||||
assert image == {"tag": None, "id": None, "size_bytes": None}
|
||||
assert receipt["models_baked_into_image"] is False
|
||||
assert receipt["runtime_registries_baked_into_image"] is False
|
||||
assert receipt["authority"] == AUTHORITY
|
||||
assert image == {
|
||||
"tag": None,
|
||||
"id": None,
|
||||
"size_bytes": None,
|
||||
"thin_layer_bytes": None,
|
||||
"maximum_thin_layer_bytes": 33554432,
|
||||
"rootfs": {
|
||||
"base_layer_count": None,
|
||||
"derived_layer_count": 1,
|
||||
"derived_layer_diff_id": None,
|
||||
"pinned_base_is_exact_prefix": True,
|
||||
},
|
||||
}
|
||||
runtime_contract = cast(dict[str, object], receipt["runtime_contract"])
|
||||
assert runtime_contract == {
|
||||
"workdir": "/opt/nodedc/mission-core",
|
||||
"entrypoint": ENTRYPOINT,
|
||||
"command": [],
|
||||
"authority": "observation-only",
|
||||
"models": "external",
|
||||
"runtime_registries": "external-read-only",
|
||||
}
|
||||
receipt_smoke = cast(dict[str, object], receipt["smoke"])
|
||||
assert receipt_smoke == {
|
||||
"network": "none",
|
||||
"read_only_rootfs": True,
|
||||
"staged_source_bytes": None,
|
||||
"embedded_context_bytes": None,
|
||||
"embedded_snapshot_manifest": None,
|
||||
"result": "not-run",
|
||||
}
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
|
||||
INSTALLER = (
|
||||
REPOSITORY_ROOT
|
||||
/ "experiments/perception/worker/observatory_portable"
|
||||
/ "Install-Worker006AgentImage.ps1"
|
||||
)
|
||||
BASE_SHA256 = "58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794"
|
||||
|
||||
|
||||
def _script() -> str:
|
||||
return INSTALLER.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_worker_agent_installer_is_local_offline_and_non_buildkit() -> None:
|
||||
script = _script()
|
||||
|
||||
assert '"docker-commit-exact-layer-v1"' in script
|
||||
assert BASE_SHA256 in script
|
||||
assert '"staging\\observatory-worker-agent-$SourceRevision"' in script
|
||||
assert "docker create `" in script
|
||||
assert "--network none" in script
|
||||
assert "--cap-drop ALL" in script
|
||||
assert "--security-opt no-new-privileges" in script
|
||||
assert "target=/nodedc-build-source,readonly" in script
|
||||
assert "docker commit --pause=true @changes $containerId $tag" in script
|
||||
assert "docker rm -f $containerId" in script
|
||||
assert "docker rm -f $containerName" not in script
|
||||
|
||||
lowered = script.lower()
|
||||
assert "docker build" not in lowered
|
||||
assert "docker buildx" not in lowered
|
||||
assert "docker pull" not in lowered
|
||||
assert "--pull" not in lowered
|
||||
assert "invoke-webrequest" not in lowered
|
||||
assert "start-bitstransfer" not in lowered
|
||||
assert "curl " not in lowered
|
||||
assert "wget " not in lowered
|
||||
assert "smb" not in lowered
|
||||
|
||||
|
||||
def test_worker_agent_installer_separates_archive_and_staged_identities() -> None:
|
||||
script = _script()
|
||||
|
||||
assert "[string]$ExpectedGitArchiveSha256" in script
|
||||
assert "[string]$ExpectedStagedSnapshotSha256" in script
|
||||
assert script.count("Get-StagedSnapshotInspection $StagedSnapshotRoot") == 2
|
||||
assert '"com.nodedc.build-context.sha256" = $ExpectedGitArchiveSha256' in script
|
||||
assert (
|
||||
'"com.nodedc.staged-snapshot.sha256" = $ExpectedStagedSnapshotSha256'
|
||||
in script
|
||||
)
|
||||
assert "LABEL com.nodedc.build-context.sha256=$ExpectedGitArchiveSha256" in script
|
||||
assert (
|
||||
"LABEL com.nodedc.staged-snapshot.sha256=$ExpectedStagedSnapshotSha256"
|
||||
in script
|
||||
)
|
||||
assert 'git_archive_verification = "external-before-extract"' in script
|
||||
assert 'canonicalization = "utf8-path-nul-length-nul-sha256-lf-v1"' in script
|
||||
assert 'Assert-ExactDirectoryChildren $Root @("experiments", "src")' in script
|
||||
assert 'Assert-ExactDirectoryChildren $srcRoot @("k1link")' in script
|
||||
assert "Dockerfile.worker-006-agent" in script
|
||||
assert "worker-006-agent-build-context.json" in script
|
||||
assert (
|
||||
'schema_version = "missioncore.observatory-worker-agent-embedded-snapshot/v1"'
|
||||
in script
|
||||
)
|
||||
assert "files = @($SnapshotInspection.files)" in script
|
||||
assert "embedded_snapshot_manifest_sha256" in script
|
||||
|
||||
|
||||
def test_worker_agent_installer_copies_only_runtime_source_and_contract() -> None:
|
||||
script = _script()
|
||||
|
||||
assert (
|
||||
"cp -a /nodedc-build-source/src/k1link "
|
||||
"/opt/nodedc/mission-core/src/k1link"
|
||||
) in script
|
||||
assert (
|
||||
'"cp /nodedc-build-source/$ContextManifestRelativePath " +'
|
||||
in script
|
||||
)
|
||||
assert "/opt/nodedc/mission-core/release/worker-006-agent-build-context.json" in script
|
||||
assert (
|
||||
"/opt/nodedc/mission-core/release/worker-006-agent-staged-snapshot.json"
|
||||
in script
|
||||
)
|
||||
assert "cp -a /nodedc-build-source/experiments" not in script
|
||||
assert "cp /nodedc-build-source/$DockerfileRelativePath" not in script
|
||||
assert "test ! -e /opt/nodedc/mission-core/experiments" in script
|
||||
assert "cmp -s" in script
|
||||
assert "find /opt/nodedc/mission-core -type d -exec chmod 0555" in script
|
||||
assert "find /opt/nodedc/mission-core -type f -exec chmod 0444" in script
|
||||
assert "chmod 0555 /run/nodedc /run/nodedc/registries" in script
|
||||
|
||||
|
||||
def test_worker_agent_installer_proves_base_chain_and_embedded_bytes() -> None:
|
||||
script = _script()
|
||||
|
||||
assert "$baseLayers = @($BaseImage.RootFS.Layers)" in script
|
||||
assert "$imageLayers = @($Image.RootFS.Layers)" in script
|
||||
assert "$imageLayers.Count -ne ($baseLayers.Count + 1)" in script
|
||||
assert "RootFS does not extend the pinned base layer chain" in script
|
||||
assert "derived_layer_diff_id = $derivedLayerDiffId" in script
|
||||
assert "pinned_base_is_exact_prefix = $true" in script
|
||||
assert "target=/nodedc-verify-source,readonly" in script
|
||||
assert "target=/nodedc-verify-snapshot.json,readonly" in script
|
||||
assert "cmp -s /nodedc-verify-snapshot.json" in script
|
||||
assert (
|
||||
"/opt/nodedc/mission-core/release/worker-006-agent-staged-snapshot.json"
|
||||
in script
|
||||
)
|
||||
assert "find /nodedc-verify-source/src/k1link -type f" in script
|
||||
assert "find /opt/nodedc/mission-core/src/k1link -type f" in script
|
||||
assert "cmp -s /nodedc-verify-source/$ContextManifestRelativePath" in script
|
||||
|
||||
|
||||
def test_worker_agent_installer_removes_failed_new_image_and_temporary_files() -> None:
|
||||
script = _script()
|
||||
|
||||
assert "$imageCommitted = $true" in script
|
||||
assert (
|
||||
"Invoke-InstalledImageSmoke $tag $StagedSnapshotRoot $embeddedManifest"
|
||||
in script
|
||||
)
|
||||
assert "docker image rm --force $committedImageId" in script
|
||||
assert "image verification failed and committed image cleanup failed" in script
|
||||
assert "Remove-Item -LiteralPath $embeddedManifest.path -Force" in script
|
||||
|
||||
|
||||
def test_worker_agent_installer_seals_runtime_contract_smoke_and_output() -> None:
|
||||
script = _script()
|
||||
|
||||
for value in (
|
||||
"PYTHONPATH=/opt/nodedc/mission-core/src",
|
||||
"PYTHONNOUSERSITE=1",
|
||||
"PYTHONDONTWRITEBYTECODE=1",
|
||||
"PYTHONUNBUFFERED=1",
|
||||
"WORKDIR $ImageWorkdir",
|
||||
"USER 0:0",
|
||||
"ENTRYPOINT $ImageEntrypoint",
|
||||
"CMD $ImageCommand",
|
||||
"com.nodedc.authority=observation-only",
|
||||
"com.nodedc.models=external",
|
||||
"com.nodedc.runtime-registries=external-read-only",
|
||||
):
|
||||
assert value in script
|
||||
assert "--read-only" in script
|
||||
assert '"/tmp:rw,noexec,nosuid,size=16m"' in script
|
||||
assert "import k1link.observatory.m49_worker_container_main" in script
|
||||
assert "import k1link.observatory.m49_worker_service" in script
|
||||
assert "compose_installed_m49_worker_service" in script
|
||||
assert "$MaximumLayerBytes = [int64](32MB)" in script
|
||||
assert "image is not within the thin-layer bound" in script
|
||||
assert (
|
||||
'schema_version = "missioncore.observatory-worker-agent-image-installation/v1"'
|
||||
in script
|
||||
)
|
||||
assert "derived_image_sha256 = ([string]$Image.Id).Substring(7)" in script
|
||||
assert 'result = "passed"' in script
|
||||
Reference in New Issue
Block a user