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}"

File diff suppressed because it is too large Load diff

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"]