From 64860be4aedf0536c09d86ca75329d887f14ff5d Mon Sep 17 00:00:00 2001 From: DCCONSTRUCTIONS Date: Sun, 23 Aug 2026 20:57:20 +0300 Subject: [PATCH] bind M4.7 graph results to Worker runtime --- scripts/Invoke-M47CanonicalGraphShadow.ps1 | 5 +- src/k1link/perception/reference_graph_cli.py | 10 +- .../perception/reference_graph_identity.py | 185 ++++++++++++++++++ .../perception/reference_graph_result.py | 30 +-- .../test_m47_worker_graph_shadow_artifact.py | 21 +- tests/test_reference_graph_result.py | 45 ++++- 6 files changed, 268 insertions(+), 28 deletions(-) create mode 100644 src/k1link/perception/reference_graph_identity.py diff --git a/scripts/Invoke-M47CanonicalGraphShadow.ps1 b/scripts/Invoke-M47CanonicalGraphShadow.ps1 index b07b3cf..95ad2c1 100644 --- a/scripts/Invoke-M47CanonicalGraphShadow.ps1 +++ b/scripts/Invoke-M47CanonicalGraphShadow.ps1 @@ -357,6 +357,7 @@ $graphArguments += @( "--triton-origin", $descriptor.container.triton_origin, "--mode", $descriptor.acceptance.run_mode, "--expected-frames", ([string]$descriptor.acceptance.expected_frames), + "--runtime-identity", "/run/mission-core/runtime-identity.json", "--output-root", "/output" ) @@ -416,7 +417,7 @@ try { throw "M4.7 graph candidate isolation contract changed" } $runtimeIdentity = [ordered]@{ - schema_version = "missioncore.reference-graph-runtime-identity/v2" + schema_version = "missioncore.reference-graph-runtime-identity/v3" worker_id = "worker-006" worker_node = $env:COMPUTERNAME worker_container_id = $candidate.Id @@ -428,8 +429,10 @@ try { historical_triton_container_id = $historicalTritonBefore.Id historical_triton_running = $historicalTritonBefore.Running artifact_sha256 = $ExpectedArtifactSha256 + patch_id = $descriptor.patch_id code_revision = $descriptor.code_revision graph_id = $descriptor.readiness.graph.graph_id + started_at_utc = [DateTime]::UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ") source_mount_read_only = $true isolated_model_service = $true public_worker_port_added = $false diff --git a/src/k1link/perception/reference_graph_cli.py b/src/k1link/perception/reference_graph_cli.py index 2a60057..6432864 100644 --- a/src/k1link/perception/reference_graph_cli.py +++ b/src/k1link/perception/reference_graph_cli.py @@ -8,6 +8,7 @@ import time from pathlib import Path from .graph_contracts import GraphRunMode, GraphRunResultV2 +from .reference_graph_identity import ReferenceGraphRuntimeIdentity from .reference_graph_parity import compare_reference_graph_to_accepted_ledgers from .reference_graph_result import seal_reference_graph_result from .reference_graph_runtime import ReferenceGraphRuntimePaths, build_reference_graph_runtime @@ -35,8 +36,12 @@ def main(argv: list[str] | None = None) -> int: default=GraphRunMode.LOSSLESS_REPLAY.value, ) parser.add_argument("--expected-frames", type=int, default=4489) + parser.add_argument("--runtime-identity", type=Path, required=True) parser.add_argument("--output-root", type=Path, required=True) args = parser.parse_args(argv) + runtime_identity = ReferenceGraphRuntimeIdentity.from_dict( + json.loads(args.runtime_identity.resolve(strict=True).read_text("utf-8")) + ) paths = ReferenceGraphRuntimePaths( graph_config=args.graph_config, baseline_profile=args.baseline_profile, @@ -65,17 +70,20 @@ def main(argv: list[str] | None = None) -> int: threat_frames_path=args.threat_parity_frames, expected_frames=args.expected_frames, ) + execution_elapsed_seconds = (time.perf_counter_ns() - started_ns) / 1_000_000_000 sealed = seal_reference_graph_result( graph_result, output_root=args.output_root, expected_frames=args.expected_frames, parity=parity, + runtime_identity=runtime_identity, + execution_elapsed_seconds=execution_elapsed_seconds, ) print( json.dumps( { "accepted": sealed.accepted, - "elapsed_seconds": (time.perf_counter_ns() - started_ns) / 1_000_000_000, + "elapsed_seconds": execution_elapsed_seconds, "result_id": sealed.result_id, "result_root": str(sealed.result_root), }, diff --git a/src/k1link/perception/reference_graph_identity.py b/src/k1link/perception/reference_graph_identity.py new file mode 100644 index 0000000..877087f --- /dev/null +++ b/src/k1link/perception/reference_graph_identity.py @@ -0,0 +1,185 @@ +"""Strict execution identity for the isolated M4.7 reference graph shadow.""" + +from __future__ import annotations + +import datetime as dt +import re +from dataclasses import dataclass +from typing import Final + +from .graph_contracts import REFERENCE_GRAPH_ID_V2 + +REFERENCE_GRAPH_RUNTIME_IDENTITY_SCHEMA: Final = "missioncore.reference-graph-runtime-identity/v3" + +_SHA256 = re.compile(r"^[a-f0-9]{64}$") +_GIT_REVISION = re.compile(r"^[a-f0-9]{40}$") +_DOCKER_IMAGE_ID = re.compile(r"^sha256:[a-f0-9]{64}$") +_IDENTIFIER = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:/-]{0,255}$") + + +class ReferenceGraphRuntimeIdentityError(ValueError): + """The Worker execution identity is incomplete or exceeds shadow authority.""" + + +@dataclass(frozen=True, slots=True) +class ReferenceGraphRuntimeIdentity: + worker_id: str + worker_node: str + worker_container_id: str + worker_image_id: str + isolated_triton_container_id: str + isolated_triton_image_id: str + historical_worker_container_id: str + historical_worker_running: bool + historical_triton_container_id: str + historical_triton_running: bool + artifact_sha256: str + patch_id: str + code_revision: str + graph_id: str + started_at_utc: str + source_mount_read_only: bool + isolated_model_service: bool + public_worker_port_added: bool + commands_enabled: bool + actuation_allowed: bool + + def __post_init__(self) -> None: + if self.worker_id != "worker-006": + raise ReferenceGraphRuntimeIdentityError("M4.7 shadow must run on Worker 006") + _identifier(self.worker_node, "worker node") + _identifier(self.patch_id, "patch id") + for value, label in ( + (self.worker_container_id, "worker container id"), + (self.isolated_triton_container_id, "isolated Triton container id"), + (self.historical_worker_container_id, "historical worker container id"), + (self.historical_triton_container_id, "historical Triton container id"), + ): + if _SHA256.fullmatch(value) is None: + raise ReferenceGraphRuntimeIdentityError(f"{label} must be a full digest") + for value, label in ( + (self.worker_image_id, "worker image id"), + (self.isolated_triton_image_id, "isolated Triton image id"), + ): + if _DOCKER_IMAGE_ID.fullmatch(value) is None: + raise ReferenceGraphRuntimeIdentityError(f"{label} must be a full image digest") + if _SHA256.fullmatch(self.artifact_sha256) is None: + raise ReferenceGraphRuntimeIdentityError("artifact must be digest-bound") + if _GIT_REVISION.fullmatch(self.code_revision) is None: + raise ReferenceGraphRuntimeIdentityError("code revision must be a full Git SHA") + if self.graph_id != REFERENCE_GRAPH_ID_V2: + raise ReferenceGraphRuntimeIdentityError("runtime graph id changed") + try: + started = dt.datetime.fromisoformat(self.started_at_utc.replace("Z", "+00:00")) + except ValueError as error: + raise ReferenceGraphRuntimeIdentityError( + "runtime start must be an ISO-8601 UTC timestamp" + ) from error + if started.tzinfo != dt.UTC: + raise ReferenceGraphRuntimeIdentityError("runtime start must use UTC") + if not self.source_mount_read_only: + raise ReferenceGraphRuntimeIdentityError("source mounts must be read-only") + if not self.isolated_model_service: + raise ReferenceGraphRuntimeIdentityError("model service must be isolated") + if self.public_worker_port_added: + raise ReferenceGraphRuntimeIdentityError("shadow must not add a public port") + if self.commands_enabled or self.actuation_allowed: + raise ReferenceGraphRuntimeIdentityError("shadow authority must remain disabled") + + def to_dict(self) -> dict[str, object]: + return { + "schema_version": REFERENCE_GRAPH_RUNTIME_IDENTITY_SCHEMA, + "worker_id": self.worker_id, + "worker_node": self.worker_node, + "worker_container_id": self.worker_container_id, + "worker_image_id": self.worker_image_id, + "isolated_triton_container_id": self.isolated_triton_container_id, + "isolated_triton_image_id": self.isolated_triton_image_id, + "historical_worker_container_id": self.historical_worker_container_id, + "historical_worker_running": self.historical_worker_running, + "historical_triton_container_id": self.historical_triton_container_id, + "historical_triton_running": self.historical_triton_running, + "artifact_sha256": self.artifact_sha256, + "patch_id": self.patch_id, + "code_revision": self.code_revision, + "graph_id": self.graph_id, + "started_at_utc": self.started_at_utc, + "source_mount_read_only": self.source_mount_read_only, + "isolated_model_service": self.isolated_model_service, + "public_worker_port_added": self.public_worker_port_added, + "commands_enabled": self.commands_enabled, + "actuation_allowed": self.actuation_allowed, + } + + @classmethod + def from_dict(cls, value: object) -> ReferenceGraphRuntimeIdentity: + if not isinstance(value, dict) or any(not isinstance(key, str) for key in value): + raise ReferenceGraphRuntimeIdentityError("runtime identity must be an object") + fields = { + "worker_id", + "worker_node", + "worker_container_id", + "worker_image_id", + "isolated_triton_container_id", + "isolated_triton_image_id", + "historical_worker_container_id", + "historical_worker_running", + "historical_triton_container_id", + "historical_triton_running", + "artifact_sha256", + "patch_id", + "code_revision", + "graph_id", + "started_at_utc", + "source_mount_read_only", + "isolated_model_service", + "public_worker_port_added", + "commands_enabled", + "actuation_allowed", + } + if set(value) != fields | {"schema_version"}: + raise ReferenceGraphRuntimeIdentityError("runtime identity fields changed") + if value.get("schema_version") != REFERENCE_GRAPH_RUNTIME_IDENTITY_SCHEMA: + raise ReferenceGraphRuntimeIdentityError("runtime identity schema changed") + strings = { + field: _string(value.get(field), field) + for field in fields + if field + not in { + "historical_worker_running", + "historical_triton_running", + "source_mount_read_only", + "isolated_model_service", + "public_worker_port_added", + "commands_enabled", + "actuation_allowed", + } + } + booleans = { + field: _boolean(value.get(field), field) for field in fields if field not in strings + } + return cls(**strings, **booleans) + + +def _string(value: object, label: str) -> str: + if not isinstance(value, str) or not value: + raise ReferenceGraphRuntimeIdentityError(f"{label} must be a non-empty string") + return value + + +def _boolean(value: object, label: str) -> bool: + if not isinstance(value, bool): + raise ReferenceGraphRuntimeIdentityError(f"{label} must be boolean") + return value + + +def _identifier(value: str, label: str) -> None: + if _IDENTIFIER.fullmatch(value) is None: + raise ReferenceGraphRuntimeIdentityError(f"{label} is invalid") + + +__all__ = [ + "REFERENCE_GRAPH_RUNTIME_IDENTITY_SCHEMA", + "ReferenceGraphRuntimeIdentity", + "ReferenceGraphRuntimeIdentityError", +] diff --git a/src/k1link/perception/reference_graph_result.py b/src/k1link/perception/reference_graph_result.py index c2b096b..8e793c9 100644 --- a/src/k1link/perception/reference_graph_result.py +++ b/src/k1link/perception/reference_graph_result.py @@ -4,6 +4,7 @@ from __future__ import annotations import hashlib import json +import math import os import shutil import uuid @@ -13,6 +14,7 @@ from pathlib import Path from typing import Final from .graph_contracts import GraphRunMode, GraphRunResultV2, GraphState, TerminalOutcomeType +from .reference_graph_identity import ReferenceGraphRuntimeIdentity from .reference_graph_parity import ReferenceGraphParityReport REFERENCE_GRAPH_RESULT_PREFIX: Final = "m47-reference-graph-" @@ -39,9 +41,15 @@ def seal_reference_graph_result( output_root: Path, expected_frames: int, parity: ReferenceGraphParityReport, + runtime_identity: ReferenceGraphRuntimeIdentity, + execution_elapsed_seconds: float, ) -> SealedReferenceGraphResult: if expected_frames < 1: raise ReferenceGraphResultError("expected frame count must be positive") + if not math.isfinite(execution_elapsed_seconds) or execution_elapsed_seconds < 0.0: + raise ReferenceGraphResultError("execution elapsed seconds must be finite and non-negative") + if runtime_identity.graph_id != result.graph_id: + raise ReferenceGraphResultError("runtime and graph result identities differ") outcomes = Counter(item.outcome.value for item in result.terminal_outcomes) gates = { "lossless_replay_mode": result.run_mode is GraphRunMode.LOSSLESS_REPLAY, @@ -58,6 +66,7 @@ def seal_reference_graph_result( "accepted_m45r_m46_parity": parity.accepted, } accepted = all(gates.values()) + runtime = runtime_identity.to_dict() report: dict[str, object] = { "schema_version": REFERENCE_GRAPH_REPORT_SCHEMA, "graph_id": result.graph_id, @@ -73,6 +82,10 @@ def seal_reference_graph_result( "accepted_parity": parity.to_dict(), "gates": gates, "accepted": accepted, + "execution": { + "elapsed_seconds": execution_elapsed_seconds, + "runtime_identity": runtime, + }, "authority": { "physical_live": False, "commands_enabled": False, @@ -90,6 +103,7 @@ def seal_reference_graph_result( frames_path = staging / "frames.jsonl" outcomes_path = staging / "outcomes.jsonl" report_path = staging / "report.json" + runtime_path = staging / "runtime.json" _write_json_lines( frames_path, tuple(delivery.canonical_dict() for delivery in result.deliveries), @@ -99,18 +113,20 @@ def seal_reference_graph_result( tuple(outcome.to_dict() for outcome in result.terminal_outcomes), ) _write_json(report_path, report) + _write_json(runtime_path, runtime) file_rows = { name: { "sha256": _sha256_file(staging / name), "bytes": (staging / name).stat().st_size, } - for name in ("frames.jsonl", "outcomes.jsonl", "report.json") + for name in ("frames.jsonl", "outcomes.jsonl", "report.json", "runtime.json") } identity: dict[str, object] = { "graph_id": result.graph_id, "source_profile_id": result.source_profile_id, "run_mode": result.run_mode.value, "canonical_payload_sha256": result.canonical_payload_sha256, + "runtime": runtime, "files": file_rows, } identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest() @@ -156,16 +172,8 @@ def _publish_immutable(staging: Path, target: Path) -> None: if target.exists(): if target.is_symlink() or not target.is_dir(): raise ReferenceGraphResultError("immutable result target is not a real directory") - expected = { - path.name: _sha256_file(path) - for path in staging.iterdir() - if path.is_file() - } - observed = { - path.name: _sha256_file(path) - for path in target.iterdir() - if path.is_file() - } + expected = {path.name: _sha256_file(path) for path in staging.iterdir() if path.is_file()} + observed = {path.name: _sha256_file(path) for path in target.iterdir() if path.is_file()} if expected != observed: raise ReferenceGraphResultError("immutable result identity collision") shutil.rmtree(staging) diff --git a/tests/test_m47_worker_graph_shadow_artifact.py b/tests/test_m47_worker_graph_shadow_artifact.py index 754f7de..7dd972e 100644 --- a/tests/test_m47_worker_graph_shadow_artifact.py +++ b/tests/test_m47_worker_graph_shadow_artifact.py @@ -43,18 +43,16 @@ def test_m47_worker_artifact_is_deterministic_and_self_contained(tmp_path: Path) with tarfile.open(first["artifact"], "r:gz") as archive: regular = _regular_files(archive) payload_names = sorted( - name.removeprefix("payload/") - for name in regular - if name.startswith("payload/") + name.removeprefix("payload/") for name in regular if name.startswith("payload/") ) assert payload_names == first["payload_files"] assert regular["files.txt"].decode().splitlines() == first["payload_files"] assert _sha256(regular[f"payload/{BUILDER.WHEEL_NAME}"]) == first["wheel_sha256"] assert regular[f"payload/{BUILDER.RUNNER.name}"] == BUILDER.RUNNER.read_bytes() for relative in BUILDER.CONFIG_PATHS: - assert regular[f"payload/{relative.name}"] == ( - BUILDER.REPOSITORY_ROOT / relative - ).read_bytes() + assert ( + regular[f"payload/{relative.name}"] == (BUILDER.REPOSITORY_ROOT / relative).read_bytes() + ) def test_m47_descriptor_preserves_nonparticipants_and_separates_readiness() -> None: @@ -70,9 +68,7 @@ def test_m47_descriptor_preserves_nonparticipants_and_separates_readiness() -> N assert descriptor["transition"] == "m47-canonical-graph-isolated-shadow-v1" assert descriptor["boundary"]["external_deploy_registry"] is False assert descriptor["container"]["public_ports"] is False - assert descriptor["container"]["triton_name"] == ( - "ndc-mission-core-m47-triton-shadow" - ) + assert descriptor["container"]["triton_name"] == ("ndc-mission-core-m47-triton-shadow") assert descriptor["container"]["model_repository_host_path"] == ( "D:\\NDC_MISSIONCORE\\runtime\\models" ) @@ -111,9 +107,7 @@ def test_m47_descriptor_preserves_nonparticipants_and_separates_readiness() -> N "terminal_accounting_required": True, "actuation_allowed": False, } - assert set(descriptor["release"]["configs"]) == { - path.name for path in BUILDER.CONFIG_PATHS - } + assert set(descriptor["release"]["configs"]) == {path.name for path in BUILDER.CONFIG_PATHS} serialized = json.dumps(descriptor).encode() assert b"PRIVATE KEY" not in serialized assert b"password=" not in serialized.lower() @@ -127,6 +121,9 @@ def test_m47_runner_calls_only_the_canonical_graph_entrypoint() -> None: assert '"--mode", $descriptor.acceptance.run_mode' in runner assert '"--temporal-parity-frames"' in runner assert '"--threat-parity-frames"' in runner + assert '"--runtime-identity", "/run/mission-core/runtime-identity.json"' in runner + assert 'schema_version = "missioncore.reference-graph-runtime-identity/v3"' in runner + assert "patch_id = $descriptor.patch_id" in runner assert 'Write-Output "PROVIDER_READINESS=accepted"' in runner assert 'Write-Output "GRAPH_READINESS=accepted"' in runner assert 'Write-Output "DURABLE_WORKER_ACTION=none"' in runner diff --git a/tests/test_reference_graph_result.py b/tests/test_reference_graph_result.py index d62c4fc..36bcb66 100644 --- a/tests/test_reference_graph_result.py +++ b/tests/test_reference_graph_result.py @@ -14,12 +14,41 @@ from k1link.perception.graph_contracts import ( build_graph_run_result_v2, ) from k1link.perception.providers import ReferencePerceptionGraphConfigV2 +from k1link.perception.reference_graph_identity import ReferenceGraphRuntimeIdentity from k1link.perception.reference_graph_parity import ReferenceGraphParityReport from k1link.perception.reference_graph_result import seal_reference_graph_result REPOSITORY_ROOT = Path(__file__).resolve().parents[1] +def _runtime() -> ReferenceGraphRuntimeIdentity: + return ReferenceGraphRuntimeIdentity.from_dict( + { + "schema_version": "missioncore.reference-graph-runtime-identity/v3", + "worker_id": "worker-006", + "worker_node": "DESKTOP-OPJ8J04", + "worker_container_id": "c" * 64, + "worker_image_id": "sha256:" + "d" * 64, + "isolated_triton_container_id": "e" * 64, + "isolated_triton_image_id": "sha256:" + "d" * 64, + "historical_worker_container_id": "f" * 64, + "historical_worker_running": False, + "historical_triton_container_id": "a" * 64, + "historical_triton_running": False, + "artifact_sha256": "b" * 64, + "patch_id": "mission-core-m47-test", + "code_revision": "1" * 40, + "graph_id": "reference-perception-graph/v2", + "started_at_utc": "2026-08-23T12:00:00.000Z", + "source_mount_read_only": True, + "isolated_model_service": True, + "public_worker_port_added": False, + "commands_enabled": False, + "actuation_allowed": False, + } + ) + + def _parity(accepted: bool = True) -> ReferenceGraphParityReport: return ReferenceGraphParityReport( expected_frames=1, @@ -110,12 +139,16 @@ def test_reference_graph_result_is_content_addressed_and_reproducible(tmp_path: output_root=tmp_path / "one", expected_frames=1, parity=_parity(), + runtime_identity=_runtime(), + execution_elapsed_seconds=1.25, ) second = seal_reference_graph_result( _result(), output_root=tmp_path / "two", expected_frames=1, parity=_parity(), + runtime_identity=_runtime(), + execution_elapsed_seconds=1.25, ) assert first.accepted is True @@ -134,9 +167,13 @@ def test_reference_graph_result_is_content_addressed_and_reproducible(tmp_path: "no_unavailable_frames": True, "accepted_m45r_m46_parity": True, } - assert { - path.name: path.read_bytes() for path in first.result_root.iterdir() - } == {path.name: path.read_bytes() for path in second.result_root.iterdir()} + assert {path.name: path.read_bytes() for path in first.result_root.iterdir()} == { + path.name: path.read_bytes() for path in second.result_root.iterdir() + } + runtime = json.loads((first.result_root / "runtime.json").read_text("utf-8")) + assert runtime == _runtime().to_dict() + assert first.manifest["runtime"] == runtime + assert first.manifest["files"]["runtime.json"]["sha256"] def test_reference_graph_result_fails_closed_on_supersession(tmp_path: Path) -> None: @@ -145,6 +182,8 @@ def test_reference_graph_result_fails_closed_on_supersession(tmp_path: Path) -> output_root=tmp_path, expected_frames=1, parity=_parity(), + runtime_identity=_runtime(), + execution_elapsed_seconds=1.25, ) assert sealed.accepted is False