feat(observatory): add durable recorded compute queue
This commit is contained in:
@@ -0,0 +1,555 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
|
||||
import pytest
|
||||
|
||||
import k1link.observatory.m49_queue_binding as binding_module
|
||||
from k1link.artifact_gateway import (
|
||||
ArtifactMember,
|
||||
CentralArtifactStore,
|
||||
LocalArtifactCache,
|
||||
)
|
||||
from k1link.observatory.m49_queue_binding import (
|
||||
OBSERVATORY_EXECUTOR_BINDING_SCHEMA,
|
||||
OBSERVATORY_SOURCE_BUNDLE_SCHEMA,
|
||||
OBSERVATORY_SOURCE_CAPABILITY_SCHEMA,
|
||||
SOURCE_DOCUMENT_STORE_DIRECTORY,
|
||||
M49QueueBindingConfig,
|
||||
M49QueueBindingIntegrityError,
|
||||
M49RecordedQueueBindingService,
|
||||
M49SourcePackIdentity,
|
||||
M49SourcePackVerifier,
|
||||
_verify_source_pack_from_cache,
|
||||
)
|
||||
from k1link.observatory.setups import LaboratorySetupRegistry
|
||||
from k1link.sessions.models import (
|
||||
SessionArtifact,
|
||||
SessionDetail,
|
||||
SessionSource,
|
||||
SessionSummary,
|
||||
)
|
||||
from k1link.sessions.store import SessionStore
|
||||
|
||||
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
|
||||
BINDING_PATH = (
|
||||
REPOSITORY_ROOT / "config" / "observatory-m49-recorded-queue-binding.json"
|
||||
)
|
||||
SETUP_REGISTRY_PATH = (
|
||||
REPOSITORY_ROOT / "config" / "observatory-laboratory-setups.json"
|
||||
)
|
||||
SOURCE_SESSION_ID = "20260720T065719Z_viewer_live"
|
||||
SOURCE_LABEL = "RAVNOVES00"
|
||||
SETUP_ID = "m49-tgs-full-shadow-v1"
|
||||
DEFINITION_SHA256 = (
|
||||
"836a66639e69f7ea00de2a9111c1c6c9c3c00f1abd726f1df596aeb4e3a88ae6"
|
||||
)
|
||||
SOURCE_PACK_SHA256 = (
|
||||
"0685d24219d8236caf8b7f1685e93f6d6b59e7fd015a768d88a92bbe8b154944"
|
||||
)
|
||||
EXECUTOR_ARTIFACT_SHA256 = (
|
||||
"5e0ea16c7a5cc760463836718b0cd8b0006ffc4b202e5f706a20a86ef2f912ab"
|
||||
)
|
||||
CODE_REVISION = "40c850b167dda366d8aa45d828520168affaf9fd"
|
||||
TRAVEL_IMAGE_SHA256 = (
|
||||
"7b412020f4d8392d1d1ed1b33beadc44140f0ea8f781e62dd69796042334300f"
|
||||
)
|
||||
PARITY_IMAGE_SHA256 = (
|
||||
"ceb13548617e4bd3f619766bfdff00af3fa5160946b367828da6d2233dcdcba0"
|
||||
)
|
||||
SOURCE_CATALOG_SHA256 = (
|
||||
"24207af81b67de515ba9f1b899577bd877a00d058c3472269508980541b1e185"
|
||||
)
|
||||
|
||||
|
||||
class _SessionStore:
|
||||
def __init__(
|
||||
self,
|
||||
data_dir: Path,
|
||||
detail: SessionDetail,
|
||||
catalog_sha256: str,
|
||||
) -> None:
|
||||
self.data_dir = data_dir.resolve()
|
||||
self.detail = detail
|
||||
self.catalog_sha256 = catalog_sha256
|
||||
|
||||
def get_session_with_catalog_snapshot(
|
||||
self, session_id: str
|
||||
) -> tuple[SessionDetail, str]:
|
||||
if session_id != self.detail.summary.session_id:
|
||||
raise KeyError(session_id)
|
||||
return self.detail, self.catalog_sha256
|
||||
|
||||
|
||||
def _canonical_sha256(value: object) -> str:
|
||||
payload = json.dumps(
|
||||
value,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
allow_nan=False,
|
||||
).encode()
|
||||
return hashlib.sha256(payload).hexdigest()
|
||||
|
||||
|
||||
def test_first_immutable_contract_store_syncs_directory_chain_bottom_up(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
payload = b'{"schema_version":"test/v1"}'
|
||||
digest = hashlib.sha256(payload).hexdigest()
|
||||
root = tmp_path / "observatory-source-contracts"
|
||||
synced: list[Path] = []
|
||||
monkeypatch.setattr(
|
||||
binding_module,
|
||||
"_fsync_directory",
|
||||
lambda path: synced.append(path),
|
||||
)
|
||||
|
||||
binding_module._write_immutable_document(root, digest, payload)
|
||||
|
||||
assert synced == [
|
||||
root / "objects" / "sha256" / digest[:2],
|
||||
root / "objects" / "sha256",
|
||||
root / "objects",
|
||||
root,
|
||||
tmp_path,
|
||||
]
|
||||
|
||||
|
||||
def test_existing_immutable_contract_replays_directory_chain_sync(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
payload = b'{"schema_version":"test/v1"}'
|
||||
digest = hashlib.sha256(payload).hexdigest()
|
||||
root = tmp_path / "observatory-source-contracts"
|
||||
binding_module._write_immutable_document(root, digest, payload)
|
||||
synced: list[Path] = []
|
||||
monkeypatch.setattr(
|
||||
binding_module,
|
||||
"_fsync_directory",
|
||||
lambda path: synced.append(path),
|
||||
)
|
||||
|
||||
binding_module._write_immutable_document(root, digest, payload)
|
||||
|
||||
assert synced == [
|
||||
root / "objects" / "sha256" / digest[:2],
|
||||
root / "objects" / "sha256",
|
||||
root / "objects",
|
||||
root,
|
||||
tmp_path,
|
||||
]
|
||||
|
||||
|
||||
def test_immutable_contract_store_rejects_intermediate_symlink(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
payload = b'{"schema_version":"test/v1"}'
|
||||
digest = hashlib.sha256(payload).hexdigest()
|
||||
root = tmp_path / "observatory-source-contracts"
|
||||
outside = tmp_path / "outside"
|
||||
root.mkdir()
|
||||
outside.mkdir()
|
||||
(root / "objects").symlink_to(outside, target_is_directory=True)
|
||||
|
||||
with pytest.raises(
|
||||
M49QueueBindingIntegrityError,
|
||||
match="store directory is invalid",
|
||||
):
|
||||
binding_module._write_immutable_document(root, digest, payload)
|
||||
|
||||
assert list(outside.iterdir()) == []
|
||||
|
||||
|
||||
def _detail() -> SessionDetail:
|
||||
summary = SessionSummary(
|
||||
session_id=SOURCE_SESSION_ID,
|
||||
display_name=SOURCE_LABEL,
|
||||
status="ready",
|
||||
started_at_utc="2026-07-20T06:57:20.888Z",
|
||||
completed_at_utc="2026-07-20T07:06:16.599Z",
|
||||
duration_seconds=535.717620042,
|
||||
modalities=("point-cloud", "trajectory", "video"),
|
||||
source_count=3,
|
||||
total_bytes=100,
|
||||
replayable=True,
|
||||
origin="xgrids-k1.viewer-live.evidence",
|
||||
)
|
||||
sources = (
|
||||
SessionSource(
|
||||
source_id="sensor.camera.right",
|
||||
semantic_channel_id="camera.video.recorded",
|
||||
modality="video",
|
||||
status="recorded",
|
||||
seekable=True,
|
||||
artifact_id="recorded-video",
|
||||
),
|
||||
SessionSource(
|
||||
source_id="sensor.lidar.primary",
|
||||
semantic_channel_id="spatial.point-cloud.recorded",
|
||||
modality="point-cloud",
|
||||
status="recorded",
|
||||
seekable=True,
|
||||
artifact_id="raw-primary",
|
||||
),
|
||||
SessionSource(
|
||||
source_id="spatial.trajectory",
|
||||
semantic_channel_id="spatial.pose.recorded",
|
||||
modality="trajectory",
|
||||
status="recorded",
|
||||
seekable=True,
|
||||
artifact_id="raw-primary",
|
||||
),
|
||||
)
|
||||
artifacts = (
|
||||
SessionArtifact(
|
||||
artifact_id="raw-primary",
|
||||
kind="raw-transport",
|
||||
media_type="application/x-nodedc-k1mqtt",
|
||||
byte_length=64,
|
||||
sha256="d" * 64,
|
||||
integrity_status="verified",
|
||||
),
|
||||
SessionArtifact(
|
||||
artifact_id="recorded-video",
|
||||
kind="recorded-video",
|
||||
media_type="video/mp4",
|
||||
byte_length=36,
|
||||
sha256=None,
|
||||
integrity_status="validated-structure",
|
||||
),
|
||||
)
|
||||
return SessionDetail(
|
||||
summary=summary,
|
||||
sources=sources,
|
||||
artifacts=artifacts,
|
||||
plugin_id="xgrids-k1",
|
||||
archive_id="viewer-live",
|
||||
)
|
||||
|
||||
|
||||
def _registry() -> LaboratorySetupRegistry:
|
||||
return LaboratorySetupRegistry.from_file(
|
||||
SETUP_REGISTRY_PATH,
|
||||
repository_root=REPOSITORY_ROOT,
|
||||
)
|
||||
|
||||
|
||||
def _artifact_cache(
|
||||
data_dir: Path,
|
||||
tmp_path: Path,
|
||||
payload: bytes,
|
||||
identity: M49SourcePackIdentity,
|
||||
) -> LocalArtifactCache:
|
||||
source = tmp_path / "source-pack.npz"
|
||||
source.write_bytes(payload)
|
||||
central = CentralArtifactStore(tmp_path / "central", create=True)
|
||||
published = central.publish_file(source)
|
||||
assert published.sha256 == identity.sha256
|
||||
cache = LocalArtifactCache(
|
||||
data_dir / "artifact-cache",
|
||||
max_bytes=1024 * 1024,
|
||||
free_space_reserve_bytes=0,
|
||||
)
|
||||
member = ArtifactMember(
|
||||
role="lidar-source-pack",
|
||||
media_type=identity.media_type,
|
||||
sha256=published.sha256,
|
||||
byte_length=published.byte_length,
|
||||
)
|
||||
cache.fetch(central, member, pin_id=f"session:{SOURCE_SESSION_ID}")
|
||||
return cache
|
||||
|
||||
|
||||
def _accept_source_pack(
|
||||
_cache: LocalArtifactCache,
|
||||
identity: M49SourcePackIdentity,
|
||||
) -> None:
|
||||
assert identity.sha256 == SOURCE_PACK_SHA256
|
||||
assert identity.byte_length == 72_996_000
|
||||
|
||||
|
||||
def _service(
|
||||
tmp_path: Path,
|
||||
*,
|
||||
catalog_sha256: str = SOURCE_CATALOG_SHA256,
|
||||
setup_registry: LaboratorySetupRegistry | None = None,
|
||||
source_pack_verifier: M49SourcePackVerifier = _accept_source_pack,
|
||||
) -> tuple[M49RecordedQueueBindingService, LocalArtifactCache]:
|
||||
data_dir = tmp_path / "data"
|
||||
data_dir.mkdir()
|
||||
config = M49QueueBindingConfig.from_file(BINDING_PATH)
|
||||
cache = LocalArtifactCache(
|
||||
data_dir / "artifact-cache",
|
||||
max_bytes=1024 * 1024,
|
||||
free_space_reserve_bytes=0,
|
||||
)
|
||||
store = _SessionStore(data_dir, _detail(), catalog_sha256)
|
||||
service = M49RecordedQueueBindingService(
|
||||
data_dir=data_dir,
|
||||
session_store=cast(SessionStore, store),
|
||||
setup_registry=setup_registry or _registry(),
|
||||
config=config,
|
||||
artifact_cache=cache,
|
||||
source_pack_verifier=source_pack_verifier,
|
||||
)
|
||||
return service, cache
|
||||
|
||||
|
||||
def test_bundled_binding_freezes_the_accepted_exact_m49_identity() -> None:
|
||||
config = M49QueueBindingConfig.from_file(BINDING_PATH)
|
||||
definition = config.recorded_definition()
|
||||
image_set = config.image_set_document()
|
||||
resource = config.resource_profile.document()
|
||||
|
||||
assert config.source.session_id == SOURCE_SESSION_ID
|
||||
assert config.source.label == SOURCE_LABEL
|
||||
assert config.source.source_pack.sha256 == SOURCE_PACK_SHA256
|
||||
assert config.source.source_pack.byte_length == 72_996_000
|
||||
assert config.setup.setup_id == SETUP_ID
|
||||
assert config.setup.definition_sha256 == DEFINITION_SHA256
|
||||
assert config.executor.artifact_sha256 == EXECUTOR_ARTIFACT_SHA256
|
||||
assert config.executor.code_revision == CODE_REVISION
|
||||
assert config.executor.service_installed is False
|
||||
assert image_set["images"] == [
|
||||
{"role": "parity", "sha256": PARITY_IMAGE_SHA256},
|
||||
{"role": "travel", "sha256": TRAVEL_IMAGE_SHA256},
|
||||
]
|
||||
assert definition.executor_image_sha256 == _canonical_sha256(image_set)
|
||||
assert definition.model_release_ids == ()
|
||||
assert definition.learned_models == ()
|
||||
assert definition.checkpoint_policy == "non-checkpointable"
|
||||
assert definition.allowed_checkpoints == ()
|
||||
assert resource["restart_from_zero"] is True
|
||||
assert resource["staging_discard_required"] is True
|
||||
assert resource["resource_release_receipt_required"] is True
|
||||
|
||||
|
||||
def test_check_is_read_only_and_admit_seals_path_free_source_contracts(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
service, cache = _service(tmp_path)
|
||||
document_root = service.data_dir / SOURCE_DOCUMENT_STORE_DIRECTORY
|
||||
cache_database_mtime_ns = cache.database_path.stat().st_mtime_ns
|
||||
|
||||
checked = service.check(
|
||||
source_session_id=SOURCE_SESSION_ID,
|
||||
setup_id=SETUP_ID,
|
||||
definition_sha256=DEFINITION_SHA256,
|
||||
)
|
||||
|
||||
assert not document_root.exists()
|
||||
assert cache.database_path.stat().st_mtime_ns == cache_database_mtime_ns
|
||||
assert checked.source_bundle.schema_version == OBSERVATORY_SOURCE_BUNDLE_SCHEMA
|
||||
assert (
|
||||
checked.capability_manifest.schema_version
|
||||
== OBSERVATORY_SOURCE_CAPABILITY_SCHEMA
|
||||
)
|
||||
assert checked.executor_binding.schema_version == OBSERVATORY_EXECUTOR_BINDING_SCHEMA
|
||||
assert checked.source_catalog_sha256 == SOURCE_CATALOG_SHA256
|
||||
assert checked.definition.definition_sha256 == DEFINITION_SHA256
|
||||
assert checked.definition.model_release_ids == ()
|
||||
assert checked.definition.checkpoint_policy == "non-checkpointable"
|
||||
assert checked.source_bundle.document()["members"] == [
|
||||
{
|
||||
"role": "lidar-source-pack",
|
||||
"artifact_id": service.config.source.source_pack.artifact_id,
|
||||
"media_type": service.config.source.source_pack.media_type,
|
||||
"sha256": service.config.source.source_pack.sha256,
|
||||
"byte_length": service.config.source.source_pack.byte_length,
|
||||
}
|
||||
]
|
||||
assert "path" not in json.dumps(checked.as_dict()).lower()
|
||||
|
||||
admitted = service.admit(
|
||||
source_session_id=SOURCE_SESSION_ID,
|
||||
setup_id=SETUP_ID,
|
||||
definition_sha256=DEFINITION_SHA256,
|
||||
)
|
||||
repeated = service.admit(
|
||||
source_session_id=SOURCE_SESSION_ID,
|
||||
setup_id=SETUP_ID,
|
||||
definition_sha256=DEFINITION_SHA256,
|
||||
)
|
||||
sealed_files = tuple(document_root.rglob("*.json"))
|
||||
|
||||
assert admitted == checked
|
||||
assert repeated == admitted
|
||||
assert len(sealed_files) == 3
|
||||
assert {path.stem for path in sealed_files} == {
|
||||
admitted.source_bundle.sha256,
|
||||
admitted.capability_manifest.sha256,
|
||||
admitted.executor_binding.sha256,
|
||||
}
|
||||
assert all(path.read_bytes() in {
|
||||
admitted.source_bundle.payload,
|
||||
admitted.capability_manifest.payload,
|
||||
admitted.executor_binding.payload,
|
||||
} for path in sealed_files)
|
||||
assert service.definitions.resolve(SETUP_ID, DEFINITION_SHA256) == (
|
||||
admitted.definition
|
||||
)
|
||||
intent = admitted.intent(idempotency_key="m49-recorded-submit-001")
|
||||
assert intent.source_session_id == SOURCE_SESSION_ID
|
||||
assert intent.source_catalog_sha256 == SOURCE_CATALOG_SHA256
|
||||
assert intent.source_bundle_sha256 == admitted.source_bundle.sha256
|
||||
assert intent.source_capability_manifest_sha256 == (
|
||||
admitted.capability_manifest.sha256
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("source_session_id", "setup_id", "definition_sha256", "message"),
|
||||
[
|
||||
("other-session", SETUP_ID, DEFINITION_SHA256, "exact source session"),
|
||||
(SOURCE_SESSION_ID, "other-setup", DEFINITION_SHA256, "exact setup"),
|
||||
(SOURCE_SESSION_ID, SETUP_ID, "f" * 64, "definition digest"),
|
||||
],
|
||||
)
|
||||
def test_binding_rejects_every_other_session_setup_or_definition(
|
||||
tmp_path: Path,
|
||||
source_session_id: str,
|
||||
setup_id: str,
|
||||
definition_sha256: str,
|
||||
message: str,
|
||||
) -> None:
|
||||
service, _cache = _service(tmp_path)
|
||||
|
||||
with pytest.raises(M49QueueBindingIntegrityError, match=message):
|
||||
service.check(
|
||||
source_session_id=source_session_id,
|
||||
setup_id=setup_id,
|
||||
definition_sha256=definition_sha256,
|
||||
)
|
||||
|
||||
|
||||
def test_binding_captures_the_current_session_catalog_snapshot(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
service, _cache = _service(tmp_path, catalog_sha256="e" * 64)
|
||||
|
||||
admission = service.check(
|
||||
source_session_id=SOURCE_SESSION_ID,
|
||||
setup_id=SETUP_ID,
|
||||
definition_sha256=DEFINITION_SHA256,
|
||||
)
|
||||
|
||||
assert admission.source_catalog_sha256 == "e" * 64
|
||||
assert admission.source_bundle.document()["catalog_sha256"] == "e" * 64
|
||||
|
||||
|
||||
def test_binding_fails_closed_when_the_cached_source_pack_changes(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
payload = b"sealed source pack fixture\n"
|
||||
identity = M49SourcePackIdentity(
|
||||
artifact_id="fixture-source-pack",
|
||||
sha256=hashlib.sha256(payload).hexdigest(),
|
||||
byte_length=len(payload),
|
||||
media_type="application/vnd.nodedc.lidar-source-pack+npz",
|
||||
expected_timeline_frames=2,
|
||||
expected_available_lidar_frames=1,
|
||||
)
|
||||
data_dir = tmp_path / "data"
|
||||
data_dir.mkdir()
|
||||
cache = _artifact_cache(data_dir, tmp_path, payload, identity)
|
||||
_verify_source_pack_from_cache(cache, identity)
|
||||
cache.object_path(identity.sha256).write_bytes(b"x" * len(payload))
|
||||
|
||||
with pytest.raises(M49QueueBindingIntegrityError, match="artifact-cache metadata"):
|
||||
_verify_source_pack_from_cache(cache, identity)
|
||||
|
||||
|
||||
def test_binding_fails_closed_when_the_setup_registry_definition_changes(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
document = json.loads(SETUP_REGISTRY_PATH.read_text(encoding="utf-8"))
|
||||
document["setups"][0]["run_definition"]["definition_id"] = (
|
||||
"m49-tgs-full-shadow-drift"
|
||||
)
|
||||
path = tmp_path / "setup-registry.json"
|
||||
path.write_text(json.dumps(document), encoding="utf-8")
|
||||
drifted_registry = LaboratorySetupRegistry.from_file(
|
||||
path,
|
||||
repository_root=REPOSITORY_ROOT,
|
||||
)
|
||||
service, _cache = _service(tmp_path, setup_registry=drifted_registry)
|
||||
|
||||
with pytest.raises(M49QueueBindingIntegrityError, match="setup definition changed"):
|
||||
service.check(
|
||||
source_session_id=SOURCE_SESSION_ID,
|
||||
setup_id=SETUP_ID,
|
||||
definition_sha256=DEFINITION_SHA256,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"mutation",
|
||||
["unknown-key", "installed", "learned-model", "source-session"],
|
||||
)
|
||||
def test_binding_loader_rejects_authority_or_contract_expansion(
|
||||
tmp_path: Path,
|
||||
mutation: str,
|
||||
) -> None:
|
||||
document = json.loads(BINDING_PATH.read_text(encoding="utf-8"))
|
||||
if mutation == "unknown-key":
|
||||
document["command"] = "run arbitrary payload"
|
||||
elif mutation == "installed":
|
||||
document["executor"]["service_installed"] = True
|
||||
elif mutation == "learned-model":
|
||||
document["executor"]["learned_models"] = ["invented-model"]
|
||||
else:
|
||||
document["source"]["session_id"] = "different-source"
|
||||
path = tmp_path / "invalid-binding.json"
|
||||
path.write_text(json.dumps(document), encoding="utf-8")
|
||||
|
||||
with pytest.raises(M49QueueBindingIntegrityError):
|
||||
M49QueueBindingConfig.from_file(path)
|
||||
|
||||
|
||||
def test_binding_requires_the_session_store_data_dir_artifact_cache(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
service, cache = _service(tmp_path)
|
||||
config = service.config
|
||||
foreign = LocalArtifactCache(
|
||||
tmp_path / "foreign-cache",
|
||||
max_bytes=1024 * 1024,
|
||||
free_space_reserve_bytes=0,
|
||||
)
|
||||
|
||||
with pytest.raises(M49QueueBindingIntegrityError, match="data-dir artifact cache"):
|
||||
M49RecordedQueueBindingService(
|
||||
data_dir=service.data_dir,
|
||||
session_store=service._session_store, # noqa: SLF001 - exact boundary test
|
||||
setup_registry=_registry(),
|
||||
config=config,
|
||||
artifact_cache=foreign,
|
||||
)
|
||||
|
||||
assert cache.root == service.data_dir / "artifact-cache"
|
||||
|
||||
|
||||
def test_binding_wraps_unavailable_default_artifact_cache(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
data_dir = tmp_path / "data"
|
||||
data_dir.mkdir()
|
||||
(data_dir / "artifact-cache").write_text("not a directory", encoding="utf-8")
|
||||
store = _SessionStore(data_dir, _detail(), SOURCE_CATALOG_SHA256)
|
||||
|
||||
with pytest.raises(
|
||||
M49QueueBindingIntegrityError,
|
||||
match="artifact cache is unavailable",
|
||||
):
|
||||
M49RecordedQueueBindingService(
|
||||
data_dir=data_dir,
|
||||
session_store=cast(SessionStore, store),
|
||||
setup_registry=_registry(),
|
||||
config=M49QueueBindingConfig.from_file(BINDING_PATH),
|
||||
)
|
||||
@@ -0,0 +1,728 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from k1link.observatory.recorded_jobs import (
|
||||
LIVE_K1_PRIORITY_RANK,
|
||||
RECORDED_JOB_DATABASE_NAME,
|
||||
RECORDED_PRIORITY_RANK,
|
||||
ObservatoryLiveLeaseIntent,
|
||||
ObservatoryNonCheckpointableCancellationReceipt,
|
||||
ObservatoryRecordedJobIntent,
|
||||
ObservatoryRecordedJobQueue,
|
||||
ObservatoryRecordedPreemptionError,
|
||||
ObservatoryRecordedQueueBusyError,
|
||||
ObservatoryRecordedQueueConflictError,
|
||||
ObservatoryRecordedQueueIntegrityError,
|
||||
ObservatoryRecordedQueueStaleClaimError,
|
||||
RecordedRunDefinition,
|
||||
RecordedRunDefinitionRegistry,
|
||||
)
|
||||
|
||||
NOW = "2026-08-30T21:00:00.000Z"
|
||||
DEFINITION_SHA = "a" * 64
|
||||
NON_CHECKPOINTABLE_DEFINITION_SHA = "b" * 64
|
||||
ADAPTER_SHA = "c" * 64
|
||||
CATALOG_SHA = "d" * 64
|
||||
RESULT_SHA = "e" * 64
|
||||
LIVE_EPOCH_SHA = "f" * 64
|
||||
SOURCE_BUNDLE_SHA = "1" * 64
|
||||
SOURCE_CAPABILITIES_SHA = "2" * 64
|
||||
EXECUTOR_RELEASE_SHA = "3" * 64
|
||||
EXECUTOR_IMAGE_SHA = "4" * 64
|
||||
MODEL_MANIFEST_SHA = "5" * 64
|
||||
RESOURCE_PROFILE_SHA = "6" * 64
|
||||
|
||||
|
||||
def _definitions() -> RecordedRunDefinitionRegistry:
|
||||
return RecordedRunDefinitionRegistry(
|
||||
(
|
||||
RecordedRunDefinition(
|
||||
setup_id="travel-tgs-eomt-v1",
|
||||
definition_id="travel-tgs-eomt",
|
||||
definition_version=1,
|
||||
definition_sha256=DEFINITION_SHA,
|
||||
source_adapter_id="sealed-session-bundle",
|
||||
source_adapter_version=1,
|
||||
source_adapter_sha256=ADAPTER_SHA,
|
||||
executor_release_id="travel-tgs-executor-v1",
|
||||
executor_release_sha256=EXECUTOR_RELEASE_SHA,
|
||||
executor_image_sha256=EXECUTOR_IMAGE_SHA,
|
||||
model_release_ids=("eomt-v1", "travel-tgs-v1"),
|
||||
model_manifest_sha256=MODEL_MANIFEST_SHA,
|
||||
resource_profile_id="worker006-single-gpu-v1",
|
||||
resource_profile_sha256=RESOURCE_PROFILE_SHA,
|
||||
checkpoint_policy="cooperative",
|
||||
allowed_checkpoints=(
|
||||
"source-bundle-ready",
|
||||
"model-batch-finished",
|
||||
"evidence-sealed",
|
||||
),
|
||||
),
|
||||
RecordedRunDefinition(
|
||||
setup_id="legacy-monolith-v1",
|
||||
definition_id="legacy-monolith",
|
||||
definition_version=1,
|
||||
definition_sha256=NON_CHECKPOINTABLE_DEFINITION_SHA,
|
||||
source_adapter_id="sealed-session-bundle",
|
||||
source_adapter_version=1,
|
||||
source_adapter_sha256=ADAPTER_SHA,
|
||||
executor_release_id="legacy-monolith-executor-v1",
|
||||
executor_release_sha256=EXECUTOR_RELEASE_SHA,
|
||||
executor_image_sha256=EXECUTOR_IMAGE_SHA,
|
||||
model_release_ids=("legacy-monolith-v1",),
|
||||
model_manifest_sha256=MODEL_MANIFEST_SHA,
|
||||
resource_profile_id="worker006-single-gpu-v1",
|
||||
resource_profile_sha256=RESOURCE_PROFILE_SHA,
|
||||
checkpoint_policy="non-checkpointable",
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _queue(
|
||||
tmp_path: Path,
|
||||
*,
|
||||
with_non_checkpointable_preemptor: bool = False,
|
||||
) -> ObservatoryRecordedJobQueue:
|
||||
preemptor = None
|
||||
if with_non_checkpointable_preemptor:
|
||||
def preemptor(request):
|
||||
return ObservatoryNonCheckpointableCancellationReceipt.sealed(
|
||||
request,
|
||||
cancellation_id=f"cancel-{request.cancellation_request_id}",
|
||||
)
|
||||
|
||||
return ObservatoryRecordedJobQueue(
|
||||
tmp_path,
|
||||
definitions=_definitions(),
|
||||
clock=lambda: NOW,
|
||||
non_checkpointable_preemptor=preemptor,
|
||||
)
|
||||
|
||||
|
||||
def _intent(
|
||||
*,
|
||||
idempotency_key: str = "recorded-request-001",
|
||||
source_session_id: str = "20260828T130511Z_viewer_live",
|
||||
source_catalog_sha256: str = CATALOG_SHA,
|
||||
setup_id: str = "travel-tgs-eomt-v1",
|
||||
definition_sha256: str = DEFINITION_SHA,
|
||||
) -> ObservatoryRecordedJobIntent:
|
||||
return ObservatoryRecordedJobIntent(
|
||||
idempotency_key=idempotency_key,
|
||||
source_session_id=source_session_id,
|
||||
source_catalog_sha256=source_catalog_sha256,
|
||||
source_bundle_sha256=SOURCE_BUNDLE_SHA,
|
||||
source_capability_manifest_sha256=SOURCE_CAPABILITIES_SHA,
|
||||
setup_id=setup_id,
|
||||
definition_sha256=definition_sha256,
|
||||
)
|
||||
|
||||
|
||||
def _live_intent(
|
||||
*,
|
||||
trigger_id: str = "k1-live-start-001",
|
||||
live_session_id: str = "20260830T210000Z_k1_live",
|
||||
acquisition_epoch_sha256: str = LIVE_EPOCH_SHA,
|
||||
) -> ObservatoryLiveLeaseIntent:
|
||||
return ObservatoryLiveLeaseIntent(
|
||||
trigger_id=trigger_id,
|
||||
live_session_id=live_session_id,
|
||||
acquisition_epoch_sha256=acquisition_epoch_sha256,
|
||||
)
|
||||
|
||||
|
||||
def _running_job(
|
||||
queue: ObservatoryRecordedJobQueue,
|
||||
*,
|
||||
intent: ObservatoryRecordedJobIntent | None = None,
|
||||
claim_request_id: str = "worker-claim-001",
|
||||
):
|
||||
job, created = queue.submit(intent or _intent())
|
||||
assert created is True
|
||||
queue.enqueue(job.job_id)
|
||||
claim = queue.claim_next(
|
||||
claimant_id="recorded-worker", claim_request_id=claim_request_id
|
||||
)
|
||||
assert claim is not None
|
||||
running = queue.start(job.job_id, claim_token=claim.claim_token)
|
||||
assert running.state == "running"
|
||||
return running, claim
|
||||
|
||||
|
||||
def test_submission_is_durable_exactly_idempotent_and_path_free(tmp_path: Path) -> None:
|
||||
queue = _queue(tmp_path)
|
||||
first, first_created = queue.submit(_intent())
|
||||
second, second_created = queue.submit(_intent())
|
||||
restored = ObservatoryRecordedJobQueue(
|
||||
tmp_path,
|
||||
definitions=_definitions(),
|
||||
clock=lambda: NOW,
|
||||
).get(first.job_id)
|
||||
|
||||
assert first_created is True
|
||||
assert second_created is False
|
||||
assert second == first
|
||||
assert restored == first
|
||||
assert first.state == "accepted"
|
||||
assert first.priority_class == "recorded"
|
||||
assert first.priority_rank == RECORDED_PRIORITY_RANK
|
||||
assert first.source_adapter_id == "sealed-session-bundle"
|
||||
assert first.checkpoint_policy == "cooperative"
|
||||
projection = first.as_dict()
|
||||
assert projection["priority"] == {
|
||||
"class": "recorded",
|
||||
"rank": RECORDED_PRIORITY_RANK,
|
||||
"server_owned": True,
|
||||
}
|
||||
assert "path" not in str(projection).lower()
|
||||
assert not hasattr(_intent(), "command")
|
||||
assert not hasattr(_intent(), "image")
|
||||
assert projection["source"]["bundle_sha256"] == SOURCE_BUNDLE_SHA
|
||||
assert projection["source"]["capability_manifest_sha256"] == (
|
||||
SOURCE_CAPABILITIES_SHA
|
||||
)
|
||||
assert projection["executor"]["release_sha256"] == EXECUTOR_RELEASE_SHA
|
||||
assert projection["executor"]["image_sha256"] == EXECUTOR_IMAGE_SHA
|
||||
assert projection["executor"]["model_manifest_sha256"] == MODEL_MANIFEST_SHA
|
||||
assert projection["executor"]["resource_profile_sha256"] == (
|
||||
RESOURCE_PROFILE_SHA
|
||||
)
|
||||
assert queue.database_path.name == RECORDED_JOB_DATABASE_NAME
|
||||
assert queue.database_path.stat().st_mode & 0o777 == 0o600
|
||||
|
||||
with pytest.raises(ObservatoryRecordedQueueConflictError, match="idempotency"):
|
||||
queue.submit(replace(_intent(), source_session_id="another-session"))
|
||||
|
||||
|
||||
def test_submission_and_enqueue_can_commit_as_one_idempotent_transaction(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
queue = _queue(tmp_path)
|
||||
|
||||
first, created = queue.submit(_intent(), enqueue=True)
|
||||
repeated, repeated_created = queue.submit(_intent(), enqueue=True)
|
||||
|
||||
assert created is True
|
||||
assert repeated_created is False
|
||||
assert first.state == "queued"
|
||||
assert repeated == first
|
||||
|
||||
|
||||
def test_session_setup_identity_changes_for_each_exact_combination(tmp_path: Path) -> None:
|
||||
queue = _queue(tmp_path)
|
||||
first, _ = queue.submit(_intent())
|
||||
second, _ = queue.submit(
|
||||
_intent(
|
||||
idempotency_key="recorded-request-002",
|
||||
source_session_id="another-session",
|
||||
)
|
||||
)
|
||||
third, _ = queue.submit(
|
||||
_intent(
|
||||
idempotency_key="recorded-request-003",
|
||||
setup_id="legacy-monolith-v1",
|
||||
definition_sha256=NON_CHECKPOINTABLE_DEFINITION_SHA,
|
||||
)
|
||||
)
|
||||
|
||||
assert len({first.identity_sha256, second.identity_sha256, third.identity_sha256}) == 3
|
||||
assert second.source_adapter_sha256 == first.source_adapter_sha256
|
||||
assert third.checkpoint_policy == "non-checkpointable"
|
||||
|
||||
with pytest.raises(ObservatoryRecordedQueueConflictError, match="allowlisted"):
|
||||
queue.submit(
|
||||
_intent(
|
||||
idempotency_key="recorded-request-004",
|
||||
definition_sha256="0" * 64,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_normal_recorded_job_lifecycle_and_terminal_idempotency(tmp_path: Path) -> None:
|
||||
queue = _queue(tmp_path)
|
||||
running, claim = _running_job(queue)
|
||||
|
||||
checkpointed = queue.checkpoint(
|
||||
running.job_id,
|
||||
claim_token=claim.claim_token,
|
||||
checkpoint_id="model-batch-finished",
|
||||
)
|
||||
assert checkpointed.state == "running"
|
||||
assert checkpointed.last_checkpoint_id == "model-batch-finished"
|
||||
|
||||
succeeded = queue.succeed(
|
||||
running.job_id,
|
||||
claim_token=claim.claim_token,
|
||||
result_id="recorded-result-001",
|
||||
result_sha256=RESULT_SHA,
|
||||
)
|
||||
replayed = queue.succeed(
|
||||
running.job_id,
|
||||
claim_token=claim.claim_token,
|
||||
result_id="recorded-result-001",
|
||||
result_sha256=RESULT_SHA,
|
||||
)
|
||||
assert succeeded.state == "succeeded"
|
||||
assert replayed == succeeded
|
||||
assert succeeded.result_sha256 == RESULT_SHA
|
||||
assert succeeded.active_claim_token is None
|
||||
|
||||
with pytest.raises(ObservatoryRecordedQueueConflictError, match="terminal"):
|
||||
queue.fail(
|
||||
running.job_id,
|
||||
claim_token=claim.claim_token,
|
||||
error_code="worker-failed",
|
||||
message="Synthetic failure.",
|
||||
)
|
||||
|
||||
|
||||
def test_success_requires_running_and_cannot_publish_during_preemption(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
queue = _queue(tmp_path)
|
||||
job, _ = queue.submit(_intent())
|
||||
queue.enqueue(job.job_id)
|
||||
claim = queue.claim_next(
|
||||
claimant_id="recorded-worker",
|
||||
claim_request_id="worker-claim-001",
|
||||
)
|
||||
assert claim is not None
|
||||
|
||||
with pytest.raises(ObservatoryRecordedQueueConflictError, match="claimed"):
|
||||
queue.succeed(
|
||||
job.job_id,
|
||||
claim_token=claim.claim_token,
|
||||
result_id="recorded-result-001",
|
||||
result_sha256=RESULT_SHA,
|
||||
)
|
||||
|
||||
queue.start(job.job_id, claim_token=claim.claim_token)
|
||||
queue.request_live(_live_intent())
|
||||
assert queue.get(job.job_id).preemption_requested is True
|
||||
with pytest.raises(
|
||||
ObservatoryRecordedQueueConflictError,
|
||||
match="preemption",
|
||||
):
|
||||
queue.succeed(
|
||||
job.job_id,
|
||||
claim_token=claim.claim_token,
|
||||
result_id="recorded-result-001",
|
||||
result_sha256=RESULT_SHA,
|
||||
)
|
||||
|
||||
|
||||
def test_claim_is_exactly_idempotent_including_empty_result(tmp_path: Path) -> None:
|
||||
queue = _queue(tmp_path)
|
||||
|
||||
empty = queue.claim_next(
|
||||
claimant_id="recorded-worker", claim_request_id="empty-poll-001"
|
||||
)
|
||||
assert empty is None
|
||||
job, _ = queue.submit(_intent())
|
||||
queue.enqueue(job.job_id)
|
||||
assert (
|
||||
queue.claim_next(
|
||||
claimant_id="recorded-worker", claim_request_id="empty-poll-001"
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
claim = queue.claim_next(
|
||||
claimant_id="recorded-worker", claim_request_id="non-empty-poll-001"
|
||||
)
|
||||
retry = queue.claim_next(
|
||||
claimant_id="recorded-worker", claim_request_id="non-empty-poll-001"
|
||||
)
|
||||
assert claim is not None
|
||||
assert retry is not None
|
||||
assert retry.claim_token == claim.claim_token
|
||||
assert retry.job.job_id == job.job_id
|
||||
|
||||
with pytest.raises(ObservatoryRecordedQueueConflictError, match="claim"):
|
||||
queue.claim_next(
|
||||
claimant_id="another-worker", claim_request_id="non-empty-poll-001"
|
||||
)
|
||||
|
||||
|
||||
def test_single_worker_resource_has_only_one_recorded_owner(tmp_path: Path) -> None:
|
||||
queue = _queue(tmp_path)
|
||||
first, _ = queue.submit(_intent())
|
||||
second, _ = queue.submit(
|
||||
_intent(
|
||||
idempotency_key="recorded-request-002",
|
||||
source_session_id="another-session",
|
||||
)
|
||||
)
|
||||
queue.enqueue(first.job_id)
|
||||
queue.enqueue(second.job_id)
|
||||
first_claim = queue.claim_next(
|
||||
claimant_id="recorded-worker", claim_request_id="owner-poll-001"
|
||||
)
|
||||
assert first_claim is not None
|
||||
assert (
|
||||
queue.claim_next(
|
||||
claimant_id="recorded-worker", claim_request_id="owner-poll-002"
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
completed_job_id = first_claim.job.job_id
|
||||
queue.fail(
|
||||
completed_job_id,
|
||||
claim_token=first_claim.claim_token,
|
||||
error_code="synthetic-failure",
|
||||
message="Release the synthetic single-Worker ownership.",
|
||||
)
|
||||
second_claim = queue.claim_next(
|
||||
claimant_id="recorded-worker", claim_request_id="owner-poll-003"
|
||||
)
|
||||
assert second_claim is not None
|
||||
assert second_claim.job.job_id in {first.job_id, second.job_id} - {completed_job_id}
|
||||
|
||||
|
||||
def test_live_lease_cooperatively_pauses_and_resumes_recorded_job(tmp_path: Path) -> None:
|
||||
queue = _queue(tmp_path)
|
||||
running, claim = _running_job(queue)
|
||||
|
||||
lease, created = queue.request_live(_live_intent())
|
||||
exact_retry, retry_created = queue.request_live(_live_intent())
|
||||
requested_job = queue.get(running.job_id)
|
||||
assert created is True
|
||||
assert retry_created is False
|
||||
assert exact_retry == lease
|
||||
assert lease.state == "pending"
|
||||
assert lease.priority_rank == LIVE_K1_PRIORITY_RANK
|
||||
assert requested_job.state == "running"
|
||||
assert requested_job.preemption_requested is True
|
||||
assert queue.admission_gate().blocked is True
|
||||
|
||||
with pytest.raises(ObservatoryRecordedQueueBusyError, match="safe preemption"):
|
||||
queue.activate_live(lease.lease_id)
|
||||
|
||||
paused = queue.checkpoint(
|
||||
running.job_id,
|
||||
claim_token=claim.claim_token,
|
||||
checkpoint_id="model-batch-finished",
|
||||
)
|
||||
assert paused.state == "paused"
|
||||
active = queue.activate_live(lease.lease_id)
|
||||
assert active.state == "active"
|
||||
|
||||
another, _ = queue.submit(
|
||||
_intent(
|
||||
idempotency_key="recorded-request-002",
|
||||
source_session_id="another-session",
|
||||
)
|
||||
)
|
||||
queue.enqueue(another.job_id)
|
||||
assert (
|
||||
queue.claim_next(
|
||||
claimant_id="recorded-worker", claim_request_id="blocked-poll-001"
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
completed = queue.finish_live(
|
||||
lease.lease_id,
|
||||
terminal_trigger_id="k1-live-finish-001",
|
||||
outcome="completed",
|
||||
)
|
||||
completed_retry = queue.finish_live(
|
||||
lease.lease_id,
|
||||
terminal_trigger_id="k1-live-finish-001",
|
||||
outcome="completed",
|
||||
)
|
||||
assert completed.state == "completed"
|
||||
assert completed_retry == completed
|
||||
assert queue.admission_gate().blocked is False
|
||||
resumed = queue.get(running.job_id)
|
||||
assert resumed.state == "queued"
|
||||
assert resumed.preemption_requested is False
|
||||
assert resumed.active_claim_token is None
|
||||
|
||||
with pytest.raises(ObservatoryRecordedQueueConflictError, match="terminal"):
|
||||
queue.finish_live(
|
||||
lease.lease_id,
|
||||
terminal_trigger_id="different-terminal-trigger",
|
||||
outcome="failed",
|
||||
)
|
||||
|
||||
|
||||
def test_cancelled_pending_live_lease_releases_cooperative_preemption_request(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
queue = _queue(tmp_path)
|
||||
running, claim = _running_job(queue)
|
||||
lease, _ = queue.request_live(_live_intent())
|
||||
assert queue.get(running.job_id).preemption_requested is True
|
||||
|
||||
cancelled = queue.finish_live(
|
||||
lease.lease_id,
|
||||
terminal_trigger_id="k1-live-cancel-001",
|
||||
outcome="cancelled",
|
||||
)
|
||||
released = queue.get(running.job_id)
|
||||
|
||||
assert cancelled.state == "cancelled"
|
||||
assert released.state == "running"
|
||||
assert released.preemption_requested is False
|
||||
assert released.active_claim_token == claim.claim_token
|
||||
|
||||
|
||||
def test_live_request_pauses_claimed_job_before_execution(tmp_path: Path) -> None:
|
||||
queue = _queue(tmp_path)
|
||||
job, _ = queue.submit(_intent())
|
||||
queue.enqueue(job.job_id)
|
||||
claim = queue.claim_next(
|
||||
claimant_id="recorded-worker", claim_request_id="worker-claim-001"
|
||||
)
|
||||
assert claim is not None
|
||||
|
||||
lease, _ = queue.request_live(_live_intent())
|
||||
paused = queue.get(job.job_id)
|
||||
assert paused.state == "paused"
|
||||
assert queue.start(job.job_id, claim_token=claim.claim_token).state == "paused"
|
||||
assert queue.activate_live(lease.lease_id).state == "active"
|
||||
queue.finish_live(
|
||||
lease.lease_id,
|
||||
terminal_trigger_id="k1-live-finish-001",
|
||||
outcome="completed",
|
||||
)
|
||||
|
||||
with pytest.raises(ObservatoryRecordedQueueStaleClaimError, match="stale"):
|
||||
queue.start(job.job_id, claim_token=claim.claim_token)
|
||||
|
||||
|
||||
def test_non_checkpointable_job_is_cancelled_and_restarts_from_zero_for_live(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
queue = _queue(tmp_path, with_non_checkpointable_preemptor=True)
|
||||
running, claim = _running_job(
|
||||
queue,
|
||||
intent=_intent(
|
||||
setup_id="legacy-monolith-v1",
|
||||
definition_sha256=NON_CHECKPOINTABLE_DEFINITION_SHA,
|
||||
),
|
||||
)
|
||||
lease, _ = queue.request_live(_live_intent())
|
||||
paused = queue.get(running.job_id)
|
||||
assert paused.state == "paused"
|
||||
assert paused.preemption_requested is True
|
||||
assert paused.restart_from_zero is True
|
||||
assert paused.preemption_receipt_sha256 is not None
|
||||
assert paused.active_claim_token is None
|
||||
|
||||
with pytest.raises(ObservatoryRecordedQueueStaleClaimError, match="stale"):
|
||||
queue.checkpoint(
|
||||
running.job_id,
|
||||
claim_token=claim.claim_token,
|
||||
checkpoint_id="model-batch-finished",
|
||||
)
|
||||
assert queue.activate_live(lease.lease_id).state == "active"
|
||||
assert (
|
||||
queue.finish_live(
|
||||
lease.lease_id,
|
||||
terminal_trigger_id="k1-live-finish-001",
|
||||
outcome="failed",
|
||||
).state
|
||||
== "failed"
|
||||
)
|
||||
resumed = queue.get(running.job_id)
|
||||
assert resumed.state == "queued"
|
||||
assert resumed.restart_from_zero is True
|
||||
|
||||
new_claim = queue.claim_next(
|
||||
claimant_id="recorded-worker", claim_request_id="worker-claim-002"
|
||||
)
|
||||
assert new_claim is not None
|
||||
assert new_claim.job.job_id == running.job_id
|
||||
assert new_claim.job.restart_from_zero is True
|
||||
|
||||
|
||||
def test_live_request_fails_closed_without_non_checkpointable_canceler(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
queue = _queue(tmp_path)
|
||||
running, _claim = _running_job(
|
||||
queue,
|
||||
intent=_intent(
|
||||
setup_id="legacy-monolith-v1",
|
||||
definition_sha256=NON_CHECKPOINTABLE_DEFINITION_SHA,
|
||||
),
|
||||
)
|
||||
|
||||
with pytest.raises(ObservatoryRecordedPreemptionError, match="scheduler-owned"):
|
||||
queue.request_live(_live_intent())
|
||||
|
||||
assert queue.admission_gate().blocked is True
|
||||
assert queue.get(running.job_id).state == "preemption-pending"
|
||||
with sqlite3.connect(queue.database_path) as connection:
|
||||
intent_state = connection.execute(
|
||||
"SELECT state FROM observatory_recorded_preemptions"
|
||||
).fetchone()[0]
|
||||
assert intent_state == "pending"
|
||||
|
||||
recovered = _queue(tmp_path, with_non_checkpointable_preemptor=True)
|
||||
lease, created = recovered.request_live(_live_intent())
|
||||
assert created is False
|
||||
assert recovered.get(running.job_id).state == "paused"
|
||||
assert recovered.get(running.job_id).restart_from_zero is True
|
||||
assert recovered.activate_live(lease.lease_id).state == "active"
|
||||
|
||||
|
||||
def test_durable_cancel_intent_survives_callback_crash_and_reuses_identity(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
cancellation_request_ids: list[str] = []
|
||||
|
||||
def crash_after_external_side_effect(request):
|
||||
cancellation_request_ids.append(request.cancellation_request_id)
|
||||
raise OSError("synthetic scheduler process loss after cancellation")
|
||||
|
||||
queue = ObservatoryRecordedJobQueue(
|
||||
tmp_path,
|
||||
definitions=_definitions(),
|
||||
clock=lambda: NOW,
|
||||
non_checkpointable_preemptor=crash_after_external_side_effect,
|
||||
)
|
||||
running, _claim = _running_job(
|
||||
queue,
|
||||
intent=_intent(
|
||||
setup_id="legacy-monolith-v1",
|
||||
definition_sha256=NON_CHECKPOINTABLE_DEFINITION_SHA,
|
||||
),
|
||||
)
|
||||
with pytest.raises(ObservatoryRecordedPreemptionError, match="did not release"):
|
||||
queue.request_live(_live_intent())
|
||||
|
||||
assert queue.get(running.job_id).state == "preemption-pending"
|
||||
|
||||
def replay_exact_cancel(request):
|
||||
cancellation_request_ids.append(request.cancellation_request_id)
|
||||
return ObservatoryNonCheckpointableCancellationReceipt.sealed(
|
||||
request,
|
||||
cancellation_id=f"cancel-{request.cancellation_request_id}",
|
||||
)
|
||||
|
||||
recovered = ObservatoryRecordedJobQueue(
|
||||
tmp_path,
|
||||
definitions=_definitions(),
|
||||
clock=lambda: NOW,
|
||||
non_checkpointable_preemptor=replay_exact_cancel,
|
||||
)
|
||||
lease, created = recovered.request_live(_live_intent())
|
||||
|
||||
assert created is False
|
||||
assert len(cancellation_request_ids) == 2
|
||||
assert cancellation_request_ids[0] == cancellation_request_ids[1]
|
||||
assert recovered.get(running.job_id).state == "paused"
|
||||
assert recovered.activate_live(lease.lease_id).state == "active"
|
||||
|
||||
|
||||
def test_learned_model_release_list_may_be_empty_for_algorithm_only_tgs() -> None:
|
||||
definition = RecordedRunDefinition(
|
||||
setup_id="travel-tgs-algorithm-v1",
|
||||
definition_id="travel-tgs-algorithm",
|
||||
definition_version=1,
|
||||
definition_sha256="7" * 64,
|
||||
source_adapter_id="sealed-session-bundle",
|
||||
source_adapter_version=1,
|
||||
source_adapter_sha256=ADAPTER_SHA,
|
||||
executor_release_id="travel-tgs-executor-v1",
|
||||
executor_release_sha256=EXECUTOR_RELEASE_SHA,
|
||||
executor_image_sha256=EXECUTOR_IMAGE_SHA,
|
||||
model_release_ids=(),
|
||||
model_manifest_sha256=MODEL_MANIFEST_SHA,
|
||||
resource_profile_id="worker006-single-gpu-v1",
|
||||
resource_profile_sha256=RESOURCE_PROFILE_SHA,
|
||||
checkpoint_policy="cooperative",
|
||||
allowed_checkpoints=("model-batch-finished",),
|
||||
)
|
||||
|
||||
assert definition.model_release_ids == ()
|
||||
assert definition.learned_models == ()
|
||||
assert definition.model_manifest_sha256 == MODEL_MANIFEST_SHA
|
||||
|
||||
|
||||
def test_reconciliation_required_is_durable_terminal_state(tmp_path: Path) -> None:
|
||||
queue = _queue(tmp_path)
|
||||
running, claim = _running_job(queue)
|
||||
|
||||
uncertain = queue.require_reconciliation(
|
||||
running.job_id,
|
||||
claim_token=claim.claim_token,
|
||||
reason_code="worker-outcome-unknown",
|
||||
message="Dispatch was accepted but its terminal receipt is unavailable.",
|
||||
)
|
||||
restored = ObservatoryRecordedJobQueue(
|
||||
tmp_path,
|
||||
definitions=_definitions(),
|
||||
clock=lambda: NOW,
|
||||
).get(running.job_id)
|
||||
assert uncertain.state == "reconciliation-required"
|
||||
assert restored == uncertain
|
||||
|
||||
|
||||
def test_reconciliation_required_quarantines_recorded_and_live_ownership(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
queue = _queue(tmp_path)
|
||||
running, claim = _running_job(queue)
|
||||
queue.require_reconciliation(
|
||||
running.job_id,
|
||||
claim_token=claim.claim_token,
|
||||
reason_code="worker-outcome-unknown",
|
||||
message="Worker ownership cannot be proven released.",
|
||||
)
|
||||
waiting, _ = queue.submit(
|
||||
_intent(idempotency_key="recorded-request-002")
|
||||
)
|
||||
queue.enqueue(waiting.job_id)
|
||||
|
||||
assert queue.claim_next(
|
||||
claimant_id="recorded-worker",
|
||||
claim_request_id="worker-claim-after-reconciliation",
|
||||
) is None
|
||||
lease, _ = queue.request_live(_live_intent())
|
||||
with pytest.raises(ObservatoryRecordedQueueBusyError, match="recorded work"):
|
||||
queue.activate_live(lease.lease_id)
|
||||
|
||||
|
||||
def test_live_lease_requires_explicit_valid_terminal_transition(tmp_path: Path) -> None:
|
||||
queue = _queue(tmp_path)
|
||||
lease, _ = queue.request_live(_live_intent())
|
||||
|
||||
with pytest.raises(ObservatoryRecordedQueueConflictError, match="complete"):
|
||||
queue.finish_live(
|
||||
lease.lease_id,
|
||||
terminal_trigger_id="k1-live-finish-001",
|
||||
outcome="completed",
|
||||
)
|
||||
cancelled = queue.finish_live(
|
||||
lease.lease_id,
|
||||
terminal_trigger_id="k1-live-cancel-001",
|
||||
outcome="cancelled",
|
||||
)
|
||||
assert cancelled.state == "cancelled"
|
||||
assert queue.admission_gate().blocked is False
|
||||
|
||||
|
||||
def test_queue_detects_mutated_immutable_identity(tmp_path: Path) -> None:
|
||||
queue = _queue(tmp_path)
|
||||
job, _ = queue.submit(_intent())
|
||||
with sqlite3.connect(queue.database_path) as connection:
|
||||
connection.execute(
|
||||
"UPDATE observatory_recorded_jobs SET source_catalog_sha256 = ? "
|
||||
"WHERE job_id = ?",
|
||||
("0" * 64, job.job_id),
|
||||
)
|
||||
connection.commit()
|
||||
|
||||
with pytest.raises(ObservatoryRecordedQueueIntegrityError, match="identity"):
|
||||
queue.get(job.job_id)
|
||||
@@ -0,0 +1,253 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from typing import Any, cast
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from k1link.observatory.m49_queue_binding import M49QueueBindingIntegrityError
|
||||
from k1link.observatory.recorded_jobs import (
|
||||
ObservatoryRecordedJobIntent,
|
||||
ObservatoryRecordedJobQueue,
|
||||
RecordedRunDefinition,
|
||||
RecordedRunDefinitionRegistry,
|
||||
)
|
||||
from k1link.observatory.setups import LaboratorySetupRegistry
|
||||
from k1link.sessions import SessionNotFoundError
|
||||
from k1link.sessions.models import SessionSummary
|
||||
from k1link.web.observatory_api import build_observatory_router
|
||||
|
||||
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
|
||||
REGISTRY_PATH = REPOSITORY_ROOT / "config" / "observatory-laboratory-setups.json"
|
||||
RAV00_SESSION_ID = "20260720T065719Z_viewer_live"
|
||||
SETUP_ID = "m49-tgs-full-shadow-v1"
|
||||
CATALOG_SHA256 = "1" * 64
|
||||
SOURCE_BUNDLE_SHA256 = "2" * 64
|
||||
CAPABILITY_SHA256 = "3" * 64
|
||||
|
||||
|
||||
def _source() -> SessionSummary:
|
||||
return SessionSummary(
|
||||
session_id=RAV00_SESSION_ID,
|
||||
display_name="RAVNOVES00",
|
||||
status="ready",
|
||||
started_at_utc="2026-07-20T06:57:19Z",
|
||||
completed_at_utc="2026-07-20T07:06:15Z",
|
||||
duration_seconds=536.0,
|
||||
modalities=("point-cloud", "trajectory", "video"),
|
||||
source_count=3,
|
||||
total_bytes=1,
|
||||
replayable=True,
|
||||
origin="recorded",
|
||||
)
|
||||
|
||||
|
||||
class _Store:
|
||||
def get_session(self, session_id: str) -> SimpleNamespace:
|
||||
if session_id != RAV00_SESSION_ID:
|
||||
raise SessionNotFoundError(session_id)
|
||||
return SimpleNamespace(summary=_source())
|
||||
|
||||
|
||||
class _Admission:
|
||||
def __init__(
|
||||
self,
|
||||
definition: RecordedRunDefinition,
|
||||
catalog_sha256: str,
|
||||
) -> None:
|
||||
self.definition = definition
|
||||
self.catalog_sha256 = catalog_sha256
|
||||
|
||||
def intent(self, *, idempotency_key: str) -> ObservatoryRecordedJobIntent:
|
||||
return ObservatoryRecordedJobIntent(
|
||||
idempotency_key=idempotency_key,
|
||||
source_session_id=RAV00_SESSION_ID,
|
||||
source_catalog_sha256=self.catalog_sha256,
|
||||
source_bundle_sha256=SOURCE_BUNDLE_SHA256,
|
||||
source_capability_manifest_sha256=CAPABILITY_SHA256,
|
||||
setup_id=self.definition.setup_id,
|
||||
definition_sha256=self.definition.definition_sha256,
|
||||
)
|
||||
|
||||
|
||||
class _BindingService:
|
||||
def __init__(self, definition: RecordedRunDefinition) -> None:
|
||||
self.config = SimpleNamespace(setup=SimpleNamespace(setup_id=SETUP_ID))
|
||||
self.definition = definition
|
||||
self.catalog_sha256 = CATALOG_SHA256
|
||||
self.check_count = 0
|
||||
self.admit_count = 0
|
||||
|
||||
def check(
|
||||
self,
|
||||
*,
|
||||
source_session_id: str,
|
||||
setup_id: str,
|
||||
definition_sha256: str,
|
||||
) -> _Admission:
|
||||
self.check_count += 1
|
||||
return self._resolve(source_session_id, setup_id, definition_sha256)
|
||||
|
||||
def admit(
|
||||
self,
|
||||
*,
|
||||
source_session_id: str,
|
||||
setup_id: str,
|
||||
definition_sha256: str,
|
||||
) -> _Admission:
|
||||
self.admit_count += 1
|
||||
return self._resolve(source_session_id, setup_id, definition_sha256)
|
||||
|
||||
def _resolve(
|
||||
self,
|
||||
source_session_id: str,
|
||||
setup_id: str,
|
||||
definition_sha256: str,
|
||||
) -> _Admission:
|
||||
if (
|
||||
source_session_id != RAV00_SESSION_ID
|
||||
or setup_id != self.definition.setup_id
|
||||
or definition_sha256 != self.definition.definition_sha256
|
||||
):
|
||||
raise M49QueueBindingIntegrityError("not the exact M4.9 binding")
|
||||
return _Admission(self.definition, self.catalog_sha256)
|
||||
|
||||
|
||||
def _services(
|
||||
tmp_path: Path,
|
||||
) -> tuple[
|
||||
LaboratorySetupRegistry,
|
||||
_BindingService,
|
||||
ObservatoryRecordedJobQueue,
|
||||
RecordedRunDefinition,
|
||||
]:
|
||||
registry = LaboratorySetupRegistry.from_file(
|
||||
REGISTRY_PATH,
|
||||
repository_root=REPOSITORY_ROOT,
|
||||
)
|
||||
catalog = registry.catalog(_source())
|
||||
setups = cast(list[dict[str, Any]], catalog["setups"])
|
||||
definition_document = cast(dict[str, object], setups[0]["run_definition"])
|
||||
definition = RecordedRunDefinition(
|
||||
setup_id=SETUP_ID,
|
||||
definition_id=str(definition_document["definition_id"]),
|
||||
definition_version=int(definition_document["version"]),
|
||||
definition_sha256=str(definition_document["definition_sha256"]),
|
||||
source_adapter_id="ravnoves00-m49-source-pack",
|
||||
source_adapter_version=1,
|
||||
source_adapter_sha256="4" * 64,
|
||||
executor_release_id="m49-tgs-full-shadow-worker-release",
|
||||
executor_release_sha256="5" * 64,
|
||||
executor_image_sha256="6" * 64,
|
||||
model_release_ids=(),
|
||||
model_manifest_sha256="7" * 64,
|
||||
resource_profile_id="worker006-cpu-single-run-v1",
|
||||
resource_profile_sha256="8" * 64,
|
||||
checkpoint_policy="non-checkpointable",
|
||||
)
|
||||
binding = _BindingService(definition)
|
||||
queue = ObservatoryRecordedJobQueue(
|
||||
tmp_path,
|
||||
definitions=RecordedRunDefinitionRegistry((definition,)),
|
||||
)
|
||||
return registry, binding, queue, definition
|
||||
|
||||
|
||||
def test_exact_m49_preflight_is_queueable_and_submit_is_idempotently_queued(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
registry, binding, queue, definition = _services(tmp_path)
|
||||
app = FastAPI()
|
||||
app.include_router(
|
||||
build_observatory_router(
|
||||
_Store(), # type: ignore[arg-type]
|
||||
setup_registry=registry,
|
||||
recorded_binding_service=binding, # type: ignore[arg-type]
|
||||
recorded_job_queue=queue,
|
||||
)
|
||||
)
|
||||
client = TestClient(app)
|
||||
|
||||
preflight = client.post(
|
||||
"/api/v1/observatory/run-preflights",
|
||||
json={
|
||||
"schema_version": "missioncore.observatory-run-preflight-request/v1",
|
||||
"source_session_id": RAV00_SESSION_ID,
|
||||
"setup_id": SETUP_ID,
|
||||
"definition_sha256": definition.definition_sha256,
|
||||
},
|
||||
)
|
||||
|
||||
assert preflight.status_code == 200
|
||||
assert preflight.json()["outcome"] == "queueable"
|
||||
assert preflight.json()["submission_allowed"] is True
|
||||
assert next(
|
||||
check
|
||||
for check in preflight.json()["checks"]
|
||||
if check["check_id"] == "durable-queue"
|
||||
)["outcome"] == "pass"
|
||||
assert binding.check_count == 1
|
||||
assert binding.admit_count == 0
|
||||
|
||||
request = {
|
||||
"schema_version": "missioncore.observatory-recorded-run-submit/v1",
|
||||
"idempotency_key": "observatory-ui:m49:stable-request",
|
||||
"source_session_id": RAV00_SESSION_ID,
|
||||
"setup_id": SETUP_ID,
|
||||
}
|
||||
submitted = client.post("/api/v1/observatory/runs", json=request)
|
||||
binding.catalog_sha256 = "9" * 64
|
||||
repeated = client.post("/api/v1/observatory/runs", json=request)
|
||||
|
||||
assert submitted.status_code == 202
|
||||
assert repeated.status_code == 202
|
||||
assert submitted.json()["job_id"] == repeated.json()["job_id"]
|
||||
assert submitted.json()["state"] == "queued"
|
||||
assert submitted.json()["source"]["session_id"] == RAV00_SESSION_ID
|
||||
assert submitted.json()["setup"]["setup_id"] == SETUP_ID
|
||||
assert submitted.json()["executor"]["learned_models"] == []
|
||||
assert submitted.json()["priority"] == {
|
||||
"class": "recorded",
|
||||
"rank": 100,
|
||||
"server_owned": True,
|
||||
}
|
||||
assert binding.admit_count == 1
|
||||
|
||||
listed = client.get(
|
||||
"/api/v1/observatory/runs",
|
||||
params={"source_session_id": RAV00_SESSION_ID, "setup_id": SETUP_ID},
|
||||
)
|
||||
assert listed.status_code == 200
|
||||
assert listed.json()["schema_version"] == (
|
||||
"missioncore.observatory-recorded-job-list/v1"
|
||||
)
|
||||
assert [item["job_id"] for item in listed.json()["items"]] == [
|
||||
submitted.json()["job_id"]
|
||||
]
|
||||
assert queue.admission_gate().blocked is False
|
||||
|
||||
|
||||
def test_recorded_run_routes_fail_closed_when_queue_initialization_failed() -> None:
|
||||
registry = LaboratorySetupRegistry.from_file(
|
||||
REGISTRY_PATH,
|
||||
repository_root=REPOSITORY_ROOT,
|
||||
)
|
||||
app = FastAPI()
|
||||
app.include_router(
|
||||
build_observatory_router(
|
||||
_Store(), # type: ignore[arg-type]
|
||||
setup_registry=registry,
|
||||
recorded_job_queue_error="queue database is incompatible",
|
||||
)
|
||||
)
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.get(
|
||||
"/api/v1/observatory/runs",
|
||||
params={"source_session_id": RAV00_SESSION_ID, "setup_id": SETUP_ID},
|
||||
)
|
||||
|
||||
assert response.status_code == 503
|
||||
assert response.json()["detail"] == "Durable-очередь расчётов недоступна."
|
||||
@@ -127,6 +127,7 @@ def test_repository_setup_registry_keeps_real_definition_and_pre_definition_resu
|
||||
]
|
||||
rav00 = registry.catalog(_source(RAV00_SESSION_ID, "RAVNOVES00"))
|
||||
m49, current = rav00["setups"]
|
||||
assert m49["display_name"] == "M4.9T5 · TRAVEL TGS · CPU-only, без ML"
|
||||
assert m49["origin"] == "archived-definition"
|
||||
assert m49["compatibility"]["compatible"] is True
|
||||
assert m49["preflight"] == {
|
||||
@@ -153,6 +154,8 @@ def test_repository_setup_registry_keeps_real_definition_and_pre_definition_resu
|
||||
available_observatory_result_ids=frozenset({RAV004_RESULT_ID}),
|
||||
)
|
||||
existing = rav004["setups"][1]
|
||||
assert existing["display_name"] == "LAB V1 · EoMT Cityscapes Large 1024 + DDRNet-39"
|
||||
assert "полный TGS и независимый YOLOX отсутствуют" in existing["description"]
|
||||
assert existing["origin"] == "existing-result"
|
||||
assert existing["run_definition"] is None
|
||||
assert existing["preflight"]["outcome"] == "existing"
|
||||
|
||||
Reference in New Issue
Block a user