diff --git a/experiments/perception/worker/observatory_portable/migrate_claim_transport_v3.py b/experiments/perception/worker/observatory_portable/migrate_claim_transport_v3.py index a6066fd..9c1deaf 100644 --- a/experiments/perception/worker/observatory_portable/migrate_claim_transport_v3.py +++ b/experiments/perception/worker/observatory_portable/migrate_claim_transport_v3.py @@ -29,8 +29,8 @@ TARGETS = { "ndc-observatory-installed-lab-worker-agent": INSTALLED_PARENT_SHA, "ndc-observatory-m49-worker-agent": M49_PARENT_SHA, } -SOURCE = "/opt/nodedc/mission-core/src/k1link/observatory/worker_http_transport.py" -BEFORE_SHA = "fb67ae174c8da66f7d04022310be663b2735fff72966883aaa5b50fdbc3e89c7" +SOURCE = "/opt/nodedc/installed-lab/src/k1link/observatory/worker_http_transport.py" +BEFORE_SHA = "c3d82ffd482a29b17c68d1fb5603e82924766abc0080137d3a0a6130f92ccdce" OLD = b"missioncore.observatory-worker-claim-request/v2" NEW = b"missioncore.observatory-worker-claim-request/v3" PARENT_LABEL = "com.nodedc.claim-transport-parent.sha256" @@ -115,6 +115,12 @@ with httpx.Client(base_url=os.environ["MISSIONCORE_OBSERVATORY_WORKER_BASE_URL"] print(json.dumps(response.json())) """ +IMPORTED_SOURCE = """import hashlib, json, pathlib +from k1link.observatory import worker_http_transport as transport +path = pathlib.Path(transport.__file__).resolve() +print(json.dumps({"path": str(path), "sha256": hashlib.sha256(path.read_bytes()).hexdigest()})) +""" + PROBE = """import json, os, pathlib, uuid from k1link.observatory.worker_http_transport import ObservatoryWorkerHttpGateway with ObservatoryWorkerHttpGateway( @@ -169,6 +175,9 @@ def plan(engine: Engine) -> dict: for name, parent in TARGETS.items(): inspection = engine.inspect(name) validate_target(name, inspection) + imported = engine.execute_json(name, IMPORTED_SOURCE) + if imported != {"path": SOURCE, "sha256": BEFORE_SHA}: + raise ValueError("loaded transport differs from the reviewed immutable source") readiness = engine.execute_json(name, READINESS) require_idle(readiness) targets.append( @@ -176,6 +185,7 @@ def plan(engine: Engine) -> dict: "name": name, "container_id": inspection["Id"], "parent_image_sha256": parent, + "imported_source": imported, "create_body_sha256": sha( canonical( { @@ -348,6 +358,10 @@ def apply(engine: Engine, expected_plan_sha: str, evidence: Path) -> dict: after = engine.inspect(name) if not after["State"]["Running"] or after["RestartCount"] != 0: raise RuntimeError("replacement agent needs reconciliation; predecessor retained") + if engine.execute_json(name, IMPORTED_SOURCE) != { + "path": SOURCE, "sha256": image["source_sha256"], + }: + raise RuntimeError("replacement agent did not load the pinned transport module") before_probe = engine.execute_json(name, READINESS) require_idle(before_probe) probe = engine.execute_json(name, PROBE) diff --git a/tests/test_observatory_claim_transport_migration.py b/tests/test_observatory_claim_transport_migration.py index ce18a3e..3a6fbe4 100644 --- a/tests/test_observatory_claim_transport_migration.py +++ b/tests/test_observatory_claim_transport_migration.py @@ -81,6 +81,8 @@ def test_evidence_is_immutable_and_control_probe_cannot_claim_models(tmp_path: P assert "range(8)" in migration.PROBE compile(migration.PROBE, "control-probe", "exec") compile(migration.READINESS, "readiness", "exec") + compile(migration.IMPORTED_SOURCE, "imported-source", "exec") + assert "transport.__file__" in migration.IMPORTED_SOURCE def test_transport_clone_retains_executor_configuration_and_rollback_snapshot() -> None: