refactor(lab): separate release contract identity
This commit is contained in:
+7
-7
@@ -142,17 +142,17 @@
|
||||
},
|
||||
{
|
||||
"asset_id": "lab-v1-portable-contracts",
|
||||
"byte_length": 114905,
|
||||
"byte_length": 115196,
|
||||
"kind": "repository-file",
|
||||
"repository_path": "src/k1link/observatory/portable_lab_v1_executor.py",
|
||||
"sha256": "c8ba210336138617a1cdb02ed2795935b25a265ae7b905f75cd0338774338c55"
|
||||
"sha256": "2a18fe862f0934c78e86156708d8b80a3c31644a68829c8e673a0f48538f3473"
|
||||
},
|
||||
{
|
||||
"asset_id": "lab-v1-portable-worker",
|
||||
"byte_length": 46859,
|
||||
"byte_length": 47008,
|
||||
"kind": "repository-file",
|
||||
"repository_path": "src/k1link/observatory/portable_lab_v1_worker.py",
|
||||
"sha256": "ad00418d59b793328a281fa432e450919690fe4957f024f296e2d125c1b3c03a"
|
||||
"sha256": "0a954b5d4d2f3cdd588d58ba4234141c32992b4b46380a03b621060c25c4ac23"
|
||||
},
|
||||
{
|
||||
"asset_id": "portable-result-contracts",
|
||||
@@ -189,7 +189,7 @@
|
||||
"navigation_or_safety_accepted": false,
|
||||
"production_accepted": false
|
||||
},
|
||||
"candidate_sha256": "6e7adbf25acbc3a9daedf8671ee57a7c00043ea73897bc9abf1e9947eaa5d0bd",
|
||||
"candidate_sha256": "546c74fb88b9571996cecc41fa915044e3907365b8891b7fceb297fb74a837c7",
|
||||
"declared_blockers": [
|
||||
"combined-executor-entrypoint-uninstalled",
|
||||
"combined-executor-image-unsealed",
|
||||
@@ -200,7 +200,7 @@
|
||||
"worker-installation-receipt-unavailable"
|
||||
],
|
||||
"definition_id": "lab-v1-eomt-ddrnet-portable",
|
||||
"definition_sha256": "3692d41cec3949f348a36eb60a501fb2cd483fed1645679b0ec58061a2fc6dc2",
|
||||
"definition_contract_sha256": "504e197db72e8bad258a2e4ec117d6d8247297847bd651470f8d43601e2e1fc7",
|
||||
"definition_version": 2,
|
||||
"executor_image_sha256": null,
|
||||
"phases": [
|
||||
@@ -213,6 +213,6 @@
|
||||
],
|
||||
"release_id": "lab-v1-eomt-ddrnet-worker006-candidate-v2",
|
||||
"result_contract_sha256": "b3dfaa8e20a0f22fc510d062ac469f010a3281c650059d9ea134f0b3ccb38d9a",
|
||||
"schema_version": "missioncore.observatory-portable-lab-v1-executor-candidate/v1",
|
||||
"schema_version": "missioncore.observatory-portable-lab-v1-executor-candidate/v2",
|
||||
"setup_id": "lab-v1-eomt-ddrnet-portable-v1"
|
||||
}
|
||||
|
||||
@@ -65,13 +65,13 @@ PORTABLE_LAB_V1_RESULT_IDENTITY_SCHEMA: Final = (
|
||||
"missioncore.recorded-eomt-ddrnet-review-identity/v2"
|
||||
)
|
||||
PORTABLE_LAB_V1_RELEASE_SCHEMA: Final = (
|
||||
"missioncore.observatory-portable-lab-v1-executor-candidate/v1"
|
||||
"missioncore.observatory-portable-lab-v1-executor-candidate/v2"
|
||||
)
|
||||
PORTABLE_LAB_V1_RELEASE_IDENTITY_SCHEMA: Final = (
|
||||
"missioncore.observatory-portable-lab-v1-executor-candidate-identity/v1"
|
||||
"missioncore.observatory-portable-lab-v1-executor-candidate-identity/v2"
|
||||
)
|
||||
PORTABLE_LAB_V1_EXECUTOR_SEAL_SCHEMA: Final = (
|
||||
"missioncore.observatory-portable-lab-v1-executor-seal/v1"
|
||||
"missioncore.observatory-portable-lab-v1-executor-seal/v2"
|
||||
)
|
||||
PORTABLE_LAB_V1_DDRNET_PROFILE_SCHEMA: Final = (
|
||||
"missioncore.lab-v1-eomt-ddrnet-portable-profile/v2"
|
||||
@@ -452,7 +452,7 @@ class PortableLabV1ReleaseCandidate:
|
||||
setup_id: str
|
||||
definition_id: str
|
||||
definition_version: int
|
||||
definition_sha256: str
|
||||
definition_contract_sha256: str
|
||||
result_contract_sha256: str
|
||||
executor_image_sha256: str | None
|
||||
assets: tuple[PortableLabV1ReleaseAsset, ...]
|
||||
@@ -474,7 +474,10 @@ class PortableLabV1ReleaseCandidate:
|
||||
or self.definition_version < 1
|
||||
):
|
||||
raise PortableLabV1ReleaseError("release definition version is invalid")
|
||||
_digest(self.definition_sha256, "release definition sha256")
|
||||
_digest(
|
||||
self.definition_contract_sha256,
|
||||
"release definition contract sha256",
|
||||
)
|
||||
_digest(self.result_contract_sha256, "release result contract sha256")
|
||||
if self.executor_image_sha256 is not None:
|
||||
_digest(self.executor_image_sha256, "release executor image sha256")
|
||||
@@ -523,7 +526,7 @@ class PortableLabV1ReleaseCandidate:
|
||||
"setup_id",
|
||||
"definition_id",
|
||||
"definition_version",
|
||||
"definition_sha256",
|
||||
"definition_contract_sha256",
|
||||
"result_contract_sha256",
|
||||
"executor_image_sha256",
|
||||
"assets",
|
||||
@@ -563,8 +566,9 @@ class PortableLabV1ReleaseCandidate:
|
||||
definition_version=_positive_int(
|
||||
document["definition_version"], "release definition version"
|
||||
),
|
||||
definition_sha256=_string(
|
||||
document["definition_sha256"], "release definition sha256"
|
||||
definition_contract_sha256=_string(
|
||||
document["definition_contract_sha256"],
|
||||
"release definition contract sha256",
|
||||
),
|
||||
result_contract_sha256=_string(
|
||||
document["result_contract_sha256"],
|
||||
@@ -587,7 +591,7 @@ class PortableLabV1ReleaseCandidate:
|
||||
"setup_id": self.setup_id,
|
||||
"definition_id": self.definition_id,
|
||||
"definition_version": self.definition_version,
|
||||
"definition_sha256": self.definition_sha256,
|
||||
"definition_contract_sha256": self.definition_contract_sha256,
|
||||
"result_contract_sha256": self.result_contract_sha256,
|
||||
"executor_image_sha256": self.executor_image_sha256,
|
||||
"assets": [asset.as_dict() for asset in self.assets],
|
||||
@@ -601,7 +605,8 @@ class PortableLabV1ReleaseCandidate:
|
||||
definition.setup_id != self.setup_id
|
||||
or definition.definition_id != self.definition_id
|
||||
or definition.version != self.definition_version
|
||||
or definition.definition_sha256 != self.definition_sha256
|
||||
or definition.executable_contract_sha256
|
||||
!= self.definition_contract_sha256
|
||||
or definition.result_contract.contract_sha256
|
||||
!= self.result_contract_sha256
|
||||
):
|
||||
@@ -661,7 +666,7 @@ class PortableLabV1ReleaseCandidate:
|
||||
"schema_version": PORTABLE_LAB_V1_EXECUTOR_SEAL_SCHEMA,
|
||||
"release_id": self.release_id,
|
||||
"candidate_sha256": self.candidate_sha256,
|
||||
"definition_sha256": self.definition_sha256,
|
||||
"definition_contract_sha256": self.definition_contract_sha256,
|
||||
"executor_image_sha256": self.executor_image_sha256,
|
||||
"asset_sha256s": [asset.sha256 for asset in self.assets],
|
||||
"authority": dict(OBSERVATION_ONLY_AUTHORITY),
|
||||
@@ -669,7 +674,7 @@ class PortableLabV1ReleaseCandidate:
|
||||
return PortableLabV1ExecutorSeal(
|
||||
release_id=self.release_id,
|
||||
candidate_sha256=self.candidate_sha256,
|
||||
definition_sha256=self.definition_sha256,
|
||||
definition_contract_sha256=self.definition_contract_sha256,
|
||||
executor_image_sha256=self.executor_image_sha256,
|
||||
release_sha256=canonical_sha256(identity),
|
||||
)
|
||||
@@ -681,7 +686,7 @@ class PortableLabV1ExecutorSeal:
|
||||
|
||||
release_id: str
|
||||
candidate_sha256: str
|
||||
definition_sha256: str
|
||||
definition_contract_sha256: str
|
||||
executor_image_sha256: str
|
||||
release_sha256: str
|
||||
|
||||
@@ -689,7 +694,10 @@ class PortableLabV1ExecutorSeal:
|
||||
_pattern(self.release_id, _IDENTIFIER, "executor seal release id")
|
||||
for value, label in (
|
||||
(self.candidate_sha256, "executor seal candidate sha256"),
|
||||
(self.definition_sha256, "executor seal definition sha256"),
|
||||
(
|
||||
self.definition_contract_sha256,
|
||||
"executor seal definition contract sha256",
|
||||
),
|
||||
(self.executor_image_sha256, "executor seal image sha256"),
|
||||
(self.release_sha256, "executor seal release sha256"),
|
||||
):
|
||||
@@ -700,7 +708,7 @@ class PortableLabV1ExecutorSeal:
|
||||
"schema_version": PORTABLE_LAB_V1_EXECUTOR_SEAL_SCHEMA,
|
||||
"release_id": self.release_id,
|
||||
"candidate_sha256": self.candidate_sha256,
|
||||
"definition_sha256": self.definition_sha256,
|
||||
"definition_contract_sha256": self.definition_contract_sha256,
|
||||
"executor_image_sha256": self.executor_image_sha256,
|
||||
"release_sha256": self.release_sha256,
|
||||
"authority": dict(OBSERVATION_ONLY_AUTHORITY),
|
||||
|
||||
@@ -505,7 +505,7 @@ def compose_lab_v1_portable_executor_adapter(
|
||||
) -> PortableWorkerExecutorAdapter:
|
||||
"""Compose the shared Worker ports without enabling or registering them."""
|
||||
|
||||
_verify_candidate_release(candidate, installation)
|
||||
_verify_candidate_release(candidate, installation, definition)
|
||||
return PortableWorkerExecutorAdapter(
|
||||
candidate=candidate,
|
||||
definition=definition,
|
||||
@@ -560,6 +560,7 @@ def _verify_runtime_plan(
|
||||
def _verify_candidate_release(
|
||||
candidate: PortableWorkerRuntimeCandidate,
|
||||
installation: PortableLabV1RunnerInstallation,
|
||||
definition: PortableRunDefinition,
|
||||
) -> None:
|
||||
release = installation.release
|
||||
release_assets = {asset.asset_id: asset for asset in release.assets}
|
||||
@@ -569,7 +570,9 @@ def _verify_candidate_release(
|
||||
candidate.setup_id != release.setup_id
|
||||
or candidate.definition_id != release.definition_id
|
||||
or candidate.definition_version != release.definition_version
|
||||
or candidate.definition_sha256 != release.definition_sha256
|
||||
or candidate.definition_sha256 != definition.definition_sha256
|
||||
or release.definition_contract_sha256
|
||||
!= definition.executable_contract_sha256
|
||||
or candidate.result_contract_sha256 != release.result_contract_sha256
|
||||
or tuple(phase.phase_id for phase in candidate.phases) != PORTABLE_LAB_V1_RUNTIME_PHASES
|
||||
or executor is None
|
||||
|
||||
@@ -32,6 +32,9 @@ PORTABLE_RUN_DEFINITION_REGISTRY_SCHEMA: Final = (
|
||||
PORTABLE_RUN_DEFINITION_IDENTITY_SCHEMA: Final = (
|
||||
"missioncore.observatory-portable-run-definition-identity/v2"
|
||||
)
|
||||
PORTABLE_RUN_DEFINITION_EXECUTABLE_CONTRACT_SCHEMA: Final = (
|
||||
"missioncore.observatory-portable-run-definition-executable-contract/v1"
|
||||
)
|
||||
PORTABLE_SOURCE_ADAPTER_IDENTITY_SCHEMA: Final = "missioncore.portable-source-adapter/v1"
|
||||
PORTABLE_MODEL_MANIFEST_SCHEMA: Final = "missioncore.observatory-portable-model-manifest/v2"
|
||||
PORTABLE_RESOURCE_PROFILE_SCHEMA: Final = "missioncore.observatory-portable-resource-profile/v2"
|
||||
@@ -571,6 +574,35 @@ class PortableRunDefinition:
|
||||
def learned_models(self) -> tuple[str, ...]:
|
||||
return tuple(model.release_id for model in self.models)
|
||||
|
||||
@property
|
||||
def executable_contract_sha256(self) -> str:
|
||||
"""Return the stable executable contract, independent of installation state.
|
||||
|
||||
Executor availability is deliberately excluded. An executor release may
|
||||
bind this digest before the resulting release identity is written back to
|
||||
the full RunDefinition without creating a definition/release digest cycle.
|
||||
"""
|
||||
|
||||
return canonical_sha256(self.executable_contract_document())
|
||||
|
||||
def executable_contract_document(self) -> dict[str, object]:
|
||||
"""Return the canonical computation contract without executor availability."""
|
||||
|
||||
return {
|
||||
"schema_version": PORTABLE_RUN_DEFINITION_EXECUTABLE_CONTRACT_SCHEMA,
|
||||
"setup_id": self.setup_id,
|
||||
"definition_id": self.definition_id,
|
||||
"version": self.version,
|
||||
"source_requirements": self.source_requirements.as_dict(),
|
||||
"source_adapter": self.source_adapter.as_dict(),
|
||||
"components": [component.as_dict() for component in self.components],
|
||||
"models": [model.as_dict() for model in self.models],
|
||||
"model_manifest_sha256": self.model_manifest_sha256,
|
||||
"resource_profile": self.resource_profile.as_dict(),
|
||||
"result_contract": self.result_contract.as_dict(),
|
||||
"authority": self.authority.as_dict(),
|
||||
}
|
||||
|
||||
def identity_document(self) -> dict[str, object]:
|
||||
"""Return the complete canonical executable identity."""
|
||||
|
||||
|
||||
@@ -840,14 +840,16 @@ def _release_for_definition(
|
||||
)
|
||||
identity = release.identity_document()
|
||||
identity["definition_version"] = definition.version
|
||||
identity["definition_sha256"] = definition.definition_sha256
|
||||
identity["definition_contract_sha256"] = (
|
||||
definition.executable_contract_sha256
|
||||
)
|
||||
identity["executor_image_sha256"] = "f" * 64
|
||||
identity["assets"] = [asset.as_dict() for asset in assets]
|
||||
identity["declared_blockers"] = []
|
||||
return replace(
|
||||
release,
|
||||
definition_version=definition.version,
|
||||
definition_sha256=definition.definition_sha256,
|
||||
definition_contract_sha256=definition.executable_contract_sha256,
|
||||
executor_image_sha256="f" * 64,
|
||||
assets=assets,
|
||||
declared_blockers=(),
|
||||
@@ -895,6 +897,39 @@ def test_release_candidate_matches_repository_but_stays_honestly_blocked(
|
||||
)
|
||||
|
||||
|
||||
def test_release_seal_survives_full_definition_executor_promotion() -> None:
|
||||
blocked = _definition()
|
||||
release = _release_for_definition(blocked)
|
||||
inspection = PortableLabV1ReleaseInspection(
|
||||
candidate_sha256=release.candidate_sha256,
|
||||
matched_assets=tuple(asset.asset_id for asset in release.assets),
|
||||
blockers=(),
|
||||
ready=True,
|
||||
)
|
||||
seal = release.seal(inspection)
|
||||
ready_executor = replace(
|
||||
blocked.executor,
|
||||
state="ready",
|
||||
release_id=seal.release_id,
|
||||
release_sha256=seal.release_sha256,
|
||||
image_sha256=seal.executor_image_sha256,
|
||||
reason_code=None,
|
||||
reason=None,
|
||||
)
|
||||
identity = blocked.identity_document()
|
||||
identity["executor"] = ready_executor.identity_document()
|
||||
promoted = replace(
|
||||
blocked,
|
||||
executor=ready_executor,
|
||||
definition_sha256=canonical_sha256(identity),
|
||||
)
|
||||
|
||||
assert promoted.definition_sha256 != blocked.definition_sha256
|
||||
assert promoted.executable_contract_sha256 == blocked.executable_contract_sha256
|
||||
assert seal.definition_contract_sha256 == blocked.executable_contract_sha256
|
||||
release.bind_definition(promoted)
|
||||
|
||||
|
||||
def test_source_materialization_and_effective_config_are_recording_independent(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
@@ -1034,6 +1069,11 @@ def test_release_manifest_is_digest_fenced() -> None:
|
||||
repository_root=REPOSITORY_ROOT,
|
||||
)
|
||||
|
||||
assert document["schema_version"].endswith("/v2")
|
||||
assert document["definition_contract_sha256"] == (
|
||||
_definition().executable_contract_sha256
|
||||
)
|
||||
assert "definition_sha256" not in document
|
||||
assert document["executor_image_sha256"] is None
|
||||
with pytest.raises(Exception, match="identity digest changed"):
|
||||
replace(release, candidate_sha256="f" * 64)
|
||||
|
||||
@@ -324,6 +324,8 @@ def test_conversion_to_recorded_definition_requires_and_preserves_sealed_identit
|
||||
|
||||
recorded = ready.to_recorded_run_definition()
|
||||
|
||||
assert ready.definition_sha256 != blocked.definition_sha256
|
||||
assert ready.executable_contract_sha256 == blocked.executable_contract_sha256
|
||||
assert recorded.setup_id == ready.setup_id
|
||||
assert recorded.definition_sha256 == ready.definition_sha256
|
||||
assert recorded.source_adapter_sha256 == ready.source_adapter.contract_sha256
|
||||
|
||||
Reference in New Issue
Block a user