Harden leoclean no-send package verification
This commit is contained in:
parent
1e5c67738f
commit
63b09bbacb
12 changed files with 2322 additions and 176 deletions
41
.github/workflows/ci.yml
vendored
41
.github/workflows/ci.yml
vendored
|
|
@ -76,6 +76,7 @@ jobs:
|
||||||
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 \
|
||||||
scripts/leo_behavior_manifest.py \
|
scripts/leo_behavior_manifest.py \
|
||||||
|
scripts/leo_identity_manifest.py \
|
||||||
scripts/leo_tool_trace.py \
|
scripts/leo_tool_trace.py \
|
||||||
scripts/compile_leoclean_nosend_runtime.py \
|
scripts/compile_leoclean_nosend_runtime.py \
|
||||||
scripts/replay_decision_engine_eval.py \
|
scripts/replay_decision_engine_eval.py \
|
||||||
|
|
@ -87,6 +88,7 @@ jobs:
|
||||||
scripts/run_leo_m3taversal_oos_handler_suite.py \
|
scripts/run_leo_m3taversal_oos_handler_suite.py \
|
||||||
scripts/verify_leo_db_first_oos_canary.py \
|
scripts/verify_leo_db_first_oos_canary.py \
|
||||||
scripts/run_leo_clone_bound_handler_checkpoint.py \
|
scripts/run_leo_clone_bound_handler_checkpoint.py \
|
||||||
|
scripts/run_gcp_leoclean_nosend_oci_smoke.py \
|
||||||
scripts/working_leo_m3taversal_oos_benchmark.py \
|
scripts/working_leo_m3taversal_oos_benchmark.py \
|
||||||
scripts/working_leo_open_ended_benchmark.py \
|
scripts/working_leo_open_ended_benchmark.py \
|
||||||
scripts/verify_leoclean_nosend_runtime.py \
|
scripts/verify_leoclean_nosend_runtime.py \
|
||||||
|
|
@ -102,6 +104,7 @@ jobs:
|
||||||
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_nosend_package.py \
|
||||||
|
tests/test_gcp_leoclean_nosend_oci_smoke.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 \
|
||||||
|
|
@ -221,23 +224,55 @@ jobs:
|
||||||
leoclean-nosend-runtime:
|
leoclean-nosend-runtime:
|
||||||
name: Leoclean no-send runtime
|
name: Leoclean no-send runtime
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 20
|
timeout-minutes: 45
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.11.9"
|
python-version: "3.11.9"
|
||||||
- name: Install exact uv toolchain
|
- name: Install exact uv toolchain
|
||||||
run: python -m pip install 'uv==0.9.30'
|
run: python -m pip install 'uv==0.9.30' 'PyYAML==6.0.3'
|
||||||
|
- name: Pull digest-pinned OCI bases
|
||||||
|
run: |
|
||||||
|
docker pull --platform=linux/amd64 "ghcr.io/astral-sh/uv:0.9.30@sha256:538e0b39736e7feae937a65983e49d2ab75e1559d35041f9878b7b7e51de91e4"
|
||||||
|
docker pull --platform=linux/amd64 "docker.io/library/python:3.11.9-slim-bookworm@sha256:8fb099199b9f2d70342674bd9dbccd3ed03a258f26bbd1d556822c6dfc60c317"
|
||||||
|
docker pull --platform=linux/amd64 "docker.io/library/postgres:16.14-bookworm@sha256:92620daddcd947f8d5ab5ba66e848702fe443d87fed30c4cea8e389fd78dfc55"
|
||||||
- name: Compile and exercise release artifact
|
- name: Compile and exercise release artifact
|
||||||
|
env:
|
||||||
|
LEOCLEAN_NOSEND_ARTIFACT_OUTPUT: ${{ runner.temp }}/leoclean-nosend-artifact
|
||||||
run: scripts/run_leoclean_nosend_runtime_canary.sh
|
run: scripts/run_leoclean_nosend_runtime_canary.sh
|
||||||
|
- name: Build and exercise one disposable OCI image
|
||||||
|
run: |
|
||||||
|
python scripts/run_gcp_leoclean_nosend_oci_smoke.py \
|
||||||
|
--artifact "${RUNNER_TEMP}/leoclean-nosend-artifact" \
|
||||||
|
--runtime-receipt .crabbox-results/leoclean-nosend-runtime.json \
|
||||||
|
--output .crabbox-results/gcp-leoclean-nosend-oci-smoke.json
|
||||||
- name: Upload no-send runtime receipt
|
- name: Upload no-send runtime receipt
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: teleo-infrastructure-leoclean-nosend-runtime
|
name: teleo-infrastructure-leoclean-nosend-runtime
|
||||||
path: .crabbox-results/leoclean-nosend-runtime.json
|
path: |
|
||||||
|
.crabbox-results/leoclean-nosend-runtime.json
|
||||||
|
.crabbox-results/gcp-leoclean-nosend-oci-smoke.json
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
- name: Remove interrupted no-send OCI smokes
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
container_inventory="$(docker ps -aq --filter 'label=livingip.leoclean-nosend-smoke')"
|
||||||
|
if [[ -n "${container_inventory}" ]]; then
|
||||||
|
mapfile -t containers <<<"${container_inventory}"
|
||||||
|
docker rm --force "${containers[@]}"
|
||||||
|
fi
|
||||||
|
image_inventory="$(docker images -q --filter 'label=livingip.leoclean-nosend-smoke')"
|
||||||
|
if [[ -n "${image_inventory}" ]]; then
|
||||||
|
mapfile -t images <<<"${image_inventory}"
|
||||||
|
docker image rm --force "${images[@]}"
|
||||||
|
fi
|
||||||
|
remaining_containers="$(docker ps -aq --filter 'label=livingip.leoclean-nosend-smoke')"
|
||||||
|
remaining_images="$(docker images -q --filter 'label=livingip.leoclean-nosend-smoke')"
|
||||||
|
test -z "${remaining_containers}"
|
||||||
|
test -z "${remaining_images}"
|
||||||
|
|
||||||
phase1b-local-proof:
|
phase1b-local-proof:
|
||||||
name: Phase 1B local proof
|
name: Phase 1B local proof
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
# syntax=docker/dockerfile:1.7
|
|
||||||
|
|
||||||
FROM ghcr.io/astral-sh/uv:0.9.30@sha256:538e0b39736e7feae937a65983e49d2ab75e1559d35041f9878b7b7e51de91e4 AS uv
|
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
|
FROM docker.io/library/python:3.11.9-slim-bookworm@sha256:8fb099199b9f2d70342674bd9dbccd3ed03a258f26bbd1d556822c6dfc60c317 AS python-build
|
||||||
|
|
@ -39,6 +37,8 @@ COPY --from=python-build /usr/local /usr/local
|
||||||
COPY --from=python-build /opt/livingip/leoclean-nosend/venv /opt/livingip/leoclean-nosend/venv
|
COPY --from=python-build /opt/livingip/leoclean-nosend/venv /opt/livingip/leoclean-nosend/venv
|
||||||
COPY artifact /opt/livingip/leoclean-nosend/artifact
|
COPY artifact /opt/livingip/leoclean-nosend/artifact
|
||||||
COPY identity /opt/livingip/leoclean-nosend/identity
|
COPY identity /opt/livingip/leoclean-nosend/identity
|
||||||
|
COPY identity-sources /opt/livingip/leoclean-nosend/identity-sources
|
||||||
|
COPY scripts /opt/livingip/leoclean-nosend/scripts
|
||||||
COPY entrypoint.py /opt/livingip/leoclean-nosend/entrypoint.py
|
COPY entrypoint.py /opt/livingip/leoclean-nosend/entrypoint.py
|
||||||
COPY package_contract.py /opt/livingip/leoclean-nosend/package_contract.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 artifact-receipt.json /opt/livingip/leoclean-nosend/metadata/artifact-receipt.json
|
||||||
|
|
@ -53,6 +53,7 @@ RUN ln -s /usr/local/bin/python3.11 /usr/bin/python3 \
|
||||||
&& chmod 0444 /usr/local/libexec/livingip/leoclean-kb/cloudsql-server-ca.pem \
|
&& chmod 0444 /usr/local/libexec/livingip/leoclean-kb/cloudsql-server-ca.pem \
|
||||||
&& chmod 0555 /opt/livingip/leoclean-nosend/entrypoint.py \
|
&& chmod 0555 /opt/livingip/leoclean-nosend/entrypoint.py \
|
||||||
&& chmod 0444 /opt/livingip/leoclean-nosend/package_contract.py \
|
&& chmod 0444 /opt/livingip/leoclean-nosend/package_contract.py \
|
||||||
|
&& find /opt/livingip/leoclean-nosend/identity -type f -exec chmod 0444 {} + \
|
||||||
&& case "$TELEO_REVISION" in (*[!0-9a-f]*|'') exit 65;; esac \
|
&& case "$TELEO_REVISION" in (*[!0-9a-f]*|'') exit 65;; esac \
|
||||||
&& test "${#TELEO_REVISION}" -eq 40 \
|
&& test "${#TELEO_REVISION}" -eq 40 \
|
||||||
&& for digest in "$ARTIFACT_SHA256" "$IDENTITY_SHA256" "$INPUT_SHA256"; do \
|
&& for digest in "$ARTIFACT_SHA256" "$IDENTITY_SHA256" "$INPUT_SHA256"; do \
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ contract = _load_adjacent_contract()
|
||||||
ROOT = Path(contract.RUNTIME_ROOT)
|
ROOT = Path(contract.RUNTIME_ROOT)
|
||||||
ARTIFACT = ROOT / "artifact"
|
ARTIFACT = ROOT / "artifact"
|
||||||
IDENTITY = ROOT / "identity"
|
IDENTITY = ROOT / "identity"
|
||||||
|
IDENTITY_SOURCES = ROOT / "identity-sources"
|
||||||
PROFILE = Path(contract.PROFILE_ROOT)
|
PROFILE = Path(contract.PROFILE_ROOT)
|
||||||
RECEIPT = ROOT / "metadata" / "artifact-receipt.json"
|
RECEIPT = ROOT / "metadata" / "artifact-receipt.json"
|
||||||
IMAGE_INPUT = ROOT / "metadata" / "image-input.json"
|
IMAGE_INPUT = ROOT / "metadata" / "image-input.json"
|
||||||
|
|
@ -56,21 +57,14 @@ def _require(condition: bool, message: str) -> None:
|
||||||
|
|
||||||
def _validate_installed() -> dict[str, Any]:
|
def _validate_installed() -> dict[str, Any]:
|
||||||
image_input = contract.load_json(IMAGE_INPUT, "installed image input")
|
image_input = contract.load_json(IMAGE_INPUT, "installed image input")
|
||||||
contract.validate_image_input(image_input)
|
contract.validate_installed_runtime_identity(
|
||||||
revision = image_input["runtime"]["teleo_git_head"]
|
image_input,
|
||||||
runtime = contract.validate_artifact(ARTIFACT, RECEIPT, revision)
|
artifact=ARTIFACT,
|
||||||
for field in (
|
receipt=RECEIPT,
|
||||||
"teleo_git_head",
|
identity=IDENTITY,
|
||||||
"artifact_sha256",
|
contract_source_root=ROOT,
|
||||||
"artifact_manifest_sha256",
|
identity_source_root=IDENTITY_SOURCES,
|
||||||
"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(contract.sha256_file(CA) == contract.CA_SHA256, "installed Cloud SQL CA drifted")
|
||||||
_require(platform.python_version() == contract.PYTHON_VERSION, "installed Python version 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")
|
_require(Path("/usr/bin/python3").resolve() == Path("/usr/local/bin/python3.11"), "system Python binding drifted")
|
||||||
|
|
@ -91,6 +85,12 @@ def _validate_installed() -> dict[str, Any]:
|
||||||
contract.sha256_file(PACKAGE_CONTRACT) == bindings["package_contract.py"],
|
contract.sha256_file(PACKAGE_CONTRACT) == bindings["package_contract.py"],
|
||||||
"installed package contract binding drifted",
|
"installed package contract binding drifted",
|
||||||
)
|
)
|
||||||
|
for relative in contract.IDENTITY_CONTRACT_SOURCES:
|
||||||
|
installed = ROOT / relative
|
||||||
|
_require(
|
||||||
|
contract.sha256_file(installed) == bindings[relative],
|
||||||
|
f"installed identity contract source drifted: {relative}",
|
||||||
|
)
|
||||||
return image_input
|
return image_input
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -105,7 +105,7 @@ def _prepare_profile() -> None:
|
||||||
shutil.copytree(source, destination, symlinks=False)
|
shutil.copytree(source, destination, symlinks=False)
|
||||||
else:
|
else:
|
||||||
shutil.copy2(source, destination, follow_symlinks=False)
|
shutil.copy2(source, destination, follow_symlinks=False)
|
||||||
for name in contract.IDENTITY_FILES:
|
for name in contract.RUNTIME_IDENTITY_FILES:
|
||||||
shutil.copy2(IDENTITY / name, PROFILE / name, follow_symlinks=False)
|
shutil.copy2(IDENTITY / name, PROFILE / name, follow_symlinks=False)
|
||||||
(PROFILE / name).chmod(0o600)
|
(PROFILE / name).chmod(0o600)
|
||||||
for name in STATE_DIRECTORIES:
|
for name in STATE_DIRECTORIES:
|
||||||
|
|
@ -219,7 +219,7 @@ def _health() -> dict[str, Any]:
|
||||||
_require(isinstance(surface, dict), "runtime tool surface is missing")
|
_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("tools") == ["skill_view", "skills_list", "terminal"], "runtime tool surface drifted")
|
||||||
_require(surface.get("send_message_present") is False, "runtime send_message surface is present")
|
_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}
|
expected_identity = {name: contract.sha256_file(PROFILE / name) for name in contract.RUNTIME_IDENTITY_FILES}
|
||||||
_require(value.get("identity_sha256") == expected_identity, "runtime readiness identity binding drifted")
|
_require(value.get("identity_sha256") == expected_identity, "runtime readiness identity binding drifted")
|
||||||
cmdline = Path("/proc/1/cmdline").read_bytes().split(b"\0")
|
cmdline = Path("/proc/1/cmdline").read_bytes().split(b"\0")
|
||||||
expected_native = [
|
expected_native = [
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,13 @@ The canary:
|
||||||
6. writes `.crabbox-results/leoclean-nosend-runtime.json` only after release
|
6. writes `.crabbox-results/leoclean-nosend-runtime.json` only after release
|
||||||
verification passes.
|
verification passes.
|
||||||
|
|
||||||
GitHub Actions runs this same wrapper and retains the receipt as a CI artifact.
|
GitHub Actions runs this same wrapper, retains the verified artifact for the
|
||||||
|
next job step, and then builds and exercises one disposable `linux/amd64` OCI
|
||||||
|
image with a synthetic noncanonical identity. The OCI smoke proves the built-in
|
||||||
|
zero-adapter/no-`send_message` health response and exact fail-closed entrypoint
|
||||||
|
dispatch for an unsupported `send-message` command, then verifies cleanup. It
|
||||||
|
does not invoke a real Hermes send tool or prove general network-egress denial.
|
||||||
|
Both bounded receipts are retained as CI artifacts.
|
||||||
|
|
||||||
## Claim ceiling and next slices
|
## Claim ceiling and next slices
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,10 @@ the closure. The final image also binds:
|
||||||
- Hermes commit and source-tree hash;
|
- Hermes commit and source-tree hash;
|
||||||
- the artifact content and manifest hashes;
|
- the artifact content and manifest hashes;
|
||||||
- the exact uv lock;
|
- the exact uv lock;
|
||||||
- the identity manifest, lock, `SOUL.md`, and bundle hashes;
|
- the synthetic identity manifest, lock, `SOUL.md`, structured `identity.json`,
|
||||||
|
original source hashes, and bundle hashes;
|
||||||
|
- the exact no-send model, skills, tool registry, proposal-only database policy,
|
||||||
|
and runtime-probe receipt that produced the identity runtime contract;
|
||||||
- the Cloud SQL server CA hash;
|
- the Cloud SQL server CA hash;
|
||||||
- the Dockerfile, entrypoint, and package-contract hashes.
|
- the Dockerfile, entrypoint, and package-contract hashes.
|
||||||
|
|
||||||
|
|
@ -64,9 +67,44 @@ from exposing an unused writable database directory to the runtime.
|
||||||
## Prepare a build context
|
## Prepare a build context
|
||||||
|
|
||||||
First compile and release-verify the no-send artifact from the exact clean
|
First compile and release-verify the no-send artifact from the exact clean
|
||||||
revision. Separately provide a reviewed identity directory containing exactly:
|
revision, retaining it only after verification passes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
LEOCLEAN_NOSEND_ARTIFACT_OUTPUT=/tmp/leoclean-nosend-artifact \
|
||||||
|
scripts/run_leoclean_nosend_runtime_canary.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Derive the identity runtime contract from that artifact and receipt, generate a
|
||||||
|
synthetic/noncanonical manifest from the committed fixture sources, and compile
|
||||||
|
the two generated views:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 ops/gcp_leoclean_nosend_package.py identity-contract \
|
||||||
|
--artifact /tmp/leoclean-nosend-artifact \
|
||||||
|
--receipt .crabbox-results/leoclean-nosend-runtime.json \
|
||||||
|
--output /tmp/leoclean-nosend-identity-contract.json
|
||||||
|
|
||||||
|
python3 ops/gcp_leoclean_nosend_package.py generate-identity-manifest \
|
||||||
|
--artifact /tmp/leoclean-nosend-artifact \
|
||||||
|
--receipt .crabbox-results/leoclean-nosend-runtime.json \
|
||||||
|
--database-fingerprint fixtures/working-leo/leo-identity-v1/leo-database-fingerprint-v1.json \
|
||||||
|
--constitution fixtures/working-leo/leo-identity-v1/leo-constitution-v1.json \
|
||||||
|
--database-identity fixtures/working-leo/leo-identity-v1/leo-database-identity-v1.json \
|
||||||
|
--identity-source-root . \
|
||||||
|
--output /tmp/leoclean-nosend-identity-manifest.json
|
||||||
|
|
||||||
|
python3 ops/gcp_leoclean_nosend_package.py compile-identity \
|
||||||
|
--artifact /tmp/leoclean-nosend-artifact \
|
||||||
|
--receipt .crabbox-results/leoclean-nosend-runtime.json \
|
||||||
|
--identity-manifest /tmp/leoclean-nosend-identity-manifest.json \
|
||||||
|
--identity-source-root . \
|
||||||
|
--output /tmp/leoclean-nosend-identity
|
||||||
|
```
|
||||||
|
|
||||||
|
The compiled identity directory contains exactly:
|
||||||
|
|
||||||
- `SOUL.md`
|
- `SOUL.md`
|
||||||
|
- `identity.json`
|
||||||
- `identity-lock.json`
|
- `identity-lock.json`
|
||||||
- `identity-manifest.json`
|
- `identity-manifest.json`
|
||||||
|
|
||||||
|
|
@ -74,16 +112,20 @@ Then run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 ops/gcp_leoclean_nosend_package.py prepare \
|
python3 ops/gcp_leoclean_nosend_package.py prepare \
|
||||||
--artifact /path/to/artifact \
|
--artifact /tmp/leoclean-nosend-artifact \
|
||||||
--receipt /path/to/leoclean-nosend-runtime.json \
|
--receipt .crabbox-results/leoclean-nosend-runtime.json \
|
||||||
--identity /path/to/identity-bundle \
|
--identity /tmp/leoclean-nosend-identity \
|
||||||
|
--identity-source-root . \
|
||||||
--output /tmp/leoclean-nosend-image-context
|
--output /tmp/leoclean-nosend-image-context
|
||||||
```
|
```
|
||||||
|
|
||||||
The command fails closed on a development receipt, revision drift, content or
|
The command fails closed on a development receipt, revision drift, content or
|
||||||
mode drift, unsafe links, an inconsistent identity lock, a changed CA, a
|
mode drift, unsafe links, an inconsistent identity lock, source or generated
|
||||||
non-staging target, or an existing output directory. The resulting context is
|
view drift, a runtime/model/skills/permission mismatch, any canonical-authority
|
||||||
secret-free.
|
claim, a changed CA, a non-staging target, or an existing output directory. The
|
||||||
|
resulting context is secret-free. Direct canonical and staging-table writes
|
||||||
|
remain denied; proposal staging remains available only through
|
||||||
|
`kb_stage.stage_leoclean_proposal(text,text,text,text,jsonb)`.
|
||||||
|
|
||||||
Use the exact values in `build-args.json` as Docker build arguments. The image
|
Use the exact values in `build-args.json` as Docker build arguments. The image
|
||||||
build itself revalidates that those values equal `image-input.json`.
|
build itself revalidates that those values equal `image-input.json`.
|
||||||
|
|
@ -107,12 +149,22 @@ extra descriptor fields, and mismatched labels are rejected.
|
||||||
|
|
||||||
## Offline evidence and live gate
|
## Offline evidence and live gate
|
||||||
|
|
||||||
Offline tests prove descriptor strictness, artifact and identity binding,
|
Offline tests prove descriptor strictness, artifact and synthetic identity binding,
|
||||||
digest-only images, pinned runtime versions, a secret-free context, the
|
digest-only images, pinned runtime versions, a secret-free context, the
|
||||||
capability-drop contract, no published ports, no Docker-socket mount, no
|
capability-drop contract, no published ports, no Docker-socket mount, no
|
||||||
environment file, and no production/VPS/Telegram target.
|
environment file, and no production/VPS/Telegram target. CI also builds exactly
|
||||||
|
one `linux/amd64` image, starts it with the reviewed restrictions, requires the
|
||||||
|
built-in zero-adapter/no-`send_message` health response, checks that an
|
||||||
|
unsupported `send-message` entrypoint command fails with the exact redacted
|
||||||
|
error, binds the running container to the dynamically resolved image ID, and
|
||||||
|
removes the candidate container, tag, and visible image ID. The receipt records
|
||||||
|
whether `linux/amd64` ran natively or through cross-platform emulation. Pinned
|
||||||
|
base images and build cache may remain.
|
||||||
|
|
||||||
They do not prove that `teleo-staging-1` currently has a compatible container
|
The entrypoint command rejection is not a real Hermes tool invocation, and the
|
||||||
|
bridge-network smoke does not prove general network-egress denial. These tests
|
||||||
|
also do not prove canonical identity, Cloud SQL permissions, Artifact Registry,
|
||||||
|
or that `teleo-staging-1` currently has a compatible container
|
||||||
engine, enough disk, the expected architecture, the expected service account,
|
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
|
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
|
engine is absent, installation is separate infrastructure work; the deploy must
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,18 @@ cut over safely and retire the VPS after soak.
|
||||||
- Complete: PR #179 least-privilege PostgreSQL authority is merged on main.
|
- Complete: PR #179 least-privilege PostgreSQL authority is merged on main.
|
||||||
- Complete: the revision-bound, structurally no-send runtime from PR #183 is
|
- Complete: the revision-bound, structurally no-send runtime from PR #183 is
|
||||||
merged into `main` at `2d88c9765bd9b43641d1efb04a1118c5ce0bce39`.
|
merged into `main` at `2d88c9765bd9b43641d1efb04a1118c5ce0bce39`.
|
||||||
- Rebuilt for human review: isolated, digest-addressed GCP staging service
|
- Rebuilt for human review: PR #182 is a single service-package commit based
|
||||||
package with an independent rollback boundary, exact release receipt,
|
directly on that merged `main` revision.
|
||||||
immutable image verification, and network-disabled service/readiness smoke
|
- PR #193 is source material only. It must not be merged wholesale; only the
|
||||||
test, based directly on that merged `main` revision in PR #182.
|
required identity-binding and OCI-lifecycle corrections belong in the narrow
|
||||||
- In progress: validate the rebuilt PR #182 revision and stop at the
|
#182/#183 stack.
|
||||||
exact-revision human review and merge gate.
|
- Complete locally: #182's synthetic identity binding now fails closed and the
|
||||||
|
package has one disposable, single-build OCI lifecycle with a truthful claim
|
||||||
|
ceiling. The focused suite passed 324 tests, the full suite passed 2,305 with
|
||||||
|
3 skips, and both independent review lanes accepted the current diff.
|
||||||
|
- Next gate: create one user-authored local commit, run the release-mode runtime
|
||||||
|
and OCI proof against that exact revision, then stop for human publication
|
||||||
|
review. No publication, merge, or deployment is implied by local validation.
|
||||||
- Not yet live-proven: staging VM/container engine, IAM/Secret Manager, Cloud
|
- Not yet live-proven: staging VM/container engine, IAM/Secret Manager, Cloud
|
||||||
SQL effective identity, restart behavior, or model/database parity.
|
SQL effective identity, restart behavior, or model/database parity.
|
||||||
|
|
||||||
|
|
@ -44,7 +50,6 @@ cut over safely and retire the VPS after soak.
|
||||||
|
|
||||||
## Current live-action boundary
|
## Current live-action boundary
|
||||||
|
|
||||||
Draft branch/PR publication is authorized only as a review handoff. No merge,
|
No merge, Artifact Registry push, GCP/Cloud SQL/Secret Manager mutation,
|
||||||
Artifact Registry push, GCP/Cloud SQL/Secret Manager mutation, service
|
service deployment/restart, Telegram change, production promotion, canonical
|
||||||
deployment/restart, Telegram change, production promotion, canonical proposal
|
proposal apply, or VPS access is part of the current offline corrective slice.
|
||||||
apply, or VPS access is part of the current offline slice.
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -4,6 +4,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import copy
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -45,6 +46,35 @@ def expected_runtime_policy() -> dict[str, Any]:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def expected_no_send_runtime_policy() -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"schema": "livingip.leocleanNoSendPermissionReceipt.v1",
|
||||||
|
"messaging_send": {
|
||||||
|
"capability": "absent",
|
||||||
|
"transport_authority": "absent",
|
||||||
|
"gateway_adapters": "none",
|
||||||
|
"send_message_tool": "absent",
|
||||||
|
},
|
||||||
|
"general_network_egress": {
|
||||||
|
"container_mode": "bridge",
|
||||||
|
"denial_enforced": False,
|
||||||
|
"denial_tested": False,
|
||||||
|
},
|
||||||
|
"database": {
|
||||||
|
"direct_canonical_writes": "denied",
|
||||||
|
"direct_stage_table_writes": "denied",
|
||||||
|
"proposal_staging": "function_only",
|
||||||
|
"proposal_function": "kb_stage.stage_leoclean_proposal(text,text,text,text,jsonb)",
|
||||||
|
"live_effective_permissions_proven": False,
|
||||||
|
},
|
||||||
|
"allowed_tools": ["skill_view", "skills_list", "terminal"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _valid_runtime_policy(value: Any) -> bool:
|
||||||
|
return value in (expected_runtime_policy(), expected_no_send_runtime_policy())
|
||||||
|
|
||||||
|
|
||||||
def expected_session_boundary() -> dict[str, Any]:
|
def expected_session_boundary() -> dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"classification": "temporary_noncanonical",
|
"classification": "temporary_noncanonical",
|
||||||
|
|
@ -426,16 +456,246 @@ def render_identity_views(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def identity_runtime_inputs_from_contract(value: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
"""Adapt one package-validated synthetic runtime contract to identity inputs."""
|
||||||
|
|
||||||
|
_require(
|
||||||
|
set(value) == {"schema", "runtime", "model", "skills", "permissions", "behavior_inputs", "contract_sha256"},
|
||||||
|
"identity runtime contract fields are invalid",
|
||||||
|
)
|
||||||
|
_require(
|
||||||
|
value.get("schema") == "livingip.leocleanNoSendIdentityRuntimeContract.v1",
|
||||||
|
"identity runtime contract schema is invalid",
|
||||||
|
)
|
||||||
|
stable = {key: item for key, item in value.items() if key != "contract_sha256"}
|
||||||
|
_require(
|
||||||
|
value.get("contract_sha256") == behavior.canonical_sha256(stable),
|
||||||
|
"identity runtime contract hash drift detected",
|
||||||
|
)
|
||||||
|
for section in ("runtime", "model", "skills", "permissions", "behavior_inputs"):
|
||||||
|
_require(isinstance(value.get(section), dict), f"identity runtime contract {section} is missing")
|
||||||
|
runtime = value["runtime"]
|
||||||
|
_require(
|
||||||
|
set(runtime)
|
||||||
|
== {
|
||||||
|
"identity_runtime_sha256",
|
||||||
|
"hermes_git_head",
|
||||||
|
"hermes_source_sha256",
|
||||||
|
"teleo_git_head",
|
||||||
|
"teleo_source_sha256",
|
||||||
|
"python",
|
||||||
|
},
|
||||||
|
"identity runtime contract runtime fields are invalid",
|
||||||
|
)
|
||||||
|
teleo_git_head = runtime.get("teleo_git_head")
|
||||||
|
_require(bool(HEX_40.fullmatch(str(teleo_git_head or ""))), "identity runtime contract Teleo revision is invalid")
|
||||||
|
_require(bool(HEX_40.fullmatch(str(runtime.get("hermes_git_head") or ""))), "identity runtime Hermes revision is invalid")
|
||||||
|
for field in ("identity_runtime_sha256", "hermes_source_sha256", "teleo_source_sha256"):
|
||||||
|
_require(_is_sha256(runtime.get(field)), f"identity runtime contract {field} is invalid")
|
||||||
|
python_runtime = runtime.get("python")
|
||||||
|
_require(
|
||||||
|
isinstance(python_runtime, dict)
|
||||||
|
and set(python_runtime)
|
||||||
|
== {
|
||||||
|
"implementation",
|
||||||
|
"python_version",
|
||||||
|
"abi_tag",
|
||||||
|
"runtime_distributions",
|
||||||
|
"runtime_distributions_sha256",
|
||||||
|
}
|
||||||
|
and python_runtime.get("implementation") == "CPython"
|
||||||
|
and bool(python_runtime.get("python_version"))
|
||||||
|
and bool(python_runtime.get("abi_tag"))
|
||||||
|
and isinstance(python_runtime.get("runtime_distributions"), dict)
|
||||||
|
and bool(python_runtime["runtime_distributions"])
|
||||||
|
and python_runtime.get("runtime_distributions_sha256")
|
||||||
|
== behavior.canonical_sha256(python_runtime["runtime_distributions"]),
|
||||||
|
"identity runtime Python binding is invalid",
|
||||||
|
)
|
||||||
|
model = value["model"]
|
||||||
|
_require(
|
||||||
|
set(model)
|
||||||
|
== {
|
||||||
|
"provider",
|
||||||
|
"default_model",
|
||||||
|
"smart_routing",
|
||||||
|
"smart_routing_model",
|
||||||
|
"gateway_smart_model_routing",
|
||||||
|
"model_section_sha256",
|
||||||
|
"hosted_weights",
|
||||||
|
"actual_model_required_in_turn_receipt",
|
||||||
|
}
|
||||||
|
and model.get("provider") == "openrouter"
|
||||||
|
and model.get("default_model") == "anthropic/claude-sonnet-4-6"
|
||||||
|
and model.get("smart_routing") is False
|
||||||
|
and model.get("smart_routing_model") is None
|
||||||
|
and model.get("gateway_smart_model_routing") is False
|
||||||
|
and _is_sha256(model.get("model_section_sha256"))
|
||||||
|
and model.get("hosted_weights") == "external_provider_managed_not_locally_hashable"
|
||||||
|
and model.get("actual_model_required_in_turn_receipt") is True,
|
||||||
|
"identity runtime model binding is invalid",
|
||||||
|
)
|
||||||
|
skills = value["skills"]
|
||||||
|
_require(
|
||||||
|
set(skills) == {"content_sha256", "file_count"}
|
||||||
|
and _is_sha256(skills.get("content_sha256"))
|
||||||
|
and isinstance(skills.get("file_count"), int)
|
||||||
|
and not isinstance(skills.get("file_count"), bool)
|
||||||
|
and skills["file_count"] > 0,
|
||||||
|
"identity runtime skills binding is invalid",
|
||||||
|
)
|
||||||
|
permissions = value["permissions"]
|
||||||
|
_require(
|
||||||
|
set(permissions)
|
||||||
|
== {
|
||||||
|
"identity_config_sha256",
|
||||||
|
"gateway_permissions_sha256",
|
||||||
|
"tool_permissions_sha256",
|
||||||
|
"runtime_policy",
|
||||||
|
"authorization_decision_required_in_runtime_receipt",
|
||||||
|
},
|
||||||
|
"identity runtime permission fields are invalid",
|
||||||
|
)
|
||||||
|
for field in ("identity_config_sha256", "gateway_permissions_sha256", "tool_permissions_sha256"):
|
||||||
|
_require(_is_sha256(permissions.get(field)), f"identity runtime permission {field} is invalid")
|
||||||
|
_require(
|
||||||
|
permissions.get("runtime_policy") == expected_no_send_runtime_policy()
|
||||||
|
and permissions.get("authorization_decision_required_in_runtime_receipt") is True,
|
||||||
|
"identity runtime contract must use the exact no-send permission policy",
|
||||||
|
)
|
||||||
|
behavior_inputs = value["behavior_inputs"]
|
||||||
|
_require(
|
||||||
|
set(behavior_inputs)
|
||||||
|
== {
|
||||||
|
"artifact_sha256",
|
||||||
|
"artifact_receipt_sha256",
|
||||||
|
"config_sha256",
|
||||||
|
"runtime_contract_sha256",
|
||||||
|
"uv_lock_sha256",
|
||||||
|
"identity_contract_sources",
|
||||||
|
"effective_policy",
|
||||||
|
"tool_surface",
|
||||||
|
},
|
||||||
|
"identity runtime behavior inputs are invalid",
|
||||||
|
)
|
||||||
|
for field in ("artifact_sha256", "artifact_receipt_sha256", "config_sha256", "runtime_contract_sha256", "uv_lock_sha256"):
|
||||||
|
_require(_is_sha256(behavior_inputs.get(field)), f"identity runtime behavior input {field} is invalid")
|
||||||
|
source_bindings = behavior_inputs.get("identity_contract_sources")
|
||||||
|
_require(
|
||||||
|
isinstance(source_bindings, dict)
|
||||||
|
and set(source_bindings) == {"scripts/leo_behavior_manifest.py", "scripts/leo_identity_manifest.py"}
|
||||||
|
and all(_is_sha256(item) for item in source_bindings.values())
|
||||||
|
and runtime.get("teleo_source_sha256") == behavior.canonical_sha256(source_bindings),
|
||||||
|
"identity runtime contract source bindings are invalid",
|
||||||
|
)
|
||||||
|
_require(
|
||||||
|
behavior_inputs.get("effective_policy")
|
||||||
|
== {
|
||||||
|
"cheap_model_route": None,
|
||||||
|
"memory_enabled": False,
|
||||||
|
"memory_nudge_interval": 0,
|
||||||
|
"skill_creation_nudge_interval": 0,
|
||||||
|
"smart_model_routing": False,
|
||||||
|
"user_profile_enabled": False,
|
||||||
|
},
|
||||||
|
"identity runtime effective policy is invalid",
|
||||||
|
)
|
||||||
|
_require(
|
||||||
|
behavior_inputs.get("tool_surface")
|
||||||
|
== {
|
||||||
|
"registry_sealed": True,
|
||||||
|
"send_message_present": False,
|
||||||
|
"terminal_handler": "livingip.restricted_teleo_kb.v1",
|
||||||
|
"terminal_restricted_to": "profile/bin/teleo-kb",
|
||||||
|
"tools": ["skill_view", "skills_list", "terminal"],
|
||||||
|
},
|
||||||
|
"identity runtime tool surface is invalid",
|
||||||
|
)
|
||||||
|
runtime_payload = {
|
||||||
|
"schema": value["schema"],
|
||||||
|
"runtime": {key: item for key, item in runtime.items() if key != "identity_runtime_sha256"},
|
||||||
|
"model": model,
|
||||||
|
"skills": skills,
|
||||||
|
"permissions": permissions,
|
||||||
|
"behavior_inputs": behavior_inputs,
|
||||||
|
}
|
||||||
|
_require(
|
||||||
|
runtime.get("identity_runtime_sha256") == behavior.canonical_sha256(runtime_payload),
|
||||||
|
"identity runtime contract runtime hash drift detected",
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"source_commit": teleo_git_head,
|
||||||
|
"runtime": copy.deepcopy(runtime),
|
||||||
|
"model": copy.deepcopy(value["model"]),
|
||||||
|
"skills": copy.deepcopy(value["skills"]),
|
||||||
|
"permissions": copy.deepcopy(value["permissions"]),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def build_identity_manifest(
|
def build_identity_manifest(
|
||||||
*,
|
*,
|
||||||
behavior_manifest: dict[str, Any],
|
behavior_manifest: dict[str, Any] | None = None,
|
||||||
|
identity_runtime_contract: dict[str, Any] | None = None,
|
||||||
database_fingerprint_path: Path,
|
database_fingerprint_path: Path,
|
||||||
constitution_path: Path,
|
constitution_path: Path,
|
||||||
database_identity_path: Path,
|
database_identity_path: Path,
|
||||||
source_root: Path,
|
source_root: Path,
|
||||||
source_commit: str | None = None,
|
source_commit: str | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
validate_behavior_manifest(behavior_manifest)
|
_require(
|
||||||
|
(behavior_manifest is None) != (identity_runtime_contract is None),
|
||||||
|
"exactly one behavior manifest or identity runtime contract is required",
|
||||||
|
)
|
||||||
|
if identity_runtime_contract is not None:
|
||||||
|
runtime_inputs = identity_runtime_inputs_from_contract(identity_runtime_contract)
|
||||||
|
contract_commit = runtime_inputs["source_commit"]
|
||||||
|
_require(
|
||||||
|
source_commit is None or source_commit == contract_commit,
|
||||||
|
"source commit does not match runtime contract",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
_require(isinstance(behavior_manifest, dict), "behavior manifest is required")
|
||||||
|
validate_behavior_manifest(behavior_manifest)
|
||||||
|
runtime_commit = behavior_manifest["teleo_infrastructure_runtime"]["git_head"]
|
||||||
|
source_commit = source_commit or runtime_commit
|
||||||
|
_require(bool(HEX_40.fullmatch(str(source_commit))), "source commit is invalid")
|
||||||
|
_require(source_commit == runtime_commit, "source commit does not match the behavior manifest")
|
||||||
|
model = behavior_manifest["model_runtime"]
|
||||||
|
components = behavior_manifest["components"]
|
||||||
|
runtime_inputs = {
|
||||||
|
"source_commit": source_commit,
|
||||||
|
"runtime": {
|
||||||
|
"identity_runtime_sha256": behavior_manifest["identity_runtime_sha256"],
|
||||||
|
"hermes_git_head": behavior_manifest["hermes_runtime"]["git_head"],
|
||||||
|
"hermes_source_sha256": behavior_manifest["hermes_runtime"]["source_tree"]["sha256"],
|
||||||
|
"teleo_git_head": runtime_commit,
|
||||||
|
"teleo_source_sha256": behavior_manifest["teleo_infrastructure_runtime"]["identity_source_tree"][
|
||||||
|
"sha256"
|
||||||
|
],
|
||||||
|
"python": behavior_manifest["python_runtime"],
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"provider": model["provider"],
|
||||||
|
"default_model": model["default_model"],
|
||||||
|
"smart_routing": model.get("smart_routing"),
|
||||||
|
"smart_routing_model": model.get("smart_routing_model"),
|
||||||
|
"gateway_smart_model_routing": model.get("gateway_smart_model_routing"),
|
||||||
|
"model_section_sha256": model["model_section_sha256"],
|
||||||
|
"hosted_weights": "external_provider_managed_not_locally_hashable",
|
||||||
|
"actual_model_required_in_turn_receipt": True,
|
||||||
|
},
|
||||||
|
"skills": {
|
||||||
|
"content_sha256": components["procedural_skills"]["content"]["sha256"],
|
||||||
|
"file_count": components["procedural_skills"]["content"]["file_count"],
|
||||||
|
},
|
||||||
|
"permissions": {
|
||||||
|
"identity_config_sha256": model["identity_config_sha256"],
|
||||||
|
"gateway_permissions_sha256": model["gateway_permissions_sha256"],
|
||||||
|
"tool_permissions_sha256": model["tool_permissions_sha256"],
|
||||||
|
"runtime_policy": model["identity_runtime_policy"],
|
||||||
|
"authorization_decision_required_in_runtime_receipt": True,
|
||||||
|
},
|
||||||
|
}
|
||||||
database_fingerprint = load_json(database_fingerprint_path, "database fingerprint")
|
database_fingerprint = load_json(database_fingerprint_path, "database fingerprint")
|
||||||
validate_database_fingerprint(database_fingerprint)
|
validate_database_fingerprint(database_fingerprint)
|
||||||
constitution = load_json(constitution_path, "constitution source")
|
constitution = load_json(constitution_path, "constitution source")
|
||||||
|
|
@ -448,46 +708,10 @@ def build_identity_manifest(
|
||||||
database_identity,
|
database_identity,
|
||||||
fingerprint=database_fingerprint,
|
fingerprint=database_fingerprint,
|
||||||
)
|
)
|
||||||
runtime_commit = behavior_manifest["teleo_infrastructure_runtime"]["git_head"]
|
|
||||||
source_commit = source_commit or runtime_commit
|
|
||||||
_require(bool(HEX_40.fullmatch(str(source_commit))), "source commit is invalid")
|
|
||||||
_require(source_commit == runtime_commit, "source commit does not match the behavior manifest")
|
|
||||||
|
|
||||||
model = behavior_manifest["model_runtime"]
|
|
||||||
components = behavior_manifest["components"]
|
|
||||||
marker = database_fingerprint["read_consistency"]["before"]
|
marker = database_fingerprint["read_consistency"]["before"]
|
||||||
core = {
|
core = {
|
||||||
"identity_name": "Leo",
|
"identity_name": "Leo",
|
||||||
"source_commit": source_commit,
|
**runtime_inputs,
|
||||||
"runtime": {
|
|
||||||
"identity_runtime_sha256": behavior_manifest["identity_runtime_sha256"],
|
|
||||||
"hermes_git_head": behavior_manifest["hermes_runtime"]["git_head"],
|
|
||||||
"hermes_source_sha256": behavior_manifest["hermes_runtime"]["source_tree"]["sha256"],
|
|
||||||
"teleo_git_head": runtime_commit,
|
|
||||||
"teleo_source_sha256": behavior_manifest["teleo_infrastructure_runtime"]["identity_source_tree"]["sha256"],
|
|
||||||
"python": behavior_manifest["python_runtime"],
|
|
||||||
},
|
|
||||||
"model": {
|
|
||||||
"provider": model["provider"],
|
|
||||||
"default_model": model["default_model"],
|
|
||||||
"smart_routing": model.get("smart_routing"),
|
|
||||||
"smart_routing_model": model.get("smart_routing_model"),
|
|
||||||
"gateway_smart_model_routing": model.get("gateway_smart_model_routing"),
|
|
||||||
"model_section_sha256": model["model_section_sha256"],
|
|
||||||
"hosted_weights": "external_provider_managed_not_locally_hashable",
|
|
||||||
"actual_model_required_in_turn_receipt": True,
|
|
||||||
},
|
|
||||||
"skills": {
|
|
||||||
"content_sha256": components["procedural_skills"]["content"]["sha256"],
|
|
||||||
"file_count": components["procedural_skills"]["content"]["file_count"],
|
|
||||||
},
|
|
||||||
"permissions": {
|
|
||||||
"identity_config_sha256": model["identity_config_sha256"],
|
|
||||||
"gateway_permissions_sha256": model["gateway_permissions_sha256"],
|
|
||||||
"tool_permissions_sha256": model["tool_permissions_sha256"],
|
|
||||||
"runtime_policy": model["identity_runtime_policy"],
|
|
||||||
"authorization_decision_required_in_runtime_receipt": True,
|
|
||||||
},
|
|
||||||
"canonical_database": {
|
"canonical_database": {
|
||||||
"database": marker["database"],
|
"database": marker["database"],
|
||||||
"database_user": marker["database_user"],
|
"database_user": marker["database_user"],
|
||||||
|
|
@ -547,10 +771,20 @@ def build_identity_manifest(
|
||||||
for name, content in sorted(views.items())
|
for name, content in sorted(views.items())
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return {**stable, "manifest_sha256": behavior.canonical_sha256(stable)}
|
manifest = {**stable, "manifest_sha256": behavior.canonical_sha256(stable)}
|
||||||
|
if identity_runtime_contract is not None:
|
||||||
|
validate_identity_manifest(
|
||||||
|
manifest,
|
||||||
|
expected_runtime_policy_value=identity_runtime_contract["permissions"]["runtime_policy"],
|
||||||
|
)
|
||||||
|
return manifest
|
||||||
|
|
||||||
|
|
||||||
def validate_identity_manifest(value: dict[str, Any]) -> None:
|
def validate_identity_manifest(
|
||||||
|
value: dict[str, Any],
|
||||||
|
*,
|
||||||
|
expected_runtime_policy_value: dict[str, Any] | None = None,
|
||||||
|
) -> None:
|
||||||
_require(value.get("schema") == SCHEMA, "unsupported identity manifest schema")
|
_require(value.get("schema") == SCHEMA, "unsupported identity manifest schema")
|
||||||
expected = value.get("manifest_sha256")
|
expected = value.get("manifest_sha256")
|
||||||
_require(_is_sha256(expected), "identity manifest sha256 is invalid")
|
_require(_is_sha256(expected), "identity manifest sha256 is invalid")
|
||||||
|
|
@ -656,9 +890,18 @@ def validate_identity_manifest(value: dict[str, Any]) -> None:
|
||||||
)
|
)
|
||||||
for field in ("identity_config_sha256", "gateway_permissions_sha256", "tool_permissions_sha256"):
|
for field in ("identity_config_sha256", "gateway_permissions_sha256", "tool_permissions_sha256"):
|
||||||
_require(_is_sha256(permissions.get(field)), f"permission binding {field} is invalid")
|
_require(_is_sha256(permissions.get(field)), f"permission binding {field} is invalid")
|
||||||
|
runtime_policy = (
|
||||||
|
expected_runtime_policy_value if expected_runtime_policy_value is not None else expected_runtime_policy()
|
||||||
|
)
|
||||||
|
_require(_valid_runtime_policy(runtime_policy), "expected identity runtime policy is invalid")
|
||||||
|
policy_error = (
|
||||||
|
"identity runtime policy must be the exact local readback policy"
|
||||||
|
if expected_runtime_policy_value is None
|
||||||
|
else "identity runtime policy differs from the exact policy expected by this validator"
|
||||||
|
)
|
||||||
_require(
|
_require(
|
||||||
permissions.get("runtime_policy") == expected_runtime_policy(),
|
permissions.get("runtime_policy") == runtime_policy,
|
||||||
"identity runtime policy must be the exact local readback policy",
|
policy_error,
|
||||||
)
|
)
|
||||||
_require(
|
_require(
|
||||||
permissions.get("authorization_decision_required_in_runtime_receipt") is True,
|
permissions.get("authorization_decision_required_in_runtime_receipt") is True,
|
||||||
|
|
@ -765,18 +1008,49 @@ def validate_identity_manifest(value: dict[str, Any]) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def verify_bound_sources(value: dict[str, Any], source_root: Path) -> dict[str, str]:
|
def _bound_source_path(source_root: Path, repo_path: str, *, label: str) -> Path:
|
||||||
validate_identity_manifest(value)
|
_require(source_root.is_dir() and not source_root.is_symlink(), "identity source root is missing or unsafe")
|
||||||
|
relative = Path(repo_path)
|
||||||
|
_require(
|
||||||
|
not relative.is_absolute() and relative.parts and ".." not in relative.parts,
|
||||||
|
f"bound {label} path is unsafe",
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
resolved_root = source_root.resolve(strict=True)
|
||||||
|
except OSError as exc:
|
||||||
|
raise IdentityManifestError("identity source root cannot be resolved") from exc
|
||||||
|
candidate = source_root
|
||||||
|
for part in relative.parts:
|
||||||
|
candidate = candidate / part
|
||||||
|
_require(not candidate.is_symlink(), f"bound {label} has a symlink component")
|
||||||
|
try:
|
||||||
|
resolved = candidate.resolve(strict=True)
|
||||||
|
resolved.relative_to(resolved_root)
|
||||||
|
except (OSError, ValueError) as exc:
|
||||||
|
raise IdentityManifestError(f"bound {label} escapes the identity source root") from exc
|
||||||
|
_require(resolved.is_file(), f"bound {label} is missing")
|
||||||
|
return resolved
|
||||||
|
|
||||||
|
|
||||||
|
def verify_bound_sources(
|
||||||
|
value: dict[str, Any],
|
||||||
|
source_root: Path,
|
||||||
|
*,
|
||||||
|
expected_runtime_policy_value: dict[str, Any] | None = None,
|
||||||
|
) -> dict[str, str]:
|
||||||
|
validate_identity_manifest(value, expected_runtime_policy_value=expected_runtime_policy_value)
|
||||||
core = value["identity_inputs"]
|
core = value["identity_inputs"]
|
||||||
bindings = {
|
bindings = {
|
||||||
"database fingerprint": core["canonical_database"]["source"],
|
"database fingerprint": core["canonical_database"]["source"],
|
||||||
"constitution source": core["identity_sources"]["static_constitution"],
|
"constitution source": core["identity_sources"]["static_constitution"],
|
||||||
"database identity source": core["identity_sources"]["database_derived_identity"],
|
"database identity source": core["identity_sources"]["database_derived_identity"],
|
||||||
}
|
}
|
||||||
paths = {label: source_root / binding["repo_path"] for label, binding in bindings.items()}
|
paths = {
|
||||||
|
label: _bound_source_path(source_root, binding["repo_path"], label=label)
|
||||||
|
for label, binding in bindings.items()
|
||||||
|
}
|
||||||
for label, binding in bindings.items():
|
for label, binding in bindings.items():
|
||||||
path = paths[label]
|
path = paths[label]
|
||||||
_require(path.is_file(), f"bound {label} is missing")
|
|
||||||
if binding.get("content_sha256"):
|
if binding.get("content_sha256"):
|
||||||
_require(behavior.file_sha256(path) == binding["content_sha256"], f"bound {label} content drift detected")
|
_require(behavior.file_sha256(path) == binding["content_sha256"], f"bound {label} content drift detected")
|
||||||
fingerprint = load_json(paths["database fingerprint"], "database fingerprint")
|
fingerprint = load_json(paths["database fingerprint"], "database fingerprint")
|
||||||
|
|
|
||||||
382
scripts/run_gcp_leoclean_nosend_oci_smoke.py
Normal file
382
scripts/run_gcp_leoclean_nosend_oci_smoke.py
Normal file
|
|
@ -0,0 +1,382 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Build and exercise one disposable synthetic leoclean no-send OCI image."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import platform as host_platform
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
import uuid
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
if __package__ in {None, ""}:
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||||
|
|
||||||
|
from ops import gcp_leoclean_nosend_package as package
|
||||||
|
|
||||||
|
SCHEMA = "livingip.leocleanNoSendOciSmoke.v1"
|
||||||
|
SUCCESS_CLAIM_CEILING = (
|
||||||
|
"One disposable image built from a synthetic noncanonical identity started, reached its built-in no-send health "
|
||||||
|
"contract, rejected an unsupported send-message entrypoint command, and was removed. This does not prove a real "
|
||||||
|
"Hermes tool-call denial, general network-egress denial, canonical identity, Cloud SQL permissions, Artifact "
|
||||||
|
"Registry publication, GCP/systemd behavior, model parity, Telegram isolation in a live service, or production "
|
||||||
|
"readiness. Only the candidate container, tag, and visible image ID are required absent; digest-pinned base images "
|
||||||
|
"and build cache may remain."
|
||||||
|
)
|
||||||
|
FAILURE_CLAIM_CEILING = (
|
||||||
|
"This failed receipt makes no affirmative image-build, startup, health, command-rejection, or cleanup claim. "
|
||||||
|
"Cleanup is passing only when Docker inventory queries succeed and show the candidate container, tag, and visible "
|
||||||
|
"image ID absent; digest-pinned base images and build cache may remain."
|
||||||
|
)
|
||||||
|
ENTRYPOINT = "/opt/livingip/leoclean-nosend/entrypoint.py"
|
||||||
|
RUNTIME_PYTHON = "/opt/livingip/leoclean-nosend/venv/bin/python"
|
||||||
|
|
||||||
|
|
||||||
|
class SmokeError(RuntimeError):
|
||||||
|
"""The disposable image did not satisfy its bounded smoke contract."""
|
||||||
|
|
||||||
|
|
||||||
|
def _require(condition: bool, message: str) -> None:
|
||||||
|
if not condition:
|
||||||
|
raise SmokeError(message)
|
||||||
|
|
||||||
|
|
||||||
|
def _run(
|
||||||
|
arguments: list[str],
|
||||||
|
*,
|
||||||
|
check: bool = True,
|
||||||
|
timeout: float = 120.0,
|
||||||
|
) -> subprocess.CompletedProcess[str]:
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
arguments,
|
||||||
|
check=False,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=timeout,
|
||||||
|
)
|
||||||
|
except (OSError, subprocess.TimeoutExpired) as exc:
|
||||||
|
raise SmokeError(f"command unavailable or timed out: {arguments[0]}") from exc
|
||||||
|
if check and result.returncode != 0:
|
||||||
|
raise SmokeError(f"command failed: {arguments[0]} {arguments[1] if len(arguments) > 1 else ''}".strip())
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _docker_json(arguments: list[str], *, timeout: float = 120.0) -> Any:
|
||||||
|
result = _run(["docker", *arguments], timeout=timeout)
|
||||||
|
try:
|
||||||
|
return json.loads(result.stdout)
|
||||||
|
except json.JSONDecodeError as exc:
|
||||||
|
raise SmokeError("Docker did not return JSON") from exc
|
||||||
|
|
||||||
|
|
||||||
|
def _write_receipt(path: Path, value: dict[str, Any]) -> None:
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
temporary = path.with_name(f".{path.name}.{uuid.uuid4().hex}.tmp")
|
||||||
|
temporary.write_text(json.dumps(value, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||||
|
temporary.replace(path)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_command(*, context: Path, tag: str, smoke_label: str, build_args: dict[str, str]) -> list[str]:
|
||||||
|
command = [
|
||||||
|
"docker",
|
||||||
|
"build",
|
||||||
|
"--platform",
|
||||||
|
package.PLATFORM,
|
||||||
|
"--pull=false",
|
||||||
|
"--tag",
|
||||||
|
tag,
|
||||||
|
"--label",
|
||||||
|
smoke_label,
|
||||||
|
]
|
||||||
|
for name, value in sorted(build_args.items()):
|
||||||
|
command.extend(["--build-arg", f"{name}={value}"])
|
||||||
|
command.append(str(context))
|
||||||
|
return command
|
||||||
|
|
||||||
|
|
||||||
|
def _run_command(*, tag: str, name: str, smoke_label: str) -> list[str]:
|
||||||
|
return [
|
||||||
|
"docker",
|
||||||
|
"run",
|
||||||
|
"--detach",
|
||||||
|
"--rm",
|
||||||
|
f"--name={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={package.PROFILE_ROOT}:rw,noexec,nosuid,nodev,size=64m,uid=0,gid=0,mode=0700",
|
||||||
|
f"--tmpfs=/tmp:rw,noexec,nosuid,nodev,size=64m,uid={package.RUNTIME_UID},gid={package.RUNTIME_GID},mode=0700",
|
||||||
|
"--tmpfs=/var/lib/postgresql/data:ro,noexec,nosuid,nodev,size=64k,uid=0,gid=0,mode=0000",
|
||||||
|
"--label",
|
||||||
|
smoke_label,
|
||||||
|
tag,
|
||||||
|
"service",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _wait_healthy(name: str, *, expected_image_id: str, timeout: float = 180.0) -> dict[str, Any]:
|
||||||
|
deadline = time.monotonic() + timeout
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
result = _run(["docker", "container", "inspect", name], check=False, timeout=15)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise SmokeError("smoke container disappeared before health verification")
|
||||||
|
try:
|
||||||
|
inspected = json.loads(result.stdout)[0]
|
||||||
|
except (IndexError, KeyError, json.JSONDecodeError) as exc:
|
||||||
|
raise SmokeError("smoke container inspection is invalid") from exc
|
||||||
|
state = inspected.get("State") or {}
|
||||||
|
_require(inspected.get("Image") == expected_image_id, "running container image ID differs from the built image")
|
||||||
|
status = state.get("Status")
|
||||||
|
health = (state.get("Health") or {}).get("Status")
|
||||||
|
if health == "healthy":
|
||||||
|
return {"container_status": status, "health_status": health, "image_id": inspected["Image"]}
|
||||||
|
if status in {"dead", "exited", "removing"} or health == "unhealthy":
|
||||||
|
raise SmokeError(f"smoke container failed before health: {status}/{health}")
|
||||||
|
time.sleep(1)
|
||||||
|
raise SmokeError("smoke container did not become healthy before the deadline")
|
||||||
|
|
||||||
|
|
||||||
|
def _verify_health(name: str) -> dict[str, Any]:
|
||||||
|
result = _run(
|
||||||
|
[
|
||||||
|
"docker",
|
||||||
|
"exec",
|
||||||
|
"--user",
|
||||||
|
f"{package.RUNTIME_UID}:{package.RUNTIME_GID}",
|
||||||
|
name,
|
||||||
|
RUNTIME_PYTHON,
|
||||||
|
ENTRYPOINT,
|
||||||
|
"health",
|
||||||
|
],
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
value = json.loads(result.stdout)
|
||||||
|
except json.JSONDecodeError as exc:
|
||||||
|
raise SmokeError("container health command did not return JSON") from exc
|
||||||
|
_require(
|
||||||
|
value
|
||||||
|
== {
|
||||||
|
"schema": "livingip.leocleanNoSendContainerHealth.v1",
|
||||||
|
"status": "pass",
|
||||||
|
"process_id": 1,
|
||||||
|
"gateway_adapter_count": 0,
|
||||||
|
"send_message_present": False,
|
||||||
|
},
|
||||||
|
"container health response drifted",
|
||||||
|
)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def _verify_unsupported_send(name: str) -> dict[str, Any]:
|
||||||
|
result = _run(
|
||||||
|
[
|
||||||
|
"docker",
|
||||||
|
"exec",
|
||||||
|
"--user",
|
||||||
|
f"{package.RUNTIME_UID}:{package.RUNTIME_GID}",
|
||||||
|
name,
|
||||||
|
RUNTIME_PYTHON,
|
||||||
|
ENTRYPOINT,
|
||||||
|
"send-message",
|
||||||
|
],
|
||||||
|
check=False,
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
_require(result.returncode == 65, "unsupported send-message command did not fail closed")
|
||||||
|
_require(result.stdout == "", "unsupported send-message command emitted stdout")
|
||||||
|
try:
|
||||||
|
error = json.loads(result.stderr)
|
||||||
|
except json.JSONDecodeError as exc:
|
||||||
|
raise SmokeError("unsupported send-message command did not return redacted JSON") from exc
|
||||||
|
_require(
|
||||||
|
error == {"status": "fail", "error": "container_contract_failed"},
|
||||||
|
"unsupported send-message failure was not the exact redacted contract error",
|
||||||
|
)
|
||||||
|
return {"exit_code": result.returncode, "stdout_empty": True, "redacted_error": error}
|
||||||
|
|
||||||
|
|
||||||
|
def _cleanup(*, name: str, tag: str, image_id: str | None, smoke_label: str) -> dict[str, Any]:
|
||||||
|
_run(["docker", "rm", "--force", name], check=False, timeout=45)
|
||||||
|
_run(["docker", "image", "rm", "--force", tag], check=False, timeout=90)
|
||||||
|
labeled = _run(
|
||||||
|
["docker", "ps", "-aq", "--filter", f"label={smoke_label}"],
|
||||||
|
timeout=15,
|
||||||
|
).stdout.split()
|
||||||
|
named = _run(["docker", "ps", "-aq", "--filter", f"name=^{name}$"], timeout=15).stdout.split()
|
||||||
|
tagged = _run(
|
||||||
|
["docker", "image", "ls", "--quiet", "--filter", f"reference={tag}"], timeout=15
|
||||||
|
).stdout.split()
|
||||||
|
labeled_images = _run(
|
||||||
|
["docker", "image", "ls", "--quiet", "--filter", f"label={smoke_label}"], timeout=15
|
||||||
|
).stdout.split()
|
||||||
|
visible_image_ids = _run(["docker", "image", "ls", "--no-trunc", "--quiet"], timeout=15).stdout.split()
|
||||||
|
result = {
|
||||||
|
"labeled_containers_absent": labeled == [],
|
||||||
|
"named_container_absent": named == [],
|
||||||
|
"image_tag_absent": tagged == [],
|
||||||
|
"labeled_images_absent": labeled_images == [],
|
||||||
|
"candidate_image_id_absent": image_id is None or image_id not in visible_image_ids,
|
||||||
|
"docker_inventory_queries_succeeded": True,
|
||||||
|
}
|
||||||
|
result["status"] = "pass" if all(result.values()) else "fail"
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def run_smoke(*, repo_root: Path, artifact: Path, runtime_receipt: Path, output: Path) -> tuple[dict[str, Any], int]:
|
||||||
|
smoke_id = uuid.uuid4().hex
|
||||||
|
tag = f"livingip-leoclean-nosend-smoke:{smoke_id}"
|
||||||
|
name = f"livingip-leoclean-nosend-smoke-{smoke_id[:12]}"
|
||||||
|
smoke_label = f"livingip.leoclean-nosend-smoke={smoke_id}"
|
||||||
|
image_id: str | None = None
|
||||||
|
report: dict[str, Any] = {
|
||||||
|
"schema": SCHEMA,
|
||||||
|
"status": "fail",
|
||||||
|
"claim_ceiling": FAILURE_CLAIM_CEILING,
|
||||||
|
"smoke_id": smoke_id,
|
||||||
|
"platform": package.PLATFORM,
|
||||||
|
"synthetic_identity": True,
|
||||||
|
"canonical_authority_granted": False,
|
||||||
|
"build_count": 1,
|
||||||
|
"general_network_egress_denial_tested": False,
|
||||||
|
"real_hermes_send_tool_invocation_tested": False,
|
||||||
|
}
|
||||||
|
exit_code = 65
|
||||||
|
try:
|
||||||
|
_require(shutil.which("docker") is not None, "Docker is unavailable")
|
||||||
|
docker_server_arch = _docker_json(["version", "--format", "{{json .Server.Arch}}"], timeout=30)
|
||||||
|
_require(isinstance(docker_server_arch, str) and bool(docker_server_arch), "Docker server architecture is invalid")
|
||||||
|
execution_mode = "native" if docker_server_arch in {"amd64", "x86_64"} else "emulated_or_cross_platform"
|
||||||
|
_require(repo_root.is_dir() and not repo_root.is_symlink(), "repository root is missing or unsafe")
|
||||||
|
revision = package._git_head(repo_root)
|
||||||
|
runtime = package.validate_artifact(artifact, runtime_receipt, revision)
|
||||||
|
contract = package.build_expected_identity_contract(
|
||||||
|
artifact=artifact,
|
||||||
|
receipt_path=runtime_receipt,
|
||||||
|
runtime=runtime,
|
||||||
|
source_revision=revision,
|
||||||
|
source_root=repo_root,
|
||||||
|
)
|
||||||
|
fixture = repo_root / "fixtures" / "working-leo" / "leo-identity-v1"
|
||||||
|
manifest = package.strict_identity_manifest.build_identity_manifest(
|
||||||
|
identity_runtime_contract=contract,
|
||||||
|
database_fingerprint_path=fixture / "leo-database-fingerprint-v1.json",
|
||||||
|
constitution_path=fixture / "leo-constitution-v1.json",
|
||||||
|
database_identity_path=fixture / "leo-database-identity-v1.json",
|
||||||
|
source_root=repo_root,
|
||||||
|
)
|
||||||
|
with tempfile.TemporaryDirectory(prefix="livingip-leoclean-oci-smoke-") as temporary:
|
||||||
|
temporary_root = Path(temporary)
|
||||||
|
identity = temporary_root / "identity"
|
||||||
|
package.compile_identity_bundle(
|
||||||
|
manifest=manifest,
|
||||||
|
expected_identity_contract=contract,
|
||||||
|
source_root=repo_root,
|
||||||
|
output=identity,
|
||||||
|
)
|
||||||
|
context = temporary_root / "context"
|
||||||
|
image_input = package.prepare_context(
|
||||||
|
repo_root=repo_root,
|
||||||
|
artifact=artifact,
|
||||||
|
receipt=runtime_receipt,
|
||||||
|
identity=identity,
|
||||||
|
identity_source_root=repo_root,
|
||||||
|
output=context,
|
||||||
|
source_revision=revision,
|
||||||
|
)
|
||||||
|
build_args = package.load_json(context / "build-args.json", "build arguments")
|
||||||
|
_run(_build_command(context=context, tag=tag, smoke_label=smoke_label, build_args=build_args), timeout=1200)
|
||||||
|
inspected = _docker_json(["image", "inspect", tag], timeout=30)
|
||||||
|
_require(isinstance(inspected, list) and len(inspected) == 1, "smoke image inspection is not exact")
|
||||||
|
image = inspected[0]
|
||||||
|
image_id = image.get("Id")
|
||||||
|
_require(isinstance(image_id, str) and image_id.startswith("sha256:"), "smoke image ID is invalid")
|
||||||
|
_require(image.get("Os") == "linux" and image.get("Architecture") == "amd64", "smoke image platform drifted")
|
||||||
|
labels = (image.get("Config") or {}).get("Labels") or {}
|
||||||
|
for key, value in package.expected_image_labels(image_input).items():
|
||||||
|
_require(labels.get(key) == value, f"smoke image label drifted: {key}")
|
||||||
|
_require(labels.get("livingip.leoclean-nosend-smoke") == smoke_id, "smoke image label is missing")
|
||||||
|
_run(_run_command(tag=tag, name=name, smoke_label=smoke_label), timeout=60)
|
||||||
|
health_wait = _wait_healthy(name, expected_image_id=image_id)
|
||||||
|
health = _verify_health(name)
|
||||||
|
unsupported = _verify_unsupported_send(name)
|
||||||
|
_run(["docker", "stop", "--time=30", name], timeout=60)
|
||||||
|
deadline = time.monotonic() + 60
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
if _run(["docker", "container", "inspect", name], check=False, timeout=15).returncode != 0:
|
||||||
|
break
|
||||||
|
time.sleep(0.5)
|
||||||
|
else:
|
||||||
|
raise SmokeError("smoke container was not removed after stop")
|
||||||
|
report.update(
|
||||||
|
{
|
||||||
|
"teleo_git_head": revision,
|
||||||
|
"artifact_sha256": runtime["artifact_sha256"],
|
||||||
|
"identity_sha256": image_input["identity"]["bundle_sha256"],
|
||||||
|
"input_sha256": image_input["input_sha256"],
|
||||||
|
"image_id": image_id,
|
||||||
|
"docker_server_architecture": docker_server_arch,
|
||||||
|
"host_machine": host_platform.machine(),
|
||||||
|
"target_execution_mode": execution_mode,
|
||||||
|
"health_wait": health_wait,
|
||||||
|
"health": health,
|
||||||
|
"unsupported_send_message": unsupported,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
exit_code = 0
|
||||||
|
except (SmokeError, package.PackageError, OSError, ValueError) as exc:
|
||||||
|
report["error"] = str(exc)
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
cleanup = _cleanup(name=name, tag=tag, image_id=image_id, smoke_label=smoke_label)
|
||||||
|
except SmokeError as exc:
|
||||||
|
cleanup = {"status": "fail", "error": str(exc)}
|
||||||
|
report["cleanup"] = cleanup
|
||||||
|
if cleanup.get("status") != "pass":
|
||||||
|
exit_code = 65
|
||||||
|
report["error"] = "disposable OCI cleanup did not pass"
|
||||||
|
report["status"] = "pass" if exit_code == 0 else "fail"
|
||||||
|
report["claim_ceiling"] = SUCCESS_CLAIM_CEILING if exit_code == 0 else FAILURE_CLAIM_CEILING
|
||||||
|
_write_receipt(output, report)
|
||||||
|
return report, exit_code
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1])
|
||||||
|
parser.add_argument("--artifact", type=Path, required=True)
|
||||||
|
parser.add_argument("--runtime-receipt", type=Path, required=True)
|
||||||
|
parser.add_argument("--output", type=Path, required=True)
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
args = _parse_args(argv)
|
||||||
|
report, exit_code = run_smoke(
|
||||||
|
repo_root=args.repo_root.resolve(),
|
||||||
|
artifact=args.artifact.resolve(),
|
||||||
|
runtime_receipt=args.runtime_receipt.resolve(),
|
||||||
|
output=args.output.absolute(),
|
||||||
|
)
|
||||||
|
stream = sys.stdout if exit_code == 0 else sys.stderr
|
||||||
|
print(json.dumps({"schema": SCHEMA, "status": report["status"]}, sort_keys=True), file=stream)
|
||||||
|
return exit_code
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
|
|
@ -6,6 +6,8 @@ results_dir="${LEOCLEAN_NOSEND_RESULTS_DIR:-${repo_root}/.crabbox-results}"
|
||||||
receipt="${LEOCLEAN_NOSEND_RECEIPT:-${results_dir}/leoclean-nosend-runtime.json}"
|
receipt="${LEOCLEAN_NOSEND_RECEIPT:-${results_dir}/leoclean-nosend-runtime.json}"
|
||||||
uv_bin="${UV_BIN:-$(command -v uv || true)}"
|
uv_bin="${UV_BIN:-$(command -v uv || true)}"
|
||||||
python_bin="${PYTHON_BIN:-python3}"
|
python_bin="${PYTHON_BIN:-python3}"
|
||||||
|
artifact_output="${LEOCLEAN_NOSEND_ARTIFACT_OUTPUT:-}"
|
||||||
|
artifact_output_partial=""
|
||||||
|
|
||||||
if [[ -z "$uv_bin" || ! -x "$uv_bin" ]]; then
|
if [[ -z "$uv_bin" || ! -x "$uv_bin" ]]; then
|
||||||
echo "uv 0.9.30 is required" >&2
|
echo "uv 0.9.30 is required" >&2
|
||||||
|
|
@ -22,6 +24,10 @@ fi
|
||||||
|
|
||||||
temporary="$(mktemp -d "${TMPDIR:-/tmp}/livingip-nosend-runtime.XXXXXX")"
|
temporary="$(mktemp -d "${TMPDIR:-/tmp}/livingip-nosend-runtime.XXXXXX")"
|
||||||
cleanup() {
|
cleanup() {
|
||||||
|
if [[ -n "$artifact_output_partial" ]]; then
|
||||||
|
chmod -R u+w "$artifact_output_partial" 2>/dev/null || true
|
||||||
|
rm -rf "$artifact_output_partial"
|
||||||
|
fi
|
||||||
chmod -R u+w "$temporary" 2>/dev/null || true
|
chmod -R u+w "$temporary" 2>/dev/null || true
|
||||||
rm -rf "$temporary"
|
rm -rf "$temporary"
|
||||||
}
|
}
|
||||||
|
|
@ -83,3 +89,19 @@ if receipt.get("status") != "pass" or receipt.get("verification_mode") != "relea
|
||||||
raise SystemExit("no-send runtime receipt did not pass in release mode")
|
raise SystemExit("no-send runtime receipt did not pass in release mode")
|
||||||
print(json.dumps({"artifact_sha256": receipt["artifact_sha256"], "status": "pass"}, sort_keys=True))
|
print(json.dumps({"artifact_sha256": receipt["artifact_sha256"], "status": "pass"}, sort_keys=True))
|
||||||
PY
|
PY
|
||||||
|
|
||||||
|
if [[ -n "$artifact_output" ]]; then
|
||||||
|
if [[ -e "$artifact_output" || -L "$artifact_output" ]]; then
|
||||||
|
echo "retained artifact output already exists: $artifact_output" >&2
|
||||||
|
exit 65
|
||||||
|
fi
|
||||||
|
mkdir -p "$(dirname "$artifact_output")"
|
||||||
|
artifact_output_partial="${artifact_output}.partial.$$"
|
||||||
|
if [[ -e "$artifact_output_partial" || -L "$artifact_output_partial" ]]; then
|
||||||
|
echo "retained artifact partial output already exists: $artifact_output_partial" >&2
|
||||||
|
exit 65
|
||||||
|
fi
|
||||||
|
cp -pR "$artifact" "$artifact_output_partial"
|
||||||
|
mv "$artifact_output_partial" "$artifact_output"
|
||||||
|
artifact_output_partial=""
|
||||||
|
fi
|
||||||
|
|
|
||||||
150
tests/test_gcp_leoclean_nosend_oci_smoke.py
Normal file
150
tests/test_gcp_leoclean_nosend_oci_smoke.py
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from scripts import run_gcp_leoclean_nosend_oci_smoke as smoke
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_command_is_one_platform_pinned_local_build(tmp_path: Path) -> None:
|
||||||
|
command = smoke._build_command(
|
||||||
|
context=tmp_path,
|
||||||
|
tag="livingip-leoclean-nosend-smoke:test",
|
||||||
|
smoke_label="livingip.leoclean-nosend-smoke=test",
|
||||||
|
build_args={"TELEO_REVISION": "1" * 40, "INPUT_SHA256": "2" * 64},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert command[:6] == ["docker", "build", "--platform", "linux/amd64", "--pull=false", "--tag"]
|
||||||
|
assert command.count("build") == 1
|
||||||
|
assert "--push" not in command
|
||||||
|
assert "--load" not in command
|
||||||
|
assert command[-1] == str(tmp_path)
|
||||||
|
assert command.index("INPUT_SHA256=" + "2" * 64) < command.index("TELEO_REVISION=" + "1" * 40)
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_command_has_no_port_secret_or_host_mount_surface() -> None:
|
||||||
|
command = smoke._run_command(
|
||||||
|
tag="livingip-leoclean-nosend-smoke:test",
|
||||||
|
name="livingip-leoclean-nosend-smoke-test",
|
||||||
|
smoke_label="livingip.leoclean-nosend-smoke=test",
|
||||||
|
)
|
||||||
|
encoded = " ".join(command).casefold()
|
||||||
|
|
||||||
|
for required in (
|
||||||
|
"--read-only",
|
||||||
|
"--cap-drop=all",
|
||||||
|
"--security-opt=no-new-privileges:true",
|
||||||
|
"--network=bridge",
|
||||||
|
"--pids-limit=512",
|
||||||
|
"--pull=never",
|
||||||
|
):
|
||||||
|
assert required in encoded
|
||||||
|
assert "--publish" not in encoded
|
||||||
|
assert " -p " not in encoded
|
||||||
|
assert "--env" not in encoded
|
||||||
|
assert "--volume" not in encoded
|
||||||
|
assert "docker.sock" not in encoded
|
||||||
|
assert "telegram" not in encoded
|
||||||
|
|
||||||
|
|
||||||
|
def test_unsupported_send_requires_exact_redacted_fail_closed_result(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
result = subprocess.CompletedProcess(
|
||||||
|
args=["docker"],
|
||||||
|
returncode=65,
|
||||||
|
stdout="",
|
||||||
|
stderr=json.dumps({"status": "fail", "error": "container_contract_failed"}) + "\n",
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(smoke, "_run", lambda *_args, **_kwargs: result)
|
||||||
|
|
||||||
|
receipt = smoke._verify_unsupported_send("fixture")
|
||||||
|
|
||||||
|
assert receipt == {
|
||||||
|
"exit_code": 65,
|
||||||
|
"stdout_empty": True,
|
||||||
|
"redacted_error": {"status": "fail", "error": "container_contract_failed"},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_claim_ceiling_does_not_overstate_entrypoint_dispatch_probe() -> None:
|
||||||
|
assert "does not prove a real Hermes tool-call denial" in smoke.SUCCESS_CLAIM_CEILING
|
||||||
|
assert "general network-egress denial" in smoke.SUCCESS_CLAIM_CEILING
|
||||||
|
assert "Cloud SQL permissions" in smoke.SUCCESS_CLAIM_CEILING
|
||||||
|
assert "production readiness" in smoke.SUCCESS_CLAIM_CEILING
|
||||||
|
assert "base images and build cache may remain" in smoke.SUCCESS_CLAIM_CEILING
|
||||||
|
assert "makes no affirmative image-build" in smoke.FAILURE_CLAIM_CEILING
|
||||||
|
|
||||||
|
|
||||||
|
def test_health_wait_rejects_container_running_a_different_image(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
inspected = [{"Image": "sha256:wrong", "State": {"Status": "running", "Health": {"Status": "healthy"}}}]
|
||||||
|
result = subprocess.CompletedProcess(args=["docker"], returncode=0, stdout=json.dumps(inspected), stderr="")
|
||||||
|
monkeypatch.setattr(smoke, "_run", lambda *_args, **_kwargs: result)
|
||||||
|
|
||||||
|
with pytest.raises(smoke.SmokeError, match="image ID differs"):
|
||||||
|
smoke._wait_healthy("fixture", expected_image_id="sha256:expected", timeout=0.1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_cleanup_fails_when_docker_inventory_is_indeterminate(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
calls = 0
|
||||||
|
|
||||||
|
def fake_run(*_args, **_kwargs):
|
||||||
|
nonlocal calls
|
||||||
|
calls += 1
|
||||||
|
if calls <= 2:
|
||||||
|
return subprocess.CompletedProcess(args=["docker"], returncode=1, stdout="", stderr="not found")
|
||||||
|
raise smoke.SmokeError("Docker inventory unavailable")
|
||||||
|
|
||||||
|
monkeypatch.setattr(smoke, "_run", fake_run)
|
||||||
|
|
||||||
|
with pytest.raises(smoke.SmokeError, match="inventory unavailable"):
|
||||||
|
smoke._cleanup(
|
||||||
|
name="fixture",
|
||||||
|
tag="livingip-leoclean-nosend-smoke:fixture",
|
||||||
|
image_id="sha256:" + "1" * 64,
|
||||||
|
smoke_label="livingip.leoclean-nosend-smoke=fixture",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_failed_receipt_never_carries_the_success_claim(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
monkeypatch.setattr(smoke.shutil, "which", lambda _name: None)
|
||||||
|
monkeypatch.setattr(smoke, "_cleanup", lambda **_kwargs: {"status": "pass"})
|
||||||
|
output = tmp_path / "receipt.json"
|
||||||
|
|
||||||
|
report, exit_code = smoke.run_smoke(
|
||||||
|
repo_root=tmp_path,
|
||||||
|
artifact=tmp_path / "artifact",
|
||||||
|
runtime_receipt=tmp_path / "runtime.json",
|
||||||
|
output=output,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert exit_code == 65
|
||||||
|
assert report["status"] == "fail"
|
||||||
|
assert report["claim_ceiling"] == smoke.FAILURE_CLAIM_CEILING
|
||||||
|
assert smoke.SUCCESS_CLAIM_CEILING not in output.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def test_runtime_canary_can_retain_only_a_successfully_verified_artifact() -> None:
|
||||||
|
source = (Path(__file__).resolve().parents[1] / "scripts" / "run_leoclean_nosend_runtime_canary.sh").read_text(
|
||||||
|
encoding="utf-8"
|
||||||
|
)
|
||||||
|
|
||||||
|
verification = source.index('receipt.get("status") != "pass"')
|
||||||
|
retention = source.index('if [[ -n "$artifact_output" ]]')
|
||||||
|
assert verification < retention
|
||||||
|
assert "retained artifact output already exists" in source
|
||||||
|
assert 'cp -pR "$artifact" "$artifact_output_partial"' in source
|
||||||
|
assert 'mv "$artifact_output_partial" "$artifact_output"' in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_ci_preloads_the_exact_target_architecture_and_checks_inventory() -> None:
|
||||||
|
workflow = (Path(__file__).resolve().parents[1] / ".github" / "workflows" / "ci.yml").read_text(
|
||||||
|
encoding="utf-8"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert workflow.count("docker pull --platform=linux/amd64") == 3
|
||||||
|
assert "container_inventory=\"$(docker ps" in workflow
|
||||||
|
assert "image_inventory=\"$(docker images" in workflow
|
||||||
|
assert "remaining_containers=\"$(docker ps" in workflow
|
||||||
|
assert "remaining_images=\"$(docker images" in workflow
|
||||||
|
|
@ -30,6 +30,13 @@ def artifact_fixture(tmp_path: Path) -> tuple[Path, Path]:
|
||||||
artifact.mkdir()
|
artifact.mkdir()
|
||||||
(artifact / "bootstrap.py").write_text("#!/usr/bin/env python3\n", encoding="utf-8")
|
(artifact / "bootstrap.py").write_text("#!/usr/bin/env python3\n", encoding="utf-8")
|
||||||
(artifact / "bootstrap.py").chmod(0o755)
|
(artifact / "bootstrap.py").chmod(0o755)
|
||||||
|
hermes = artifact / "hermes-agent"
|
||||||
|
hermes.mkdir()
|
||||||
|
(hermes / "uv.lock").write_text(
|
||||||
|
'version = 1\n\n[[package]]\nname = "pyyaml"\nversion = "6.0.3"\n',
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
uv_lock_sha256 = package.sha256_file(hermes / "uv.lock")
|
||||||
write_json(
|
write_json(
|
||||||
artifact / "runtime-lock.json",
|
artifact / "runtime-lock.json",
|
||||||
{
|
{
|
||||||
|
|
@ -39,15 +46,58 @@ def artifact_fixture(tmp_path: Path) -> tuple[Path, Path]:
|
||||||
},
|
},
|
||||||
"hermes": {
|
"hermes": {
|
||||||
"commit": package.HERMES_COMMIT,
|
"commit": package.HERMES_COMMIT,
|
||||||
"uv_lock_sha256": "3" * 64,
|
"uv_lock_sha256": uv_lock_sha256,
|
||||||
|
"patched_source_tree_sha256": "4" * 64,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mode=0o644,
|
mode=0o644,
|
||||||
)
|
)
|
||||||
profile = artifact / "profile-template"
|
profile = artifact / "profile-template"
|
||||||
profile.mkdir()
|
profile.mkdir()
|
||||||
(profile / "config.yaml").write_text("model: fixture\n", encoding="utf-8")
|
(profile / "config.yaml").write_text(
|
||||||
|
"""model:
|
||||||
|
provider: openrouter
|
||||||
|
default: anthropic/claude-sonnet-4-6
|
||||||
|
max_tokens: 8192
|
||||||
|
smart_model_routing: false
|
||||||
|
agent:
|
||||||
|
max_turns: 90
|
||||||
|
memory:
|
||||||
|
enabled: false
|
||||||
|
memory_enabled: false
|
||||||
|
user_profile_enabled: false
|
||||||
|
nudge_interval: 0
|
||||||
|
skills:
|
||||||
|
creation_nudge_interval: 0
|
||||||
|
platform_toolsets:
|
||||||
|
api_server: [livingip-leoclean-nosend]
|
||||||
|
known_plugin_toolsets:
|
||||||
|
api_server: []
|
||||||
|
mcp_servers: {}
|
||||||
|
display:
|
||||||
|
tool_progress: false
|
||||||
|
""",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
(profile / "config.yaml").chmod(0o600)
|
(profile / "config.yaml").chmod(0o600)
|
||||||
|
skills = profile / "skills" / "teleo-kb-bridge"
|
||||||
|
skills.mkdir(parents=True)
|
||||||
|
(skills / "SKILL.md").write_text("# fixture skill\n", encoding="utf-8")
|
||||||
|
write_json(
|
||||||
|
artifact / "runtime-contract.json",
|
||||||
|
{
|
||||||
|
"schema": "livingip.leocleanNoSendRuntimeContract.v1",
|
||||||
|
"transport_authority": "absent",
|
||||||
|
"toolset": {"allowed_tools": ["skills_list", "skill_view", "terminal"]},
|
||||||
|
"database": {
|
||||||
|
"direct_canonical_writes": "denied",
|
||||||
|
"direct_stage_table_writes": "denied",
|
||||||
|
"proposal_staging": "function_only",
|
||||||
|
"proposal_function": package.PROPOSAL_FUNCTION,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mode=0o644,
|
||||||
|
)
|
||||||
content = package.tree_manifest(artifact)
|
content = package.tree_manifest(artifact)
|
||||||
manifest = {
|
manifest = {
|
||||||
"schema": package.ARTIFACT_MANIFEST_SCHEMA,
|
"schema": package.ARTIFACT_MANIFEST_SCHEMA,
|
||||||
|
|
@ -68,6 +118,8 @@ def artifact_fixture(tmp_path: Path) -> tuple[Path, Path]:
|
||||||
"artifact_sha256": content["sha256"],
|
"artifact_sha256": content["sha256"],
|
||||||
"hermes_commit": package.HERMES_COMMIT,
|
"hermes_commit": package.HERMES_COMMIT,
|
||||||
"hermes_source_sha256": "4" * 64,
|
"hermes_source_sha256": "4" * 64,
|
||||||
|
"config_sha256": package.sha256_file(profile / "config.yaml"),
|
||||||
|
"runtime_probe": runtime_probe_fixture(package.sha256_file(profile / "config.yaml")),
|
||||||
"claim_ceiling": CLAIM_CEILING,
|
"claim_ceiling": CLAIM_CEILING,
|
||||||
},
|
},
|
||||||
mode=0o644,
|
mode=0o644,
|
||||||
|
|
@ -75,47 +127,150 @@ def artifact_fixture(tmp_path: Path) -> tuple[Path, Path]:
|
||||||
return artifact, receipt
|
return artifact, receipt
|
||||||
|
|
||||||
|
|
||||||
def identity_fixture(tmp_path: Path) -> Path:
|
def runtime_surface_fixture() -> dict[str, object]:
|
||||||
identity = tmp_path / "identity"
|
return {
|
||||||
identity.mkdir(parents=True)
|
"registry_sealed": True,
|
||||||
soul = identity / "SOUL.md"
|
"send_message_present": False,
|
||||||
soul.write_text("# Leo fixture\n", encoding="utf-8")
|
"terminal_handler": "livingip.restricted_teleo_kb.v1",
|
||||||
soul.chmod(0o600)
|
"terminal_restricted_to": "profile/bin/teleo-kb",
|
||||||
stable = {
|
"tools": ["skill_view", "skills_list", "terminal"],
|
||||||
"schema": package.IDENTITY_MANIFEST_SCHEMA,
|
}
|
||||||
"identity_inputs": {
|
|
||||||
"canonical_database": {
|
|
||||||
"authority": "synthetic_noncanonical_fixture",
|
def runtime_probe_fixture(config_sha256: str) -> dict[str, object]:
|
||||||
"canonical_authority_granted": False,
|
surface = runtime_surface_fixture()
|
||||||
}
|
return {
|
||||||
|
"schema": "livingip.leocleanNoSendRuntimeProbe.v1",
|
||||||
|
"status": "pass",
|
||||||
|
"config_sha256": config_sha256,
|
||||||
|
"gateway_adapter_count": 0,
|
||||||
|
"connected_platforms": [],
|
||||||
|
"python_version": package.PYTHON_VERSION,
|
||||||
|
"model": package.DEFAULT_MODEL,
|
||||||
|
"provider_credential_names_present": ["OPENROUTER_API_KEY"],
|
||||||
|
"provider_credential_sentinel_redaction": "pass",
|
||||||
|
"effective_policy": {
|
||||||
|
"cheap_model_route": None,
|
||||||
|
"memory_enabled": False,
|
||||||
|
"memory_nudge_interval": 0,
|
||||||
|
"skill_creation_nudge_interval": 0,
|
||||||
|
"smart_model_routing": False,
|
||||||
|
"user_profile_enabled": False,
|
||||||
},
|
},
|
||||||
"identity_inputs_sha256": "5" * 64,
|
"tool_surface": surface,
|
||||||
"compiled_views": {
|
"lifecycle": {
|
||||||
"SOUL.md": {"sha256": package.sha256_file(soul)},
|
"started": True,
|
||||||
"identity.json": {"sha256": "6" * 64},
|
"stopped": True,
|
||||||
|
"post_start_registry": surface,
|
||||||
|
"post_discovery_registry": surface,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
manifest = {**stable, "manifest_sha256": package.canonical_sha256(stable)}
|
|
||||||
write_json(identity / "identity-manifest.json", manifest)
|
|
||||||
write_json(
|
def identity_fixture(tmp_path: Path, artifact: Path, receipt: Path) -> tuple[Path, dict[str, object]]:
|
||||||
identity / "identity-lock.json",
|
runtime = package.validate_artifact(artifact, receipt, REVISION)
|
||||||
|
contract = package.build_expected_identity_contract(
|
||||||
|
artifact=artifact,
|
||||||
|
receipt_path=receipt,
|
||||||
|
runtime=runtime,
|
||||||
|
source_revision=REVISION,
|
||||||
|
source_root=ROOT,
|
||||||
|
)
|
||||||
|
fixture = ROOT / "fixtures" / "working-leo" / "leo-identity-v1"
|
||||||
|
manifest = package.strict_identity_manifest.build_identity_manifest(
|
||||||
|
identity_runtime_contract=contract,
|
||||||
|
database_fingerprint_path=fixture / "leo-database-fingerprint-v1.json",
|
||||||
|
constitution_path=fixture / "leo-constitution-v1.json",
|
||||||
|
database_identity_path=fixture / "leo-database-identity-v1.json",
|
||||||
|
source_root=ROOT,
|
||||||
|
)
|
||||||
|
identity = tmp_path / "identity"
|
||||||
|
package.compile_identity_bundle(
|
||||||
|
manifest=manifest,
|
||||||
|
expected_identity_contract=contract,
|
||||||
|
source_root=ROOT,
|
||||||
|
output=identity,
|
||||||
|
)
|
||||||
|
return identity, contract
|
||||||
|
|
||||||
|
|
||||||
|
def fully_rehash_identity_bundle(identity: Path) -> None:
|
||||||
|
manifest_path = identity / "identity-manifest.json"
|
||||||
|
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||||
|
inputs_hash = package.canonical_sha256(manifest["identity_inputs"])
|
||||||
|
manifest["identity_inputs_sha256"] = inputs_hash
|
||||||
|
fixture = ROOT / "fixtures" / "working-leo" / "leo-identity-v1"
|
||||||
|
fingerprint = package.strict_identity_manifest.load_json(
|
||||||
|
fixture / "leo-database-fingerprint-v1.json", "database fingerprint"
|
||||||
|
)
|
||||||
|
rules = package.strict_identity_manifest.canonical_rules(
|
||||||
|
package.strict_identity_manifest.load_json(fixture / "leo-constitution-v1.json", "constitution")
|
||||||
|
)
|
||||||
|
database_identity = package.strict_identity_manifest.load_json(
|
||||||
|
fixture / "leo-database-identity-v1.json", "database identity"
|
||||||
|
)
|
||||||
|
records = package.strict_identity_manifest.canonical_database_records(
|
||||||
|
database_identity,
|
||||||
|
fingerprint_sha256=fingerprint["fingerprint_sha256"],
|
||||||
|
)
|
||||||
|
provenance = package.strict_identity_manifest.database_identity_provenance(
|
||||||
|
database_identity,
|
||||||
|
fingerprint=fingerprint,
|
||||||
|
)
|
||||||
|
rendered = package.strict_identity_manifest.render_identity_views(
|
||||||
|
identity_inputs_sha256=inputs_hash,
|
||||||
|
database_fingerprint_sha256=fingerprint["fingerprint_sha256"],
|
||||||
|
database_provenance=provenance,
|
||||||
|
rules=rules,
|
||||||
|
records=records,
|
||||||
|
)
|
||||||
|
for name, content in rendered.items():
|
||||||
|
(identity / name).write_text(content, encoding="utf-8")
|
||||||
|
(identity / name).chmod(0o600)
|
||||||
|
manifest["compiled_views"][name]["sha256"] = package.sha256_file(identity / name)
|
||||||
|
manifest["compiled_views"][name]["generated_from_identity_inputs_sha256"] = inputs_hash
|
||||||
|
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"]
|
||||||
|
lock["identity_inputs_sha256"] = inputs_hash
|
||||||
|
lock["compiled_views"] = {
|
||||||
|
name: {"sha256": package.sha256_file(identity / name)} for name in sorted(rendered)
|
||||||
|
}
|
||||||
|
write_json(lock_path, lock)
|
||||||
|
|
||||||
|
|
||||||
|
def fully_rehash_identity_contract(contract: dict[str, object]) -> None:
|
||||||
|
runtime = contract["runtime"]
|
||||||
|
assert isinstance(runtime, dict)
|
||||||
|
runtime["identity_runtime_sha256"] = package.canonical_sha256(package._identity_runtime_payload(contract))
|
||||||
|
stable = {key: value for key, value in contract.items() if key != "contract_sha256"}
|
||||||
|
contract["contract_sha256"] = package.canonical_sha256(stable)
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_image_identity_binding(image_input: dict[str, object], identity: Path) -> None:
|
||||||
|
binding = image_input["identity"]
|
||||||
|
assert isinstance(binding, dict)
|
||||||
|
manifest = json.loads((identity / "identity-manifest.json").read_text(encoding="utf-8"))
|
||||||
|
binding.update(
|
||||||
{
|
{
|
||||||
"schema": package.IDENTITY_LOCK_SCHEMA,
|
"bundle_sha256": package.identity_bundle_manifest(identity)["sha256"],
|
||||||
"manifest_sha256": manifest["manifest_sha256"],
|
"manifest_sha256": manifest["manifest_sha256"],
|
||||||
"identity_inputs_sha256": manifest["identity_inputs_sha256"],
|
"identity_inputs_sha256": manifest["identity_inputs_sha256"],
|
||||||
"compiled_views": {
|
"identity_lock_sha256": package.sha256_file(identity / "identity-lock.json"),
|
||||||
"SOUL.md": {"sha256": package.sha256_file(soul)},
|
"soul_sha256": package.sha256_file(identity / "SOUL.md"),
|
||||||
"identity.json": {"sha256": "6" * 64},
|
"identity_view_sha256": package.sha256_file(identity / "identity.json"),
|
||||||
},
|
}
|
||||||
"session_boundary": {"classification": "temporary_noncanonical"},
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
return identity
|
stable = {key: value for key, value in image_input.items() if key != "input_sha256"}
|
||||||
|
image_input["input_sha256"] = package.canonical_sha256(stable)
|
||||||
|
|
||||||
|
|
||||||
def image_input_fixture(tmp_path: Path) -> dict[str, object]:
|
def image_input_fixture(tmp_path: Path) -> dict[str, object]:
|
||||||
artifact, receipt = artifact_fixture(tmp_path)
|
artifact, receipt = artifact_fixture(tmp_path)
|
||||||
identity = identity_fixture(tmp_path)
|
identity, _contract = identity_fixture(tmp_path, artifact, receipt)
|
||||||
return package.build_image_input(
|
return package.build_image_input(
|
||||||
artifact=artifact,
|
artifact=artifact,
|
||||||
receipt=receipt,
|
receipt=receipt,
|
||||||
|
|
@ -125,6 +280,8 @@ def image_input_fixture(tmp_path: Path) -> dict[str, object]:
|
||||||
dockerfile=ROOT / "docker" / "leoclean-nosend" / "Dockerfile",
|
dockerfile=ROOT / "docker" / "leoclean-nosend" / "Dockerfile",
|
||||||
entrypoint=ROOT / "docker" / "leoclean-nosend" / "entrypoint.py",
|
entrypoint=ROOT / "docker" / "leoclean-nosend" / "entrypoint.py",
|
||||||
package_contract=ROOT / "ops" / "gcp_leoclean_nosend_package.py",
|
package_contract=ROOT / "ops" / "gcp_leoclean_nosend_package.py",
|
||||||
|
contract_source_root=ROOT,
|
||||||
|
identity_source_root=ROOT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -143,6 +300,14 @@ def test_image_input_binds_exact_runtime_identity_and_staging_target(tmp_path: P
|
||||||
assert value["runtime"]["python_version"] == "3.11.9"
|
assert value["runtime"]["python_version"] == "3.11.9"
|
||||||
assert value["runtime"]["postgres_version"] == "16.14"
|
assert value["runtime"]["postgres_version"] == "16.14"
|
||||||
assert value["identity"]["canonical_authority_granted"] is False
|
assert value["identity"]["canonical_authority_granted"] is False
|
||||||
|
database_policy = value["identity"]["runtime_contract"]["permissions"]["runtime_policy"]["database"]
|
||||||
|
assert database_policy == {
|
||||||
|
"direct_canonical_writes": "denied",
|
||||||
|
"direct_stage_table_writes": "denied",
|
||||||
|
"proposal_staging": "function_only",
|
||||||
|
"proposal_function": "kb_stage.stage_leoclean_proposal(text,text,text,text,jsonb)",
|
||||||
|
"live_effective_permissions_proven": False,
|
||||||
|
}
|
||||||
assert value["base_images"]["postgres"].endswith(
|
assert value["base_images"]["postgres"].endswith(
|
||||||
"@sha256:92620daddcd947f8d5ab5ba66e848702fe443d87fed30c4cea8e389fd78dfc55"
|
"@sha256:92620daddcd947f8d5ab5ba66e848702fe443d87fed30c4cea8e389fd78dfc55"
|
||||||
)
|
)
|
||||||
|
|
@ -166,21 +331,38 @@ def test_artifact_requires_release_receipt_exact_revision_and_unchanged_content(
|
||||||
package.validate_artifact(artifact, receipt, REVISION)
|
package.validate_artifact(artifact, receipt, REVISION)
|
||||||
|
|
||||||
|
|
||||||
|
def test_artifact_binds_runtime_probe_to_the_exact_packaged_config(tmp_path: Path) -> None:
|
||||||
|
artifact, receipt = artifact_fixture(tmp_path)
|
||||||
|
value = json.loads(receipt.read_text(encoding="utf-8"))
|
||||||
|
value["runtime_probe"]["config_sha256"] = "9" * 64
|
||||||
|
write_json(receipt, value, mode=0o644)
|
||||||
|
|
||||||
|
with pytest.raises(package.PackageError, match="runtime probe config differs"):
|
||||||
|
package.validate_artifact(artifact, receipt, REVISION)
|
||||||
|
|
||||||
|
|
||||||
def test_identity_bundle_rejects_drift_extra_files_and_false_authority(tmp_path: Path) -> None:
|
def test_identity_bundle_rejects_drift_extra_files_and_false_authority(tmp_path: Path) -> None:
|
||||||
identity = identity_fixture(tmp_path)
|
artifact, receipt = artifact_fixture(tmp_path)
|
||||||
package.validate_identity_bundle(identity)
|
identity, contract = identity_fixture(tmp_path, artifact, receipt)
|
||||||
|
validate = lambda root: package.validate_identity_bundle( # noqa: E731
|
||||||
|
root,
|
||||||
|
expected_revision=REVISION,
|
||||||
|
expected_identity_contract=contract,
|
||||||
|
source_root=ROOT,
|
||||||
|
)
|
||||||
|
validate(identity)
|
||||||
|
|
||||||
(identity / "SOUL.md").write_text("drift\n", encoding="utf-8")
|
(identity / "SOUL.md").write_text("drift\n", encoding="utf-8")
|
||||||
(identity / "SOUL.md").chmod(0o600)
|
(identity / "SOUL.md").chmod(0o600)
|
||||||
with pytest.raises(package.PackageError, match="SOUL content differs"):
|
with pytest.raises(package.PackageError, match="view derivation drifted"):
|
||||||
package.validate_identity_bundle(identity)
|
validate(identity)
|
||||||
|
|
||||||
identity = identity_fixture(tmp_path / "second")
|
identity, _contract = identity_fixture(tmp_path / "second", artifact, receipt)
|
||||||
(identity / "extra.txt").write_text("extra\n", encoding="utf-8")
|
(identity / "extra.txt").write_text("extra\n", encoding="utf-8")
|
||||||
with pytest.raises(package.PackageError, match="entry set"):
|
with pytest.raises(package.PackageError, match="entry set"):
|
||||||
package.validate_identity_bundle(identity)
|
validate(identity)
|
||||||
|
|
||||||
identity = identity_fixture(tmp_path / "third")
|
identity, _contract = identity_fixture(tmp_path / "third", artifact, receipt)
|
||||||
manifest_path = identity / "identity-manifest.json"
|
manifest_path = identity / "identity-manifest.json"
|
||||||
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||||
manifest["identity_inputs"]["canonical_database"] = {
|
manifest["identity_inputs"]["canonical_database"] = {
|
||||||
|
|
@ -194,8 +376,162 @@ def test_identity_bundle_rejects_drift_extra_files_and_false_authority(tmp_path:
|
||||||
lock = json.loads(lock_path.read_text(encoding="utf-8"))
|
lock = json.loads(lock_path.read_text(encoding="utf-8"))
|
||||||
lock["manifest_sha256"] = manifest["manifest_sha256"]
|
lock["manifest_sha256"] = manifest["manifest_sha256"]
|
||||||
write_json(lock_path, lock)
|
write_json(lock_path, lock)
|
||||||
with pytest.raises(package.PackageError, match="authority"):
|
with pytest.raises(package.PackageError, match="semantic/source validation"):
|
||||||
package.validate_identity_bundle(identity)
|
validate(identity)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("path", "value"),
|
||||||
|
[
|
||||||
|
(("source_commit",), "2" * 40),
|
||||||
|
(("runtime", "hermes_source_sha256"), "7" * 64),
|
||||||
|
(("model", "default_model"), "attacker/model"),
|
||||||
|
(("skills", "content_sha256"), "8" * 64),
|
||||||
|
(("permissions", "runtime_policy", "general_network_egress", "denial_tested"), True),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_fully_rehashed_identity_runtime_mutations_fail_closed(
|
||||||
|
tmp_path: Path, path: tuple[str, ...], value: object
|
||||||
|
) -> None:
|
||||||
|
artifact, receipt = artifact_fixture(tmp_path)
|
||||||
|
identity, contract = identity_fixture(tmp_path, artifact, receipt)
|
||||||
|
manifest_path = identity / "identity-manifest.json"
|
||||||
|
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||||
|
target = manifest["identity_inputs"]
|
||||||
|
for key in path[:-1]:
|
||||||
|
target = target[key]
|
||||||
|
target[path[-1]] = value
|
||||||
|
if path == ("source_commit",):
|
||||||
|
manifest["identity_inputs"]["runtime"]["teleo_git_head"] = value
|
||||||
|
write_json(manifest_path, manifest)
|
||||||
|
fully_rehash_identity_bundle(identity)
|
||||||
|
|
||||||
|
with pytest.raises(package.PackageError):
|
||||||
|
package.validate_identity_bundle(
|
||||||
|
identity,
|
||||||
|
expected_revision=REVISION,
|
||||||
|
expected_identity_contract=contract,
|
||||||
|
source_root=ROOT,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_custom_no_send_policy_requires_explicit_validator_authority(tmp_path: Path) -> None:
|
||||||
|
artifact, receipt = artifact_fixture(tmp_path)
|
||||||
|
identity, contract = identity_fixture(tmp_path, artifact, receipt)
|
||||||
|
manifest = json.loads((identity / "identity-manifest.json").read_text(encoding="utf-8"))
|
||||||
|
|
||||||
|
with pytest.raises(package.strict_identity_manifest.IdentityManifestError, match="exact local readback policy"):
|
||||||
|
package.strict_identity_manifest.validate_identity_manifest(manifest)
|
||||||
|
package.strict_identity_manifest.validate_identity_manifest(
|
||||||
|
manifest,
|
||||||
|
expected_runtime_policy_value=contract["permissions"]["runtime_policy"],
|
||||||
|
)
|
||||||
|
with pytest.raises(package.strict_identity_manifest.IdentityManifestError, match="expected identity runtime policy"):
|
||||||
|
package.strict_identity_manifest.validate_identity_manifest(manifest, expected_runtime_policy_value={})
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mutation", ["legacy_policy", "malformed_behavior_inputs"])
|
||||||
|
def test_runtime_contract_adapter_rejects_weaker_or_malformed_contracts(tmp_path: Path, mutation: str) -> None:
|
||||||
|
artifact, receipt = artifact_fixture(tmp_path)
|
||||||
|
_identity, contract = identity_fixture(tmp_path, artifact, receipt)
|
||||||
|
mutated = copy.deepcopy(contract)
|
||||||
|
if mutation == "legacy_policy":
|
||||||
|
mutated["permissions"]["runtime_policy"] = package.strict_identity_manifest.expected_runtime_policy()
|
||||||
|
else:
|
||||||
|
del mutated["behavior_inputs"]["tool_surface"]
|
||||||
|
fully_rehash_identity_contract(mutated)
|
||||||
|
|
||||||
|
with pytest.raises(package.strict_identity_manifest.IdentityManifestError):
|
||||||
|
package.strict_identity_manifest.identity_runtime_inputs_from_contract(mutated)
|
||||||
|
|
||||||
|
|
||||||
|
def test_identity_source_content_drift_fails_closed(tmp_path: Path) -> None:
|
||||||
|
artifact, receipt = artifact_fixture(tmp_path)
|
||||||
|
identity, contract = identity_fixture(tmp_path, artifact, receipt)
|
||||||
|
source_root = tmp_path / "source-root"
|
||||||
|
fixture_relative = Path("fixtures/working-leo/leo-identity-v1")
|
||||||
|
shutil.copytree(ROOT / fixture_relative, source_root / fixture_relative)
|
||||||
|
package.validate_identity_bundle(
|
||||||
|
identity,
|
||||||
|
expected_revision=REVISION,
|
||||||
|
expected_identity_contract=contract,
|
||||||
|
source_root=source_root,
|
||||||
|
)
|
||||||
|
constitution = source_root / fixture_relative / "leo-constitution-v1.json"
|
||||||
|
constitution.write_text(constitution.read_text(encoding="utf-8") + "\n", encoding="utf-8")
|
||||||
|
|
||||||
|
with pytest.raises(package.PackageError, match="semantic/source validation"):
|
||||||
|
package.validate_identity_bundle(
|
||||||
|
identity,
|
||||||
|
expected_revision=REVISION,
|
||||||
|
expected_identity_contract=contract,
|
||||||
|
source_root=source_root,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_identity_source_intermediate_symlink_fails_closed(tmp_path: Path) -> None:
|
||||||
|
artifact, receipt = artifact_fixture(tmp_path)
|
||||||
|
identity, contract = identity_fixture(tmp_path, artifact, receipt)
|
||||||
|
outside = tmp_path / "outside"
|
||||||
|
shutil.copytree(ROOT / "fixtures", outside / "fixtures")
|
||||||
|
source_root = tmp_path / "source-root"
|
||||||
|
source_root.mkdir()
|
||||||
|
(source_root / "fixtures").symlink_to(outside / "fixtures", target_is_directory=True)
|
||||||
|
|
||||||
|
with pytest.raises(package.PackageError, match="semantic/source validation"):
|
||||||
|
package.validate_identity_bundle(
|
||||||
|
identity,
|
||||||
|
expected_revision=REVISION,
|
||||||
|
expected_identity_contract=contract,
|
||||||
|
source_root=source_root,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mutation", ["skills", "behavior_artifact_and_uv"])
|
||||||
|
def test_installed_validation_rederives_fully_rehashed_identity_contract(tmp_path: Path, mutation: str) -> None:
|
||||||
|
artifact, receipt = artifact_fixture(tmp_path)
|
||||||
|
identity, _contract = identity_fixture(tmp_path, artifact, receipt)
|
||||||
|
image_input = 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",
|
||||||
|
contract_source_root=ROOT,
|
||||||
|
identity_source_root=ROOT,
|
||||||
|
)
|
||||||
|
embedded = image_input["identity"]["runtime_contract"]
|
||||||
|
manifest_path = identity / "identity-manifest.json"
|
||||||
|
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||||
|
if mutation == "skills":
|
||||||
|
embedded["skills"] = {"content_sha256": "8" * 64, "file_count": 999}
|
||||||
|
manifest["identity_inputs"]["skills"] = copy.deepcopy(embedded["skills"])
|
||||||
|
else:
|
||||||
|
embedded["behavior_inputs"]["artifact_sha256"] = "8" * 64
|
||||||
|
embedded["behavior_inputs"]["uv_lock_sha256"] = "9" * 64
|
||||||
|
image_input["runtime"]["artifact_sha256"] = "8" * 64
|
||||||
|
image_input["runtime"]["uv_lock_sha256"] = "9" * 64
|
||||||
|
fully_rehash_identity_contract(embedded)
|
||||||
|
manifest["identity_inputs"]["runtime"]["identity_runtime_sha256"] = embedded["runtime"][
|
||||||
|
"identity_runtime_sha256"
|
||||||
|
]
|
||||||
|
write_json(manifest_path, manifest)
|
||||||
|
fully_rehash_identity_bundle(identity)
|
||||||
|
refresh_image_identity_binding(image_input, identity)
|
||||||
|
|
||||||
|
package.validate_image_input(image_input)
|
||||||
|
with pytest.raises(package.PackageError):
|
||||||
|
package.validate_installed_runtime_identity(
|
||||||
|
image_input,
|
||||||
|
artifact=artifact,
|
||||||
|
receipt=receipt,
|
||||||
|
identity=identity,
|
||||||
|
contract_source_root=ROOT,
|
||||||
|
identity_source_root=ROOT,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_release_requires_exact_digest_repository_and_strict_schema(tmp_path: Path) -> None:
|
def test_release_requires_exact_digest_repository_and_strict_schema(tmp_path: Path) -> None:
|
||||||
|
|
@ -261,13 +597,14 @@ def test_rendered_unit_is_isolated_digest_pinned_and_has_no_credential_or_transp
|
||||||
|
|
||||||
def test_prepare_context_is_secret_free_normalized_and_idempotently_refuses_overwrite(tmp_path: Path) -> None:
|
def test_prepare_context_is_secret_free_normalized_and_idempotently_refuses_overwrite(tmp_path: Path) -> None:
|
||||||
artifact, receipt = artifact_fixture(tmp_path)
|
artifact, receipt = artifact_fixture(tmp_path)
|
||||||
identity = identity_fixture(tmp_path)
|
identity, _contract = identity_fixture(tmp_path, artifact, receipt)
|
||||||
output = tmp_path / "context"
|
output = tmp_path / "context"
|
||||||
result = package.prepare_context(
|
result = package.prepare_context(
|
||||||
repo_root=ROOT,
|
repo_root=ROOT,
|
||||||
artifact=artifact,
|
artifact=artifact,
|
||||||
receipt=receipt,
|
receipt=receipt,
|
||||||
identity=identity,
|
identity=identity,
|
||||||
|
identity_source_root=ROOT,
|
||||||
output=output,
|
output=output,
|
||||||
source_revision=REVISION,
|
source_revision=REVISION,
|
||||||
)
|
)
|
||||||
|
|
@ -281,8 +618,10 @@ def test_prepare_context_is_secret_free_normalized_and_idempotently_refuses_over
|
||||||
"cloudsql-server-ca.pem",
|
"cloudsql-server-ca.pem",
|
||||||
"entrypoint.py",
|
"entrypoint.py",
|
||||||
"identity",
|
"identity",
|
||||||
|
"identity-sources",
|
||||||
"image-input.json",
|
"image-input.json",
|
||||||
"package_contract.py",
|
"package_contract.py",
|
||||||
|
"scripts",
|
||||||
]
|
]
|
||||||
assert all((output / "identity" / name).stat().st_mode & 0o777 == 0o600 for name in package.IDENTITY_FILES)
|
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()
|
encoded = (output / "image-input.json").read_text(encoding="utf-8").casefold()
|
||||||
|
|
@ -295,6 +634,7 @@ def test_prepare_context_is_secret_free_normalized_and_idempotently_refuses_over
|
||||||
artifact=artifact,
|
artifact=artifact,
|
||||||
receipt=receipt,
|
receipt=receipt,
|
||||||
identity=identity,
|
identity=identity,
|
||||||
|
identity_source_root=ROOT,
|
||||||
output=output,
|
output=output,
|
||||||
source_revision=REVISION,
|
source_revision=REVISION,
|
||||||
)
|
)
|
||||||
|
|
@ -306,6 +646,7 @@ def test_dockerfile_has_only_digest_pinned_bases_and_no_floating_package_install
|
||||||
|
|
||||||
assert len(from_lines) == 3
|
assert len(from_lines) == 3
|
||||||
assert all("@sha256:" in line for line in from_lines)
|
assert all("@sha256:" in line for line in from_lines)
|
||||||
|
assert "# syntax=" not in dockerfile
|
||||||
assert "postgres:16.14-bookworm@sha256:92620d" in dockerfile
|
assert "postgres:16.14-bookworm@sha256:92620d" in dockerfile
|
||||||
assert "python:3.11.9-slim-bookworm@sha256:8fb099" in dockerfile
|
assert "python:3.11.9-slim-bookworm@sha256:8fb099" in dockerfile
|
||||||
assert "uv:0.9.30@sha256:538e0b" in dockerfile
|
assert "uv:0.9.30@sha256:538e0b" in dockerfile
|
||||||
|
|
@ -316,6 +657,7 @@ def test_dockerfile_has_only_digest_pinned_bases_and_no_floating_package_install
|
||||||
assert "--no-editable" in dockerfile
|
assert "--no-editable" in dockerfile
|
||||||
assert "HEALTHCHECK" in dockerfile
|
assert "HEALTHCHECK" in dockerfile
|
||||||
assert "verify-build" in dockerfile
|
assert "verify-build" in dockerfile
|
||||||
|
assert "identity -type f -exec chmod 0444" in dockerfile
|
||||||
|
|
||||||
|
|
||||||
def test_entrypoint_prepares_exact_profile_drops_privileges_and_health_checks_pid_one() -> None:
|
def test_entrypoint_prepares_exact_profile_drops_privileges_and_health_checks_pid_one() -> None:
|
||||||
|
|
@ -345,6 +687,10 @@ def test_entrypoint_loads_adjacent_contract_with_python_safe_path(tmp_path: Path
|
||||||
entrypoint = image_root / "entrypoint.py"
|
entrypoint = image_root / "entrypoint.py"
|
||||||
shutil.copy2(ROOT / "docker" / "leoclean-nosend" / "entrypoint.py", entrypoint)
|
shutil.copy2(ROOT / "docker" / "leoclean-nosend" / "entrypoint.py", entrypoint)
|
||||||
shutil.copy2(ROOT / "ops" / "gcp_leoclean_nosend_package.py", image_root / "package_contract.py")
|
shutil.copy2(ROOT / "ops" / "gcp_leoclean_nosend_package.py", image_root / "package_contract.py")
|
||||||
|
scripts = image_root / "scripts"
|
||||||
|
scripts.mkdir()
|
||||||
|
for relative in package.IDENTITY_CONTRACT_SOURCES:
|
||||||
|
shutil.copy2(ROOT / relative, image_root / relative)
|
||||||
environment = {**os.environ, "PYTHONSAFEPATH": "1"}
|
environment = {**os.environ, "PYTHONSAFEPATH": "1"}
|
||||||
|
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue