Package leoclean no-send GCP staging service
This commit is contained in:
parent
2d88c9765b
commit
1e5c67738f
13 changed files with 1787 additions and 20 deletions
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
|
|
@ -42,6 +42,7 @@ jobs:
|
||||||
lib/evaluate.py \
|
lib/evaluate.py \
|
||||||
lib/llm.py \
|
lib/llm.py \
|
||||||
lib/post_extract.py \
|
lib/post_extract.py \
|
||||||
|
docker/leoclean-nosend/entrypoint.py \
|
||||||
ops/apply_gcp_iam_split.py \
|
ops/apply_gcp_iam_split.py \
|
||||||
ops/attest_gcp_reasoning_compute.py \
|
ops/attest_gcp_reasoning_compute.py \
|
||||||
ops/capture_vps_canonical_postgres_snapshot.py \
|
ops/capture_vps_canonical_postgres_snapshot.py \
|
||||||
|
|
@ -50,6 +51,7 @@ jobs:
|
||||||
ops/apply_gcp_runtime_baseline.py \
|
ops/apply_gcp_runtime_baseline.py \
|
||||||
ops/check_gcp_service_communications.py \
|
ops/check_gcp_service_communications.py \
|
||||||
ops/derive_leoclean_runtime_query_contract.py \
|
ops/derive_leoclean_runtime_query_contract.py \
|
||||||
|
ops/gcp_leoclean_nosend_package.py \
|
||||||
ops/plan_gcp_iam_split.py \
|
ops/plan_gcp_iam_split.py \
|
||||||
ops/private_receipt_io.py \
|
ops/private_receipt_io.py \
|
||||||
ops/run_local_canonical_postgres_rebuild.py \
|
ops/run_local_canonical_postgres_rebuild.py \
|
||||||
|
|
@ -63,8 +65,13 @@ jobs:
|
||||||
ops/verify_postgres_parity_manifest.py \
|
ops/verify_postgres_parity_manifest.py \
|
||||||
ops/verify_vps_canonical_snapshot_delta.py \
|
ops/verify_vps_canonical_snapshot_delta.py \
|
||||||
telegram/approvals.py \
|
telegram/approvals.py \
|
||||||
|
hermes-agent/patches/enforce_leoclean_learning_policy.py \
|
||||||
hermes-agent/leoclean-bin/kb_tool.py \
|
hermes-agent/leoclean-bin/kb_tool.py \
|
||||||
hermes-agent/leoclean-bin/cloudsql_memory_tool.py \
|
hermes-agent/leoclean-bin/cloudsql_memory_tool.py \
|
||||||
|
hermes-agent/runtime/leoclean-nosend/bootstrap.py \
|
||||||
|
hermes-agent/runtime/leoclean-nosend/profile-sources/cloudsql_memory_runtime.py \
|
||||||
|
hermes-agent/runtime/leoclean-nosend/profile-sources/leo_db_context.py \
|
||||||
|
hermes-agent/runtime/leoclean-nosend/profile-sources/operational_contracts.py \
|
||||||
scripts/check_crabbox_ci_contract.py \
|
scripts/check_crabbox_ci_contract.py \
|
||||||
scripts/check_llm_refinement_contract.py \
|
scripts/check_llm_refinement_contract.py \
|
||||||
scripts/build_working_leo_m3taversal_outcome_sandbox.py \
|
scripts/build_working_leo_m3taversal_outcome_sandbox.py \
|
||||||
|
|
@ -94,6 +101,7 @@ jobs:
|
||||||
tests/test_gcp_infra_execute_canary.py \
|
tests/test_gcp_infra_execute_canary.py \
|
||||||
tests/test_gcp_infra_readiness_checker.py \
|
tests/test_gcp_infra_readiness_checker.py \
|
||||||
tests/test_gcp_leoclean_runtime_permissions.py \
|
tests/test_gcp_leoclean_runtime_permissions.py \
|
||||||
|
tests/test_gcp_leoclean_nosend_package.py \
|
||||||
tests/test_gcp_leoclean_service_environment.py \
|
tests/test_gcp_leoclean_service_environment.py \
|
||||||
tests/test_gcp_runtime_baseline_apply.py \
|
tests/test_gcp_runtime_baseline_apply.py \
|
||||||
tests/test_gcp_service_communications.py \
|
tests/test_gcp_service_communications.py \
|
||||||
|
|
|
||||||
78
docker/leoclean-nosend/Dockerfile
Normal file
78
docker/leoclean-nosend/Dockerfile
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
# syntax=docker/dockerfile:1.7
|
||||||
|
|
||||||
|
FROM ghcr.io/astral-sh/uv:0.9.30@sha256:538e0b39736e7feae937a65983e49d2ab75e1559d35041f9878b7b7e51de91e4 AS uv
|
||||||
|
|
||||||
|
FROM docker.io/library/python:3.11.9-slim-bookworm@sha256:8fb099199b9f2d70342674bd9dbccd3ed03a258f26bbd1d556822c6dfc60c317 AS python-build
|
||||||
|
|
||||||
|
COPY --from=uv /uv /usr/local/bin/uv
|
||||||
|
COPY artifact/hermes-agent /opt/livingip/leoclean-nosend/artifact/hermes-agent
|
||||||
|
|
||||||
|
RUN /usr/local/bin/uv venv \
|
||||||
|
--python /usr/local/bin/python3.11 \
|
||||||
|
--no-project \
|
||||||
|
/opt/livingip/leoclean-nosend/venv \
|
||||||
|
&& VIRTUAL_ENV=/opt/livingip/leoclean-nosend/venv /usr/local/bin/uv sync \
|
||||||
|
--directory /opt/livingip/leoclean-nosend/artifact/hermes-agent \
|
||||||
|
--active \
|
||||||
|
--frozen \
|
||||||
|
--no-dev \
|
||||||
|
--extra cron \
|
||||||
|
--no-editable \
|
||||||
|
&& /usr/local/bin/uv pip check --python /opt/livingip/leoclean-nosend/venv/bin/python
|
||||||
|
|
||||||
|
FROM docker.io/library/postgres:16.14-bookworm@sha256:92620daddcd947f8d5ab5ba66e848702fe443d87fed30c4cea8e389fd78dfc55
|
||||||
|
|
||||||
|
ARG TELEO_REVISION
|
||||||
|
ARG ARTIFACT_SHA256
|
||||||
|
ARG IDENTITY_SHA256
|
||||||
|
ARG INPUT_SHA256
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.source="https://github.com/living-ip/teleo-infrastructure" \
|
||||||
|
org.opencontainers.image.revision="${TELEO_REVISION}" \
|
||||||
|
livingip.artifact.sha256="${ARTIFACT_SHA256}" \
|
||||||
|
livingip.identity.sha256="${IDENTITY_SHA256}" \
|
||||||
|
livingip.input.sha256="${INPUT_SHA256}" \
|
||||||
|
livingip.runtime="leoclean-nosend" \
|
||||||
|
livingip.target="gcp-staging"
|
||||||
|
|
||||||
|
COPY --from=python-build /usr/local /usr/local
|
||||||
|
COPY --from=python-build /opt/livingip/leoclean-nosend/venv /opt/livingip/leoclean-nosend/venv
|
||||||
|
COPY artifact /opt/livingip/leoclean-nosend/artifact
|
||||||
|
COPY identity /opt/livingip/leoclean-nosend/identity
|
||||||
|
COPY entrypoint.py /opt/livingip/leoclean-nosend/entrypoint.py
|
||||||
|
COPY package_contract.py /opt/livingip/leoclean-nosend/package_contract.py
|
||||||
|
COPY artifact-receipt.json /opt/livingip/leoclean-nosend/metadata/artifact-receipt.json
|
||||||
|
COPY image-input.json /opt/livingip/leoclean-nosend/metadata/image-input.json
|
||||||
|
COPY Dockerfile /opt/livingip/leoclean-nosend/metadata/Dockerfile
|
||||||
|
COPY cloudsql-server-ca.pem /usr/local/libexec/livingip/leoclean-kb/cloudsql-server-ca.pem
|
||||||
|
|
||||||
|
RUN ln -s /usr/local/bin/python3.11 /usr/bin/python3 \
|
||||||
|
&& mkdir -p /usr/local/libexec/livingip/leoclean-kb/gcloud-config \
|
||||||
|
&& chmod 0755 /usr/local/libexec/livingip/leoclean-kb \
|
||||||
|
&& chmod 0555 /usr/local/libexec/livingip/leoclean-kb/gcloud-config \
|
||||||
|
&& chmod 0444 /usr/local/libexec/livingip/leoclean-kb/cloudsql-server-ca.pem \
|
||||||
|
&& chmod 0555 /opt/livingip/leoclean-nosend/entrypoint.py \
|
||||||
|
&& chmod 0444 /opt/livingip/leoclean-nosend/package_contract.py \
|
||||||
|
&& case "$TELEO_REVISION" in (*[!0-9a-f]*|'') exit 65;; esac \
|
||||||
|
&& test "${#TELEO_REVISION}" -eq 40 \
|
||||||
|
&& for digest in "$ARTIFACT_SHA256" "$IDENTITY_SHA256" "$INPUT_SHA256"; do \
|
||||||
|
case "$digest" in (*[!0-9a-f]*|'') exit 65;; esac; \
|
||||||
|
test "${#digest}" -eq 64; \
|
||||||
|
done \
|
||||||
|
&& test "$(/usr/local/bin/python3.11 -c 'import platform; print(platform.python_version())')" = "3.11.9" \
|
||||||
|
&& /usr/bin/psql --version | grep -Eq '^psql \(PostgreSQL\) 16[.]14([[:space:]]|$)' \
|
||||||
|
&& test "$(sha256sum /usr/local/libexec/livingip/leoclean-kb/cloudsql-server-ca.pem | cut -d ' ' -f 1)" = "80701e768f0e1f6b9d621aa0b53f6e851daaa276c6d9a8e51a300fbc015539cb" \
|
||||||
|
&& /opt/livingip/leoclean-nosend/venv/bin/python /opt/livingip/leoclean-nosend/entrypoint.py \
|
||||||
|
verify-build "$TELEO_REVISION" "$ARTIFACT_SHA256" "$IDENTITY_SHA256" "$INPUT_SHA256"
|
||||||
|
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
PYTHONNOUSERSITE=1 \
|
||||||
|
PYTHONSAFEPATH=1
|
||||||
|
|
||||||
|
STOPSIGNAL SIGTERM
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=15s --timeout=10s --start-period=30s --retries=4 \
|
||||||
|
CMD ["/opt/livingip/leoclean-nosend/venv/bin/python", "/opt/livingip/leoclean-nosend/entrypoint.py", "health"]
|
||||||
|
|
||||||
|
ENTRYPOINT ["/opt/livingip/leoclean-nosend/venv/bin/python", "/opt/livingip/leoclean-nosend/entrypoint.py"]
|
||||||
|
CMD ["service"]
|
||||||
295
docker/leoclean-nosend/entrypoint.py
Normal file
295
docker/leoclean-nosend/entrypoint.py
Normal file
|
|
@ -0,0 +1,295 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Validate and launch the immutable leoclean no-send container."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import ctypes
|
||||||
|
import importlib.util
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import shutil
|
||||||
|
import stat
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
def _load_adjacent_contract() -> Any:
|
||||||
|
path = Path(__file__).resolve().with_name("package_contract.py")
|
||||||
|
spec = importlib.util.spec_from_file_location("livingip_leoclean_nosend_package_contract", path)
|
||||||
|
if spec is None or spec.loader is None:
|
||||||
|
raise ImportError("the adjacent package contract cannot be loaded")
|
||||||
|
module = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(module)
|
||||||
|
return module
|
||||||
|
|
||||||
|
|
||||||
|
contract = _load_adjacent_contract()
|
||||||
|
|
||||||
|
ROOT = Path(contract.RUNTIME_ROOT)
|
||||||
|
ARTIFACT = ROOT / "artifact"
|
||||||
|
IDENTITY = ROOT / "identity"
|
||||||
|
PROFILE = Path(contract.PROFILE_ROOT)
|
||||||
|
RECEIPT = ROOT / "metadata" / "artifact-receipt.json"
|
||||||
|
IMAGE_INPUT = ROOT / "metadata" / "image-input.json"
|
||||||
|
DOCKERFILE = ROOT / "metadata" / "Dockerfile"
|
||||||
|
ENTRYPOINT = ROOT / "entrypoint.py"
|
||||||
|
PACKAGE_CONTRACT = ROOT / "package_contract.py"
|
||||||
|
CA = Path(contract.CA_DESTINATION)
|
||||||
|
READINESS = PROFILE / "state" / "readiness.json"
|
||||||
|
PSQL = Path("/usr/bin/psql")
|
||||||
|
RUNTIME_PYTHON = ROOT / "venv" / "bin" / "python"
|
||||||
|
BOOTSTRAP = ARTIFACT / "bootstrap.py"
|
||||||
|
STATE_DIRECTORIES = ("cache", "cron", "logs", "memories", "platforms", "sessions", "state")
|
||||||
|
|
||||||
|
|
||||||
|
class EntrypointError(RuntimeError):
|
||||||
|
"""The installed image or live process violates its release contract."""
|
||||||
|
|
||||||
|
|
||||||
|
def _require(condition: bool, message: str) -> None:
|
||||||
|
if not condition:
|
||||||
|
raise EntrypointError(message)
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_installed() -> dict[str, Any]:
|
||||||
|
image_input = contract.load_json(IMAGE_INPUT, "installed image input")
|
||||||
|
contract.validate_image_input(image_input)
|
||||||
|
revision = image_input["runtime"]["teleo_git_head"]
|
||||||
|
runtime = contract.validate_artifact(ARTIFACT, RECEIPT, revision)
|
||||||
|
for field in (
|
||||||
|
"teleo_git_head",
|
||||||
|
"artifact_sha256",
|
||||||
|
"artifact_manifest_sha256",
|
||||||
|
"artifact_receipt_sha256",
|
||||||
|
"hermes_commit",
|
||||||
|
"hermes_source_sha256",
|
||||||
|
"uv_lock_sha256",
|
||||||
|
):
|
||||||
|
_require(runtime.get(field) == image_input["runtime"].get(field), f"installed runtime binding drifted: {field}")
|
||||||
|
identity = contract.validate_identity_bundle(IDENTITY)
|
||||||
|
_require(identity == image_input["identity"], "installed identity bundle drifted")
|
||||||
|
_require(contract.sha256_file(CA) == contract.CA_SHA256, "installed Cloud SQL CA drifted")
|
||||||
|
_require(platform.python_version() == contract.PYTHON_VERSION, "installed Python version drifted")
|
||||||
|
_require(Path("/usr/bin/python3").resolve() == Path("/usr/local/bin/python3.11"), "system Python binding drifted")
|
||||||
|
psql_version = subprocess.run(
|
||||||
|
[str(PSQL), "--version"],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=10,
|
||||||
|
).stdout.strip()
|
||||||
|
_require(
|
||||||
|
psql_version.startswith(f"psql (PostgreSQL) {contract.POSTGRES_VERSION}"), "installed psql version drifted"
|
||||||
|
)
|
||||||
|
bindings = image_input["source_bindings"]
|
||||||
|
_require(contract.sha256_file(DOCKERFILE) == bindings["Dockerfile"], "installed Dockerfile binding drifted")
|
||||||
|
_require(contract.sha256_file(ENTRYPOINT) == bindings["entrypoint.py"], "installed entrypoint binding drifted")
|
||||||
|
_require(
|
||||||
|
contract.sha256_file(PACKAGE_CONTRACT) == bindings["package_contract.py"],
|
||||||
|
"installed package contract binding drifted",
|
||||||
|
)
|
||||||
|
return image_input
|
||||||
|
|
||||||
|
|
||||||
|
def _prepare_profile() -> None:
|
||||||
|
_require(os.geteuid() == 0, "profile preparation requires the capability-limited container bootstrap user")
|
||||||
|
_require(PROFILE.is_dir() and not PROFILE.is_symlink(), "runtime profile tmpfs is missing or unsafe")
|
||||||
|
_require(not any(PROFILE.iterdir()), "runtime profile tmpfs is not empty")
|
||||||
|
template = ARTIFACT / "profile-template"
|
||||||
|
for source in template.iterdir():
|
||||||
|
destination = PROFILE / source.name
|
||||||
|
if source.is_dir():
|
||||||
|
shutil.copytree(source, destination, symlinks=False)
|
||||||
|
else:
|
||||||
|
shutil.copy2(source, destination, follow_symlinks=False)
|
||||||
|
for name in contract.IDENTITY_FILES:
|
||||||
|
shutil.copy2(IDENTITY / name, PROFILE / name, follow_symlinks=False)
|
||||||
|
(PROFILE / name).chmod(0o600)
|
||||||
|
for name in STATE_DIRECTORIES:
|
||||||
|
path = PROFILE / name
|
||||||
|
path.mkdir(mode=0o700)
|
||||||
|
profile_entries = sorted(
|
||||||
|
PROFILE.rglob("*"),
|
||||||
|
key=lambda path: len(path.relative_to(PROFILE).parts),
|
||||||
|
reverse=True,
|
||||||
|
)
|
||||||
|
for path in profile_entries:
|
||||||
|
_require(not path.is_symlink(), "prepared runtime profile contains a symlink")
|
||||||
|
for path in profile_entries:
|
||||||
|
os.chown(path, contract.RUNTIME_UID, contract.RUNTIME_GID)
|
||||||
|
runtime_identity = PROFILE / "bin" / "gcp-runtime-identity.json"
|
||||||
|
os.chown(runtime_identity, 0, 0)
|
||||||
|
runtime_identity.chmod(0o644)
|
||||||
|
os.chown(PROFILE, contract.RUNTIME_UID, contract.RUNTIME_GID)
|
||||||
|
|
||||||
|
|
||||||
|
def _drop_privileges() -> None:
|
||||||
|
if os.geteuid() == contract.RUNTIME_UID and os.getegid() == contract.RUNTIME_GID:
|
||||||
|
return
|
||||||
|
_require(os.geteuid() == 0, "container bootstrap user is unexpected")
|
||||||
|
libc = ctypes.CDLL(None, use_errno=True)
|
||||||
|
pr_capbset_drop = 24
|
||||||
|
for capability in (0, 6, 7, 8): # CHOWN, SETGID, SETUID, SETPCAP (last)
|
||||||
|
if libc.prctl(pr_capbset_drop, capability, 0, 0, 0) != 0:
|
||||||
|
raise EntrypointError("container bootstrap capability drop failed")
|
||||||
|
os.setgroups([])
|
||||||
|
os.setgid(contract.RUNTIME_GID)
|
||||||
|
os.setuid(contract.RUNTIME_UID)
|
||||||
|
_require(os.geteuid() == contract.RUNTIME_UID and os.getegid() == contract.RUNTIME_GID, "privilege drop failed")
|
||||||
|
|
||||||
|
|
||||||
|
def _runtime_environment() -> dict[str, str]:
|
||||||
|
return {
|
||||||
|
"HOME": str(PROFILE / "state"),
|
||||||
|
"HERMES_HOME": str(PROFILE),
|
||||||
|
"LANG": "C",
|
||||||
|
"LC_ALL": "C",
|
||||||
|
"PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||||
|
"PYTHONDONTWRITEBYTECODE": "1",
|
||||||
|
"PYTHONNOUSERSITE": "1",
|
||||||
|
"PYTHONSAFEPATH": "1",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _exec_runtime(command: str) -> None:
|
||||||
|
_prepare_profile()
|
||||||
|
_drop_privileges()
|
||||||
|
argv = [str(RUNTIME_PYTHON), str(BOOTSTRAP), command, "--profile", str(PROFILE)]
|
||||||
|
if command == "service":
|
||||||
|
argv.extend(["--readiness-file", str(READINESS)])
|
||||||
|
os.execve(str(RUNTIME_PYTHON), argv, _runtime_environment())
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_zero_capabilities() -> None:
|
||||||
|
status = Path("/proc/1/status").read_text(encoding="utf-8")
|
||||||
|
fields = {}
|
||||||
|
for line in status.splitlines():
|
||||||
|
key, separator, value = line.partition(":")
|
||||||
|
if separator:
|
||||||
|
fields[key] = value.strip()
|
||||||
|
for name in ("CapInh", "CapPrm", "CapEff", "CapBnd", "CapAmb"):
|
||||||
|
_require(fields.get(name) == "0000000000000000", f"runtime process retains {name}")
|
||||||
|
_require(fields.get("NoNewPrivs") == "1", "runtime process does not enforce no-new-privileges")
|
||||||
|
_require(
|
||||||
|
fields.get("Uid")
|
||||||
|
== f"{contract.RUNTIME_UID}\t{contract.RUNTIME_UID}\t{contract.RUNTIME_UID}\t{contract.RUNTIME_UID}",
|
||||||
|
"runtime uid drifted",
|
||||||
|
)
|
||||||
|
_require(
|
||||||
|
fields.get("Gid")
|
||||||
|
== f"{contract.RUNTIME_GID}\t{contract.RUNTIME_GID}\t{contract.RUNTIME_GID}\t{contract.RUNTIME_GID}",
|
||||||
|
"runtime gid drifted",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _health() -> dict[str, Any]:
|
||||||
|
_drop_privileges()
|
||||||
|
_validate_zero_capabilities()
|
||||||
|
_require(READINESS.is_file() and not READINESS.is_symlink(), "runtime readiness receipt is missing or unsafe")
|
||||||
|
_require(stat.S_IMODE(READINESS.stat().st_mode) == 0o600, "runtime readiness receipt mode drifted")
|
||||||
|
value = contract.load_json(READINESS, "runtime readiness receipt")
|
||||||
|
_require(
|
||||||
|
set(value)
|
||||||
|
== {
|
||||||
|
"schema",
|
||||||
|
"status",
|
||||||
|
"process_id",
|
||||||
|
"runtime_mode",
|
||||||
|
"config_sha256",
|
||||||
|
"gateway_adapter_count",
|
||||||
|
"connected_platforms",
|
||||||
|
"tool_surface",
|
||||||
|
"identity_sha256",
|
||||||
|
},
|
||||||
|
"runtime readiness fields are not exact",
|
||||||
|
)
|
||||||
|
_require(value.get("schema") == "livingip.leocleanNoSendServiceReadiness.v1", "runtime readiness schema drifted")
|
||||||
|
_require(
|
||||||
|
value.get("status") == "ready" and value.get("process_id") == 1, "runtime readiness process binding drifted"
|
||||||
|
)
|
||||||
|
_require(value.get("runtime_mode") == "gcp_staging_no_send", "runtime readiness mode drifted")
|
||||||
|
_require(
|
||||||
|
value.get("gateway_adapter_count") == 0 and value.get("connected_platforms") == [],
|
||||||
|
"runtime has a transport adapter",
|
||||||
|
)
|
||||||
|
surface = value.get("tool_surface")
|
||||||
|
_require(isinstance(surface, dict), "runtime tool surface is missing")
|
||||||
|
_require(surface.get("tools") == ["skill_view", "skills_list", "terminal"], "runtime tool surface drifted")
|
||||||
|
_require(surface.get("send_message_present") is False, "runtime send_message surface is present")
|
||||||
|
expected_identity = {name: contract.sha256_file(PROFILE / name) for name in contract.IDENTITY_FILES}
|
||||||
|
_require(value.get("identity_sha256") == expected_identity, "runtime readiness identity binding drifted")
|
||||||
|
cmdline = Path("/proc/1/cmdline").read_bytes().split(b"\0")
|
||||||
|
expected_native = [
|
||||||
|
str(RUNTIME_PYTHON).encode(),
|
||||||
|
str(BOOTSTRAP).encode(),
|
||||||
|
b"service",
|
||||||
|
b"--profile",
|
||||||
|
str(PROFILE).encode(),
|
||||||
|
b"--readiness-file",
|
||||||
|
str(READINESS).encode(),
|
||||||
|
b"",
|
||||||
|
]
|
||||||
|
expected_binfmt = [expected_native[0], *expected_native]
|
||||||
|
_require(cmdline in (expected_native, expected_binfmt), "runtime process command line drifted")
|
||||||
|
return {
|
||||||
|
"schema": "livingip.leocleanNoSendContainerHealth.v1",
|
||||||
|
"status": "pass",
|
||||||
|
"process_id": 1,
|
||||||
|
"gateway_adapter_count": 0,
|
||||||
|
"send_message_present": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _verification_result(image_input: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"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": contract.PYTHON_VERSION,
|
||||||
|
"postgres_version": contract.POSTGRES_VERSION,
|
||||||
|
"cloudsql_ca_sha256": contract.CA_SHA256,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
arguments = argv if argv is not None else sys.argv[1:]
|
||||||
|
arguments = arguments or ["service"]
|
||||||
|
command = arguments[0]
|
||||||
|
try:
|
||||||
|
image_input = _validate_installed()
|
||||||
|
if command in {"probe", "service"}:
|
||||||
|
_require(len(arguments) == 1, "runtime command arguments are not exact")
|
||||||
|
_exec_runtime(command)
|
||||||
|
if command == "health":
|
||||||
|
_require(len(arguments) == 1, "health command arguments are not exact")
|
||||||
|
result = _health()
|
||||||
|
elif command == "verify":
|
||||||
|
_require(len(arguments) == 1, "verify command arguments are not exact")
|
||||||
|
result = _verification_result(image_input)
|
||||||
|
elif command == "verify-build":
|
||||||
|
_require(len(arguments) == 5, "build verification arguments are not exact")
|
||||||
|
expected = (
|
||||||
|
image_input["runtime"]["teleo_git_head"],
|
||||||
|
image_input["runtime"]["artifact_sha256"],
|
||||||
|
image_input["identity"]["bundle_sha256"],
|
||||||
|
image_input["input_sha256"],
|
||||||
|
)
|
||||||
|
_require(tuple(arguments[1:]) == expected, "image build arguments drifted from image input")
|
||||||
|
result = _verification_result(image_input)
|
||||||
|
else:
|
||||||
|
raise EntrypointError("unsupported container command")
|
||||||
|
print(json.dumps(result, separators=(",", ":"), sort_keys=True))
|
||||||
|
return 0
|
||||||
|
except (EntrypointError, contract.PackageError, OSError, subprocess.SubprocessError):
|
||||||
|
print(json.dumps({"status": "fail", "error": "container_contract_failed"}, sort_keys=True), file=sys.stderr)
|
||||||
|
return 65
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
|
|
@ -94,3 +94,7 @@ Those remain separate rollback boundaries:
|
||||||
projection, and runtime receipts; and
|
projection, and runtime receipts; and
|
||||||
6. cut over only after soak and restore testing, then revoke the VPS credentials
|
6. cut over only after soak and restore testing, then revoke the VPS credentials
|
||||||
and retire the VPS.
|
and retire the VPS.
|
||||||
|
|
||||||
|
The independently reversible OCI/systemd packaging contract for step 2 is
|
||||||
|
documented in `docs/gcp-leoclean-nosend-service-package.md`. Its offline proof
|
||||||
|
does not count as a live deployment or satisfy steps 1, 3, 4, 5, or 6.
|
||||||
|
|
|
||||||
138
docs/gcp-leoclean-nosend-service-package.md
Normal file
138
docs/gcp-leoclean-nosend-service-package.md
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
# GCP leoclean no-send service package
|
||||||
|
|
||||||
|
## Outcome
|
||||||
|
|
||||||
|
This slice packages the revision-bound Hermes/leoclean no-send artifact and one
|
||||||
|
explicit identity bundle as an immutable OCI image for a separate
|
||||||
|
`leoclean-gcp-nosend.service` on `teleo-staging-1`.
|
||||||
|
|
||||||
|
It is target-architecture staging work, not a production cutover. The image is
|
||||||
|
the atomic compatibility boundary because Python, the locked Hermes virtual
|
||||||
|
environment, PostgreSQL client, Cloud SQL CA, runtime artifact, identity files,
|
||||||
|
and container entrypoint must move and roll back together.
|
||||||
|
|
||||||
|
## Exact target and authority boundary
|
||||||
|
|
||||||
|
- project: `teleo-501523`
|
||||||
|
- zone: `europe-west6-a`
|
||||||
|
- instance: `teleo-staging-1`
|
||||||
|
- service account:
|
||||||
|
`sa-teleo-staging-vm@teleo-501523.iam.gserviceaccount.com`
|
||||||
|
- service: `leoclean-gcp-nosend.service`
|
||||||
|
- platform: `linux/amd64`
|
||||||
|
- image repository:
|
||||||
|
`europe-west6-docker.pkg.dev/teleo-501523/teleo/leoclean-nosend-staging`
|
||||||
|
|
||||||
|
The package adds only a deployable, digest-addressed staging service surface.
|
||||||
|
It does not add IAM, Secret Manager, database-role, proposal-approval,
|
||||||
|
canonical-apply, transport, production, or VPS authority.
|
||||||
|
|
||||||
|
The service has no provider credential in this slice. It can prove the gateway
|
||||||
|
and database-tool boundary, but paid model calls require a separately reviewed
|
||||||
|
internal no-send ingress and credential-delivery design during the parity
|
||||||
|
slice.
|
||||||
|
|
||||||
|
## Immutable closure
|
||||||
|
|
||||||
|
The dedicated Dockerfile uses only digest-pinned base images:
|
||||||
|
|
||||||
|
- Python `3.11.9`;
|
||||||
|
- uv `0.9.30`;
|
||||||
|
- PostgreSQL client `16.14` on Bookworm.
|
||||||
|
|
||||||
|
There is no `apt-get`, floating `pip install`, editable install, or image tag in
|
||||||
|
the closure. The final image also binds:
|
||||||
|
|
||||||
|
- the exact Teleo Git revision and release-pass artifact receipt;
|
||||||
|
- Hermes commit and source-tree hash;
|
||||||
|
- the artifact content and manifest hashes;
|
||||||
|
- the exact uv lock;
|
||||||
|
- the identity manifest, lock, `SOUL.md`, and bundle hashes;
|
||||||
|
- the Cloud SQL server CA hash;
|
||||||
|
- the Dockerfile, entrypoint, and package-contract hashes.
|
||||||
|
|
||||||
|
The runtime image starts with a capability-limited bootstrap that creates a
|
||||||
|
fresh tmpfs profile, then drops all groups, UID/GID to `65532`, and all Linux
|
||||||
|
capability bounding sets before executing Hermes. The gateway readiness receipt
|
||||||
|
is written only after the real zero-adapter runner starts and its sealed tool
|
||||||
|
registry is rechecked.
|
||||||
|
|
||||||
|
The PostgreSQL base image's inherited data-volume path is explicitly shadowed
|
||||||
|
by a tiny read-only, non-executable, mode-`0000` tmpfs. This prevents Docker
|
||||||
|
from exposing an unused writable database directory to the runtime.
|
||||||
|
|
||||||
|
## Prepare a build context
|
||||||
|
|
||||||
|
First compile and release-verify the no-send artifact from the exact clean
|
||||||
|
revision. Separately provide a reviewed identity directory containing exactly:
|
||||||
|
|
||||||
|
- `SOUL.md`
|
||||||
|
- `identity-lock.json`
|
||||||
|
- `identity-manifest.json`
|
||||||
|
|
||||||
|
Then run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 ops/gcp_leoclean_nosend_package.py prepare \
|
||||||
|
--artifact /path/to/artifact \
|
||||||
|
--receipt /path/to/leoclean-nosend-runtime.json \
|
||||||
|
--identity /path/to/identity-bundle \
|
||||||
|
--output /tmp/leoclean-nosend-image-context
|
||||||
|
```
|
||||||
|
|
||||||
|
The command fails closed on a development receipt, revision drift, content or
|
||||||
|
mode drift, unsafe links, an inconsistent identity lock, a changed CA, a
|
||||||
|
non-staging target, or an existing output directory. The resulting context is
|
||||||
|
secret-free.
|
||||||
|
|
||||||
|
Use the exact values in `build-args.json` as Docker build arguments. The image
|
||||||
|
build itself revalidates that those values equal `image-input.json`.
|
||||||
|
|
||||||
|
## Finalize one digest-bound release
|
||||||
|
|
||||||
|
After an authorized build and Artifact Registry push returns the immutable
|
||||||
|
image digest:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 ops/gcp_leoclean_nosend_package.py finalize \
|
||||||
|
--image-input /tmp/leoclean-nosend-image-context/image-input.json \
|
||||||
|
--image-reference \
|
||||||
|
europe-west6-docker.pkg.dev/teleo-501523/teleo/leoclean-nosend-staging@sha256:<64-hex-digest> \
|
||||||
|
--output-release /tmp/leoclean-nosend-release.json \
|
||||||
|
--output-unit /tmp/leoclean-gcp-nosend.service
|
||||||
|
```
|
||||||
|
|
||||||
|
Tags, alternate repositories, alternate projects/instances/service accounts,
|
||||||
|
extra descriptor fields, and mismatched labels are rejected.
|
||||||
|
|
||||||
|
## Offline evidence and live gate
|
||||||
|
|
||||||
|
Offline tests prove descriptor strictness, artifact and identity binding,
|
||||||
|
digest-only images, pinned runtime versions, a secret-free context, the
|
||||||
|
capability-drop contract, no published ports, no Docker-socket mount, no
|
||||||
|
environment file, and no production/VPS/Telegram target.
|
||||||
|
|
||||||
|
They do not prove that `teleo-staging-1` currently has a compatible container
|
||||||
|
engine, enough disk, the expected architecture, the expected service account,
|
||||||
|
or an unused unit name. Those are the next read-only live preflight. If the
|
||||||
|
engine is absent, installation is separate infrastructure work; the deploy must
|
||||||
|
stop instead of falling back to the production sync script or a mutable host
|
||||||
|
virtual environment.
|
||||||
|
|
||||||
|
Publishing the draft branch and stacked PR is only a review handoff. Live
|
||||||
|
installation, Artifact Registry publication, service restart, IAM/secret
|
||||||
|
changes, Cloud SQL access, and proposal staging remain separate approvals.
|
||||||
|
|
||||||
|
## Rollback and sunset
|
||||||
|
|
||||||
|
The rollback boundary is one release descriptor, one image digest, and the
|
||||||
|
dedicated no-send unit. Rollback restores the prior descriptor/unit and prior
|
||||||
|
digest; it must never restart or edit the production Leo, Telegram, or VPS
|
||||||
|
services.
|
||||||
|
|
||||||
|
The fresh tmpfs profile intentionally has no session continuity. That is
|
||||||
|
appropriate for isolated gateway/database and fresh-turn parity proof, but it
|
||||||
|
is not the production state design. Before traffic cutover, a separate reviewed
|
||||||
|
slice must bind durable noncanonical session state without making it identity or
|
||||||
|
knowledge authority. The tmpfs-only state contract sunsets when that cutover
|
||||||
|
candidate is proven.
|
||||||
50
docs/workstreams/gcp-leoclean/goals.md
Normal file
50
docs/workstreams/gcp-leoclean/goals.md
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
# GCP leoclean migration goal
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
Deploy and prove Leo's least-privilege Hermes/leoclean runtime in GCP staging
|
||||||
|
against canonical Cloud SQL, establish behavioral and database parity, connect
|
||||||
|
the protected Observatory to canonical/proposal/vector/runtime receipts, then
|
||||||
|
cut over safely and retire the VPS after soak.
|
||||||
|
|
||||||
|
## Architecture baseline
|
||||||
|
|
||||||
|
- Hermes with the `leoclean` profile remains Leo's intended runtime.
|
||||||
|
- Cloud SQL is canonical PostgreSQL authority.
|
||||||
|
- The protected Observatory is a separate frontend surface.
|
||||||
|
- The VPS remains untouched until an explicitly approved, parity-proven
|
||||||
|
cutover and retirement sequence.
|
||||||
|
|
||||||
|
## Current state
|
||||||
|
|
||||||
|
- Complete: PR #179 least-privilege PostgreSQL authority is merged on main.
|
||||||
|
- Complete: the revision-bound, structurally no-send runtime from PR #183 is
|
||||||
|
merged into `main` at `2d88c9765bd9b43641d1efb04a1118c5ce0bce39`.
|
||||||
|
- Rebuilt for human review: isolated, digest-addressed GCP staging service
|
||||||
|
package with an independent rollback boundary, exact release receipt,
|
||||||
|
immutable image verification, and network-disabled service/readiness smoke
|
||||||
|
test, based directly on that merged `main` revision in PR #182.
|
||||||
|
- In progress: validate the rebuilt PR #182 revision and stop at the
|
||||||
|
exact-revision human review and merge gate.
|
||||||
|
- Not yet live-proven: staging VM/container engine, IAM/Secret Manager, Cloud
|
||||||
|
SQL effective identity, restart behavior, or model/database parity.
|
||||||
|
|
||||||
|
## Remaining order
|
||||||
|
|
||||||
|
1. Obtain exact-revision human review for the no-send service-package stack.
|
||||||
|
2. Run the read-only `teleo-staging-1` host/engine/service-account preflight.
|
||||||
|
3. Provision the scoped Cloud SQL role/secret and deploy the separate staging
|
||||||
|
service.
|
||||||
|
4. Prove effective identity, reads, function-only proposal staging, denied
|
||||||
|
writes/escalation, receipts, restart stability, and rollback.
|
||||||
|
5. Run behavioral and database parity backtesting.
|
||||||
|
6. Connect the protected Observatory to canonical claims, proposals, pgvector
|
||||||
|
projection, and runtime receipts.
|
||||||
|
7. Cut over, soak, restore-test, revoke VPS credentials, and retire the VPS.
|
||||||
|
|
||||||
|
## Current live-action boundary
|
||||||
|
|
||||||
|
Draft branch/PR publication is authorized only as a review handoff. No merge,
|
||||||
|
Artifact Registry push, GCP/Cloud SQL/Secret Manager mutation, service
|
||||||
|
deployment/restart, Telegram change, production promotion, canonical proposal
|
||||||
|
apply, or VPS access is part of the current offline slice.
|
||||||
|
|
@ -15,9 +15,12 @@ import signal
|
||||||
import stat
|
import stat
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
READINESS_SCHEMA = "livingip.leocleanNoSendServiceReadiness.v1"
|
||||||
|
|
||||||
|
|
||||||
class NoSendContractError(RuntimeError):
|
class NoSendContractError(RuntimeError):
|
||||||
"""The runtime does not satisfy its no-send contract."""
|
"""The runtime does not satisfy its no-send contract."""
|
||||||
|
|
@ -738,7 +741,58 @@ def probe(profile: Path, *, template: bool) -> dict[str, Any]:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def _run_service(profile: Path) -> bool:
|
def _write_readiness(
|
||||||
|
profile: Path,
|
||||||
|
readiness_file: Path,
|
||||||
|
contract: dict[str, Any],
|
||||||
|
runner: Any,
|
||||||
|
tool_surface: dict[str, Any],
|
||||||
|
) -> None:
|
||||||
|
state = profile / "state"
|
||||||
|
_require(state.is_dir() and not state.is_symlink(), "service state directory is missing or unsafe")
|
||||||
|
expected_parent = state.resolve(strict=True)
|
||||||
|
_require(
|
||||||
|
readiness_file.parent.resolve(strict=True) == expected_parent, "readiness file must be inside profile state"
|
||||||
|
)
|
||||||
|
_require(readiness_file.name == "readiness.json", "readiness file name is not exact")
|
||||||
|
_require(not readiness_file.is_symlink(), "readiness file cannot be a symlink")
|
||||||
|
identity_hashes = {}
|
||||||
|
for name in ("SOUL.md", "identity-lock.json", "identity-manifest.json"):
|
||||||
|
path = profile / name
|
||||||
|
_require(path.is_file() and not path.is_symlink(), f"runtime identity entry is unsafe: {name}")
|
||||||
|
identity_hashes[name] = _sha256(path)
|
||||||
|
payload = {
|
||||||
|
"schema": READINESS_SCHEMA,
|
||||||
|
"status": "ready",
|
||||||
|
"process_id": os.getpid(),
|
||||||
|
"runtime_mode": contract["runtime_mode"],
|
||||||
|
"config_sha256": _sha256(profile / "config.yaml"),
|
||||||
|
"gateway_adapter_count": len(runner.adapters),
|
||||||
|
"connected_platforms": [],
|
||||||
|
"tool_surface": tool_surface,
|
||||||
|
"identity_sha256": identity_hashes,
|
||||||
|
}
|
||||||
|
encoded = (json.dumps(payload, separators=(",", ":"), sort_keys=True) + "\n").encode("utf-8")
|
||||||
|
descriptor = None
|
||||||
|
temporary = None
|
||||||
|
try:
|
||||||
|
descriptor, temporary = tempfile.mkstemp(prefix=".readiness.", dir=expected_parent)
|
||||||
|
os.fchmod(descriptor, 0o600)
|
||||||
|
with os.fdopen(descriptor, "wb", closefd=True) as handle:
|
||||||
|
descriptor = None
|
||||||
|
handle.write(encoded)
|
||||||
|
handle.flush()
|
||||||
|
os.fsync(handle.fileno())
|
||||||
|
os.replace(temporary, readiness_file)
|
||||||
|
temporary = None
|
||||||
|
finally:
|
||||||
|
if descriptor is not None:
|
||||||
|
os.close(descriptor)
|
||||||
|
if temporary is not None:
|
||||||
|
Path(temporary).unlink(missing_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
async def _run_service(profile: Path, readiness_file: Path | None = None) -> bool:
|
||||||
contract, _config, _surface, _effective_policy = _prepare(profile, template=False)
|
contract, _config, _surface, _effective_policy = _prepare(profile, template=False)
|
||||||
from gateway.config import GatewayConfig
|
from gateway.config import GatewayConfig
|
||||||
from gateway.run import GatewayRunner
|
from gateway.run import GatewayRunner
|
||||||
|
|
@ -749,7 +803,7 @@ async def _run_service(profile: Path) -> bool:
|
||||||
_require(getattr(runner, "_smart_model_routing", None) == {}, "service retained smart-model routing")
|
_require(getattr(runner, "_smart_model_routing", None) == {}, "service retained smart-model routing")
|
||||||
_assert_tool_registry(profile, contract)
|
_assert_tool_registry(profile, contract)
|
||||||
importlib.import_module("model_tools")
|
importlib.import_module("model_tools")
|
||||||
_assert_tool_registry(profile, contract)
|
tool_surface = _assert_tool_registry(profile, contract)
|
||||||
|
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
stop_tasks: set[asyncio.Task[Any]] = set()
|
stop_tasks: set[asyncio.Task[Any]] = set()
|
||||||
|
|
@ -766,9 +820,15 @@ async def _run_service(profile: Path) -> bool:
|
||||||
pass
|
pass
|
||||||
started = await runner.start()
|
started = await runner.start()
|
||||||
_require(started, "Hermes no-send gateway did not start")
|
_require(started, "Hermes no-send gateway did not start")
|
||||||
_assert_tool_registry(profile, contract)
|
tool_surface = _assert_tool_registry(profile, contract)
|
||||||
await runner.wait_for_shutdown()
|
if readiness_file is not None:
|
||||||
return True
|
_write_readiness(profile, readiness_file, contract, runner, tool_surface)
|
||||||
|
try:
|
||||||
|
await runner.wait_for_shutdown()
|
||||||
|
return True
|
||||||
|
finally:
|
||||||
|
if readiness_file is not None:
|
||||||
|
readiness_file.unlink(missing_ok=True)
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
|
|
@ -779,12 +839,15 @@ def main() -> int:
|
||||||
child.add_argument("--profile", required=True, type=Path)
|
child.add_argument("--profile", required=True, type=Path)
|
||||||
if name == "probe":
|
if name == "probe":
|
||||||
child.add_argument("--template", action="store_true")
|
child.add_argument("--template", action="store_true")
|
||||||
|
else:
|
||||||
|
child.add_argument("--readiness-file", type=Path)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
try:
|
try:
|
||||||
if args.command == "probe":
|
if args.command == "probe":
|
||||||
print(json.dumps(probe(args.profile.resolve(), template=args.template), sort_keys=True))
|
print(json.dumps(probe(args.profile.resolve(), template=args.template), sort_keys=True))
|
||||||
return 0
|
return 0
|
||||||
return 0 if asyncio.run(_run_service(args.profile.resolve())) else 1
|
readiness_file = args.readiness_file.absolute() if args.readiness_file else None
|
||||||
|
return 0 if asyncio.run(_run_service(args.profile.resolve(), readiness_file)) else 1
|
||||||
except NoSendContractError as exc:
|
except NoSendContractError as exc:
|
||||||
print(json.dumps({"status": "fail", "error": str(exc)}, sort_keys=True), file=sys.stderr)
|
print(json.dumps({"status": "fail", "error": str(exc)}, sort_keys=True), file=sys.stderr)
|
||||||
return 65
|
return 65
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@
|
||||||
{
|
{
|
||||||
"source": "hermes-agent/runtime/leoclean-nosend/profile-template/config.yaml",
|
"source": "hermes-agent/runtime/leoclean-nosend/profile-template/config.yaml",
|
||||||
"target": "config.yaml",
|
"target": "config.yaml",
|
||||||
"mode": "0600"
|
"mode": "0444"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"source": "hermes-agent/leoclean-bin/teleo-kb-gcp",
|
"source": "hermes-agent/leoclean-bin/teleo-kb-gcp",
|
||||||
|
|
|
||||||
674
ops/gcp_leoclean_nosend_package.py
Normal file
674
ops/gcp_leoclean_nosend_package.py
Normal file
|
|
@ -0,0 +1,674 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Build and validate the immutable GCP leoclean no-send service package."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import stat
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from collections.abc import Mapping
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
IMAGE_INPUT_SCHEMA = "livingip.leocleanNoSendImageInput.v1"
|
||||||
|
RELEASE_SCHEMA = "livingip.leocleanNoSendRelease.v1"
|
||||||
|
ARTIFACT_MANIFEST_SCHEMA = "livingip.leocleanNoSendArtifactManifest.v1"
|
||||||
|
ARTIFACT_RECEIPT_SCHEMA = "livingip.leocleanNoSendArtifactVerification.v1"
|
||||||
|
IDENTITY_MANIFEST_SCHEMA = "livingip.leoIdentityManifest.v1"
|
||||||
|
IDENTITY_LOCK_SCHEMA = "livingip.leoIdentityProfileLock.v1"
|
||||||
|
|
||||||
|
PROJECT = "teleo-501523"
|
||||||
|
ZONE = "europe-west6-a"
|
||||||
|
INSTANCE = "teleo-staging-1"
|
||||||
|
SERVICE = "leoclean-gcp-nosend.service"
|
||||||
|
SERVICE_ACCOUNT = "sa-teleo-staging-vm@teleo-501523.iam.gserviceaccount.com"
|
||||||
|
PLATFORM = "linux/amd64"
|
||||||
|
IMAGE_REPOSITORY = "europe-west6-docker.pkg.dev/teleo-501523/teleo/leoclean-nosend-staging"
|
||||||
|
CONTAINER_NAME = "livingip-leoclean-nosend"
|
||||||
|
|
||||||
|
HERMES_COMMIT = "b2f477a30b3c05d0f383c543af98496ae8a96070"
|
||||||
|
PYTHON_VERSION = "3.11.9"
|
||||||
|
UV_VERSION = "0.9.30"
|
||||||
|
POSTGRES_VERSION = "16.14"
|
||||||
|
PYTHON_IMAGE = (
|
||||||
|
"docker.io/library/python:3.11.9-slim-bookworm@"
|
||||||
|
"sha256:8fb099199b9f2d70342674bd9dbccd3ed03a258f26bbd1d556822c6dfc60c317"
|
||||||
|
)
|
||||||
|
UV_IMAGE = "ghcr.io/astral-sh/uv:0.9.30@sha256:538e0b39736e7feae937a65983e49d2ab75e1559d35041f9878b7b7e51de91e4"
|
||||||
|
POSTGRES_IMAGE = (
|
||||||
|
"docker.io/library/postgres:16.14-bookworm@sha256:92620daddcd947f8d5ab5ba66e848702fe443d87fed30c4cea8e389fd78dfc55"
|
||||||
|
)
|
||||||
|
CA_SHA256 = "80701e768f0e1f6b9d621aa0b53f6e851daaa276c6d9a8e51a300fbc015539cb"
|
||||||
|
CA_DESTINATION = "/usr/local/libexec/livingip/leoclean-kb/cloudsql-server-ca.pem"
|
||||||
|
RUNTIME_ROOT = "/opt/livingip/leoclean-nosend"
|
||||||
|
PROFILE_ROOT = "/run/leoclean-profile"
|
||||||
|
RUNTIME_UID = 65532
|
||||||
|
RUNTIME_GID = 65532
|
||||||
|
|
||||||
|
HEX_40 = re.compile(r"^[0-9a-f]{40}$")
|
||||||
|
HEX_64 = re.compile(r"^[0-9a-f]{64}$")
|
||||||
|
IMAGE_REFERENCE = re.compile(rf"^{re.escape(IMAGE_REPOSITORY)}@sha256:([0-9a-f]{{64}})$")
|
||||||
|
FORBIDDEN_DESCRIPTOR_MARKERS = (
|
||||||
|
"77.42.65.182",
|
||||||
|
"gcp-teleo-pgvector-standby-postgres-password",
|
||||||
|
"leoclean-gateway.service",
|
||||||
|
"sa-teleo-prod-vm",
|
||||||
|
"telegram_bot_token",
|
||||||
|
"teleo-prod-1",
|
||||||
|
)
|
||||||
|
IDENTITY_FILES = ("SOUL.md", "identity-lock.json", "identity-manifest.json")
|
||||||
|
|
||||||
|
|
||||||
|
class PackageError(RuntimeError):
|
||||||
|
"""The image or service package does not satisfy the staging contract."""
|
||||||
|
|
||||||
|
|
||||||
|
def _require(condition: bool, message: str) -> None:
|
||||||
|
if not condition:
|
||||||
|
raise PackageError(message)
|
||||||
|
|
||||||
|
|
||||||
|
def sha256_file(path: Path) -> str:
|
||||||
|
digest = hashlib.sha256()
|
||||||
|
with path.open("rb") as handle:
|
||||||
|
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
||||||
|
digest.update(chunk)
|
||||||
|
return digest.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def canonical_sha256(value: Any) -> str:
|
||||||
|
encoded = json.dumps(value, ensure_ascii=True, separators=(",", ":"), sort_keys=True).encode("utf-8")
|
||||||
|
return hashlib.sha256(encoded).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def load_json(path: Path, label: str) -> dict[str, Any]:
|
||||||
|
try:
|
||||||
|
value = json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
except (OSError, json.JSONDecodeError) as exc:
|
||||||
|
raise PackageError(f"cannot read {label}: {type(exc).__name__}") from exc
|
||||||
|
_require(isinstance(value, dict), f"{label} must be a JSON object")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def write_json(path: Path, value: Mapping[str, Any], mode: int = 0o644) -> None:
|
||||||
|
path.write_text(json.dumps(value, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||||
|
path.chmod(mode)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_sha256(value: Any) -> bool:
|
||||||
|
return isinstance(value, str) and bool(HEX_64.fullmatch(value))
|
||||||
|
|
||||||
|
|
||||||
|
def _is_revision(value: Any) -> bool:
|
||||||
|
return isinstance(value, str) and bool(HEX_40.fullmatch(value))
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_tree(root: Path, label: str) -> None:
|
||||||
|
_require(root.is_dir() and not root.is_symlink(), f"{label} root is missing or unsafe")
|
||||||
|
for path in root.rglob("*"):
|
||||||
|
_require(not path.is_symlink(), f"{label} contains a symlink")
|
||||||
|
_require(path.is_dir() or path.is_file(), f"{label} contains an unsupported entry")
|
||||||
|
|
||||||
|
|
||||||
|
def tree_manifest(root: Path, *, excluded: frozenset[str] = frozenset()) -> dict[str, Any]:
|
||||||
|
_safe_tree(root, "tree")
|
||||||
|
files: list[dict[str, Any]] = []
|
||||||
|
for path in sorted(root.rglob("*"), key=lambda item: item.relative_to(root).as_posix()):
|
||||||
|
relative = path.relative_to(root).as_posix()
|
||||||
|
if relative in excluded or path.is_dir():
|
||||||
|
continue
|
||||||
|
mode = stat.S_IMODE(path.stat().st_mode)
|
||||||
|
files.append(
|
||||||
|
{
|
||||||
|
"path": relative,
|
||||||
|
"bytes": path.stat().st_size,
|
||||||
|
"mode": f"0o{mode:03o}",
|
||||||
|
"sha256": sha256_file(path),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
stable = {"files": files, "file_count": len(files), "total_bytes": sum(item["bytes"] for item in files)}
|
||||||
|
return {**stable, "sha256": canonical_sha256(stable)}
|
||||||
|
|
||||||
|
|
||||||
|
def identity_bundle_manifest(root: Path) -> dict[str, Any]:
|
||||||
|
_safe_tree(root, "identity bundle")
|
||||||
|
actual = sorted(path.name for path in root.iterdir())
|
||||||
|
_require(actual == sorted(IDENTITY_FILES), "identity bundle entry set is not exact")
|
||||||
|
files = []
|
||||||
|
for name in IDENTITY_FILES:
|
||||||
|
path = root / name
|
||||||
|
_require(path.is_file() and not path.is_symlink(), f"identity file is unsafe: {name}")
|
||||||
|
_require(path.stat().st_size <= 1024 * 1024, f"identity file is too large: {name}")
|
||||||
|
_require(not stat.S_IMODE(path.stat().st_mode) & 0o022, f"identity file is group/world writable: {name}")
|
||||||
|
files.append({"path": name, "bytes": path.stat().st_size, "sha256": sha256_file(path)})
|
||||||
|
stable = {"files": files, "file_count": len(files), "total_bytes": sum(item["bytes"] for item in files)}
|
||||||
|
return {**stable, "sha256": canonical_sha256(stable)}
|
||||||
|
|
||||||
|
|
||||||
|
def validate_identity_bundle(root: Path) -> dict[str, Any]:
|
||||||
|
bundle = identity_bundle_manifest(root)
|
||||||
|
manifest = load_json(root / "identity-manifest.json", "identity manifest")
|
||||||
|
lock = load_json(root / "identity-lock.json", "identity lock")
|
||||||
|
_require(
|
||||||
|
set(manifest) == {"schema", "identity_inputs", "identity_inputs_sha256", "compiled_views", "manifest_sha256"},
|
||||||
|
"identity manifest fields are not exact",
|
||||||
|
)
|
||||||
|
_require(manifest.get("schema") == IDENTITY_MANIFEST_SCHEMA, "identity manifest schema is invalid")
|
||||||
|
_require(_is_sha256(manifest.get("manifest_sha256")), "identity manifest hash is invalid")
|
||||||
|
_require(_is_sha256(manifest.get("identity_inputs_sha256")), "identity input hash is invalid")
|
||||||
|
stable = {key: value for key, value in manifest.items() if key != "manifest_sha256"}
|
||||||
|
_require(canonical_sha256(stable) == manifest["manifest_sha256"], "identity manifest self-hash drifted")
|
||||||
|
views = manifest.get("compiled_views")
|
||||||
|
_require(
|
||||||
|
isinstance(views, dict) and set(views) == {"SOUL.md", "identity.json"}, "compiled identity views are incomplete"
|
||||||
|
)
|
||||||
|
soul = views["SOUL.md"] if isinstance(views, dict) else None
|
||||||
|
_require(isinstance(soul, dict) and _is_sha256(soul.get("sha256")), "SOUL identity binding is invalid")
|
||||||
|
_require(soul["sha256"] == sha256_file(root / "SOUL.md"), "SOUL content differs from identity manifest")
|
||||||
|
_require(
|
||||||
|
set(lock) == {"schema", "manifest_sha256", "identity_inputs_sha256", "compiled_views", "session_boundary"},
|
||||||
|
"identity lock fields are not exact",
|
||||||
|
)
|
||||||
|
_require(lock.get("schema") == IDENTITY_LOCK_SCHEMA, "identity lock schema is invalid")
|
||||||
|
_require(lock.get("manifest_sha256") == manifest["manifest_sha256"], "identity lock manifest binding drifted")
|
||||||
|
_require(
|
||||||
|
lock.get("identity_inputs_sha256") == manifest["identity_inputs_sha256"],
|
||||||
|
"identity lock input binding drifted",
|
||||||
|
)
|
||||||
|
_require(
|
||||||
|
isinstance(lock.get("compiled_views"), dict)
|
||||||
|
and (lock["compiled_views"].get("SOUL.md") or {}).get("sha256") == soul["sha256"],
|
||||||
|
"identity lock SOUL binding drifted",
|
||||||
|
)
|
||||||
|
inputs = manifest.get("identity_inputs")
|
||||||
|
database = inputs.get("canonical_database") if isinstance(inputs, dict) else None
|
||||||
|
_require(isinstance(database, dict), "identity database authority is missing")
|
||||||
|
authority = database.get("authority")
|
||||||
|
canonical = database.get("canonical_authority_granted")
|
||||||
|
_require(
|
||||||
|
(authority, canonical)
|
||||||
|
in {
|
||||||
|
("synthetic_noncanonical_fixture", False),
|
||||||
|
("canonical_database_same_transaction_export", True),
|
||||||
|
},
|
||||||
|
"identity database authority is invalid",
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"bundle_sha256": bundle["sha256"],
|
||||||
|
"manifest_sha256": manifest["manifest_sha256"],
|
||||||
|
"identity_inputs_sha256": manifest["identity_inputs_sha256"],
|
||||||
|
"soul_sha256": soul["sha256"],
|
||||||
|
"authority": authority,
|
||||||
|
"canonical_authority_granted": canonical,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def validate_artifact(artifact: Path, receipt_path: Path, expected_revision: str) -> dict[str, Any]:
|
||||||
|
_require(_is_revision(expected_revision), "expected Teleo revision is invalid")
|
||||||
|
_safe_tree(artifact, "runtime artifact")
|
||||||
|
manifest = load_json(artifact / "artifact-manifest.json", "artifact manifest")
|
||||||
|
receipt = load_json(receipt_path, "artifact verification receipt")
|
||||||
|
_require(manifest.get("schema") == ARTIFACT_MANIFEST_SCHEMA, "artifact manifest schema is invalid")
|
||||||
|
_require(receipt.get("schema") == ARTIFACT_RECEIPT_SCHEMA, "artifact receipt schema is invalid")
|
||||||
|
_require(receipt.get("status") == "pass", "artifact receipt did not pass")
|
||||||
|
_require(receipt.get("verification_mode") == "release", "artifact receipt is not release evidence")
|
||||||
|
_require(manifest.get("teleo_git_head") == expected_revision, "artifact revision differs from source revision")
|
||||||
|
_require(receipt.get("teleo_git_head") == expected_revision, "receipt revision differs from source revision")
|
||||||
|
content = tree_manifest(artifact, excluded=frozenset({"artifact-manifest.json"}))
|
||||||
|
_require(manifest.get("content") == content, "artifact content manifest drifted")
|
||||||
|
_require(receipt.get("artifact_sha256") == content["sha256"], "artifact receipt content hash drifted")
|
||||||
|
_require(manifest.get("hermes_commit") == HERMES_COMMIT, "artifact Hermes commit drifted")
|
||||||
|
_require(receipt.get("hermes_commit") == HERMES_COMMIT, "receipt Hermes commit drifted")
|
||||||
|
runtime_lock = load_json(artifact / "runtime-lock.json", "artifact runtime lock")
|
||||||
|
_require(
|
||||||
|
runtime_lock.get("base_images") == {"python": PYTHON_IMAGE.removeprefix("docker.io/library/"), "uv": UV_IMAGE},
|
||||||
|
"artifact base-image lock drifted",
|
||||||
|
)
|
||||||
|
hermes = runtime_lock.get("hermes")
|
||||||
|
_require(isinstance(hermes, dict) and hermes.get("commit") == HERMES_COMMIT, "artifact Hermes lock drifted")
|
||||||
|
claim_ceiling = manifest.get("claim_ceiling")
|
||||||
|
_require(isinstance(claim_ceiling, str) and claim_ceiling == receipt.get("claim_ceiling"), "claim ceiling drifted")
|
||||||
|
return {
|
||||||
|
"teleo_git_head": expected_revision,
|
||||||
|
"artifact_sha256": content["sha256"],
|
||||||
|
"artifact_manifest_sha256": sha256_file(artifact / "artifact-manifest.json"),
|
||||||
|
"artifact_receipt_sha256": sha256_file(receipt_path),
|
||||||
|
"hermes_commit": HERMES_COMMIT,
|
||||||
|
"hermes_source_sha256": receipt.get("hermes_source_sha256"),
|
||||||
|
"uv_lock_sha256": hermes.get("uv_lock_sha256"),
|
||||||
|
"claim_ceiling": claim_ceiling,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _target() -> dict[str, str]:
|
||||||
|
return {
|
||||||
|
"project": PROJECT,
|
||||||
|
"zone": ZONE,
|
||||||
|
"instance": INSTANCE,
|
||||||
|
"service": SERVICE,
|
||||||
|
"service_account": SERVICE_ACCOUNT,
|
||||||
|
"platform": PLATFORM,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _base_images() -> dict[str, str]:
|
||||||
|
return {"python": PYTHON_IMAGE, "uv": UV_IMAGE, "postgres": POSTGRES_IMAGE}
|
||||||
|
|
||||||
|
|
||||||
|
def _assert_no_forbidden_descriptor_markers(value: Mapping[str, Any]) -> None:
|
||||||
|
encoded = json.dumps(value, ensure_ascii=True, separators=(",", ":"), sort_keys=True).casefold()
|
||||||
|
for marker in FORBIDDEN_DESCRIPTOR_MARKERS:
|
||||||
|
_require(
|
||||||
|
marker.casefold() not in encoded, "release descriptor contains a forbidden production/transport marker"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_image_input(value: dict[str, Any]) -> None:
|
||||||
|
_require(
|
||||||
|
set(value)
|
||||||
|
== {
|
||||||
|
"schema",
|
||||||
|
"target",
|
||||||
|
"runtime",
|
||||||
|
"identity",
|
||||||
|
"base_images",
|
||||||
|
"source_bindings",
|
||||||
|
"claim_ceiling",
|
||||||
|
"input_sha256",
|
||||||
|
},
|
||||||
|
"image input fields are not exact",
|
||||||
|
)
|
||||||
|
_require(value.get("schema") == IMAGE_INPUT_SCHEMA, "image input schema is invalid")
|
||||||
|
_require(value.get("target") == _target(), "image input target is not exact GCP staging")
|
||||||
|
_require(value.get("base_images") == _base_images(), "image input base-image closure drifted")
|
||||||
|
runtime = value.get("runtime")
|
||||||
|
_require(isinstance(runtime, dict), "image runtime binding is missing")
|
||||||
|
_require(
|
||||||
|
set(runtime)
|
||||||
|
== {
|
||||||
|
"teleo_git_head",
|
||||||
|
"artifact_sha256",
|
||||||
|
"artifact_manifest_sha256",
|
||||||
|
"artifact_receipt_sha256",
|
||||||
|
"hermes_commit",
|
||||||
|
"hermes_source_sha256",
|
||||||
|
"uv_lock_sha256",
|
||||||
|
"python_version",
|
||||||
|
"uv_version",
|
||||||
|
"postgres_version",
|
||||||
|
"cloudsql_ca_sha256",
|
||||||
|
},
|
||||||
|
"image runtime binding fields are not exact",
|
||||||
|
)
|
||||||
|
_require(_is_revision(runtime.get("teleo_git_head")), "image Teleo revision is invalid")
|
||||||
|
for field in (
|
||||||
|
"artifact_sha256",
|
||||||
|
"artifact_manifest_sha256",
|
||||||
|
"artifact_receipt_sha256",
|
||||||
|
"hermes_source_sha256",
|
||||||
|
"uv_lock_sha256",
|
||||||
|
"cloudsql_ca_sha256",
|
||||||
|
):
|
||||||
|
_require(_is_sha256(runtime.get(field)), f"image runtime {field} is invalid")
|
||||||
|
_require(runtime.get("hermes_commit") == HERMES_COMMIT, "image Hermes commit drifted")
|
||||||
|
_require(runtime.get("python_version") == PYTHON_VERSION, "image Python version drifted")
|
||||||
|
_require(runtime.get("uv_version") == UV_VERSION, "image uv version drifted")
|
||||||
|
_require(runtime.get("postgres_version") == POSTGRES_VERSION, "image PostgreSQL version drifted")
|
||||||
|
_require(runtime.get("cloudsql_ca_sha256") == CA_SHA256, "image Cloud SQL CA drifted")
|
||||||
|
identity = value.get("identity")
|
||||||
|
_require(
|
||||||
|
isinstance(identity, dict)
|
||||||
|
and set(identity)
|
||||||
|
== {
|
||||||
|
"bundle_sha256",
|
||||||
|
"manifest_sha256",
|
||||||
|
"identity_inputs_sha256",
|
||||||
|
"soul_sha256",
|
||||||
|
"authority",
|
||||||
|
"canonical_authority_granted",
|
||||||
|
},
|
||||||
|
"image identity binding fields are not exact",
|
||||||
|
)
|
||||||
|
for field in ("bundle_sha256", "manifest_sha256", "identity_inputs_sha256", "soul_sha256"):
|
||||||
|
_require(_is_sha256(identity.get(field)), f"image identity {field} is invalid")
|
||||||
|
_require(
|
||||||
|
(identity.get("authority"), identity.get("canonical_authority_granted"))
|
||||||
|
in {
|
||||||
|
("synthetic_noncanonical_fixture", False),
|
||||||
|
("canonical_database_same_transaction_export", True),
|
||||||
|
},
|
||||||
|
"image identity authority is invalid",
|
||||||
|
)
|
||||||
|
bindings = value.get("source_bindings")
|
||||||
|
_require(
|
||||||
|
isinstance(bindings, dict)
|
||||||
|
and set(bindings) == {"Dockerfile", "entrypoint.py", "package_contract.py"}
|
||||||
|
and all(_is_sha256(item) for item in bindings.values()),
|
||||||
|
"image packaging source bindings are invalid",
|
||||||
|
)
|
||||||
|
_require(
|
||||||
|
isinstance(value.get("claim_ceiling"), str) and bool(value["claim_ceiling"]), "image claim ceiling is missing"
|
||||||
|
)
|
||||||
|
stable = {key: item for key, item in value.items() if key != "input_sha256"}
|
||||||
|
_require(value.get("input_sha256") == canonical_sha256(stable), "image input self-hash drifted")
|
||||||
|
_assert_no_forbidden_descriptor_markers(value)
|
||||||
|
|
||||||
|
|
||||||
|
def build_image_input(
|
||||||
|
*,
|
||||||
|
artifact: Path,
|
||||||
|
receipt: Path,
|
||||||
|
identity: Path,
|
||||||
|
ca: Path,
|
||||||
|
source_revision: str,
|
||||||
|
dockerfile: Path,
|
||||||
|
entrypoint: Path,
|
||||||
|
package_contract: Path,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
_require(sha256_file(ca) == CA_SHA256, "Cloud SQL CA does not match the reviewed certificate")
|
||||||
|
runtime = validate_artifact(artifact, receipt, source_revision)
|
||||||
|
runtime.update(
|
||||||
|
{
|
||||||
|
"python_version": PYTHON_VERSION,
|
||||||
|
"uv_version": UV_VERSION,
|
||||||
|
"postgres_version": POSTGRES_VERSION,
|
||||||
|
"cloudsql_ca_sha256": CA_SHA256,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
identity_binding = validate_identity_bundle(identity)
|
||||||
|
stable = {
|
||||||
|
"schema": IMAGE_INPUT_SCHEMA,
|
||||||
|
"target": _target(),
|
||||||
|
"runtime": {key: runtime[key] for key in runtime if key != "claim_ceiling"},
|
||||||
|
"identity": identity_binding,
|
||||||
|
"base_images": _base_images(),
|
||||||
|
"source_bindings": {
|
||||||
|
"Dockerfile": sha256_file(dockerfile),
|
||||||
|
"entrypoint.py": sha256_file(entrypoint),
|
||||||
|
"package_contract.py": sha256_file(package_contract),
|
||||||
|
},
|
||||||
|
"claim_ceiling": runtime["claim_ceiling"],
|
||||||
|
}
|
||||||
|
value = {**stable, "input_sha256": canonical_sha256(stable)}
|
||||||
|
validate_image_input(value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_context(
|
||||||
|
*,
|
||||||
|
repo_root: Path,
|
||||||
|
artifact: Path,
|
||||||
|
receipt: Path,
|
||||||
|
identity: Path,
|
||||||
|
output: Path,
|
||||||
|
source_revision: str,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
_require(not output.exists() and not output.is_symlink(), "output context already exists")
|
||||||
|
dockerfile = repo_root / "docker" / "leoclean-nosend" / "Dockerfile"
|
||||||
|
entrypoint = repo_root / "docker" / "leoclean-nosend" / "entrypoint.py"
|
||||||
|
package_contract = repo_root / "ops" / "gcp_leoclean_nosend_package.py"
|
||||||
|
ca = repo_root / "ops" / "gcp-teleo-pgvector-standby-server-ca.pem"
|
||||||
|
for path, label in (
|
||||||
|
(dockerfile, "Dockerfile"),
|
||||||
|
(entrypoint, "entrypoint"),
|
||||||
|
(package_contract, "package contract"),
|
||||||
|
(ca, "Cloud SQL CA"),
|
||||||
|
):
|
||||||
|
_require(path.is_file() and not path.is_symlink(), f"{label} source is missing or unsafe")
|
||||||
|
image_input = build_image_input(
|
||||||
|
artifact=artifact,
|
||||||
|
receipt=receipt,
|
||||||
|
identity=identity,
|
||||||
|
ca=ca,
|
||||||
|
source_revision=source_revision,
|
||||||
|
dockerfile=dockerfile,
|
||||||
|
entrypoint=entrypoint,
|
||||||
|
package_contract=package_contract,
|
||||||
|
)
|
||||||
|
output.mkdir(parents=True, mode=0o700)
|
||||||
|
try:
|
||||||
|
shutil.copytree(artifact, output / "artifact", symlinks=False)
|
||||||
|
shutil.copytree(identity, output / "identity", symlinks=False)
|
||||||
|
for name in IDENTITY_FILES:
|
||||||
|
(output / "identity" / name).chmod(0o600)
|
||||||
|
shutil.copyfile(receipt, output / "artifact-receipt.json")
|
||||||
|
shutil.copyfile(ca, output / "cloudsql-server-ca.pem")
|
||||||
|
shutil.copyfile(dockerfile, output / "Dockerfile")
|
||||||
|
shutil.copyfile(entrypoint, output / "entrypoint.py")
|
||||||
|
shutil.copyfile(package_contract, output / "package_contract.py")
|
||||||
|
for name in (
|
||||||
|
"artifact-receipt.json",
|
||||||
|
"cloudsql-server-ca.pem",
|
||||||
|
"Dockerfile",
|
||||||
|
"entrypoint.py",
|
||||||
|
"package_contract.py",
|
||||||
|
):
|
||||||
|
(output / name).chmod(0o644)
|
||||||
|
write_json(output / "image-input.json", image_input)
|
||||||
|
write_json(
|
||||||
|
output / "build-args.json",
|
||||||
|
{
|
||||||
|
"ARTIFACT_SHA256": image_input["runtime"]["artifact_sha256"],
|
||||||
|
"IDENTITY_SHA256": image_input["identity"]["bundle_sha256"],
|
||||||
|
"INPUT_SHA256": image_input["input_sha256"],
|
||||||
|
"TELEO_REVISION": source_revision,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
_safe_tree(output, "prepared image context")
|
||||||
|
return image_input
|
||||||
|
except BaseException:
|
||||||
|
shutil.rmtree(output, ignore_errors=True)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def build_release_descriptor(image_input: dict[str, Any], image_reference: str) -> dict[str, Any]:
|
||||||
|
validate_image_input(image_input)
|
||||||
|
match = IMAGE_REFERENCE.fullmatch(image_reference)
|
||||||
|
_require(match is not None, "release image must be the exact staging repository at a sha256 digest")
|
||||||
|
digest = f"sha256:{match.group(1)}"
|
||||||
|
labels = expected_image_labels(image_input)
|
||||||
|
stable = {
|
||||||
|
"schema": RELEASE_SCHEMA,
|
||||||
|
"target": _target(),
|
||||||
|
"image": {
|
||||||
|
"reference": image_reference,
|
||||||
|
"digest": digest,
|
||||||
|
"platform": PLATFORM,
|
||||||
|
"labels": labels,
|
||||||
|
"input_sha256": image_input["input_sha256"],
|
||||||
|
},
|
||||||
|
"image_input": image_input,
|
||||||
|
}
|
||||||
|
value = {**stable, "release_sha256": canonical_sha256(stable)}
|
||||||
|
validate_release_descriptor(value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def expected_image_labels(image_input: dict[str, Any]) -> dict[str, str]:
|
||||||
|
validate_image_input(image_input)
|
||||||
|
return {
|
||||||
|
"livingip.artifact.sha256": image_input["runtime"]["artifact_sha256"],
|
||||||
|
"livingip.identity.sha256": image_input["identity"]["bundle_sha256"],
|
||||||
|
"livingip.input.sha256": image_input["input_sha256"],
|
||||||
|
"livingip.runtime": "leoclean-nosend",
|
||||||
|
"livingip.target": "gcp-staging",
|
||||||
|
"org.opencontainers.image.revision": image_input["runtime"]["teleo_git_head"],
|
||||||
|
"org.opencontainers.image.source": "https://github.com/living-ip/teleo-infrastructure",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def validate_release_descriptor(value: dict[str, Any]) -> None:
|
||||||
|
_require(
|
||||||
|
set(value) == {"schema", "target", "image", "image_input", "release_sha256"},
|
||||||
|
"release descriptor fields are not exact",
|
||||||
|
)
|
||||||
|
_require(value.get("schema") == RELEASE_SCHEMA, "release descriptor schema is invalid")
|
||||||
|
_require(value.get("target") == _target(), "release target is not exact GCP staging")
|
||||||
|
image_input = value.get("image_input")
|
||||||
|
_require(isinstance(image_input, dict), "release image input is missing")
|
||||||
|
validate_image_input(image_input)
|
||||||
|
image = value.get("image")
|
||||||
|
_require(
|
||||||
|
isinstance(image, dict) and set(image) == {"reference", "digest", "platform", "labels", "input_sha256"},
|
||||||
|
"release image fields are not exact",
|
||||||
|
)
|
||||||
|
reference = image.get("reference")
|
||||||
|
match = IMAGE_REFERENCE.fullmatch(reference) if isinstance(reference, str) else None
|
||||||
|
_require(match is not None, "release image is not digest-pinned in the staging repository")
|
||||||
|
_require(image.get("digest") == f"sha256:{match.group(1)}", "release image digest differs from its reference")
|
||||||
|
_require(image.get("platform") == PLATFORM, "release platform is not exact")
|
||||||
|
_require(image.get("input_sha256") == image_input["input_sha256"], "release image input binding drifted")
|
||||||
|
_require(image.get("labels") == expected_image_labels(image_input), "release image labels are not exact")
|
||||||
|
stable = {key: item for key, item in value.items() if key != "release_sha256"}
|
||||||
|
_require(value.get("release_sha256") == canonical_sha256(stable), "release descriptor self-hash drifted")
|
||||||
|
_assert_no_forbidden_descriptor_markers(value)
|
||||||
|
|
||||||
|
|
||||||
|
def render_systemd_unit(release: dict[str, Any]) -> str:
|
||||||
|
validate_release_descriptor(release)
|
||||||
|
image = release["image"]["reference"]
|
||||||
|
release_sha = release["release_sha256"]
|
||||||
|
profile_tmpfs = f"{PROFILE_ROOT}:rw,noexec,nosuid,nodev,size=64m,uid=0,gid=0,mode=0700"
|
||||||
|
temp_tmpfs = f"/tmp:rw,noexec,nosuid,nodev,size=64m,uid={RUNTIME_UID},gid={RUNTIME_GID},mode=0700"
|
||||||
|
postgres_data_tmpfs = "/var/lib/postgresql/data:ro,noexec,nosuid,nodev,size=64k,uid=0,gid=0,mode=0000"
|
||||||
|
exec_start = " ".join(
|
||||||
|
[
|
||||||
|
"/usr/bin/docker run",
|
||||||
|
"--rm",
|
||||||
|
f"--name={CONTAINER_NAME}",
|
||||||
|
"--pull=never",
|
||||||
|
"--read-only",
|
||||||
|
"--cap-drop=ALL",
|
||||||
|
"--cap-add=CHOWN",
|
||||||
|
"--cap-add=SETGID",
|
||||||
|
"--cap-add=SETUID",
|
||||||
|
"--cap-add=SETPCAP",
|
||||||
|
"--security-opt=no-new-privileges:true",
|
||||||
|
"--pids-limit=512",
|
||||||
|
"--network=bridge",
|
||||||
|
"--stop-timeout=30",
|
||||||
|
f"--tmpfs={profile_tmpfs}",
|
||||||
|
f"--tmpfs={temp_tmpfs}",
|
||||||
|
f"--tmpfs={postgres_data_tmpfs}",
|
||||||
|
f"--label=livingip.release.sha256={release_sha}",
|
||||||
|
image,
|
||||||
|
"service",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
unit = f"""[Unit]
|
||||||
|
Description=LivingIP leoclean-gcp-nosend staging
|
||||||
|
Documentation=https://github.com/living-ip/teleo-infrastructure
|
||||||
|
After=docker.service network-online.target
|
||||||
|
Requires=docker.service
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart={exec_start}
|
||||||
|
ExecStop=/usr/bin/docker stop --time=30 {CONTAINER_NAME}
|
||||||
|
ExecStopPost=-/usr/bin/docker rm --force {CONTAINER_NAME}
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5s
|
||||||
|
TimeoutStartSec=120s
|
||||||
|
TimeoutStopSec=45s
|
||||||
|
UMask=0077
|
||||||
|
NoNewPrivileges=yes
|
||||||
|
CapabilityBoundingSet=
|
||||||
|
AmbientCapabilities=
|
||||||
|
PrivateDevices=yes
|
||||||
|
PrivateTmp=yes
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=yes
|
||||||
|
ProtectKernelTunables=yes
|
||||||
|
ProtectKernelModules=yes
|
||||||
|
ProtectKernelLogs=yes
|
||||||
|
ProtectControlGroups=yes
|
||||||
|
LockPersonality=yes
|
||||||
|
RestrictSUIDSGID=yes
|
||||||
|
RestrictRealtime=yes
|
||||||
|
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
||||||
|
SystemCallArchitectures=native
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
"""
|
||||||
|
_require("--publish" not in unit and " -p " not in unit, "service unit publishes a port")
|
||||||
|
_require("docker.sock" not in unit, "service unit mounts the Docker socket")
|
||||||
|
_require(
|
||||||
|
"EnvironmentFile" not in unit and "LoadCredential" not in unit,
|
||||||
|
"service unit adds an unreviewed credential source",
|
||||||
|
)
|
||||||
|
_require("telegram" not in unit.casefold(), "service unit contains a messaging marker")
|
||||||
|
return unit
|
||||||
|
|
||||||
|
|
||||||
|
def _git_head(repo_root: Path) -> str:
|
||||||
|
try:
|
||||||
|
return subprocess.run(
|
||||||
|
["git", "-C", str(repo_root), "rev-parse", "HEAD"],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
).stdout.strip()
|
||||||
|
except (OSError, subprocess.CalledProcessError) as exc:
|
||||||
|
raise PackageError("cannot determine source revision") from exc
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||||
|
prepare = subparsers.add_parser("prepare")
|
||||||
|
prepare.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1])
|
||||||
|
prepare.add_argument("--artifact", type=Path, required=True)
|
||||||
|
prepare.add_argument("--receipt", type=Path, required=True)
|
||||||
|
prepare.add_argument("--identity", type=Path, required=True)
|
||||||
|
prepare.add_argument("--output", type=Path, required=True)
|
||||||
|
finalize = subparsers.add_parser("finalize")
|
||||||
|
finalize.add_argument("--image-input", type=Path, required=True)
|
||||||
|
finalize.add_argument("--image-reference", required=True)
|
||||||
|
finalize.add_argument("--output-release", type=Path, required=True)
|
||||||
|
finalize.add_argument("--output-unit", type=Path, required=True)
|
||||||
|
validate = subparsers.add_parser("validate")
|
||||||
|
validate.add_argument("--release", type=Path, required=True)
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
try:
|
||||||
|
args = _parse_args(argv)
|
||||||
|
if args.command == "prepare":
|
||||||
|
repo_root = args.repo_root.resolve()
|
||||||
|
result = prepare_context(
|
||||||
|
repo_root=repo_root,
|
||||||
|
artifact=args.artifact.resolve(),
|
||||||
|
receipt=args.receipt.resolve(),
|
||||||
|
identity=args.identity.resolve(),
|
||||||
|
output=args.output.absolute(),
|
||||||
|
source_revision=_git_head(repo_root),
|
||||||
|
)
|
||||||
|
elif args.command == "finalize":
|
||||||
|
image_input = load_json(args.image_input, "image input")
|
||||||
|
result = build_release_descriptor(image_input, args.image_reference)
|
||||||
|
for path in (args.output_release, args.output_unit):
|
||||||
|
_require(not path.exists() and not path.is_symlink(), "finalize output already exists")
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
write_json(args.output_release, result)
|
||||||
|
args.output_unit.write_text(render_systemd_unit(result), encoding="utf-8")
|
||||||
|
args.output_unit.chmod(0o644)
|
||||||
|
else:
|
||||||
|
result = load_json(args.release, "release descriptor")
|
||||||
|
validate_release_descriptor(result)
|
||||||
|
print(json.dumps({"status": "pass", "schema": result["schema"]}, sort_keys=True))
|
||||||
|
return 0
|
||||||
|
except PackageError as exc:
|
||||||
|
print(json.dumps({"status": "fail", "error": str(exc)}, sort_keys=True), file=sys.stderr)
|
||||||
|
return 65
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
|
|
@ -46,11 +46,7 @@ def _canonical_sha256(value: Any) -> str:
|
||||||
|
|
||||||
|
|
||||||
def _is_sha256(value: Any) -> bool:
|
def _is_sha256(value: Any) -> bool:
|
||||||
return (
|
return isinstance(value, str) and len(value) == 64 and all(character in "0123456789abcdef" for character in value)
|
||||||
isinstance(value, str)
|
|
||||||
and len(value) == 64
|
|
||||||
and all(character in "0123456789abcdef" for character in value)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _safe_relative(value: Any, label: str) -> str:
|
def _safe_relative(value: Any, label: str) -> str:
|
||||||
|
|
@ -72,7 +68,10 @@ def _load_json(path: Path, label: str) -> dict[str, Any]:
|
||||||
|
|
||||||
def _validate_lock(value: dict[str, Any]) -> None:
|
def _validate_lock(value: dict[str, Any]) -> None:
|
||||||
_require(value.get("schema") == LOCK_SCHEMA, "unsupported runtime lock schema")
|
_require(value.get("schema") == LOCK_SCHEMA, "unsupported runtime lock schema")
|
||||||
_require(set(value) == {"schema", "hermes", "base_images", "patches", "profile_sources"}, "runtime lock fields are invalid")
|
_require(
|
||||||
|
set(value) == {"schema", "hermes", "base_images", "patches", "profile_sources"},
|
||||||
|
"runtime lock fields are invalid",
|
||||||
|
)
|
||||||
hermes = value.get("hermes")
|
hermes = value.get("hermes")
|
||||||
_require(isinstance(hermes, dict), "Hermes lock is missing")
|
_require(isinstance(hermes, dict), "Hermes lock is missing")
|
||||||
_require(
|
_require(
|
||||||
|
|
@ -90,7 +89,10 @@ def _validate_lock(value: dict[str, Any]) -> None:
|
||||||
"Hermes lock fields are invalid",
|
"Hermes lock fields are invalid",
|
||||||
)
|
)
|
||||||
commit = hermes.get("commit")
|
commit = hermes.get("commit")
|
||||||
_require(isinstance(commit, str) and len(commit) == 40 and all(ch in "0123456789abcdef" for ch in commit), "Hermes commit must be an exact SHA")
|
_require(
|
||||||
|
isinstance(commit, str) and len(commit) == 40 and all(ch in "0123456789abcdef" for ch in commit),
|
||||||
|
"Hermes commit must be an exact SHA",
|
||||||
|
)
|
||||||
_require(hermes.get("repository") == "https://github.com/NousResearch/hermes-agent", "Hermes repository is invalid")
|
_require(hermes.get("repository") == "https://github.com/NousResearch/hermes-agent", "Hermes repository is invalid")
|
||||||
_require(
|
_require(
|
||||||
hermes.get("archive_url") == f"https://codeload.github.com/NousResearch/hermes-agent/tar.gz/{commit}",
|
hermes.get("archive_url") == f"https://codeload.github.com/NousResearch/hermes-agent/tar.gz/{commit}",
|
||||||
|
|
@ -108,7 +110,10 @@ def _validate_lock(value: dict[str, Any]) -> None:
|
||||||
targets: set[str] = set()
|
targets: set[str] = set()
|
||||||
for patch in patches:
|
for patch in patches:
|
||||||
_require(isinstance(patch, dict), "patch lock entry must be an object")
|
_require(isinstance(patch, dict), "patch lock entry must be an object")
|
||||||
_require(set(patch) == {"source", "source_sha256", "target", "before_sha256", "after_sha256"}, "patch lock fields are invalid")
|
_require(
|
||||||
|
set(patch) == {"source", "source_sha256", "target", "before_sha256", "after_sha256"},
|
||||||
|
"patch lock fields are invalid",
|
||||||
|
)
|
||||||
_safe_relative(patch["source"], "patch source")
|
_safe_relative(patch["source"], "patch source")
|
||||||
target = _safe_relative(patch["target"], "patch target")
|
target = _safe_relative(patch["target"], "patch target")
|
||||||
_require(isinstance(target, str) and target not in targets, "patch targets must be unique")
|
_require(isinstance(target, str) and target not in targets, "patch targets must be unique")
|
||||||
|
|
@ -119,12 +124,15 @@ def _validate_lock(value: dict[str, Any]) -> None:
|
||||||
_require(isinstance(sources, list) and bool(sources), "profile sources are missing")
|
_require(isinstance(sources, list) and bool(sources), "profile sources are missing")
|
||||||
profile_targets: set[str] = set()
|
profile_targets: set[str] = set()
|
||||||
for source in sources:
|
for source in sources:
|
||||||
_require(isinstance(source, dict) and set(source) == {"source", "target", "mode"}, "profile source fields are invalid")
|
_require(
|
||||||
|
isinstance(source, dict) and set(source) == {"source", "target", "mode"},
|
||||||
|
"profile source fields are invalid",
|
||||||
|
)
|
||||||
_safe_relative(source["source"], "profile source")
|
_safe_relative(source["source"], "profile source")
|
||||||
target = _safe_relative(source["target"], "profile target")
|
target = _safe_relative(source["target"], "profile target")
|
||||||
_require(target not in profile_targets, "duplicate profile target")
|
_require(target not in profile_targets, "duplicate profile target")
|
||||||
profile_targets.add(target)
|
profile_targets.add(target)
|
||||||
_require(source["mode"] in {"0600", "0644", "0755"}, "profile mode is invalid")
|
_require(source["mode"] in {"0444", "0600", "0644", "0755"}, "profile mode is invalid")
|
||||||
|
|
||||||
|
|
||||||
def _download(url: str, target: Path, timeout: float) -> None:
|
def _download(url: str, target: Path, timeout: float) -> None:
|
||||||
|
|
@ -190,7 +198,9 @@ def _apply_patches(repo_root: Path, hermes_root: Path, lock: dict[str, Any]) ->
|
||||||
patcher = _bound_file(repo_root, entry["source"], f"managed patcher {entry['source']}")
|
patcher = _bound_file(repo_root, entry["source"], f"managed patcher {entry['source']}")
|
||||||
target = _bound_file(hermes_root, entry["target"], f"Hermes patch target {entry['target']}")
|
target = _bound_file(hermes_root, entry["target"], f"Hermes patch target {entry['target']}")
|
||||||
_require(_sha256(patcher) == entry["source_sha256"], f"managed patcher drifted: {entry['source']}")
|
_require(_sha256(patcher) == entry["source_sha256"], f"managed patcher drifted: {entry['source']}")
|
||||||
_require(_sha256(target) == entry["before_sha256"], f"Hermes patch target drifted before apply: {entry['target']}")
|
_require(
|
||||||
|
_sha256(target) == entry["before_sha256"], f"Hermes patch target drifted before apply: {entry['target']}"
|
||||||
|
)
|
||||||
module = _load_patcher(patcher)
|
module = _load_patcher(patcher)
|
||||||
result, code = module.install(target, check=False)
|
result, code = module.install(target, check=False)
|
||||||
_require(code == 0, f"managed patch failed: {entry['source']}")
|
_require(code == 0, f"managed patch failed: {entry['source']}")
|
||||||
|
|
@ -219,6 +229,7 @@ def _copy_profile(repo_root: Path, artifact: Path, lock: dict[str, Any]) -> None
|
||||||
target.parent.mkdir(parents=True, exist_ok=True)
|
target.parent.mkdir(parents=True, exist_ok=True)
|
||||||
shutil.copyfile(source, target)
|
shutil.copyfile(source, target)
|
||||||
target.chmod(int(entry["mode"], 8))
|
target.chmod(int(entry["mode"], 8))
|
||||||
|
profile.chmod(0o555)
|
||||||
|
|
||||||
|
|
||||||
def _tree_manifest(root: Path, *, excluded: frozenset[str] = frozenset()) -> dict[str, Any]:
|
def _tree_manifest(root: Path, *, excluded: frozenset[str] = frozenset()) -> dict[str, Any]:
|
||||||
|
|
@ -304,8 +315,12 @@ def compile_artifact(
|
||||||
extracted = staging / "extracted"
|
extracted = staging / "extracted"
|
||||||
extracted.mkdir()
|
extracted.mkdir()
|
||||||
hermes_source = _safe_extract(archive_path, extracted, lock["hermes"]["archive_root"])
|
hermes_source = _safe_extract(archive_path, extracted, lock["hermes"]["archive_root"])
|
||||||
_require(_sha256(hermes_source / "pyproject.toml") == lock["hermes"]["pyproject_sha256"], "Hermes pyproject drifted")
|
_require(
|
||||||
_require(_sha256(hermes_source / "uv.lock") == lock["hermes"]["uv_lock_sha256"], "Hermes dependency lock drifted")
|
_sha256(hermes_source / "pyproject.toml") == lock["hermes"]["pyproject_sha256"], "Hermes pyproject drifted"
|
||||||
|
)
|
||||||
|
_require(
|
||||||
|
_sha256(hermes_source / "uv.lock") == lock["hermes"]["uv_lock_sha256"], "Hermes dependency lock drifted"
|
||||||
|
)
|
||||||
patch_receipts = _apply_patches(repo_root, hermes_source, lock)
|
patch_receipts = _apply_patches(repo_root, hermes_source, lock)
|
||||||
patched_source = _tree_manifest(hermes_source)
|
patched_source = _tree_manifest(hermes_source)
|
||||||
_require(
|
_require(
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ fi
|
||||||
|
|
||||||
temporary="$(mktemp -d "${TMPDIR:-/tmp}/livingip-nosend-runtime.XXXXXX")"
|
temporary="$(mktemp -d "${TMPDIR:-/tmp}/livingip-nosend-runtime.XXXXXX")"
|
||||||
cleanup() {
|
cleanup() {
|
||||||
|
chmod -R u+w "$temporary" 2>/dev/null || true
|
||||||
rm -rf "$temporary"
|
rm -rf "$temporary"
|
||||||
}
|
}
|
||||||
trap cleanup EXIT INT TERM
|
trap cleanup EXIT INT TERM
|
||||||
|
|
|
||||||
362
tests/test_gcp_leoclean_nosend_package.py
Normal file
362
tests/test_gcp_leoclean_nosend_package.py
Normal file
|
|
@ -0,0 +1,362 @@
|
||||||
|
"""Offline contracts for the isolated GCP leoclean no-send service image."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from ops import gcp_leoclean_nosend_package as package
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
REVISION = "1" * 40
|
||||||
|
IMAGE_DIGEST = "2" * 64
|
||||||
|
CLAIM_CEILING = "Offline package proof only; live GCP identity, database access, restart, and parity remain unproven."
|
||||||
|
|
||||||
|
|
||||||
|
def write_json(path: Path, value: object, mode: int = 0o600) -> None:
|
||||||
|
path.write_text(json.dumps(value, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||||
|
path.chmod(mode)
|
||||||
|
|
||||||
|
|
||||||
|
def artifact_fixture(tmp_path: Path) -> tuple[Path, Path]:
|
||||||
|
artifact = tmp_path / "artifact"
|
||||||
|
artifact.mkdir()
|
||||||
|
(artifact / "bootstrap.py").write_text("#!/usr/bin/env python3\n", encoding="utf-8")
|
||||||
|
(artifact / "bootstrap.py").chmod(0o755)
|
||||||
|
write_json(
|
||||||
|
artifact / "runtime-lock.json",
|
||||||
|
{
|
||||||
|
"base_images": {
|
||||||
|
"python": package.PYTHON_IMAGE.removeprefix("docker.io/library/"),
|
||||||
|
"uv": package.UV_IMAGE,
|
||||||
|
},
|
||||||
|
"hermes": {
|
||||||
|
"commit": package.HERMES_COMMIT,
|
||||||
|
"uv_lock_sha256": "3" * 64,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mode=0o644,
|
||||||
|
)
|
||||||
|
profile = artifact / "profile-template"
|
||||||
|
profile.mkdir()
|
||||||
|
(profile / "config.yaml").write_text("model: fixture\n", encoding="utf-8")
|
||||||
|
(profile / "config.yaml").chmod(0o600)
|
||||||
|
content = package.tree_manifest(artifact)
|
||||||
|
manifest = {
|
||||||
|
"schema": package.ARTIFACT_MANIFEST_SCHEMA,
|
||||||
|
"teleo_git_head": REVISION,
|
||||||
|
"hermes_commit": package.HERMES_COMMIT,
|
||||||
|
"claim_ceiling": CLAIM_CEILING,
|
||||||
|
"content": content,
|
||||||
|
}
|
||||||
|
write_json(artifact / "artifact-manifest.json", manifest, mode=0o644)
|
||||||
|
receipt = tmp_path / "receipt.json"
|
||||||
|
write_json(
|
||||||
|
receipt,
|
||||||
|
{
|
||||||
|
"schema": package.ARTIFACT_RECEIPT_SCHEMA,
|
||||||
|
"status": "pass",
|
||||||
|
"verification_mode": "release",
|
||||||
|
"teleo_git_head": REVISION,
|
||||||
|
"artifact_sha256": content["sha256"],
|
||||||
|
"hermes_commit": package.HERMES_COMMIT,
|
||||||
|
"hermes_source_sha256": "4" * 64,
|
||||||
|
"claim_ceiling": CLAIM_CEILING,
|
||||||
|
},
|
||||||
|
mode=0o644,
|
||||||
|
)
|
||||||
|
return artifact, receipt
|
||||||
|
|
||||||
|
|
||||||
|
def identity_fixture(tmp_path: Path) -> Path:
|
||||||
|
identity = tmp_path / "identity"
|
||||||
|
identity.mkdir(parents=True)
|
||||||
|
soul = identity / "SOUL.md"
|
||||||
|
soul.write_text("# Leo fixture\n", encoding="utf-8")
|
||||||
|
soul.chmod(0o600)
|
||||||
|
stable = {
|
||||||
|
"schema": package.IDENTITY_MANIFEST_SCHEMA,
|
||||||
|
"identity_inputs": {
|
||||||
|
"canonical_database": {
|
||||||
|
"authority": "synthetic_noncanonical_fixture",
|
||||||
|
"canonical_authority_granted": False,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"identity_inputs_sha256": "5" * 64,
|
||||||
|
"compiled_views": {
|
||||||
|
"SOUL.md": {"sha256": package.sha256_file(soul)},
|
||||||
|
"identity.json": {"sha256": "6" * 64},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
manifest = {**stable, "manifest_sha256": package.canonical_sha256(stable)}
|
||||||
|
write_json(identity / "identity-manifest.json", manifest)
|
||||||
|
write_json(
|
||||||
|
identity / "identity-lock.json",
|
||||||
|
{
|
||||||
|
"schema": package.IDENTITY_LOCK_SCHEMA,
|
||||||
|
"manifest_sha256": manifest["manifest_sha256"],
|
||||||
|
"identity_inputs_sha256": manifest["identity_inputs_sha256"],
|
||||||
|
"compiled_views": {
|
||||||
|
"SOUL.md": {"sha256": package.sha256_file(soul)},
|
||||||
|
"identity.json": {"sha256": "6" * 64},
|
||||||
|
},
|
||||||
|
"session_boundary": {"classification": "temporary_noncanonical"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return identity
|
||||||
|
|
||||||
|
|
||||||
|
def image_input_fixture(tmp_path: Path) -> dict[str, object]:
|
||||||
|
artifact, receipt = artifact_fixture(tmp_path)
|
||||||
|
identity = identity_fixture(tmp_path)
|
||||||
|
return package.build_image_input(
|
||||||
|
artifact=artifact,
|
||||||
|
receipt=receipt,
|
||||||
|
identity=identity,
|
||||||
|
ca=ROOT / "ops" / "gcp-teleo-pgvector-standby-server-ca.pem",
|
||||||
|
source_revision=REVISION,
|
||||||
|
dockerfile=ROOT / "docker" / "leoclean-nosend" / "Dockerfile",
|
||||||
|
entrypoint=ROOT / "docker" / "leoclean-nosend" / "entrypoint.py",
|
||||||
|
package_contract=ROOT / "ops" / "gcp_leoclean_nosend_package.py",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_image_input_binds_exact_runtime_identity_and_staging_target(tmp_path: Path) -> None:
|
||||||
|
value = image_input_fixture(tmp_path)
|
||||||
|
package.validate_image_input(value)
|
||||||
|
|
||||||
|
assert value["target"] == {
|
||||||
|
"project": "teleo-501523",
|
||||||
|
"zone": "europe-west6-a",
|
||||||
|
"instance": "teleo-staging-1",
|
||||||
|
"service": "leoclean-gcp-nosend.service",
|
||||||
|
"service_account": "sa-teleo-staging-vm@teleo-501523.iam.gserviceaccount.com",
|
||||||
|
"platform": "linux/amd64",
|
||||||
|
}
|
||||||
|
assert value["runtime"]["python_version"] == "3.11.9"
|
||||||
|
assert value["runtime"]["postgres_version"] == "16.14"
|
||||||
|
assert value["identity"]["canonical_authority_granted"] is False
|
||||||
|
assert value["base_images"]["postgres"].endswith(
|
||||||
|
"@sha256:92620daddcd947f8d5ab5ba66e848702fe443d87fed30c4cea8e389fd78dfc55"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_artifact_requires_release_receipt_exact_revision_and_unchanged_content(tmp_path: Path) -> None:
|
||||||
|
artifact, receipt = artifact_fixture(tmp_path)
|
||||||
|
package.validate_artifact(artifact, receipt, REVISION)
|
||||||
|
|
||||||
|
development = json.loads(receipt.read_text(encoding="utf-8"))
|
||||||
|
development["verification_mode"] = "development"
|
||||||
|
write_json(receipt, development, mode=0o644)
|
||||||
|
with pytest.raises(package.PackageError, match="not release"):
|
||||||
|
package.validate_artifact(artifact, receipt, REVISION)
|
||||||
|
|
||||||
|
development["verification_mode"] = "release"
|
||||||
|
write_json(receipt, development, mode=0o644)
|
||||||
|
(artifact / "bootstrap.py").write_text("mutated\n", encoding="utf-8")
|
||||||
|
(artifact / "bootstrap.py").chmod(0o755)
|
||||||
|
with pytest.raises(package.PackageError, match="content manifest drifted"):
|
||||||
|
package.validate_artifact(artifact, receipt, REVISION)
|
||||||
|
|
||||||
|
|
||||||
|
def test_identity_bundle_rejects_drift_extra_files_and_false_authority(tmp_path: Path) -> None:
|
||||||
|
identity = identity_fixture(tmp_path)
|
||||||
|
package.validate_identity_bundle(identity)
|
||||||
|
|
||||||
|
(identity / "SOUL.md").write_text("drift\n", encoding="utf-8")
|
||||||
|
(identity / "SOUL.md").chmod(0o600)
|
||||||
|
with pytest.raises(package.PackageError, match="SOUL content differs"):
|
||||||
|
package.validate_identity_bundle(identity)
|
||||||
|
|
||||||
|
identity = identity_fixture(tmp_path / "second")
|
||||||
|
(identity / "extra.txt").write_text("extra\n", encoding="utf-8")
|
||||||
|
with pytest.raises(package.PackageError, match="entry set"):
|
||||||
|
package.validate_identity_bundle(identity)
|
||||||
|
|
||||||
|
identity = identity_fixture(tmp_path / "third")
|
||||||
|
manifest_path = identity / "identity-manifest.json"
|
||||||
|
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||||
|
manifest["identity_inputs"]["canonical_database"] = {
|
||||||
|
"authority": "synthetic_noncanonical_fixture",
|
||||||
|
"canonical_authority_granted": True,
|
||||||
|
}
|
||||||
|
stable = {key: value for key, value in manifest.items() if key != "manifest_sha256"}
|
||||||
|
manifest["manifest_sha256"] = package.canonical_sha256(stable)
|
||||||
|
write_json(manifest_path, manifest)
|
||||||
|
lock_path = identity / "identity-lock.json"
|
||||||
|
lock = json.loads(lock_path.read_text(encoding="utf-8"))
|
||||||
|
lock["manifest_sha256"] = manifest["manifest_sha256"]
|
||||||
|
write_json(lock_path, lock)
|
||||||
|
with pytest.raises(package.PackageError, match="authority"):
|
||||||
|
package.validate_identity_bundle(identity)
|
||||||
|
|
||||||
|
|
||||||
|
def test_release_requires_exact_digest_repository_and_strict_schema(tmp_path: Path) -> None:
|
||||||
|
image_input = image_input_fixture(tmp_path)
|
||||||
|
reference = f"{package.IMAGE_REPOSITORY}@sha256:{IMAGE_DIGEST}"
|
||||||
|
release = package.build_release_descriptor(image_input, reference)
|
||||||
|
package.validate_release_descriptor(release)
|
||||||
|
|
||||||
|
assert release["image"]["reference"] == reference
|
||||||
|
assert release["image"]["digest"] == f"sha256:{IMAGE_DIGEST}"
|
||||||
|
assert release["image"]["platform"] == "linux/amd64"
|
||||||
|
|
||||||
|
with pytest.raises(package.PackageError, match="sha256 digest"):
|
||||||
|
package.build_release_descriptor(image_input, f"{package.IMAGE_REPOSITORY}:latest")
|
||||||
|
with pytest.raises(package.PackageError, match="staging repository"):
|
||||||
|
package.build_release_descriptor(
|
||||||
|
image_input,
|
||||||
|
f"europe-west6-docker.pkg.dev/teleo-501523/teleo/other@sha256:{IMAGE_DIGEST}",
|
||||||
|
)
|
||||||
|
|
||||||
|
extra = copy.deepcopy(release)
|
||||||
|
extra["unexpected"] = True
|
||||||
|
with pytest.raises(package.PackageError, match="fields are not exact"):
|
||||||
|
package.validate_release_descriptor(extra)
|
||||||
|
|
||||||
|
wrong_label = copy.deepcopy(release)
|
||||||
|
wrong_label["image"]["labels"]["livingip.artifact.sha256"] = "9" * 64
|
||||||
|
stable = {key: value for key, value in wrong_label.items() if key != "release_sha256"}
|
||||||
|
wrong_label["release_sha256"] = package.canonical_sha256(stable)
|
||||||
|
with pytest.raises(package.PackageError, match="labels are not exact"):
|
||||||
|
package.validate_release_descriptor(wrong_label)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rendered_unit_is_isolated_digest_pinned_and_has_no_credential_or_transport_surface(tmp_path: Path) -> None:
|
||||||
|
image_input = image_input_fixture(tmp_path)
|
||||||
|
release = package.build_release_descriptor(
|
||||||
|
image_input,
|
||||||
|
f"{package.IMAGE_REPOSITORY}@sha256:{IMAGE_DIGEST}",
|
||||||
|
)
|
||||||
|
unit = package.render_systemd_unit(release)
|
||||||
|
|
||||||
|
assert "leoclean-gcp-nosend" in unit
|
||||||
|
assert f"@sha256:{IMAGE_DIGEST}" in unit
|
||||||
|
assert "--pull=never" in unit
|
||||||
|
assert "--read-only" in unit
|
||||||
|
assert "--cap-drop=ALL" in unit
|
||||||
|
assert "--cap-add=CHOWN" in unit
|
||||||
|
assert "--cap-add=SETGID" in unit
|
||||||
|
assert "--cap-add=SETUID" in unit
|
||||||
|
assert "--cap-add=SETPCAP" in unit
|
||||||
|
assert "--network=bridge" in unit
|
||||||
|
assert "--tmpfs=/run/leoclean-profile:" in unit
|
||||||
|
assert "--tmpfs=/var/lib/postgresql/data:ro,noexec,nosuid,nodev,size=64k,uid=0,gid=0,mode=0000" in unit
|
||||||
|
assert "NoNewPrivileges=yes" in unit
|
||||||
|
assert "EnvironmentFile" not in unit
|
||||||
|
assert "LoadCredential" not in unit
|
||||||
|
assert "docker.sock" not in unit
|
||||||
|
assert "--publish" not in unit
|
||||||
|
assert "teleo-prod-1" not in unit
|
||||||
|
assert "77.42.65.182" not in unit
|
||||||
|
assert "telegram" not in unit.casefold()
|
||||||
|
|
||||||
|
|
||||||
|
def test_prepare_context_is_secret_free_normalized_and_idempotently_refuses_overwrite(tmp_path: Path) -> None:
|
||||||
|
artifact, receipt = artifact_fixture(tmp_path)
|
||||||
|
identity = identity_fixture(tmp_path)
|
||||||
|
output = tmp_path / "context"
|
||||||
|
result = package.prepare_context(
|
||||||
|
repo_root=ROOT,
|
||||||
|
artifact=artifact,
|
||||||
|
receipt=receipt,
|
||||||
|
identity=identity,
|
||||||
|
output=output,
|
||||||
|
source_revision=REVISION,
|
||||||
|
)
|
||||||
|
|
||||||
|
package.validate_image_input(result)
|
||||||
|
assert sorted(path.name for path in output.iterdir()) == [
|
||||||
|
"Dockerfile",
|
||||||
|
"artifact",
|
||||||
|
"artifact-receipt.json",
|
||||||
|
"build-args.json",
|
||||||
|
"cloudsql-server-ca.pem",
|
||||||
|
"entrypoint.py",
|
||||||
|
"identity",
|
||||||
|
"image-input.json",
|
||||||
|
"package_contract.py",
|
||||||
|
]
|
||||||
|
assert all((output / "identity" / name).stat().st_mode & 0o777 == 0o600 for name in package.IDENTITY_FILES)
|
||||||
|
encoded = (output / "image-input.json").read_text(encoding="utf-8").casefold()
|
||||||
|
assert "telegram_bot_token" not in encoded
|
||||||
|
assert "postgres-password" not in encoded
|
||||||
|
|
||||||
|
with pytest.raises(package.PackageError, match="already exists"):
|
||||||
|
package.prepare_context(
|
||||||
|
repo_root=ROOT,
|
||||||
|
artifact=artifact,
|
||||||
|
receipt=receipt,
|
||||||
|
identity=identity,
|
||||||
|
output=output,
|
||||||
|
source_revision=REVISION,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_dockerfile_has_only_digest_pinned_bases_and_no_floating_package_install() -> None:
|
||||||
|
dockerfile = (ROOT / "docker" / "leoclean-nosend" / "Dockerfile").read_text(encoding="utf-8")
|
||||||
|
from_lines = [line for line in dockerfile.splitlines() if line.startswith("FROM ")]
|
||||||
|
|
||||||
|
assert len(from_lines) == 3
|
||||||
|
assert all("@sha256:" in line for line in from_lines)
|
||||||
|
assert "postgres:16.14-bookworm@sha256:92620d" in dockerfile
|
||||||
|
assert "python:3.11.9-slim-bookworm@sha256:8fb099" in dockerfile
|
||||||
|
assert "uv:0.9.30@sha256:538e0b" in dockerfile
|
||||||
|
assert "apt-get" not in dockerfile
|
||||||
|
assert "pip install" not in dockerfile
|
||||||
|
assert "uv sync" in dockerfile
|
||||||
|
assert "--frozen" in dockerfile
|
||||||
|
assert "--no-editable" in dockerfile
|
||||||
|
assert "HEALTHCHECK" in dockerfile
|
||||||
|
assert "verify-build" in dockerfile
|
||||||
|
|
||||||
|
|
||||||
|
def test_entrypoint_prepares_exact_profile_drops_privileges_and_health_checks_pid_one() -> None:
|
||||||
|
source = (ROOT / "docker" / "leoclean-nosend" / "entrypoint.py").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert "os.setgroups([])" in source
|
||||||
|
assert "os.setgid(contract.RUNTIME_GID)" in source
|
||||||
|
assert "os.setuid(contract.RUNTIME_UID)" in source
|
||||||
|
assert "pr_capbset_drop = 24" in source
|
||||||
|
assert "reverse=True" in source
|
||||||
|
assert source.index("os.chown(runtime_identity, 0, 0)") < source.index(
|
||||||
|
"os.chown(PROFILE, contract.RUNTIME_UID, contract.RUNTIME_GID)"
|
||||||
|
)
|
||||||
|
assert 'value.get("process_id") == 1' in source
|
||||||
|
assert "expected_binfmt = [expected_native[0], *expected_native]" in source
|
||||||
|
assert "cmdline in (expected_native, expected_binfmt)" in source
|
||||||
|
assert 'surface.get("send_message_present") is False' in source
|
||||||
|
assert "gateway_adapter_count" in source
|
||||||
|
assert "os.execve" in source
|
||||||
|
assert "OPENROUTER_API_KEY" not in source
|
||||||
|
assert "TELEGRAM_BOT_TOKEN" not in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_entrypoint_loads_adjacent_contract_with_python_safe_path(tmp_path: Path) -> None:
|
||||||
|
image_root = tmp_path / "image"
|
||||||
|
image_root.mkdir()
|
||||||
|
entrypoint = image_root / "entrypoint.py"
|
||||||
|
shutil.copy2(ROOT / "docker" / "leoclean-nosend" / "entrypoint.py", entrypoint)
|
||||||
|
shutil.copy2(ROOT / "ops" / "gcp_leoclean_nosend_package.py", image_root / "package_contract.py")
|
||||||
|
environment = {**os.environ, "PYTHONSAFEPATH": "1"}
|
||||||
|
|
||||||
|
result = subprocess.run(
|
||||||
|
[sys.executable, "-P", str(entrypoint), "verify"],
|
||||||
|
cwd=tmp_path,
|
||||||
|
env=environment,
|
||||||
|
check=False,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=10,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.returncode == 65
|
||||||
|
assert json.loads(result.stderr) == {"error": "container_contract_failed", "status": "fail"}
|
||||||
|
assert "Traceback" not in result.stderr
|
||||||
|
|
@ -7,6 +7,7 @@ import copy
|
||||||
import importlib.util
|
import importlib.util
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
@ -52,6 +53,7 @@ def copy_profile(tmp_path: Path, bootstrap) -> Path:
|
||||||
bootstrap._runtime_root = lambda: runtime_root
|
bootstrap._runtime_root = lambda: runtime_root
|
||||||
profile = tmp_path / "profile"
|
profile = tmp_path / "profile"
|
||||||
shutil.copytree(runtime_root / "profile-template", profile)
|
shutil.copytree(runtime_root / "profile-template", profile)
|
||||||
|
profile.chmod(0o700)
|
||||||
return profile
|
return profile
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -100,6 +102,19 @@ def test_committed_lock_and_contract_are_exactly_pinned() -> None:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_compiled_immutable_profile_is_readable_after_privilege_drop(tmp_path: Path) -> None:
|
||||||
|
runtime_root = tmp_path / "runtime"
|
||||||
|
runtime_root.mkdir()
|
||||||
|
|
||||||
|
compiler._copy_profile(ROOT, runtime_root, LOCK)
|
||||||
|
|
||||||
|
profile = runtime_root / "profile-template"
|
||||||
|
assert profile.stat().st_mode & 0o777 == 0o555
|
||||||
|
assert (profile / "config.yaml").stat().st_mode & 0o777 == 0o444
|
||||||
|
assert all(path.stat().st_mode & 0o004 for path in profile.rglob("*") if path.is_file())
|
||||||
|
assert all(path.stat().st_mode & 0o001 for path in profile.rglob("*") if path.is_dir())
|
||||||
|
|
||||||
|
|
||||||
def test_profile_sources_keep_gcp_adapters_out_of_vps_deploy_paths() -> None:
|
def test_profile_sources_keep_gcp_adapters_out_of_vps_deploy_paths() -> None:
|
||||||
by_target = {entry["target"]: entry for entry in LOCK["profile_sources"]}
|
by_target = {entry["target"]: entry for entry in LOCK["profile_sources"]}
|
||||||
assert by_target["bin/cloudsql_memory_tool.py"]["source"] == (
|
assert by_target["bin/cloudsql_memory_tool.py"]["source"] == (
|
||||||
|
|
@ -219,6 +234,67 @@ def test_profile_template_and_runtime_state_are_exact(tmp_path: Path) -> None:
|
||||||
bootstrap._validate_profile(profile, CONTRACT, template=False)
|
bootstrap._validate_profile(profile, CONTRACT, template=False)
|
||||||
|
|
||||||
|
|
||||||
|
def test_service_readiness_is_atomic_sanitized_and_identity_bound(tmp_path: Path) -> None:
|
||||||
|
bootstrap = load_bootstrap()
|
||||||
|
profile = copy_profile(tmp_path, bootstrap)
|
||||||
|
for name in CONTRACT["profile"]["required_runtime_entries"]:
|
||||||
|
(profile / name).write_text(f"{name}\n", encoding="utf-8")
|
||||||
|
state = profile / "state"
|
||||||
|
state.mkdir()
|
||||||
|
readiness = state / "readiness.json"
|
||||||
|
tool_surface = {
|
||||||
|
"registry_sealed": True,
|
||||||
|
"send_message_present": False,
|
||||||
|
"tools": ["skill_view", "skills_list", "terminal"],
|
||||||
|
}
|
||||||
|
|
||||||
|
bootstrap._write_readiness(
|
||||||
|
profile,
|
||||||
|
readiness,
|
||||||
|
CONTRACT,
|
||||||
|
SimpleNamespace(adapters={}),
|
||||||
|
tool_surface,
|
||||||
|
)
|
||||||
|
|
||||||
|
payload = json.loads(readiness.read_text(encoding="utf-8"))
|
||||||
|
assert payload == {
|
||||||
|
"schema": bootstrap.READINESS_SCHEMA,
|
||||||
|
"status": "ready",
|
||||||
|
"process_id": os.getpid(),
|
||||||
|
"runtime_mode": "gcp_staging_no_send",
|
||||||
|
"config_sha256": bootstrap._sha256(profile / "config.yaml"),
|
||||||
|
"gateway_adapter_count": 0,
|
||||||
|
"connected_platforms": [],
|
||||||
|
"tool_surface": tool_surface,
|
||||||
|
"identity_sha256": {
|
||||||
|
name: bootstrap._sha256(profile / name)
|
||||||
|
for name in ("SOUL.md", "identity-lock.json", "identity-manifest.json")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert readiness.stat().st_mode & 0o777 == 0o600
|
||||||
|
assert str(profile) not in readiness.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def test_service_readiness_rejects_escape_and_symlink(tmp_path: Path) -> None:
|
||||||
|
bootstrap = load_bootstrap()
|
||||||
|
profile = copy_profile(tmp_path, bootstrap)
|
||||||
|
for name in CONTRACT["profile"]["required_runtime_entries"]:
|
||||||
|
(profile / name).write_text("{}\n", encoding="utf-8")
|
||||||
|
state = profile / "state"
|
||||||
|
state.mkdir()
|
||||||
|
runner = SimpleNamespace(adapters={})
|
||||||
|
|
||||||
|
with pytest.raises(bootstrap.NoSendContractError, match="inside profile state"):
|
||||||
|
bootstrap._write_readiness(profile, tmp_path / "readiness.json", CONTRACT, runner, {})
|
||||||
|
|
||||||
|
target = state / "target.json"
|
||||||
|
target.write_text("{}\n", encoding="utf-8")
|
||||||
|
linked = state / "readiness.json"
|
||||||
|
linked.symlink_to(target)
|
||||||
|
with pytest.raises(bootstrap.NoSendContractError, match="symlink"):
|
||||||
|
bootstrap._write_readiness(profile, linked, CONTRACT, runner, {})
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("section", "field", "unsafe"),
|
("section", "field", "unsafe"),
|
||||||
[
|
[
|
||||||
|
|
@ -240,6 +316,7 @@ def test_profile_rejects_learning_policy_drift(
|
||||||
bootstrap = load_bootstrap()
|
bootstrap = load_bootstrap()
|
||||||
profile = copy_profile(tmp_path, bootstrap)
|
profile = copy_profile(tmp_path, bootstrap)
|
||||||
config_path = profile / "config.yaml"
|
config_path = profile / "config.yaml"
|
||||||
|
config_path.chmod(0o600)
|
||||||
config = yaml.safe_load(config_path.read_text(encoding="utf-8"))
|
config = yaml.safe_load(config_path.read_text(encoding="utf-8"))
|
||||||
config[section][field] = unsafe
|
config[section][field] = unsafe
|
||||||
config_path.write_text(yaml.safe_dump(config, sort_keys=False), encoding="utf-8")
|
config_path.write_text(yaml.safe_dump(config, sort_keys=False), encoding="utf-8")
|
||||||
|
|
@ -254,6 +331,7 @@ def test_profile_rejects_smart_model_routing(tmp_path: Path) -> None:
|
||||||
bootstrap = load_bootstrap()
|
bootstrap = load_bootstrap()
|
||||||
profile = copy_profile(tmp_path, bootstrap)
|
profile = copy_profile(tmp_path, bootstrap)
|
||||||
config_path = profile / "config.yaml"
|
config_path = profile / "config.yaml"
|
||||||
|
config_path.chmod(0o600)
|
||||||
config = yaml.safe_load(config_path.read_text(encoding="utf-8"))
|
config = yaml.safe_load(config_path.read_text(encoding="utf-8"))
|
||||||
config["smart_model_routing"] = {
|
config["smart_model_routing"] = {
|
||||||
"enabled": True,
|
"enabled": True,
|
||||||
|
|
@ -750,6 +828,7 @@ def test_ci_runs_the_release_canary_and_retains_its_receipt() -> None:
|
||||||
assert "--development" not in wrapper
|
assert "--development" not in wrapper
|
||||||
assert "--allow-dirty" not in wrapper
|
assert "--allow-dirty" not in wrapper
|
||||||
assert 'rm -f "$receipt"' in wrapper
|
assert 'rm -f "$receipt"' in wrapper
|
||||||
|
assert 'chmod -R u+w "$temporary"' in wrapper
|
||||||
assert 'repeat_artifact="$temporary/artifact-repeat"' in wrapper
|
assert 'repeat_artifact="$temporary/artifact-repeat"' in wrapper
|
||||||
assert "first != second" in wrapper
|
assert "first != second" in wrapper
|
||||||
assert "compile-primary.json" in wrapper
|
assert "compile-primary.json" in wrapper
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue