fix: make no-send release publication repeatable

This commit is contained in:
twentyOne2x 2026-07-22 05:44:23 +02:00
parent d36a6d8cc1
commit 7aabfa4ab8
4 changed files with 1408 additions and 117 deletions

View file

@ -24,15 +24,6 @@ jobs:
name: Build, smoke, and publish exact no-send release name: Build, smoke, and publish exact no-send release
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 90 timeout-minutes: 90
env:
RELEASE_ROOT: ${{ runner.temp }}/leoclean-nosend-release
RUNTIME_ARTIFACT: ${{ runner.temp }}/leoclean-nosend-release/runtime-artifact
RUNTIME_RECEIPT: ${{ runner.temp }}/leoclean-nosend-release/evidence/runtime-receipt.json
OCI_SMOKE_RECEIPT: ${{ runner.temp }}/leoclean-nosend-release/evidence/oci-smoke.json
IMAGE_CONTEXT: ${{ runner.temp }}/leoclean-nosend-release/image-context
BUILD_PUSH_RECEIPT: ${{ runner.temp }}/leoclean-nosend-release/receipts/build-push-receipt.json
RELEASE_BUNDLE: ${{ runner.temp }}/leoclean-nosend-release/final/release-v3
DOCKER_CONFIG: ${{ runner.temp }}/leoclean-nosend-release/docker-config
steps: steps:
- name: Require manual main dispatch - name: Require manual main dispatch
shell: bash shell: bash
@ -41,6 +32,9 @@ jobs:
test "${GITHUB_EVENT_NAME}" = "workflow_dispatch" test "${GITHUB_EVENT_NAME}" = "workflow_dispatch"
test "${GITHUB_REF}" = "refs/heads/main" test "${GITHUB_REF}" = "refs/heads/main"
test "${GITHUB_REF_TYPE}" = "branch" test "${GITHUB_REF_TYPE}" = "branch"
test "${GITHUB_REPOSITORY}" = "living-ip/teleo-infrastructure"
test "${GITHUB_WORKFLOW_REF}" = \
"living-ip/teleo-infrastructure/.github/workflows/gcp-leoclean-nosend-release.yml@refs/heads/main"
- name: Check out exact dispatched revision - name: Check out exact dispatched revision
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
@ -57,6 +51,24 @@ jobs:
test "$(git rev-parse --verify refs/remotes/origin/main^{commit})" = "${GITHUB_SHA}" test "$(git rev-parse --verify refs/remotes/origin/main^{commit})" = "${GITHUB_SHA}"
test -z "$(git status --porcelain=v1 --untracked-files=all)" test -z "$(git status --porcelain=v1 --untracked-files=all)"
- name: Initialize private release paths
shell: bash
run: |
set -euo pipefail
release_root="${RUNNER_TEMP}/leoclean-nosend-release"
install -d -m 0700 "${release_root}"
{
echo "RELEASE_ROOT=${release_root}"
echo "RUNTIME_ARTIFACT=${release_root}/runtime-artifact"
echo "RUNTIME_RECEIPT=${release_root}/evidence/runtime-receipt.json"
echo "OCI_SMOKE_RECEIPT=${release_root}/evidence/oci-smoke.json"
echo "IMAGE_CONTEXT=${release_root}/image-context"
echo "BUILD_PUSH_RECEIPT=${release_root}/receipts/build-push-receipt.json"
echo "PUBLISH_OUTCOME=${release_root}/evidence/publish-outcome.json"
echo "RELEASE_BUNDLE=${release_root}/final/release-v3"
echo "DOCKER_CONFIG=${release_root}/docker-config"
} >>"${GITHUB_ENV}"
- name: Set up exact Python - name: Set up exact Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with: with:
@ -169,6 +181,16 @@ jobs:
assert smoke["identity_sha256"] == image_input["identity"]["bundle_sha256"] assert smoke["identity_sha256"] == image_input["identity"]["bundle_sha256"]
PY PY
- name: Initialize pre-mutation publish outcome
id: init_outcome
shell: bash
run: |
set -euo pipefail
install -d -m 0700 "$(dirname "${PUBLISH_OUTCOME}")"
python ops/gcp_leoclean_nosend_package.py init-outcome \
--image-input "${IMAGE_CONTEXT}/image-input.json" \
--output-journal "${PUBLISH_OUTCOME}"
- name: Authenticate to Google Cloud through WIF - name: Authenticate to Google Cloud through WIF
id: auth id: auth
uses: google-github-actions/auth@c200f3691d83b41bf9bbd8638997a462592937ed uses: google-github-actions/auth@c200f3691d83b41bf9bbd8638997a462592937ed
@ -189,6 +211,8 @@ jobs:
set -euo pipefail set -euo pipefail
repository_readback="${RUNNER_TEMP}/leoclean-nosend-repository.json" repository_readback="${RUNNER_TEMP}/leoclean-nosend-repository.json"
umask 077 umask 077
test "$(gcloud auth list --filter=status:ACTIVE --format='value(account)')" = \
"${ARTIFACT_SERVICE_ACCOUNT}"
gcloud artifacts repositories describe "${ARTIFACT_REPOSITORY}" \ gcloud artifacts repositories describe "${ARTIFACT_REPOSITORY}" \
--project "${PROJECT_ID}" \ --project "${PROJECT_ID}" \
--location "${REGION}" \ --location "${REGION}" \
@ -218,6 +242,7 @@ jobs:
chmod 0600 "${DOCKER_CONFIG}/config.json" chmod 0600 "${DOCKER_CONFIG}/config.json"
- name: Build and push one immutable candidate - name: Build and push one immutable candidate
id: build_push
shell: bash shell: bash
run: | run: |
set -euo pipefail set -euo pipefail
@ -228,10 +253,13 @@ jobs:
--docker-binary /usr/bin/docker \ --docker-binary /usr/bin/docker \
--docker-host unix:///var/run/docker.sock \ --docker-host unix:///var/run/docker.sock \
--docker-config "${DOCKER_CONFIG}" \ --docker-config "${DOCKER_CONFIG}" \
--gcloud-binary "$(command -v gcloud)" \
--output-receipt "${BUILD_PUSH_RECEIPT}" \ --output-receipt "${BUILD_PUSH_RECEIPT}" \
--outcome-journal "${PUBLISH_OUTCOME}" \
--execute-staging-push --execute-staging-push
- name: Finalize and validate release v3 bundle - name: Finalize and validate release v3 bundle
id: finalize
shell: bash shell: bash
run: | run: |
set -euo pipefail set -euo pipefail
@ -265,20 +293,41 @@ jobs:
) )
PY PY
- name: Upload exact build and release evidence - name: Remove ephemeral Docker authentication
id: cleanup
if: always()
shell: bash
run: |
set -euo pipefail
rm -rf -- "${DOCKER_CONFIG}"
test ! -e "${DOCKER_CONFIG}"
test ! -L "${DOCKER_CONFIG}"
- name: Finalize publish outcome
if: always() && steps.init_outcome.outcome == 'success'
shell: bash
run: |
set -euo pipefail
python ops/gcp_leoclean_nosend_package.py complete-outcome \
--image-input "${IMAGE_CONTEXT}/image-input.json" \
--outcome-journal "${PUBLISH_OUTCOME}" \
--build-push-receipt "${BUILD_PUSH_RECEIPT}" \
--release "${RELEASE_BUNDLE}/release.json" \
--build-outcome "${{ steps.build_push.outcome }}" \
--finalize-outcome "${{ steps.finalize.outcome }}" \
--cleanup-outcome "${{ steps.cleanup.outcome }}"
- name: Upload exact available build and release evidence
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with: with:
name: leoclean-nosend-release-${{ github.sha }} name: leoclean-nosend-release-${{ github.sha }}
path: | path: |
${{ env.RUNTIME_RECEIPT }} ${{ runner.temp }}/leoclean-nosend-release/evidence/runtime-receipt.json
${{ env.OCI_SMOKE_RECEIPT }} ${{ runner.temp }}/leoclean-nosend-release/evidence/oci-smoke.json
${{ env.BUILD_PUSH_RECEIPT }} ${{ runner.temp }}/leoclean-nosend-release/evidence/publish-outcome.json
${{ env.RELEASE_BUNDLE }}/release.json ${{ runner.temp }}/leoclean-nosend-release/receipts/build-push-receipt.json
${{ env.RELEASE_BUNDLE }}/leoclean-gcp-nosend.service ${{ runner.temp }}/leoclean-nosend-release/final/release-v3/release.json
if-no-files-found: error ${{ runner.temp }}/leoclean-nosend-release/final/release-v3/leoclean-gcp-nosend.service
if-no-files-found: warn
retention-days: 30 retention-days: 30
- name: Remove ephemeral Docker authentication
if: always()
shell: bash
run: rm -rf -- "${DOCKER_CONFIG}"

View file

@ -4,6 +4,7 @@
from __future__ import annotations from __future__ import annotations
import argparse import argparse
import copy
import ctypes import ctypes
import errno import errno
import fcntl import fcntl
@ -15,6 +16,7 @@ import shutil
import stat import stat
import subprocess import subprocess
import sys import sys
import time
import tomllib import tomllib
import uuid import uuid
from collections.abc import Mapping from collections.abc import Mapping
@ -34,6 +36,7 @@ from scripts import leo_identity_manifest as strict_identity_manifest # noqa: E
IMAGE_INPUT_SCHEMA = "livingip.leocleanNoSendImageInput.v2" IMAGE_INPUT_SCHEMA = "livingip.leocleanNoSendImageInput.v2"
IMAGE_INSPECTION_SCHEMA = "livingip.leocleanNoSendImageInspection.v1" IMAGE_INSPECTION_SCHEMA = "livingip.leocleanNoSendImageInspection.v1"
BUILD_PUSH_RECEIPT_SCHEMA = "livingip.leocleanNoSendBuildPushReceipt.v1" BUILD_PUSH_RECEIPT_SCHEMA = "livingip.leocleanNoSendBuildPushReceipt.v1"
PUBLISH_OUTCOME_SCHEMA = "livingip.leocleanNoSendPublishOutcome.v1"
RELEASE_SCHEMA = "livingip.leocleanNoSendRelease.v3" RELEASE_SCHEMA = "livingip.leocleanNoSendRelease.v3"
ARTIFACT_MANIFEST_SCHEMA = "livingip.leocleanNoSendArtifactManifest.v1" ARTIFACT_MANIFEST_SCHEMA = "livingip.leocleanNoSendArtifactManifest.v1"
ARTIFACT_RECEIPT_SCHEMA = "livingip.leocleanNoSendArtifactVerification.v1" ARTIFACT_RECEIPT_SCHEMA = "livingip.leocleanNoSendArtifactVerification.v1"
@ -50,6 +53,9 @@ SERVICE = "leoclean-gcp-nosend.service"
SERVICE_ACCOUNT = "sa-teleo-staging-vm@teleo-501523.iam.gserviceaccount.com" SERVICE_ACCOUNT = "sa-teleo-staging-vm@teleo-501523.iam.gserviceaccount.com"
PLATFORM = "linux/amd64" PLATFORM = "linux/amd64"
IMAGE_REPOSITORY = "europe-west6-docker.pkg.dev/teleo-501523/teleo/leoclean-nosend-staging" IMAGE_REPOSITORY = "europe-west6-docker.pkg.dev/teleo-501523/teleo/leoclean-nosend-staging"
ARTIFACT_PACKAGE_RESOURCE = (
"projects/teleo-501523/locations/europe-west6/repositories/teleo/packages/leoclean-nosend-staging"
)
CONTAINER_NAME = "livingip-leoclean-nosend" CONTAINER_NAME = "livingip-leoclean-nosend"
LOCAL_BUILD_REPOSITORY = "livingip-leoclean-nosend-build" LOCAL_BUILD_REPOSITORY = "livingip-leoclean-nosend-build"
DOCKER_BINARY = "/usr/bin/docker" DOCKER_BINARY = "/usr/bin/docker"
@ -83,7 +89,9 @@ RUNTIME_UID = 65532
RUNTIME_GID = 65532 RUNTIME_GID = 65532
MAX_DOCKER_INSPECTION_BYTES = 1024 * 1024 MAX_DOCKER_INSPECTION_BYTES = 1024 * 1024
MAX_DOCKER_COMMAND_OUTPUT_BYTES = 1024 * 1024 MAX_DOCKER_COMMAND_OUTPUT_BYTES = 1024 * 1024
MAX_GCLOUD_COMMAND_OUTPUT_BYTES = 1024 * 1024
MAX_BUILD_PUSH_RECEIPT_BYTES = 256 * 1024 MAX_BUILD_PUSH_RECEIPT_BYTES = 256 * 1024
MAX_PUBLISH_OUTCOME_BYTES = 256 * 1024
IMAGE_RUNTIME_CONFIG = { IMAGE_RUNTIME_CONFIG = {
"user": None, "user": None,
"environment": [ "environment": [
@ -138,6 +146,8 @@ IMAGE_RUNTIME_CONFIG = {
HEX_40 = re.compile(r"^[0-9a-f]{40}$") HEX_40 = re.compile(r"^[0-9a-f]{40}$")
HEX_64 = re.compile(r"^[0-9a-f]{64}$") HEX_64 = re.compile(r"^[0-9a-f]{64}$")
IMAGE_REFERENCE = re.compile(rf"^{re.escape(IMAGE_REPOSITORY)}@sha256:([0-9a-f]{{64}})$") IMAGE_REFERENCE = re.compile(rf"^{re.escape(IMAGE_REPOSITORY)}@sha256:([0-9a-f]{{64}})$")
CANDIDATE_TAG = re.compile(r"^candidate-([0-9a-f]{64})$")
CANDIDATE_REFERENCE = re.compile(rf"^{re.escape(IMAGE_REPOSITORY)}:candidate-([0-9a-f]{{64}})$")
FORBIDDEN_DESCRIPTOR_MARKERS = ( FORBIDDEN_DESCRIPTOR_MARKERS = (
"77.42.65.182", "77.42.65.182",
"gcp-teleo-pgvector-standby-postgres-password", "gcp-teleo-pgvector-standby-postgres-password",
@ -1821,6 +1831,244 @@ def build_build_push_receipt(
return receipt return receipt
def _seal_publish_outcome(stable: dict[str, Any]) -> dict[str, Any]:
value = {**stable, "outcome_sha256": canonical_sha256(stable)}
validate_publish_outcome(value)
return value
def validate_publish_outcome(value: dict[str, Any], *, image_input: dict[str, Any] | None = None) -> None:
_require(
set(value)
== {
"schema",
"status",
"source_revision",
"input_sha256",
"candidate",
"registry",
"phase",
"failure_phase",
"cleanup",
"release",
"events",
"outcome_sha256",
},
"publish outcome fields are not exact",
)
_require(value.get("schema") == PUBLISH_OUTCOME_SCHEMA, "publish outcome schema is invalid")
_require(value.get("status") in {"in_progress", "pass", "fail"}, "publish outcome status is invalid")
_require(_is_revision(value.get("source_revision")), "publish outcome source revision is invalid")
_require(_is_sha256(value.get("input_sha256")), "publish outcome input hash is invalid")
if image_input is not None:
validate_image_input(image_input)
_require(
value["source_revision"] == image_input["runtime"]["teleo_git_head"]
and value["input_sha256"] == image_input["input_sha256"],
"publish outcome image-input binding drifted",
)
candidate = value.get("candidate")
_require(
isinstance(candidate, dict) and set(candidate) == {"tag", "reference", "local_build_config_digest"},
"publish outcome candidate fields are not exact",
)
candidate_values = (
candidate.get("tag"),
candidate.get("reference"),
candidate.get("local_build_config_digest"),
)
if candidate_values == (None, None, None):
pass
else:
tag, reference, local_build_config_digest = candidate_values
_require(
isinstance(tag, str)
and CANDIDATE_TAG.fullmatch(tag) is not None
and isinstance(reference, str)
and CANDIDATE_REFERENCE.fullmatch(reference) is not None
and isinstance(local_build_config_digest, str)
and local_build_config_digest.startswith("sha256:")
and _is_sha256(local_build_config_digest.removeprefix("sha256:")),
"publish outcome candidate is invalid",
)
_require(
tag == f"candidate-{value['input_sha256']}" and reference == f"{IMAGE_REPOSITORY}:{tag}",
"candidate binding drifted",
)
registry = value.get("registry")
_require(
isinstance(registry, dict)
and set(registry) == {"repository", "preexisting", "push_attempted", "action", "observed_digest"},
"publish outcome registry fields are not exact",
)
_require(registry.get("repository") == IMAGE_REPOSITORY, "publish outcome repository is not exact")
_require(registry.get("preexisting") in {None, True, False}, "publish outcome preexisting state is invalid")
_require(type(registry.get("push_attempted")) is bool, "publish outcome push-attempt state is invalid")
_require(
registry.get("action")
in {"none", "observed", "fresh_push", "reused", "indeterminate", "reconciled_after_attempt"},
"publish outcome registry action is invalid",
)
observed_digest = registry.get("observed_digest")
_require(
observed_digest is None
or (
isinstance(observed_digest, str)
and observed_digest.startswith("sha256:")
and _is_sha256(observed_digest.removeprefix("sha256:"))
),
"publish outcome observed digest is invalid",
)
candidate_is_set = candidate_values != (None, None, None)
registry_state = (
registry["preexisting"],
registry["push_attempted"],
registry["action"],
observed_digest is not None,
)
_require(
registry_state
in {
(None, False, "none", False),
(False, False, "none", False),
(True, False, "observed", True),
(True, False, "reused", True),
(False, True, "indeterminate", False),
(False, True, "indeterminate", True),
(False, True, "fresh_push", True),
(False, True, "reconciled_after_attempt", True),
},
"publish outcome registry state is inconsistent",
)
_require(
candidate_is_set or registry_state == (None, False, "none", False),
"publish outcome registry activity has no candidate",
)
phase = value.get("phase")
failure_phase = value.get("failure_phase")
_require(isinstance(phase, str) and bool(re.fullmatch(r"[a-z][a-z0-9_]{0,63}", phase)), "publish phase is invalid")
_require(
failure_phase is None
or (isinstance(failure_phase, str) and bool(re.fullmatch(r"[a-z][a-z0-9_]{0,63}", failure_phase))),
"publish failure phase is invalid",
)
_require(
(value["status"] == "fail") == (failure_phase is not None),
"publish failure status and phase disagree",
)
cleanup = value.get("cleanup")
_require(
isinstance(cleanup, dict)
and cleanup
== {
"docker_config": cleanup.get("docker_config"),
"local_daemon": "runner_ephemeral_references_retained",
"remote_candidate": "immutable_evidence_retained",
}
and cleanup.get("docker_config") in {"pending", "pass", "fail"},
"publish cleanup state is invalid",
)
release = value.get("release")
_require(
isinstance(release, dict)
and set(release) == {"build_push_receipt_present", "bundle_present", "release_sha256"}
and type(release.get("build_push_receipt_present")) is bool
and type(release.get("bundle_present")) is bool,
"publish release state is invalid",
)
release_sha256 = release.get("release_sha256")
_require(release_sha256 is None or _is_sha256(release_sha256), "publish release hash is invalid")
if value["status"] == "pass":
_require(
registry["action"] in {"fresh_push", "reused", "reconciled_after_attempt"}
and observed_digest is not None
and cleanup["docker_config"] == "pass"
and release["build_push_receipt_present"] is True
and release["bundle_present"] is True
and _is_sha256(release_sha256),
"passing publish outcome is incomplete",
)
events = value.get("events")
_require(isinstance(events, list) and bool(events), "publish outcome events are missing")
for event in events:
_require(
isinstance(event, dict)
and set(event) == {"phase", "status"}
and isinstance(event.get("phase"), str)
and bool(re.fullmatch(r"[a-z][a-z0-9_]{0,63}", event["phase"]))
and event.get("status")
in {"initialized", "attempted", "absent", "present", "pass", "fail", "recovered", "reused", "skipped"},
"publish outcome event is invalid",
)
stable = {key: item for key, item in value.items() if key != "outcome_sha256"}
_require(value.get("outcome_sha256") == canonical_sha256(stable), "publish outcome self-hash drifted")
_assert_no_forbidden_descriptor_markers(value)
def build_initial_publish_outcome(image_input: dict[str, Any]) -> dict[str, Any]:
validate_image_input(image_input)
stable = {
"schema": PUBLISH_OUTCOME_SCHEMA,
"status": "in_progress",
"source_revision": image_input["runtime"]["teleo_git_head"],
"input_sha256": image_input["input_sha256"],
"candidate": {"tag": None, "reference": None, "local_build_config_digest": None},
"registry": {
"repository": IMAGE_REPOSITORY,
"preexisting": None,
"push_attempted": False,
"action": "none",
"observed_digest": None,
},
"phase": "initialized",
"failure_phase": None,
"cleanup": {
"docker_config": "pending",
"local_daemon": "runner_ephemeral_references_retained",
"remote_candidate": "immutable_evidence_retained",
},
"release": {
"build_push_receipt_present": False,
"bundle_present": False,
"release_sha256": None,
},
"events": [{"phase": "initialized", "status": "initialized"}],
}
return _seal_publish_outcome(stable)
def advance_publish_outcome(
value: dict[str, Any],
*,
phase: str,
event_status: str,
status: str | None = None,
failure_phase: str | None = None,
candidate: dict[str, Any] | None = None,
registry: dict[str, Any] | None = None,
cleanup: dict[str, Any] | None = None,
release: dict[str, Any] | None = None,
) -> dict[str, Any]:
validate_publish_outcome(value)
_require(value["status"] != "pass", "completed publish outcome cannot advance")
stable = copy.deepcopy({key: item for key, item in value.items() if key != "outcome_sha256"})
stable["phase"] = phase
stable["events"].append({"phase": phase, "status": event_status})
if status is not None:
stable["status"] = status
if failure_phase is not None:
stable["failure_phase"] = failure_phase
for key, updates in (("candidate", candidate), ("registry", registry), ("cleanup", cleanup), ("release", release)):
if updates is not None:
stable[key].update(updates)
return _seal_publish_outcome(stable)
def build_image_inspection( def build_image_inspection(
image_input: dict[str, Any], image_input: dict[str, Any],
image_reference: str, image_reference: str,
@ -2133,6 +2381,197 @@ def _bounded_docker_output(result: subprocess.CompletedProcess[bytes], label: st
) )
def _validate_gcloud_binary(gcloud_binary: Path) -> None:
_require(gcloud_binary.is_absolute() and "\x00" not in str(gcloud_binary), "gcloud binary must be absolute")
try:
observed = gcloud_binary.stat(follow_symlinks=False)
except OSError as exc:
raise PackageError("gcloud binary is unavailable") from exc
_require(
stat.S_ISREG(observed.st_mode)
and observed.st_uid in {0, os.geteuid()}
and bool(observed.st_mode & 0o111)
and observed.st_mode & 0o022 == 0,
"gcloud binary posture is unsafe",
)
def _run_gcloud(gcloud_binary: Path, arguments: list[str], *, timeout: int = 60) -> subprocess.CompletedProcess[bytes]:
_validate_gcloud_binary(gcloud_binary)
environment = os.environ.copy()
environment["CLOUDSDK_CORE_DISABLE_PROMPTS"] = "1"
try:
result = subprocess.run(
[str(gcloud_binary), *arguments],
check=False,
capture_output=True,
stdin=subprocess.DEVNULL,
timeout=timeout,
env=environment,
)
except (OSError, subprocess.SubprocessError) as exc:
raise PackageError("registry tag lookup failed") from exc
_require(
len(result.stdout) <= MAX_GCLOUD_COMMAND_OUTPUT_BYTES and len(result.stderr) <= MAX_GCLOUD_COMMAND_OUTPUT_BYTES,
"registry tag lookup output is too large",
)
_require(result.returncode == 0, "registry tag lookup failed")
return result
def _registry_candidate_digest(gcloud_binary: Path, candidate_tag: str) -> str | None:
_require(CANDIDATE_TAG.fullmatch(candidate_tag) is not None, "registry candidate tag is invalid")
expected_tag = f"{ARTIFACT_PACKAGE_RESOURCE}/tags/{candidate_tag}"
expected_version_prefix = f"{ARTIFACT_PACKAGE_RESOURCE}/versions/sha256:"
result = _run_gcloud(
gcloud_binary,
[
"artifacts",
"docker",
"tags",
"list",
IMAGE_REPOSITORY,
"--project",
PROJECT,
f"--filter=tag={expected_tag}",
"--format=json(tag,version)",
"--limit=2",
],
)
try:
rows = _strict_json_loads(result.stdout.decode("utf-8"), "registry candidate lookup")
except UnicodeError as exc:
raise PackageError("registry candidate lookup is invalid") from exc
_require(isinstance(rows, list), "registry candidate lookup is invalid")
if not rows:
return None
_require(
len(rows) == 1
and isinstance(rows[0], dict)
and set(rows[0]) == {"tag", "version"}
and rows[0].get("tag") == expected_tag,
"registry candidate lookup returned an unexpected tag",
)
row = rows[0]
version = row.get("version")
digest = version.removeprefix(expected_version_prefix) if isinstance(version, str) else ""
_require(
version == f"{expected_version_prefix}{digest}" and _is_sha256(digest), "registry candidate version is invalid"
)
return f"sha256:{digest}"
def _wait_registry_candidate_digest(
gcloud_binary: Path,
candidate_tag: str,
*,
attempts: int = 6,
interval_seconds: float = 2.0,
) -> str | None:
_require(type(attempts) is int and attempts > 0, "registry lookup attempts are invalid")
for attempt in range(attempts):
digest = _registry_candidate_digest(gcloud_binary, candidate_tag)
if digest is not None:
return digest
if attempt + 1 < attempts:
time.sleep(interval_seconds)
return None
def _pull_validated_registry_candidate(
image_input: dict[str, Any],
expected_local: dict[str, Any],
manifest_digest: str,
*,
docker_binary: Path,
docker_host: str,
docker_config: Path,
authority: _DockerCacheLock,
) -> dict[str, Any]:
_require(
isinstance(manifest_digest, str)
and manifest_digest.startswith("sha256:")
and _is_sha256(manifest_digest.removeprefix("sha256:")),
"registry candidate digest is invalid",
)
reference = f"{IMAGE_REPOSITORY}@{manifest_digest}"
pulled = _run_docker(
docker_binary,
docker_host,
docker_config,
["pull", "--quiet", "--platform", PLATFORM, reference],
check=True,
timeout=600,
authority=authority,
)
_bounded_docker_output(pulled, "registry candidate pull")
remote_image = _inspect_local_image(
docker_binary,
docker_host,
docker_config,
reference,
authority=authority,
)
remote_configuration = validate_image_configuration(image_input, remote_image)
for field in ("platform", "labels", "runtime_config"):
_require(
remote_configuration[field] == expected_local[field],
"existing registry candidate runtime differs from the exact input",
)
_require(
_target_repository_digests(remote_image) == [reference],
"existing registry candidate digest binding is not exact",
)
verified = _run_docker(
docker_binary,
docker_host,
docker_config,
[
"run",
"--rm",
"--pull=never",
"--platform",
PLATFORM,
"--network=none",
"--read-only",
"--cap-drop=ALL",
"--security-opt=no-new-privileges:true",
"--pids-limit=64",
f"--user={RUNTIME_UID}:{RUNTIME_GID}",
reference,
"verify-build",
image_input["runtime"]["teleo_git_head"],
image_input["runtime"]["artifact_sha256"],
image_input["identity"]["bundle_sha256"],
image_input["input_sha256"],
],
check=True,
timeout=120,
authority=authority,
)
_bounded_docker_output(verified, "registry candidate runtime verification")
try:
verification = _strict_json_loads(verified.stdout.decode("utf-8"), "registry candidate verification")
except UnicodeError as exc:
raise PackageError("registry candidate verification is invalid") from exc
_require(
verification
== {
"schema": "livingip.leocleanNoSendContainerVerification.v1",
"status": "pass",
"teleo_git_head": image_input["runtime"]["teleo_git_head"],
"artifact_sha256": image_input["runtime"]["artifact_sha256"],
"identity_sha256": image_input["identity"]["bundle_sha256"],
"input_sha256": image_input["input_sha256"],
"python_version": PYTHON_VERSION,
"postgres_version": POSTGRES_VERSION,
"cloudsql_ca_sha256": CA_SHA256,
},
"registry candidate verification differs from the exact input",
)
return remote_image
def _inspect_local_image( def _inspect_local_image(
docker_binary: Path, docker_binary: Path,
docker_host: str, docker_host: str,
@ -2282,22 +2721,56 @@ def build_and_push_staging_image(
docker_binary: Path, docker_binary: Path,
docker_host: str, docker_host: str,
docker_config: Path, docker_config: Path,
gcloud_binary: Path,
output_receipt: Path, output_receipt: Path,
outcome_journal: Path,
execute_staging_push: bool, execute_staging_push: bool,
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Build one reviewed context, push one content-bound candidate, and receipt the result.""" """Build and reconcile one content-bound candidate, then receipt the exact digest."""
_require(execute_staging_push is True, "staging image push requires explicit execution authorization") _require(execute_staging_push is True, "staging image push requires explicit execution authorization")
image_input, build_args = validate_prepared_image_context(context) image_input, build_args = validate_prepared_image_context(context)
validate_reviewed_context_source(repo_root, context, image_input) outcome = load_publish_outcome(outcome_journal, image_input=image_input)
_validate_docker_authority(docker_binary, docker_host, docker_config) _require(outcome["status"] == "in_progress", "publish outcome is not active")
_validate_receipt_output_path(output_receipt) current_phase = "preflight"
_validate_receipt_context_separation(context, output_receipt) push_attempted = False
observed_digest: str | None = None
def record(
phase: str,
event_status: str,
*,
status: str | None = None,
failure_phase: str | None = None,
candidate: dict[str, Any] | None = None,
registry: dict[str, Any] | None = None,
release: dict[str, Any] | None = None,
) -> None:
nonlocal outcome, current_phase
current_phase = phase
outcome = advance_publish_outcome(
outcome,
phase=phase,
event_status=event_status,
status=status,
failure_phase=failure_phase,
candidate=candidate,
registry=registry,
release=release,
)
publish_publish_outcome(outcome_journal, outcome, create=False)
build_tag = f"input-{image_input['input_sha256']}" build_tag = f"input-{image_input['input_sha256']}"
build_reference = f"{LOCAL_BUILD_REPOSITORY}:{build_tag}" build_reference = f"{LOCAL_BUILD_REPOSITORY}:{build_tag}"
validate_reviewed_context_source(repo_root, context, image_input)
_validate_docker_authority(docker_binary, docker_host, docker_config)
_validate_gcloud_binary(gcloud_binary)
_validate_receipt_output_path(output_receipt)
_validate_receipt_context_separation(context, output_receipt)
record("preflight", "pass")
try: try:
with _DockerCacheLock(docker_config) as authority: with _DockerCacheLock(docker_config) as authority:
current_phase = "local_build"
_require( _require(
_local_tag_config_id( _local_tag_config_id(
docker_binary, docker_binary,
@ -2366,8 +2839,44 @@ def build_and_push_staging_image(
) )
validate_reviewed_context_source(repo_root, context, post_build_input) validate_reviewed_context_source(repo_root, context, post_build_input)
candidate_tag = f"candidate-{build_config_id.removeprefix('sha256:')}" candidate_tag = f"candidate-{image_input['input_sha256']}"
candidate_reference = f"{IMAGE_REPOSITORY}:{candidate_tag}" candidate_reference = f"{IMAGE_REPOSITORY}:{candidate_tag}"
record(
"candidate_derived",
"pass",
candidate={
"tag": candidate_tag,
"reference": candidate_reference,
"local_build_config_digest": build_config_id,
},
)
current_phase = "remote_reconcile"
existing_digest = _registry_candidate_digest(gcloud_binary, candidate_tag)
if existing_digest is not None:
observed_digest = existing_digest
record(
"remote_reconcile",
"present",
registry={
"preexisting": True,
"action": "observed",
"observed_digest": existing_digest,
},
)
remote_image = _pull_validated_registry_candidate(
image_input,
observed,
existing_digest,
docker_binary=docker_binary,
docker_host=docker_host,
docker_config=docker_config,
authority=authority,
)
receipt = build_build_push_receipt(image_input, post_push_image=remote_image)
record("remote_readback", "reused", registry={"action": "reused"})
else:
record("remote_reconcile", "absent", registry={"preexisting": False})
_require( _require(
_local_tag_config_id( _local_tag_config_id(
docker_binary, docker_binary,
@ -2417,6 +2926,14 @@ def build_and_push_staging_image(
_target_repository_digests(pre_push_image) == [], _target_repository_digests(pre_push_image) == [],
"staging candidate already has a repository digest before push", "staging candidate already has a repository digest before push",
) )
push_attempted = True
record(
"push",
"attempted",
registry={"push_attempted": True, "action": "indeterminate"},
)
push_error: BaseException | None = None
try:
pushed = _run_docker( pushed = _run_docker(
docker_binary, docker_binary,
docker_host, docker_host,
@ -2427,31 +2944,58 @@ def build_and_push_staging_image(
authority=authority, authority=authority,
) )
_bounded_docker_output(pushed, "staging image push") _bounded_docker_output(pushed, "staging image push")
post_push_image = _inspect_local_image( except (OSError, subprocess.SubprocessError, PackageError) as exc:
docker_binary, push_error = exc
docker_host, observed_digest = _wait_registry_candidate_digest(gcloud_binary, candidate_tag)
docker_config, _require(observed_digest is not None, "staging push produced no observable immutable candidate")
candidate_reference, remote_image = _pull_validated_registry_candidate(
image_input,
observed,
observed_digest,
docker_binary=docker_binary,
docker_host=docker_host,
docker_config=docker_config,
authority=authority, authority=authority,
) )
_require( record(
validate_image_configuration(image_input, post_push_image) == observed, "remote_readback",
"staging candidate configuration drifted across push", "recovered" if push_error is not None else "pass",
registry={
"action": "reconciled_after_attempt" if push_error is not None else "fresh_push",
"observed_digest": observed_digest,
},
) )
repository_digests = _target_repository_digests(post_push_image) receipt = build_build_push_receipt(image_input, post_push_image=remote_image)
_require(len(repository_digests) == 1, "staging push did not produce one exact repository digest")
registry_reference = repository_digests[0] current_phase = "receipt"
match = IMAGE_REFERENCE.fullmatch(registry_reference)
_require(match is not None, "staging push repository digest is invalid")
receipt = build_build_push_receipt(image_input, post_push_image=post_push_image)
except (OSError, subprocess.SubprocessError, PackageError) as exc:
raise PackageError("staging image build/push failed") from exc
publish_build_push_receipt( publish_build_push_receipt(
output_receipt, output_receipt,
receipt, receipt,
image_input=image_input, image_input=image_input,
outside_context=context, outside_context=context,
) )
record("receipt", "pass", release={"build_push_receipt_present": True})
except (OSError, subprocess.SubprocessError, PackageError) as exc:
registry_updates: dict[str, Any] = {}
if observed_digest is not None:
registry_updates["observed_digest"] = observed_digest
if push_attempted and outcome["registry"]["action"] not in {"fresh_push", "reconciled_after_attempt"}:
registry_updates["action"] = "indeterminate"
try:
outcome = advance_publish_outcome(
outcome,
phase=current_phase,
event_status="fail",
status="fail",
failure_phase=current_phase,
registry=registry_updates or None,
)
publish_publish_outcome(outcome_journal, outcome, create=False)
except (OSError, PackageError) as journal_error:
raise PackageError("staging image build/push failed and outcome journaling failed") from journal_error
if current_phase == "receipt" and isinstance(exc, PackageError):
raise
raise PackageError("staging image build/push failed") from exc
return receipt return receipt
@ -3065,6 +3609,235 @@ def publish_build_push_receipt(
os.close(parent_descriptor) os.close(parent_descriptor)
def publish_publish_outcome(path: Path, value: dict[str, Any], *, create: bool) -> None:
"""Atomically create or replace the non-authority publish outcome journal."""
validate_publish_outcome(value)
_require(path.is_absolute() and path.name not in {"", ".", ".."}, "publish outcome path is invalid")
try:
payload = (json.dumps(value, allow_nan=False, indent=2, sort_keys=True) + "\n").encode("utf-8")
except (TypeError, ValueError) as exc:
raise PackageError("cannot encode publish outcome") from exc
_require(len(payload) <= MAX_PUBLISH_OUTCOME_BYTES, "publish outcome is too large")
parent_descriptor = _open_private_operator_directory(path.parent, "publish outcome parent")
staged_name = f".{path.name}.{uuid.uuid4().hex}.tmp"
descriptor: int | None = None
try:
existing = _directory_entry_stat(parent_descriptor, path.name)
if create:
_require(existing is None, "publish outcome already exists")
else:
_require(
existing is not None
and stat.S_ISREG(existing.st_mode)
and existing.st_uid == os.geteuid()
and stat.S_IMODE(existing.st_mode) == 0o600,
"publish outcome update target is unsafe",
)
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
descriptor = os.open(staged_name, flags, 0o600, dir_fd=parent_descriptor)
os.fchmod(descriptor, 0o600)
with os.fdopen(descriptor, "wb", closefd=False) as handle:
handle.write(payload)
handle.flush()
os.fsync(descriptor)
if create:
os.link(
staged_name,
path.name,
src_dir_fd=parent_descriptor,
dst_dir_fd=parent_descriptor,
follow_symlinks=False,
)
os.unlink(staged_name, dir_fd=parent_descriptor)
else:
os.replace(
staged_name,
path.name,
src_dir_fd=parent_descriptor,
dst_dir_fd=parent_descriptor,
)
observed = os.stat(path.name, dir_fd=parent_descriptor, follow_symlinks=False)
_require(
stat.S_ISREG(observed.st_mode)
and observed.st_uid == os.geteuid()
and stat.S_IMODE(observed.st_mode) == 0o600
and observed.st_size == len(payload),
"published outcome posture drifted",
)
_fsync_directory_descriptor(parent_descriptor)
except OSError as exc:
raise PackageError("cannot publish outcome journal") from exc
finally:
if descriptor is not None:
os.close(descriptor)
staged = _directory_entry_stat(parent_descriptor, staged_name)
if staged is not None:
try:
os.unlink(staged_name, dir_fd=parent_descriptor)
_fsync_directory_descriptor(parent_descriptor)
except OSError as exc:
os.close(parent_descriptor)
raise PackageError("cannot clean staged outcome journal") from exc
os.close(parent_descriptor)
def load_publish_outcome(path: Path, *, image_input: dict[str, Any] | None = None) -> dict[str, Any]:
_require(path.is_absolute() and path.name not in {"", ".", ".."}, "publish outcome path is invalid")
parent_descriptor = _open_private_operator_directory(path.parent, "publish outcome parent")
descriptor: int | None = None
try:
flags = os.O_RDONLY
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
descriptor = os.open(path.name, flags, dir_fd=parent_descriptor)
before = os.fstat(descriptor)
_require(
stat.S_ISREG(before.st_mode)
and before.st_uid == os.geteuid()
and stat.S_IMODE(before.st_mode) == 0o600
and before.st_size <= MAX_PUBLISH_OUTCOME_BYTES,
"publish outcome file posture is unsafe",
)
chunks: list[bytes] = []
remaining = MAX_PUBLISH_OUTCOME_BYTES + 1
while remaining > 0:
chunk = os.read(descriptor, min(64 * 1024, remaining))
if not chunk:
break
chunks.append(chunk)
remaining -= len(chunk)
payload = b"".join(chunks)
after = os.fstat(descriptor)
named = os.stat(path.name, dir_fd=parent_descriptor, follow_symlinks=False)
_require(
len(payload) <= MAX_PUBLISH_OUTCOME_BYTES
and (before.st_dev, before.st_ino, before.st_size, before.st_mtime_ns, before.st_ctime_ns)
== (after.st_dev, after.st_ino, after.st_size, after.st_mtime_ns, after.st_ctime_ns)
and (after.st_dev, after.st_ino) == (named.st_dev, named.st_ino)
and len(payload) == after.st_size,
"publish outcome changed while it was read",
)
except (OSError, UnicodeError) as exc:
raise PackageError("cannot read publish outcome") from exc
finally:
if descriptor is not None:
os.close(descriptor)
os.close(parent_descriptor)
try:
value = _strict_json_loads(payload.decode("utf-8"), "publish outcome")
except UnicodeError as exc:
raise PackageError("cannot decode publish outcome") from exc
_require(isinstance(value, dict), "publish outcome must be a JSON object")
validate_publish_outcome(value, image_input=image_input)
return value
def complete_publish_outcome(
*,
image_input_path: Path,
outcome_journal: Path,
build_push_receipt_path: Path,
release_path: Path,
build_outcome: str,
finalize_outcome: str,
cleanup_outcome: str,
) -> dict[str, Any]:
"""Finalize the mutation journal without masking an earlier workflow failure."""
allowed_step_outcomes = {"success", "failure", "cancelled", "skipped"}
_require(
{build_outcome, finalize_outcome, cleanup_outcome} <= allowed_step_outcomes,
"workflow step outcome is invalid",
)
image_input = load_json(image_input_path, "image input")
outcome = load_publish_outcome(outcome_journal, image_input=image_input)
_require(outcome["status"] != "pass", "publish outcome is already complete")
release_updates = {
"build_push_receipt_present": False,
"bundle_present": False,
"release_sha256": None,
}
evidence_failure: str | None = None
receipt: dict[str, Any] | None = None
release: dict[str, Any] | None = None
if build_push_receipt_path.is_file() and not build_push_receipt_path.is_symlink():
try:
receipt = load_private_json(build_push_receipt_path, "build/push receipt")
validate_build_push_receipt(receipt, image_input=image_input)
_require(
receipt["registry"]["manifest_digest"] == outcome["registry"]["observed_digest"],
"build/push receipt differs from publish outcome",
)
release_updates["build_push_receipt_present"] = True
except PackageError:
receipt = None
evidence_failure = "receipt_validation"
if release_path.is_file() and not release_path.is_symlink():
try:
release = load_json(release_path, "release descriptor")
validate_release_descriptor(release)
_require(
receipt is not None
and release["build_push_receipt"] == receipt
and release["image_input"] == image_input
and release["image"]["digest"] == outcome["registry"]["observed_digest"],
"release descriptor differs from publish outcome",
)
release_updates.update(
{
"bundle_present": True,
"release_sha256": release["release_sha256"],
}
)
except PackageError:
release = None
evidence_failure = evidence_failure or "release_validation"
if build_outcome == "success" and release_updates["build_push_receipt_present"] is not True:
evidence_failure = evidence_failure or "receipt_missing"
if finalize_outcome == "success" and release_updates["bundle_present"] is not True:
evidence_failure = evidence_failure or "release_missing"
failure_phase = outcome["failure_phase"]
if failure_phase is None and build_outcome != "success":
failure_phase = {
"failure": "build_push",
"cancelled": "workflow_cancelled",
"skipped": "pre_publish",
}[build_outcome]
if failure_phase is None and finalize_outcome != "success":
failure_phase = "finalize"
if failure_phase is None and evidence_failure is not None:
failure_phase = evidence_failure
if failure_phase is None and cleanup_outcome != "success":
failure_phase = "cleanup"
passing = (
failure_phase is None
and build_outcome == "success"
and finalize_outcome == "success"
and cleanup_outcome == "success"
and release_updates["build_push_receipt_present"] is True
and release_updates["bundle_present"] is True
and _is_sha256(release_updates["release_sha256"])
)
completed = advance_publish_outcome(
outcome,
phase="complete",
event_status="pass" if passing else "fail",
status="pass" if passing else "fail",
failure_phase=None if passing else failure_phase or "completion",
cleanup={"docker_config": "pass" if cleanup_outcome == "success" else "fail"},
release=release_updates,
)
publish_publish_outcome(outcome_journal, completed, create=False)
return completed
def _remove_published_bundle(output_bundle: Path, expected_identity: tuple[int, int]) -> None: def _remove_published_bundle(output_bundle: Path, expected_identity: tuple[int, int]) -> None:
try: try:
observed = output_bundle.stat(follow_symlinks=False) observed = output_bundle.stat(follow_symlinks=False)
@ -3289,8 +4062,21 @@ def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
build_push.add_argument("--docker-binary", type=Path, required=True) build_push.add_argument("--docker-binary", type=Path, required=True)
build_push.add_argument("--docker-host", required=True) build_push.add_argument("--docker-host", required=True)
build_push.add_argument("--docker-config", type=Path, required=True) build_push.add_argument("--docker-config", type=Path, required=True)
build_push.add_argument("--gcloud-binary", type=Path, required=True)
build_push.add_argument("--output-receipt", type=Path, required=True) build_push.add_argument("--output-receipt", type=Path, required=True)
build_push.add_argument("--outcome-journal", type=Path, required=True)
build_push.add_argument("--execute-staging-push", action="store_true") build_push.add_argument("--execute-staging-push", action="store_true")
init_outcome = subparsers.add_parser("init-outcome")
init_outcome.add_argument("--image-input", type=Path, required=True)
init_outcome.add_argument("--output-journal", type=Path, required=True)
complete_outcome = subparsers.add_parser("complete-outcome")
complete_outcome.add_argument("--image-input", type=Path, required=True)
complete_outcome.add_argument("--outcome-journal", type=Path, required=True)
complete_outcome.add_argument("--build-push-receipt", type=Path, required=True)
complete_outcome.add_argument("--release", type=Path, required=True)
complete_outcome.add_argument("--build-outcome", required=True)
complete_outcome.add_argument("--finalize-outcome", required=True)
complete_outcome.add_argument("--cleanup-outcome", required=True)
finalize = subparsers.add_parser("finalize") finalize = subparsers.add_parser("finalize")
finalize.add_argument("--image-input", type=Path, required=True) finalize.add_argument("--image-input", type=Path, required=True)
finalize.add_argument("--build-push-receipt", type=Path, required=True) finalize.add_argument("--build-push-receipt", type=Path, required=True)
@ -3375,9 +4161,38 @@ def main(argv: list[str] | None = None) -> int:
docker_binary=args.docker_binary, docker_binary=args.docker_binary,
docker_host=args.docker_host, docker_host=args.docker_host,
docker_config=args.docker_config, docker_config=args.docker_config,
gcloud_binary=args.gcloud_binary,
output_receipt=args.output_receipt.absolute(), output_receipt=args.output_receipt.absolute(),
outcome_journal=args.outcome_journal.absolute(),
execute_staging_push=args.execute_staging_push, execute_staging_push=args.execute_staging_push,
) )
elif args.command == "init-outcome":
image_input = load_json(args.image_input, "image input")
result = build_initial_publish_outcome(image_input)
publish_publish_outcome(args.output_journal.absolute(), result, create=True)
elif args.command == "complete-outcome":
result = complete_publish_outcome(
image_input_path=args.image_input,
outcome_journal=args.outcome_journal.absolute(),
build_push_receipt_path=args.build_push_receipt.absolute(),
release_path=args.release.absolute(),
build_outcome=args.build_outcome,
finalize_outcome=args.finalize_outcome,
cleanup_outcome=args.cleanup_outcome,
)
if result["status"] != "pass":
print(
json.dumps(
{
"status": "fail",
"schema": result["schema"],
"failure_phase": result["failure_phase"],
},
sort_keys=True,
),
file=sys.stderr,
)
return 65
elif args.command == "finalize": elif args.command == "finalize":
image_input = load_json(args.image_input, "image input") image_input = load_json(args.image_input, "image input")
build_push_receipt = load_private_json( build_push_receipt = load_private_json(

View file

@ -19,6 +19,9 @@ from ops import gcp_leoclean_nosend_package as package
ROOT = Path(__file__).resolve().parents[1] ROOT = Path(__file__).resolve().parents[1]
REVISION = package._git_commit(ROOT) REVISION = package._git_commit(ROOT)
IMAGE_DIGEST = "2" * 64 IMAGE_DIGEST = "2" * 64
LOCAL_CONFIG_DIGEST = "sha256:" + "3" * 64
REMOTE_CONFIG_DIGEST = "sha256:" + "6" * 64
ROOTFS_LAYERS = ["sha256:" + "7" * 64, "sha256:" + "8" * 64]
CLAIM_CEILING = "Offline package proof only; live GCP identity, database access, restart, and parity remain unproven." CLAIM_CEILING = "Offline package proof only; live GCP identity, database access, restart, and parity remain unproven."
@ -394,7 +397,8 @@ def docker_inspection_fixture(
image_input: dict[str, object], image_input: dict[str, object],
reference: str, reference: str,
*, *,
config_digest: str = "sha256:" + "3" * 64, config_digest: str = LOCAL_CONFIG_DIGEST,
rootfs_layers: list[str] | None = None,
) -> dict[str, object]: ) -> dict[str, object]:
runtime = copy.deepcopy(package.IMAGE_RUNTIME_CONFIG) runtime = copy.deepcopy(package.IMAGE_RUNTIME_CONFIG)
healthcheck = runtime["healthcheck"] healthcheck = runtime["healthcheck"]
@ -404,6 +408,7 @@ def docker_inspection_fixture(
"Architecture": "amd64", "Architecture": "amd64",
"Os": "linux", "Os": "linux",
"Variant": None, "Variant": None,
"RootFS": {"Type": "layers", "Layers": list(rootfs_layers or ROOTFS_LAYERS)},
"Config": { "Config": {
"Labels": package.expected_image_labels(image_input), "Labels": package.expected_image_labels(image_input),
"User": runtime["user"], "User": runtime["user"],
@ -447,6 +452,32 @@ def docker_authority(tmp_path: Path) -> tuple[Path, str, Path]:
return docker_binary, "unix:///tmp/livingip-docker.sock", docker_config return docker_binary, "unix:///tmp/livingip-docker.sock", docker_config
def publish_outcome_authority(
tmp_path: Path,
image_input: dict[str, object],
) -> tuple[Path, Path]:
gcloud_binary = tmp_path / "bin" / "gcloud"
gcloud_binary.parent.mkdir(exist_ok=True)
gcloud_binary.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8")
gcloud_binary.chmod(0o755)
outcome_journal = tmp_path / "publish-outcome.json"
package.publish_publish_outcome(
outcome_journal,
package.build_initial_publish_outcome(image_input),
create=True,
)
return gcloud_binary, outcome_journal
def mock_fresh_registry_reconcile(
monkeypatch: pytest.MonkeyPatch,
*,
manifest_digest: str = "sha256:" + IMAGE_DIGEST,
) -> None:
monkeypatch.setattr(package, "_registry_candidate_digest", lambda *_args: None)
monkeypatch.setattr(package, "_wait_registry_candidate_digest", lambda *_args: manifest_digest)
def docker_inventory_fixture( def docker_inventory_fixture(
reference: str, reference: str,
*, *,
@ -505,19 +536,28 @@ def mocked_build_push_docker(
*, *,
post_push_references: list[str] | None = None, post_push_references: list[str] | None = None,
fail_after_side_effect: str | None = None, fail_after_side_effect: str | None = None,
remote_config_digest: str = LOCAL_CONFIG_DIGEST,
remote_rootfs_layers: list[str] | None = None,
remote_verification_valid: bool = True,
) -> tuple[object, dict[str, object], list[tuple[list[str], dict[str, object]]]]: ) -> tuple[object, dict[str, object], list[tuple[list[str], dict[str, object]]]]:
config_digest = "sha256:" + "3" * 64 config_digest = LOCAL_CONFIG_DIGEST
exact_reference = f"{package.IMAGE_REPOSITORY}@sha256:{IMAGE_DIGEST}" exact_reference = f"{package.IMAGE_REPOSITORY}@sha256:{IMAGE_DIGEST}"
references = list(post_push_references if post_push_references is not None else [exact_reference]) references = list(post_push_references if post_push_references is not None else [exact_reference])
build_tag = f"input-{image_input['input_sha256']}" build_tag = f"input-{image_input['input_sha256']}"
build_reference = f"{package.LOCAL_BUILD_REPOSITORY}:{build_tag}" build_reference = f"{package.LOCAL_BUILD_REPOSITORY}:{build_tag}"
candidate_tag = f"candidate-{config_digest.removeprefix('sha256:')}" candidate_tag = f"candidate-{image_input['input_sha256']}"
candidate_reference = f"{package.IMAGE_REPOSITORY}:{candidate_tag}" candidate_reference = f"{package.IMAGE_REPOSITORY}:{candidate_tag}"
state: dict[str, object] = {"build": False, "candidate": False, "digests": False, "pushes": 0} state: dict[str, object] = {"build": False, "candidate": False, "digests": False, "pushes": 0}
calls: list[tuple[list[str], dict[str, object]]] = [] calls: list[tuple[list[str], dict[str, object]]] = []
def inspection(reference: str) -> bytes: def inspection(reference: str) -> bytes:
value = docker_inspection_fixture(image_input, reference, config_digest=config_digest) is_remote = "@sha256:" in reference
value = docker_inspection_fixture(
image_input,
reference,
config_digest=remote_config_digest if is_remote else config_digest,
rootfs_layers=remote_rootfs_layers if is_remote else ROOTFS_LAYERS,
)
value["RepoDigests"] = references if state["digests"] else [] value["RepoDigests"] = references if state["digests"] else []
return json.dumps([value]).encode() return json.dumps([value]).encode()
@ -576,6 +616,52 @@ def mocked_build_push_docker(
stdout=("sha256:" + "9" * 64 + "\n").encode(), stdout=("sha256:" + "9" * 64 + "\n").encode(),
stderr=b"", stderr=b"",
) )
if operation[0] == "pull":
assert operation[1:4] == ["--quiet", "--platform", package.PLATFORM]
assert operation[4] in references
state["digests"] = True
return subprocess.CompletedProcess(arguments, 0, stdout=b"pulled\n", stderr=b"")
if operation[0] == "run":
assert operation == [
"run",
"--rm",
"--pull=never",
"--platform",
package.PLATFORM,
"--network=none",
"--read-only",
"--cap-drop=ALL",
"--security-opt=no-new-privileges:true",
"--pids-limit=64",
f"--user={package.RUNTIME_UID}:{package.RUNTIME_GID}",
exact_reference,
"verify-build",
image_input["runtime"]["teleo_git_head"],
image_input["runtime"]["artifact_sha256"],
image_input["identity"]["bundle_sha256"],
image_input["input_sha256"],
]
verification = (
{
"schema": "livingip.leocleanNoSendContainerVerification.v1",
"status": "pass",
"teleo_git_head": image_input["runtime"]["teleo_git_head"],
"artifact_sha256": image_input["runtime"]["artifact_sha256"],
"identity_sha256": image_input["identity"]["bundle_sha256"],
"input_sha256": image_input["input_sha256"],
"python_version": package.PYTHON_VERSION,
"postgres_version": package.POSTGRES_VERSION,
"cloudsql_ca_sha256": package.CA_SHA256,
}
if remote_verification_valid
else {"status": "fail"}
)
return subprocess.CompletedProcess(
arguments,
0,
stdout=(json.dumps(verification) + "\n").encode(),
stderr=b"",
)
if operation[:2] == ["image", "inspect"]: if operation[:2] == ["image", "inspect"]:
reference = operation[2] reference = operation[2]
present = ( present = (
@ -602,6 +688,98 @@ def mocked_build_push_docker(
return fake_run, state, calls return fake_run, state, calls
def test_registry_candidate_lookup_uses_exact_resource_shape_and_command(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
candidate_tag = "candidate-" + "a" * 64
digest = "b" * 64
expected_tag = f"{package.ARTIFACT_PACKAGE_RESOURCE}/tags/{candidate_tag}"
fixture = [
{
"tag": expected_tag,
"version": f"{package.ARTIFACT_PACKAGE_RESOURCE}/versions/sha256:{digest}",
}
]
observed: dict[str, object] = {}
def fake_gcloud(
binary: Path,
arguments: list[str],
*,
timeout: int = 60,
) -> subprocess.CompletedProcess[bytes]:
observed.update({"binary": binary, "arguments": arguments, "timeout": timeout})
return subprocess.CompletedProcess(arguments, 0, stdout=json.dumps(fixture).encode(), stderr=b"")
monkeypatch.setattr(package, "_run_gcloud", fake_gcloud)
gcloud_binary = tmp_path / "gcloud"
assert package._registry_candidate_digest(gcloud_binary, candidate_tag) == f"sha256:{digest}"
assert observed == {
"binary": gcloud_binary,
"arguments": [
"artifacts",
"docker",
"tags",
"list",
package.IMAGE_REPOSITORY,
"--project",
package.PROJECT,
f"--filter=tag={expected_tag}",
"--format=json(tag,version)",
"--limit=2",
],
"timeout": 60,
}
assert "--location" not in observed["arguments"]
def test_registry_candidate_lookup_rejects_mixed_exact_and_unexpected_rows(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
candidate_tag = "candidate-" + "a" * 64
digest = "b" * 64
expected_tag = f"{package.ARTIFACT_PACKAGE_RESOURCE}/tags/{candidate_tag}"
rows = [
{
"tag": expected_tag,
"version": f"{package.ARTIFACT_PACKAGE_RESOURCE}/versions/sha256:{digest}",
},
{
"tag": f"{package.ARTIFACT_PACKAGE_RESOURCE}/tags/candidate-{'c' * 64}",
"version": f"{package.ARTIFACT_PACKAGE_RESOURCE}/versions/sha256:{'d' * 64}",
},
]
monkeypatch.setattr(
package,
"_run_gcloud",
lambda *_args, **_kwargs: subprocess.CompletedProcess(
[],
0,
stdout=json.dumps(rows).encode(),
stderr=b"",
),
)
with pytest.raises(package.PackageError, match="unexpected tag"):
package._registry_candidate_digest(tmp_path / "gcloud", candidate_tag)
def test_publish_outcome_loader_handles_short_reads(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
image_input = image_input_fixture(tmp_path)
_gcloud_binary, outcome_journal = publish_outcome_authority(tmp_path, image_input)
expected = package.build_initial_publish_outcome(image_input)
original_read = os.read
monkeypatch.setattr(package.os, "read", lambda descriptor, size: original_read(descriptor, min(size, 7)))
assert package.load_publish_outcome(outcome_journal, image_input=image_input) == expected
def test_image_input_binds_exact_runtime_identity_and_staging_target(tmp_path: Path) -> None: def test_image_input_binds_exact_runtime_identity_and_staging_target(tmp_path: Path) -> None:
value = image_input_fixture(tmp_path) value = image_input_fixture(tmp_path)
package.validate_image_input(value) package.validate_image_input(value)
@ -1065,6 +1243,7 @@ def test_build_push_derives_receipt_and_retains_all_daemon_references(
repo_root.mkdir() repo_root.mkdir()
output = tmp_path / "build-push-receipt.json" output = tmp_path / "build-push-receipt.json"
docker_binary, docker_host, docker_config = docker_authority(tmp_path) docker_binary, docker_host, docker_config = docker_authority(tmp_path)
gcloud_binary, outcome_journal = publish_outcome_authority(tmp_path, image_input)
fake_run, state, calls = mocked_build_push_docker(image_input) fake_run, state, calls = mocked_build_push_docker(image_input)
reviewed: list[tuple[Path, Path, dict[str, object]]] = [] reviewed: list[tuple[Path, Path, dict[str, object]]] = []
monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args)) monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args))
@ -1074,6 +1253,7 @@ def test_build_push_derives_receipt_and_retains_all_daemon_references(
lambda root, observed_context, value: reviewed.append((root, observed_context, value)), lambda root, observed_context, value: reviewed.append((root, observed_context, value)),
) )
monkeypatch.setattr(package.subprocess, "run", fake_run) monkeypatch.setattr(package.subprocess, "run", fake_run)
mock_fresh_registry_reconcile(monkeypatch)
monkeypatch.setenv("DOCKER_TLS_VERIFY", "1") monkeypatch.setenv("DOCKER_TLS_VERIFY", "1")
monkeypatch.setenv("BUILDKIT_PROGRESS", "plain") monkeypatch.setenv("BUILDKIT_PROGRESS", "plain")
monkeypatch.setenv("BUILDX_BUILDER", "unreviewed") monkeypatch.setenv("BUILDX_BUILDER", "unreviewed")
@ -1084,13 +1264,24 @@ def test_build_push_derives_receipt_and_retains_all_daemon_references(
docker_binary=docker_binary, docker_binary=docker_binary,
docker_host=docker_host, docker_host=docker_host,
docker_config=docker_config, docker_config=docker_config,
gcloud_binary=gcloud_binary,
output_receipt=output, output_receipt=output,
outcome_journal=outcome_journal,
execute_staging_push=True, execute_staging_push=True,
) )
assert json.loads(output.read_text(encoding="utf-8")) == receipt assert json.loads(output.read_text(encoding="utf-8")) == receipt
assert receipt["registry"]["manifest_digest"] == f"sha256:{IMAGE_DIGEST}" assert receipt["registry"]["manifest_digest"] == f"sha256:{IMAGE_DIGEST}"
assert receipt["registry"]["manifest_digest"] != "sha256:" + "9" * 64 assert receipt["registry"]["manifest_digest"] != "sha256:" + "9" * 64
outcome = package.load_publish_outcome(outcome_journal, image_input=image_input)
assert outcome["candidate"]["tag"] == f"candidate-{image_input['input_sha256']}"
assert outcome["registry"] == {
"repository": package.IMAGE_REPOSITORY,
"preexisting": False,
"push_attempted": True,
"action": "fresh_push",
"observed_digest": f"sha256:{IMAGE_DIGEST}",
}
assert reviewed == [(repo_root, context, image_input), (repo_root, context, image_input)] assert reviewed == [(repo_root, context, image_input), (repo_root, context, image_input)]
assert state == {"build": True, "candidate": True, "digests": True, "pushes": 1} assert state == {"build": True, "candidate": True, "digests": True, "pushes": 1}
build = next(operation for operation, _kwargs in calls if operation and operation[0] == "build") build = next(operation for operation, _kwargs in calls if operation and operation[0] == "build")
@ -1134,7 +1325,9 @@ def test_build_push_requires_explicit_execution_flag_before_validation(tmp_path:
docker_binary=tmp_path / "docker", docker_binary=tmp_path / "docker",
docker_host="unix:///tmp/docker.sock", docker_host="unix:///tmp/docker.sock",
docker_config=tmp_path, docker_config=tmp_path,
gcloud_binary=tmp_path / "gcloud",
output_receipt=tmp_path / "receipt.json", output_receipt=tmp_path / "receipt.json",
outcome_journal=tmp_path / "outcome.json",
execute_staging_push=False, execute_staging_push=False,
) )
@ -1156,6 +1349,7 @@ def test_build_push_rejects_writable_docker_config_before_docker(
repo_root.mkdir() repo_root.mkdir()
output = tmp_path / "writable-config-build-push-receipt.json" output = tmp_path / "writable-config-build-push-receipt.json"
docker_binary, docker_host, docker_config = docker_authority(tmp_path) docker_binary, docker_host, docker_config = docker_authority(tmp_path)
gcloud_binary, outcome_journal = publish_outcome_authority(tmp_path, image_input)
docker_config.chmod(0o777) docker_config.chmod(0o777)
docker_calls: list[list[str]] = [] docker_calls: list[list[str]] = []
monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args)) monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args))
@ -1173,7 +1367,9 @@ def test_build_push_rejects_writable_docker_config_before_docker(
docker_binary=docker_binary, docker_binary=docker_binary,
docker_host=docker_host, docker_host=docker_host,
docker_config=docker_config, docker_config=docker_config,
gcloud_binary=gcloud_binary,
output_receipt=output, output_receipt=output,
outcome_journal=outcome_journal,
execute_staging_push=True, execute_staging_push=True,
) )
@ -1282,6 +1478,7 @@ def test_build_push_rejects_replaced_docker_config_before_mutation(
repo_root.mkdir() repo_root.mkdir()
output = tmp_path / "replaced-config-build-push-receipt.json" output = tmp_path / "replaced-config-build-push-receipt.json"
docker_binary, docker_host, docker_config = docker_authority(tmp_path) docker_binary, docker_host, docker_config = docker_authority(tmp_path)
gcloud_binary, outcome_journal = publish_outcome_authority(tmp_path, image_input)
retained_config = tmp_path / "retained-docker-config" retained_config = tmp_path / "retained-docker-config"
docker_calls: list[list[str]] = [] docker_calls: list[list[str]] = []
monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args)) monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args))
@ -1306,7 +1503,9 @@ def test_build_push_rejects_replaced_docker_config_before_mutation(
docker_binary=docker_binary, docker_binary=docker_binary,
docker_host=docker_host, docker_host=docker_host,
docker_config=docker_config, docker_config=docker_config,
gcloud_binary=gcloud_binary,
output_receipt=output, output_receipt=output,
outcome_journal=outcome_journal,
execute_staging_push=True, execute_staging_push=True,
) )
@ -1335,6 +1534,7 @@ def test_build_push_rejects_receipt_parent_symlink_alias_before_docker(
repo_root = tmp_path / "repo" repo_root = tmp_path / "repo"
repo_root.mkdir() repo_root.mkdir()
docker_binary, docker_host, docker_config = docker_authority(tmp_path) docker_binary, docker_host, docker_config = docker_authority(tmp_path)
gcloud_binary, outcome_journal = publish_outcome_authority(tmp_path, image_input)
docker_calls: list[list[str]] = [] docker_calls: list[list[str]] = []
monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args)) monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args))
monkeypatch.setattr(package, "validate_reviewed_context_source", lambda *_args: None) monkeypatch.setattr(package, "validate_reviewed_context_source", lambda *_args: None)
@ -1351,7 +1551,9 @@ def test_build_push_rejects_receipt_parent_symlink_alias_before_docker(
docker_binary=docker_binary, docker_binary=docker_binary,
docker_host=docker_host, docker_host=docker_host,
docker_config=docker_config, docker_config=docker_config,
gcloud_binary=gcloud_binary,
output_receipt=output, output_receipt=output,
outcome_journal=outcome_journal,
execute_staging_push=True, execute_staging_push=True,
) )
@ -1380,11 +1582,13 @@ def test_build_push_rechecks_receipt_context_separation_at_publication(
retained_parent = tmp_path / "retained-receipts" retained_parent = tmp_path / "retained-receipts"
output = output_parent / "receipt.json" output = output_parent / "receipt.json"
docker_binary, docker_host, docker_config = docker_authority(tmp_path) docker_binary, docker_host, docker_config = docker_authority(tmp_path)
gcloud_binary, outcome_journal = publish_outcome_authority(tmp_path, image_input)
fake_run, state, calls = mocked_build_push_docker(image_input) fake_run, state, calls = mocked_build_push_docker(image_input)
original_publish = package.publish_build_push_receipt original_publish = package.publish_build_push_receipt
monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args)) monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args))
monkeypatch.setattr(package, "validate_reviewed_context_source", lambda *_args: None) monkeypatch.setattr(package, "validate_reviewed_context_source", lambda *_args: None)
monkeypatch.setattr(package.subprocess, "run", fake_run) monkeypatch.setattr(package.subprocess, "run", fake_run)
mock_fresh_registry_reconcile(monkeypatch)
def alias_parent_before_publish(*args: object, **kwargs: object) -> None: def alias_parent_before_publish(*args: object, **kwargs: object) -> None:
output_parent.rename(retained_parent) output_parent.rename(retained_parent)
@ -1400,7 +1604,9 @@ def test_build_push_rechecks_receipt_context_separation_at_publication(
docker_binary=docker_binary, docker_binary=docker_binary,
docker_host=docker_host, docker_host=docker_host,
docker_config=docker_config, docker_config=docker_config,
gcloud_binary=gcloud_binary,
output_receipt=output, output_receipt=output,
outcome_journal=outcome_journal,
execute_staging_push=True, execute_staging_push=True,
) )
@ -1437,6 +1643,7 @@ def test_build_push_fails_closed_without_one_exact_post_push_digest(
repo_root = tmp_path / "repo" repo_root = tmp_path / "repo"
repo_root.mkdir() repo_root.mkdir()
docker_binary, docker_host, docker_config = docker_authority(tmp_path) docker_binary, docker_host, docker_config = docker_authority(tmp_path)
gcloud_binary, outcome_journal = publish_outcome_authority(tmp_path, image_input)
fake_run, state, _calls = mocked_build_push_docker( fake_run, state, _calls = mocked_build_push_docker(
image_input, image_input,
post_push_references=references, post_push_references=references,
@ -1444,6 +1651,12 @@ def test_build_push_fails_closed_without_one_exact_post_push_digest(
monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args)) monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args))
monkeypatch.setattr(package, "validate_reviewed_context_source", lambda *_args: None) monkeypatch.setattr(package, "validate_reviewed_context_source", lambda *_args: None)
monkeypatch.setattr(package.subprocess, "run", fake_run) monkeypatch.setattr(package.subprocess, "run", fake_run)
monkeypatch.setattr(package, "_registry_candidate_digest", lambda *_args: None)
monkeypatch.setattr(
package,
"_wait_registry_candidate_digest",
lambda *_args: f"sha256:{IMAGE_DIGEST}" if references else None,
)
output = tmp_path / "failed-build-push-receipt.json" output = tmp_path / "failed-build-push-receipt.json"
with pytest.raises(package.PackageError, match="build/push failed"): with pytest.raises(package.PackageError, match="build/push failed"):
@ -1453,7 +1666,9 @@ def test_build_push_fails_closed_without_one_exact_post_push_digest(
docker_binary=docker_binary, docker_binary=docker_binary,
docker_host=docker_host, docker_host=docker_host,
docker_config=docker_config, docker_config=docker_config,
gcloud_binary=gcloud_binary,
output_receipt=output, output_receipt=output,
outcome_journal=outcome_journal,
execute_staging_push=True, execute_staging_push=True,
) )
@ -1462,7 +1677,7 @@ def test_build_push_fails_closed_without_one_exact_post_push_digest(
assert state["candidate"] is True assert state["candidate"] is True
@pytest.mark.parametrize("failure", ["build", "tag", "push"]) @pytest.mark.parametrize("failure", ["build", "tag"])
def test_build_push_retains_daemon_references_when_cli_fails_after_side_effect( def test_build_push_retains_daemon_references_when_cli_fails_after_side_effect(
tmp_path: Path, tmp_path: Path,
monkeypatch: pytest.MonkeyPatch, monkeypatch: pytest.MonkeyPatch,
@ -1480,6 +1695,7 @@ def test_build_push_retains_daemon_references_when_cli_fails_after_side_effect(
repo_root = tmp_path / "repo" repo_root = tmp_path / "repo"
repo_root.mkdir() repo_root.mkdir()
docker_binary, docker_host, docker_config = docker_authority(tmp_path) docker_binary, docker_host, docker_config = docker_authority(tmp_path)
gcloud_binary, outcome_journal = publish_outcome_authority(tmp_path, image_input)
fake_run, state, calls = mocked_build_push_docker( fake_run, state, calls = mocked_build_push_docker(
image_input, image_input,
fail_after_side_effect=failure, fail_after_side_effect=failure,
@ -1487,6 +1703,7 @@ def test_build_push_retains_daemon_references_when_cli_fails_after_side_effect(
monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args)) monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args))
monkeypatch.setattr(package, "validate_reviewed_context_source", lambda *_args: None) monkeypatch.setattr(package, "validate_reviewed_context_source", lambda *_args: None)
monkeypatch.setattr(package.subprocess, "run", fake_run) monkeypatch.setattr(package.subprocess, "run", fake_run)
mock_fresh_registry_reconcile(monkeypatch)
output = tmp_path / "failed-side-effect-receipt.json" output = tmp_path / "failed-side-effect-receipt.json"
with pytest.raises(package.PackageError, match="build/push failed") as raised: with pytest.raises(package.PackageError, match="build/push failed") as raised:
@ -1496,7 +1713,9 @@ def test_build_push_retains_daemon_references_when_cli_fails_after_side_effect(
docker_binary=docker_binary, docker_binary=docker_binary,
docker_host=docker_host, docker_host=docker_host,
docker_config=docker_config, docker_config=docker_config,
gcloud_binary=gcloud_binary,
output_receipt=output, output_receipt=output,
outcome_journal=outcome_journal,
execute_staging_push=True, execute_staging_push=True,
) )
@ -1504,14 +1723,188 @@ def test_build_push_retains_daemon_references_when_cli_fails_after_side_effect(
assert not output.exists() assert not output.exists()
assert state == { assert state == {
"build": True, "build": True,
"candidate": failure in {"tag", "push"}, "candidate": failure == "tag",
"digests": failure == "push", "digests": False,
"pushes": 1 if failure == "push" else 0, "pushes": 0,
} }
removals = [operation for operation, _kwargs in calls if operation[:2] == ["image", "rm"]] removals = [operation for operation, _kwargs in calls if operation[:2] == ["image", "rm"]]
assert removals == [] assert removals == []
def test_build_push_reuses_verified_candidate_despite_nondeterministic_rootfs(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
image_input = image_input_fixture(tmp_path)
build_args = {
"ARTIFACT_SHA256": image_input["runtime"]["artifact_sha256"],
"IDENTITY_SHA256": image_input["identity"]["bundle_sha256"],
"INPUT_SHA256": image_input["input_sha256"],
"TELEO_REVISION": image_input["runtime"]["teleo_git_head"],
}
context = tmp_path / "context"
context.mkdir()
repo_root = tmp_path / "repo"
repo_root.mkdir()
output = tmp_path / "reused-build-push-receipt.json"
docker_binary, docker_host, docker_config = docker_authority(tmp_path)
gcloud_binary, outcome_journal = publish_outcome_authority(tmp_path, image_input)
fake_run, state, calls = mocked_build_push_docker(
image_input,
remote_config_digest=REMOTE_CONFIG_DIGEST,
remote_rootfs_layers=["sha256:" + "9" * 64],
)
monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args))
monkeypatch.setattr(package, "validate_reviewed_context_source", lambda *_args: None)
monkeypatch.setattr(package.subprocess, "run", fake_run)
monkeypatch.setattr(
package,
"_registry_candidate_digest",
lambda *_args: f"sha256:{IMAGE_DIGEST}",
)
receipt = package.build_and_push_staging_image(
repo_root,
context,
docker_binary=docker_binary,
docker_host=docker_host,
docker_config=docker_config,
gcloud_binary=gcloud_binary,
output_receipt=output,
outcome_journal=outcome_journal,
execute_staging_push=True,
)
assert receipt["local_image"]["config_digest"] == REMOTE_CONFIG_DIGEST
assert state == {"build": True, "candidate": False, "digests": True, "pushes": 0}
assert not any(operation[:2] == ["image", "push"] for operation, _kwargs in calls)
assert any(operation and operation[0] == "pull" for operation, _kwargs in calls)
assert any(operation and operation[0] == "run" for operation, _kwargs in calls)
outcome = package.load_publish_outcome(outcome_journal, image_input=image_input)
assert outcome["candidate"] == {
"tag": f"candidate-{image_input['input_sha256']}",
"reference": f"{package.IMAGE_REPOSITORY}:candidate-{image_input['input_sha256']}",
"local_build_config_digest": LOCAL_CONFIG_DIGEST,
}
assert outcome["registry"] == {
"repository": package.IMAGE_REPOSITORY,
"preexisting": True,
"push_attempted": False,
"action": "reused",
"observed_digest": f"sha256:{IMAGE_DIGEST}",
}
def test_build_push_rejects_preexisting_failed_runtime_verification_without_claiming_reuse(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
image_input = image_input_fixture(tmp_path)
build_args = {
"ARTIFACT_SHA256": image_input["runtime"]["artifact_sha256"],
"IDENTITY_SHA256": image_input["identity"]["bundle_sha256"],
"INPUT_SHA256": image_input["input_sha256"],
"TELEO_REVISION": image_input["runtime"]["teleo_git_head"],
}
context = tmp_path / "context"
context.mkdir()
repo_root = tmp_path / "repo"
repo_root.mkdir()
output = tmp_path / "mismatched-build-push-receipt.json"
docker_binary, docker_host, docker_config = docker_authority(tmp_path)
gcloud_binary, outcome_journal = publish_outcome_authority(tmp_path, image_input)
fake_run, state, calls = mocked_build_push_docker(
image_input,
remote_config_digest=REMOTE_CONFIG_DIGEST,
remote_verification_valid=False,
)
monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args))
monkeypatch.setattr(package, "validate_reviewed_context_source", lambda *_args: None)
monkeypatch.setattr(package.subprocess, "run", fake_run)
monkeypatch.setattr(
package,
"_registry_candidate_digest",
lambda *_args: f"sha256:{IMAGE_DIGEST}",
)
with pytest.raises(package.PackageError, match="build/push failed"):
package.build_and_push_staging_image(
repo_root,
context,
docker_binary=docker_binary,
docker_host=docker_host,
docker_config=docker_config,
gcloud_binary=gcloud_binary,
output_receipt=output,
outcome_journal=outcome_journal,
execute_staging_push=True,
)
assert not output.exists()
assert state == {"build": True, "candidate": False, "digests": True, "pushes": 0}
assert not any(operation[:2] == ["image", "push"] for operation, _kwargs in calls)
outcome = package.load_publish_outcome(outcome_journal, image_input=image_input)
assert outcome["status"] == "fail"
assert outcome["registry"]["preexisting"] is True
assert outcome["registry"]["observed_digest"] == f"sha256:{IMAGE_DIGEST}"
assert outcome["registry"]["action"] == "observed"
assert all(event["status"] != "reused" for event in outcome["events"])
def test_post_push_validation_failure_retains_mutation_evidence(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
image_input = image_input_fixture(tmp_path)
build_args = {
"ARTIFACT_SHA256": image_input["runtime"]["artifact_sha256"],
"IDENTITY_SHA256": image_input["identity"]["bundle_sha256"],
"INPUT_SHA256": image_input["input_sha256"],
"TELEO_REVISION": image_input["runtime"]["teleo_git_head"],
}
context = tmp_path / "context"
context.mkdir()
repo_root = tmp_path / "repo"
repo_root.mkdir()
output = tmp_path / "post-push-failure-receipt.json"
docker_binary, docker_host, docker_config = docker_authority(tmp_path)
gcloud_binary, outcome_journal = publish_outcome_authority(tmp_path, image_input)
fake_run, state, calls = mocked_build_push_docker(
image_input,
remote_verification_valid=False,
)
monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args))
monkeypatch.setattr(package, "validate_reviewed_context_source", lambda *_args: None)
monkeypatch.setattr(package.subprocess, "run", fake_run)
mock_fresh_registry_reconcile(monkeypatch)
with pytest.raises(package.PackageError, match="build/push failed"):
package.build_and_push_staging_image(
repo_root,
context,
docker_binary=docker_binary,
docker_host=docker_host,
docker_config=docker_config,
gcloud_binary=gcloud_binary,
output_receipt=output,
outcome_journal=outcome_journal,
execute_staging_push=True,
)
assert not output.exists()
assert state == {"build": True, "candidate": True, "digests": True, "pushes": 1}
assert any(operation[:2] == ["image", "push"] for operation, _kwargs in calls)
outcome = package.load_publish_outcome(outcome_journal, image_input=image_input)
assert outcome["status"] == "fail"
assert outcome["registry"] == {
"repository": package.IMAGE_REPOSITORY,
"preexisting": False,
"push_attempted": True,
"action": "indeterminate",
"observed_digest": f"sha256:{IMAGE_DIGEST}",
}
def test_build_push_receipt_publication_is_atomic_repeatable_and_no_clobber(tmp_path: Path) -> None: def test_build_push_receipt_publication_is_atomic_repeatable_and_no_clobber(tmp_path: Path) -> None:
image_input = image_input_fixture(tmp_path) image_input = image_input_fixture(tmp_path)
receipt = build_push_receipt_fixture(image_input) receipt = build_push_receipt_fixture(image_input)

View file

@ -35,6 +35,8 @@ def test_release_workflow_is_manual_main_only_and_serialized() -> None:
assert 'test "${GITHUB_EVENT_NAME}" = "workflow_dispatch"' in guard assert 'test "${GITHUB_EVENT_NAME}" = "workflow_dispatch"' in guard
assert 'test "${GITHUB_REF}" = "refs/heads/main"' in guard assert 'test "${GITHUB_REF}" = "refs/heads/main"' in guard
assert 'test "${GITHUB_REF_TYPE}" = "branch"' in guard assert 'test "${GITHUB_REF_TYPE}" = "branch"' in guard
assert 'test "${GITHUB_REPOSITORY}" = "living-ip/teleo-infrastructure"' in guard
assert '"living-ip/teleo-infrastructure/.github/workflows/gcp-leoclean-nosend-release.yml@refs/heads/main"' in guard
def test_release_workflow_checks_out_exact_clean_revision_without_persisted_token() -> None: def test_release_workflow_checks_out_exact_clean_revision_without_persisted_token() -> None:
@ -56,11 +58,15 @@ def test_release_workflow_smokes_before_requesting_cloud_identity() -> None:
workflow = WORKFLOW.read_text(encoding="utf-8") workflow = WORKFLOW.read_text(encoding="utf-8")
smoke = workflow.index("Smoke exact no-send OCI surface") smoke = workflow.index("Smoke exact no-send OCI surface")
prepare = workflow.index("Prepare exact candidate context")
initialize = workflow.index("Initialize pre-mutation publish outcome")
auth = workflow.index("Authenticate to Google Cloud through WIF") auth = workflow.index("Authenticate to Google Cloud through WIF")
push = workflow.index("Build and push one immutable candidate") push = workflow.index("Build and push one immutable candidate")
finalize = workflow.index("Finalize and validate release v3 bundle") finalize = workflow.index("Finalize and validate release v3 bundle")
upload = workflow.index("Upload exact build and release evidence") cleanup = workflow.index("Remove ephemeral Docker authentication")
assert smoke < auth < push < finalize < upload complete = workflow.index("Finalize publish outcome")
upload = workflow.index("Upload exact available build and release evidence")
assert smoke < prepare < initialize < auth < push < finalize < cleanup < complete < upload
assert "scripts/run_leoclean_nosend_runtime_canary.sh" in workflow assert "scripts/run_leoclean_nosend_runtime_canary.sh" in workflow
assert "scripts/run_gcp_leoclean_nosend_oci_smoke.py" in workflow assert "scripts/run_gcp_leoclean_nosend_oci_smoke.py" in workflow
assert 'assert smoke["input_sha256"] == image_input["input_sha256"]' in workflow assert 'assert smoke["input_sha256"] == image_input["input_sha256"]' in workflow
@ -93,6 +99,22 @@ def test_release_workflow_uses_exact_wif_artifact_builder_without_repository_sec
} }
def test_release_workflow_keeps_runner_temp_out_of_job_environment() -> None:
workflow = load_workflow()
jobs = workflow["jobs"]
assert isinstance(jobs, dict)
publish = jobs["publish"]
assert isinstance(publish, dict)
root_env = workflow["env"]
job_env = publish.get("env", {})
assert isinstance(root_env, dict)
assert isinstance(job_env, dict)
assert all("runner.temp" not in value for value in [*root_env.values(), *job_env.values()])
initialize = workflow_step("Initialize private release paths")["run"]
assert 'release_root="${RUNNER_TEMP}/leoclean-nosend-release"' in initialize
def test_release_workflow_requires_immutable_repository_and_package_owned_candidate_push() -> None: def test_release_workflow_requires_immutable_repository_and_package_owned_candidate_push() -> None:
workflow = WORKFLOW.read_text(encoding="utf-8") workflow = WORKFLOW.read_text(encoding="utf-8")
immutable = workflow_step("Require immutable fixed Artifact Registry repository")["run"] immutable = workflow_step("Require immutable fixed Artifact Registry repository")["run"]
@ -104,27 +126,39 @@ def test_release_workflow_requires_immutable_repository_and_package_owned_candid
assert "--execute-staging-push" in push assert "--execute-staging-push" in push
assert "--docker-binary /usr/bin/docker" in push assert "--docker-binary /usr/bin/docker" in push
assert "--docker-host unix:///var/run/docker.sock" in push assert "--docker-host unix:///var/run/docker.sock" in push
assert '--gcloud-binary "$(command -v gcloud)"' in push
assert '--outcome-journal "${PUBLISH_OUTCOME}"' in push
assert "docker push" not in workflow assert "docker push" not in workflow
assert "docker tag" not in workflow assert "docker tag" not in workflow
assert "candidate-" not in workflow assert "candidate-" not in workflow
def test_release_workflow_finalizes_v3_and_uploads_only_allowlisted_evidence() -> None: def test_release_workflow_always_finalizes_journal_and_uploads_only_allowlisted_evidence() -> None:
finalize = workflow_step("Finalize and validate release v3 bundle")["run"] finalize = workflow_step("Finalize and validate release v3 bundle")["run"]
upload = workflow_step("Upload exact build and release evidence") cleanup = workflow_step("Remove ephemeral Docker authentication")
complete = workflow_step("Finalize publish outcome")
upload = workflow_step("Upload exact available build and release evidence")
paths = upload["with"]["path"].splitlines() paths = upload["with"]["path"].splitlines()
assert "ops/gcp_leoclean_nosend_package.py finalize" in finalize assert "ops/gcp_leoclean_nosend_package.py finalize" in finalize
assert "ops/gcp_leoclean_nosend_package.py validate" in finalize assert "ops/gcp_leoclean_nosend_package.py validate" in finalize
assert 'assert release["schema"] == "livingip.leocleanNoSendRelease.v3"' in finalize assert 'assert release["schema"] == "livingip.leocleanNoSendRelease.v3"' in finalize
assert cleanup["if"] == "always()"
assert complete["if"] == "always() && steps.init_outcome.outcome == 'success'"
assert "ops/gcp_leoclean_nosend_package.py complete-outcome" in complete["run"]
assert '--build-outcome "${{ steps.build_push.outcome }}"' in complete["run"]
assert '--finalize-outcome "${{ steps.finalize.outcome }}"' in complete["run"]
assert '--cleanup-outcome "${{ steps.cleanup.outcome }}"' in complete["run"]
assert upload["if"] == "always()"
assert paths == [ assert paths == [
"${{ env.RUNTIME_RECEIPT }}", "${{ runner.temp }}/leoclean-nosend-release/evidence/runtime-receipt.json",
"${{ env.OCI_SMOKE_RECEIPT }}", "${{ runner.temp }}/leoclean-nosend-release/evidence/oci-smoke.json",
"${{ env.BUILD_PUSH_RECEIPT }}", "${{ runner.temp }}/leoclean-nosend-release/evidence/publish-outcome.json",
"${{ env.RELEASE_BUNDLE }}/release.json", "${{ runner.temp }}/leoclean-nosend-release/receipts/build-push-receipt.json",
"${{ env.RELEASE_BUNDLE }}/leoclean-gcp-nosend.service", "${{ runner.temp }}/leoclean-nosend-release/final/release-v3/release.json",
"${{ runner.temp }}/leoclean-nosend-release/final/release-v3/leoclean-gcp-nosend.service",
] ]
assert upload["with"]["if-no-files-found"] == "error" assert upload["with"]["if-no-files-found"] == "warn"
assert upload["with"]["retention-days"] == "30" assert upload["with"]["retention-days"] == "30"
assert "*" not in upload["with"]["path"] assert "*" not in upload["with"]["path"]
assert "google-github-actions" not in upload["with"]["path"] assert "google-github-actions" not in upload["with"]["path"]