feat(observatory): ship modular AI inference labs

This commit is contained in:
DCCONSTRUCTIONS
2026-09-04 17:59:05 +03:00
parent eff60e490a
commit cada687173
145 changed files with 17651 additions and 1667 deletions
+45
View File
@@ -0,0 +1,45 @@
from __future__ import annotations
from pathlib import Path
from k1link.observatory.composition_runs import CompositionRunStore
from k1link.observatory.modular_composition import COMPOSITION_SCHEMA, ModuleRegistry
def test_operator_projection_can_be_renamed_and_hidden_without_changing_run(tmp_path: Path) -> None:
registry = ModuleRegistry.from_file(
Path(__file__).parents[1] / "config" / "observatory-ai-modules.json"
)
module = next(item for item in registry.modules if item.module_id == "tgs")
composition = registry.compose(
{
"schema_version": COMPOSITION_SCHEMA,
"selections": [
{
"group": module.group,
"module_id": module.module_id,
"module_sha256": module.sha256,
"parameters": {},
}
],
}
)
store = CompositionRunStore(tmp_path / "runs")
run = store.save(
source_session_id="source-1",
composition=composition,
setup_ids=("setup-1",),
job_ids=("job-1",),
idempotency_key="run-1",
created_at_utc="2026-09-04T12:00:00.000Z",
)
assert store.display_name(run.run_id) is None
assert store.list(source_session_id="source-1", include_hidden=False) == (run,)
assert store.rename_projection(run.run_id, " Маршрут у школы ") == "Маршрут у школы"
assert store.display_name(run.run_id) == "Маршрут у школы"
store.delete_projection(run.run_id)
assert store.list(source_session_id="source-1", include_hidden=False) == ()
assert store.list(source_session_id="source-1") == (run,)
assert store.get(run.run_id) == run
+174
View File
@@ -0,0 +1,174 @@
from __future__ import annotations
import hashlib
import json
from pathlib import Path
import pytest
from test_lidar_replay import _capture
from k1link.compute import lidar_replay
from k1link.compute.lidar_preparation import prepare_lidar_replay_pack_v2
from k1link.compute.lidar_replay import LidarReplayError
def test_warm_preparation_reuses_legacy_identity_without_source_decode(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
source = _capture(tmp_path)
producer = Path(lidar_replay.__file__)
producer_digest = hashlib.sha256(producer.read_bytes()).hexdigest()
original = lidar_replay.build_lidar_replay_pack_v2(source, tmp_path / "packs")
manifest_before = (original / "manifest.json").read_bytes()
closed = []
original_close = lidar_replay.LidarReplayPackV2.close
def no_decode(*args: object, **kwargs: object) -> None:
pytest.fail("cache hit must not decode the original capture")
def close(pack: lidar_replay.LidarReplayPackV2) -> None:
original_close(pack)
closed.append(not pack.arrays._arrays)
monkeypatch.setattr(lidar_replay, "_capture_arrays", no_decode)
monkeypatch.setattr(lidar_replay.LidarReplayPackV2, "close", close)
assert prepare_lidar_replay_pack_v2(source, tmp_path / "packs") == original
assert (original / "manifest.json").read_bytes() == manifest_before
assert json.loads(manifest_before)["identity"]["producer_sha256"] == producer_digest
assert hashlib.sha256(producer.read_bytes()).hexdigest() == producer_digest
assert closed == [True]
def test_cold_preparation_calls_existing_builder_once(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
source = _capture(tmp_path)
calls = []
original = lidar_replay._capture_arrays
def decode(path: Path):
calls.append(path)
return original(path)
monkeypatch.setattr(lidar_replay, "_capture_arrays", decode)
result = prepare_lidar_replay_pack_v2(source, tmp_path / "packs")
assert result.is_dir()
assert calls == [source]
@pytest.mark.parametrize("changed", ["metadata", "origin", "remove-origin", "raw", "session"])
def test_changed_source_never_reuses_previous_pack(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
changed: str,
) -> None:
source = _capture(tmp_path)
output = tmp_path / "packs"
old = prepare_lidar_replay_pack_v2(source, output)
if changed == "metadata":
path = source.with_name("mqtt.metadata.jsonl")
path.write_bytes(path.read_bytes().replace(b"5010000000", b"5009000000"))
elif changed == "origin":
path = source.with_name("mqtt.timeline.origin.json")
path.write_bytes(path.read_bytes().replace(b"5000000000", b"4999999999"))
elif changed == "remove-origin":
source.with_name("mqtt.timeline.origin.json").unlink()
elif changed == "raw":
# The cache boundary checks bytes before trying to decode invalid data.
source.write_bytes(source.read_bytes()[:-1] + b"x")
called = []
def build(*args: object, **kwargs: object) -> Path:
called.append((args, kwargs))
return output / "new-test-pack"
monkeypatch.setattr(lidar_replay, "build_lidar_replay_pack_v2", build)
result = prepare_lidar_replay_pack_v2(
source,
output,
session_id="other-session" if changed == "session" else None,
)
assert result != old
assert len(called) == 1
assert old.is_dir()
@pytest.mark.parametrize(
"artifact", ["lidar-replay.npz", "quality-report.json", "equivalence-report.json"]
)
def test_corrupt_matching_pack_fails_without_rebuild_or_overwrite(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
artifact: str,
) -> None:
source = _capture(tmp_path)
root = prepare_lidar_replay_pack_v2(source, tmp_path / "packs")
path = root / artifact
damaged = b"damaged-evidence"
path.write_bytes(damaged)
def forbidden(*args: object, **kwargs: object) -> None:
pytest.fail("must preserve corrupt evidence instead of overwriting")
monkeypatch.setattr(lidar_replay, "build_lidar_replay_pack_v2", forbidden)
with pytest.raises(LidarReplayError, match="artifact identity changed"):
prepare_lidar_replay_pack_v2(source, tmp_path / "packs")
assert path.read_bytes() == damaged
def test_report_threshold_collision_is_explicit_and_releases_arrays(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
source = _capture(tmp_path)
root = prepare_lidar_replay_pack_v2(source, tmp_path / "packs")
closed = []
original = lidar_replay.LidarReplayPackV2.close
def close(pack: lidar_replay.LidarReplayPackV2) -> None:
original(pack)
closed.append(not pack.arrays._arrays)
monkeypatch.setattr(lidar_replay.LidarReplayPackV2, "close", close)
with pytest.raises(LidarReplayError, match="threshold differs"):
prepare_lidar_replay_pack_v2(source, tmp_path / "packs", pose_coverage_threshold_ms=50)
assert closed == [True]
assert root.is_dir()
def test_source_mutation_during_cache_validation_is_rejected(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
source = _capture(tmp_path)
prepare_lidar_replay_pack_v2(source, tmp_path / "packs")
original = lidar_replay.LidarReplayPackV2.__init__
def changed(pack: lidar_replay.LidarReplayPackV2, root: Path) -> None:
original(pack, root)
source.with_name("mqtt.metadata.jsonl").touch()
monkeypatch.setattr(lidar_replay.LidarReplayPackV2, "__init__", changed)
with pytest.raises(LidarReplayError, match="changed during preparation"):
prepare_lidar_replay_pack_v2(source, tmp_path / "packs")
def test_missing_metadata_never_uses_cache(tmp_path: Path) -> None:
source = _capture(tmp_path)
prepare_lidar_replay_pack_v2(source, tmp_path / "packs")
source.with_name("mqtt.metadata.jsonl").unlink()
with pytest.raises(LidarReplayError, match="exact host timing"):
prepare_lidar_replay_pack_v2(source, tmp_path / "packs")
def test_symlink_manifest_is_not_followed(tmp_path: Path) -> None:
source = _capture(tmp_path)
root = prepare_lidar_replay_pack_v2(source, tmp_path / "packs")
manifest = root / "manifest.json"
saved = root / "saved-manifest.json"
manifest.rename(saved)
manifest.symlink_to(saved)
with pytest.raises(LidarReplayError, match="manifest cannot be a symlink"):
prepare_lidar_replay_pack_v2(source, tmp_path / "packs")
+1 -1
View File
@@ -707,7 +707,7 @@ def test_fixed_worker_stage_consumer_requires_manifested_metadata_member(
built_from.append(capture)
return worker_root
monkeypatch.setattr(source_module, "build_lidar_replay_pack_v2", fake_build)
monkeypatch.setattr(source_module, "prepare_lidar_replay_pack_v2", fake_build)
class _DeliveredSource:
def materialize(self, requested: SealedObservatoryRecordedJob) -> PortableWorkerSourceStage:
+508
View File
@@ -0,0 +1,508 @@
from __future__ import annotations
import json
from pathlib import Path
from types import SimpleNamespace
from fastapi import FastAPI
from fastapi.testclient import TestClient
from k1link.observatory.composition_runs import CompositionRunStore
from k1link.observatory.lab_view_profiles import PROFILE_SCHEMA, LabViewProfileStore
from k1link.observatory.modular_composition import COMPOSITION_SCHEMA, ModuleRegistry
from k1link.observatory.modular_composition_store import ModularCompositionStore
from k1link.observatory.source_admission import PortableSourceNotPreparedError
from k1link.web.modular_observatory_api import build_modular_observatory_router
def _client(tmp_path: Path) -> tuple[TestClient, dict]:
registry = ModuleRegistry.from_file(
Path(__file__).parents[1] / "config" / "observatory-ai-modules.json"
)
class Store:
def get_session(self, session_id: str) -> object:
return SimpleNamespace(summary=SimpleNamespace(session_id=session_id, lab=None))
app = FastAPI()
app.include_router(
build_modular_observatory_router(
store=Store(), # type: ignore[arg-type]
compositions=ModularCompositionStore(tmp_path / "compositions", registry),
view_profiles=LabViewProfileStore(tmp_path / "view-profiles"),
)
)
catalog = TestClient(app).get("/api/v1/observatory/ai-module-catalog").json()
return TestClient(app), catalog
def test_composition_projection_can_be_renamed_and_removed_through_api(
tmp_path: Path,
) -> None:
registry = ModuleRegistry.from_file(
Path(__file__).parents[1] / "config" / "observatory-ai-modules.json"
)
tgs = next(module for module in registry.modules if module.module_id == "tgs")
composition = registry.compose(
{
"schema_version": COMPOSITION_SCHEMA,
"selections": [
{
"group": tgs.group,
"module_id": tgs.module_id,
"module_sha256": tgs.sha256,
"parameters": {},
}
],
}
)
runs = CompositionRunStore(tmp_path / "runs")
run = runs.save(
source_session_id="source-1",
composition=composition,
setup_ids=("setup-1",),
job_ids=("job-1",),
idempotency_key="run-1",
created_at_utc="2026-09-04T12:00:00.000Z",
)
class Store:
def get_session(self, session_id: str) -> object:
return SimpleNamespace(summary=SimpleNamespace(session_id=session_id, lab=None))
app = FastAPI()
app.include_router(
build_modular_observatory_router(
store=Store(), # type: ignore[arg-type]
compositions=ModularCompositionStore(tmp_path / "compositions", registry),
composition_runs=runs,
)
)
client = TestClient(app)
renamed = client.patch(
f"/api/v1/observatory/ai-composition-runs/{run.run_id}",
json={
"schema_version": "missioncore.observatory-ai-composition-run-rename/v1",
"display_name": " Контрольный прогон ",
},
)
assert renamed.status_code == 200
assert renamed.json() == {
"schema_version": "missioncore.observatory-ai-composition-run-projection/v1",
"run_id": run.run_id,
"display_name": "Контрольный прогон",
}
assert runs.display_name(run.run_id) == "Контрольный прогон"
removed = client.delete(f"/api/v1/observatory/ai-composition-runs/{run.run_id}")
assert removed.status_code == 204
assert runs.list(source_session_id="source-1", include_hidden=False) == ()
assert runs.get(run.run_id) == run
def test_lab_view_profile_is_saved_against_the_exact_result(tmp_path: Path) -> None:
client, _catalog = _client(tmp_path)
result_id = "modular-composition-result-123"
endpoint = f"/api/v1/observatory/lab-view-profiles/{result_id}"
assert client.get(endpoint).status_code == 404
document = {
"schema_version": PROFILE_SCHEMA,
"result_id": result_id,
"scene_settings": {
"point_size": 128.5,
"accumulation_seconds": 50_000,
"color_mode": "height",
"palette": "viridis",
"show_grid": False,
"show_labels": True,
"show_camera_frustums": False,
},
}
saved = client.put(endpoint, json=document)
assert saved.status_code == 200
assert saved.json()["scene_settings"] == document["scene_settings"]
assert saved.json()["updated_at_utc"].endswith("Z")
assert client.get(endpoint).json() == saved.json()
foreign = {**document, "result_id": "another-result"}
rejected = client.put(endpoint, json=foreign)
assert rejected.status_code == 422
assert rejected.json()["detail"] == "Профиль отображения относится к другой LAB."
def test_catalog_and_idempotent_independent_ddrnet_composition(tmp_path: Path) -> None:
client, catalog = _client(tmp_path)
assert catalog["schema_version"] == "missioncore.observatory-ai-module-catalog/v1"
segmenters = next(group for group in catalog["groups"] if group["group"] == "segmentation")
assert [module["docker_name"] for module in segmenters["modules"]] == [
"ndc-mission-core-ai-module-ddrnet",
"ndc-mission-core-ai-module-eomt",
]
geometry = next(group for group in catalog["groups"] if group["group"] == "geometry")
assert [module["docker_name"] for module in geometry["modules"]] == [
"ndc-mission-core-ai-module-tgs"
]
detection = next(group for group in catalog["groups"] if group["group"] == "detection")
assert [module["docker_name"] for module in detection["modules"]] == [
"ndc-mission-core-ai-module-rf-detr"
]
ranges = next(group for group in catalog["groups"] if group["group"] == "range")
assert [module["docker_name"] for module in ranges["modules"]] == [
"ndc-mission-core-ai-module-object-distance"
]
ddrnet = segmenters["modules"][0]
request = {
"schema_version": COMPOSITION_SCHEMA,
"source_session_id": "source-1",
"idempotency_key": "ai-layer:source-1:ddrnet",
"selections": [
{
"group": "segmentation",
"module_id": "ddrnet",
"module_sha256": ddrnet["module_sha256"],
"parameters": {},
}
],
}
first = client.post("/api/v1/observatory/ai-compositions", json=request)
second = client.post("/api/v1/observatory/ai-compositions", json=request)
assert first.status_code == second.status_code == 200
assert first.json()["created"] is True and second.json()["created"] is False
assert first.json()["composition_sha256"] == second.json()["composition_sha256"]
assert first.json()["composition"]["outputs"] == ["segmentation.mask"]
stored = list((tmp_path / "compositions").glob("*.json"))
assert len(stored) == 1
assert json.loads(stored[0].read_bytes())["execution"]["max_parallel_nodes"] == 1
def test_server_rejects_two_segmentation_providers(tmp_path: Path) -> None:
client, catalog = _client(tmp_path)
modules = next(group for group in catalog["groups"] if group["group"] == "segmentation")[
"modules"
]
response = client.post(
"/api/v1/observatory/ai-compositions",
json={
"schema_version": COMPOSITION_SCHEMA,
"source_session_id": "source-1",
"idempotency_key": "ai-layer:source-1:both",
"selections": [
{
"group": "segmentation",
"module_id": module["module_id"],
"module_sha256": module["module_sha256"],
"parameters": {},
}
for module in modules
],
},
)
assert response.status_code == 409
assert response.json()["detail"] == "В одном слое можно выбрать только один AI-модуль."
def test_tgs_browser_numeric_default_is_accepted_and_bad_value_is_explained(
tmp_path: Path,
) -> None:
client, catalog = _client(tmp_path)
tgs = next(
module
for group in catalog["groups"]
for module in group["modules"]
if module["module_id"] == "tgs"
)
def request(value: int) -> dict:
return {
"schema_version": COMPOSITION_SCHEMA,
"source_session_id": "source-1",
"idempotency_key": f"ai-layer:source-1:tgs:{value}",
"selections": [
{
"group": "geometry",
"module_id": "tgs",
"module_sha256": tgs["module_sha256"],
"parameters": {"history-seconds": value},
}
],
}
accepted = client.post("/api/v1/observatory/ai-compositions", json=request(1))
assert accepted.status_code == 200
rejected = client.post("/api/v1/observatory/ai-compositions", json=request(2))
assert rejected.status_code == 409
assert rejected.json()["detail"] == (
"Параметры выбранного AI-модуля устарели. "
"Закройте окно, откройте его снова и повторите расчёт."
)
def test_tgs_and_object_distance_dispatch_as_two_independent_worker_jobs(tmp_path: Path) -> None:
registry = ModuleRegistry.from_file(
Path(__file__).parents[1] / "config" / "observatory-ai-modules.json"
)
class Store:
def get_session(self, session_id: str) -> object:
return SimpleNamespace(summary=SimpleNamespace(session_id=session_id, lab=None))
class Definitions:
def resolve_setup(self, setup_id: str) -> object:
return SimpleNamespace(definition_sha256=(setup_id.encode().hex() + "0" * 64)[:64])
submitted: list[str] = []
class Binding:
def check(self, **kwargs: object) -> object:
return SimpleNamespace(check_sha256="c" * 64)
def submit(self, **kwargs: object) -> tuple[object, bool]:
setup_id = str(kwargs["setup_id"])
submitted.append(setup_id)
return SimpleNamespace(as_dict=lambda: {"setup_id": setup_id}), True
app = FastAPI()
app.include_router(
build_modular_observatory_router(
store=Store(), # type: ignore[arg-type]
compositions=ModularCompositionStore(tmp_path / "compositions", registry),
definitions=Definitions(), # type: ignore[arg-type]
binding=Binding(), # type: ignore[arg-type]
)
)
catalog = TestClient(app).get("/api/v1/observatory/ai-module-catalog").json()
by_id = {
module["module_id"]: {**module, "group": group["group"]}
for group in catalog["groups"]
for module in group["modules"]
}
response = TestClient(app).post(
"/api/v1/observatory/ai-compositions",
json={
"schema_version": COMPOSITION_SCHEMA,
"source_session_id": "source-1",
"idempotency_key": "ai-layer:source-1:tgs-and-range",
"selections": [
{
"group": by_id[module_id]["group"],
"module_id": module_id,
"module_sha256": by_id[module_id]["module_sha256"],
"parameters": {},
}
for module_id in ("tgs", "rf-detr", "object-distance")
],
},
)
assert response.status_code == 200
assert response.json()["dispatch"]["ready"] is True
assert submitted == ["m49-tgs-portable-v2", "ai-range-object-distance-v1"]
assert response.json()["dispatch"]["setup_ids"] == submitted
def test_existing_record_module_is_rejected_before_a_second_worker_submission(
tmp_path: Path,
) -> None:
registry = ModuleRegistry.from_file(
Path(__file__).parents[1] / "config" / "observatory-ai-modules.json"
)
tgs = next(module for module in registry.modules if module.module_id == "tgs")
class Store:
def get_session(self, session_id: str) -> object:
return SimpleNamespace(summary=SimpleNamespace(session_id=session_id, lab=None))
class Definitions:
def resolve_setup(self, setup_id: str) -> object:
return SimpleNamespace(definition_sha256="d" * 64)
class Binding:
def check(self, **kwargs: object) -> object:
raise AssertionError("duplicate must be rejected before source preparation")
def submit(self, **kwargs: object) -> tuple[object, bool]:
raise AssertionError("duplicate must not reach Worker submission")
class Queue:
def list_jobs(self, **kwargs: object) -> list[object]:
assert kwargs == {
"source_session_id": "source-1",
"setup_id": "m49-tgs-portable-v2",
"definition_sha256": "d" * 64,
"limit": 20,
}
return [
SimpleNamespace(
job_id="already-calculated",
state="succeeded",
)
]
app = FastAPI()
app.include_router(
build_modular_observatory_router(
store=Store(), # type: ignore[arg-type]
compositions=ModularCompositionStore(tmp_path / "compositions", registry),
definitions=Definitions(), # type: ignore[arg-type]
binding=Binding(), # type: ignore[arg-type]
queue=Queue(), # type: ignore[arg-type]
)
)
response = TestClient(app).post(
"/api/v1/observatory/ai-compositions",
json={
"schema_version": COMPOSITION_SCHEMA,
"source_session_id": "source-1",
"idempotency_key": "ai-layer:source-1:tgs-again",
"selections": [
{
"group": "geometry",
"module_id": "tgs",
"module_sha256": tgs.sha256,
"parameters": {},
}
],
},
)
assert response.status_code == 409
assert response.json()["detail"] == (
"Эта конфигурация уже рассчитана или поставлена в очередь. Выберите другую конфигурацию."
)
def test_existing_module_is_reused_inside_a_new_composition(tmp_path: Path) -> None:
registry = ModuleRegistry.from_file(
Path(__file__).parents[1] / "config" / "observatory-ai-modules.json"
)
class Store:
def get_session(self, session_id: str) -> object:
return SimpleNamespace(summary=SimpleNamespace(session_id=session_id, lab=None))
class Definitions:
def resolve_setup(self, setup_id: str) -> object:
return SimpleNamespace(definition_sha256=(setup_id.encode().hex() + "0" * 64)[:64])
existing_tgs = SimpleNamespace(
job_id="existing-tgs",
setup_id="m49-tgs-portable-v2",
state="succeeded",
as_dict=lambda: {"setup_id": "m49-tgs-portable-v2", "reused": True},
)
submitted: list[str] = []
class Queue:
def list_jobs(self, **kwargs: object) -> list[object]:
if kwargs["setup_id"] == "m49-tgs-portable-v2":
return [existing_tgs]
return []
class Binding:
def check(self, **kwargs: object) -> object:
assert kwargs["setup_id"] == "ai-detection-rf-detr-v1"
return SimpleNamespace(check_sha256="c" * 64)
def submit(self, **kwargs: object) -> tuple[object, bool]:
setup_id = str(kwargs["setup_id"])
submitted.append(setup_id)
return SimpleNamespace(
setup_id=setup_id,
as_dict=lambda: {"setup_id": setup_id, "reused": False},
), True
modules = {item.module_id: item for item in registry.modules}
app = FastAPI()
app.include_router(
build_modular_observatory_router(
store=Store(), # type: ignore[arg-type]
compositions=ModularCompositionStore(tmp_path / "compositions", registry),
definitions=Definitions(), # type: ignore[arg-type]
binding=Binding(), # type: ignore[arg-type]
queue=Queue(), # type: ignore[arg-type]
)
)
response = TestClient(app).post(
"/api/v1/observatory/ai-compositions",
json={
"schema_version": COMPOSITION_SCHEMA,
"source_session_id": "source-1",
"idempotency_key": "ai-layer:source-1:tgs-and-rf-detr",
"selections": [
{
"group": modules[module_id].group,
"module_id": module_id,
"module_sha256": modules[module_id].sha256,
"parameters": {},
}
for module_id in ("tgs", "rf-detr")
],
},
)
assert response.status_code == 200
assert submitted == ["ai-detection-rf-detr-v1"]
assert response.json()["dispatch"]["setup_ids"] == [
"m49-tgs-portable-v2",
"ai-detection-rf-detr-v1",
]
assert [job["reused"] for job in response.json()["dispatch"]["jobs"]] == [True, False]
assert "готовые результаты использованы повторно" in response.json()["dispatch"]["reason"]
def test_composition_prepares_an_unopened_recording_before_submit(tmp_path: Path) -> None:
registry = ModuleRegistry.from_file(
Path(__file__).parents[1] / "config" / "observatory-ai-modules.json"
)
detector = next(module for module in registry.modules if module.module_id == "rf-detr")
class Store:
def get_session(self, session_id: str) -> object:
return SimpleNamespace(summary=SimpleNamespace(session_id=session_id, lab=None))
class Definitions:
def resolve_setup(self, setup_id: str) -> object:
return SimpleNamespace(definition_sha256="d" * 64)
calls: list[str] = []
class Binding:
def check(self, **kwargs: object) -> object:
calls.append("check")
raise PortableSourceNotPreparedError("camera manifest is not prepared")
def prepare_check(self, **kwargs: object) -> object:
calls.append("prepare-check")
return SimpleNamespace(check_sha256="c" * 64)
def submit(self, **kwargs: object) -> tuple[object, bool]:
calls.append("submit")
return SimpleNamespace(as_dict=lambda: {"setup_id": kwargs["setup_id"]}), True
app = FastAPI()
app.include_router(
build_modular_observatory_router(
store=Store(), # type: ignore[arg-type]
compositions=ModularCompositionStore(tmp_path / "compositions", registry),
definitions=Definitions(), # type: ignore[arg-type]
binding=Binding(), # type: ignore[arg-type]
)
)
response = TestClient(app).post(
"/api/v1/observatory/ai-compositions",
json={
"schema_version": COMPOSITION_SCHEMA,
"source_session_id": "source-1",
"idempotency_key": "ai-layer:source-1:prepare-camera",
"selections": [
{
"group": "detection",
"module_id": "rf-detr",
"module_sha256": detector.sha256,
"parameters": {},
}
],
},
)
assert response.status_code == 200
assert response.json()["dispatch"]["ready"] is True
assert calls == ["check", "prepare-check", "submit"]
@@ -0,0 +1,221 @@
from __future__ import annotations
import hashlib
import json
from pathlib import Path
import pytest
from k1link.observatory.modular_composition import (
COMPOSITION_SCHEMA,
CompositionError,
ModuleRegistry,
ModuleSpec,
node_input_identity,
)
from k1link.observatory.modular_node_cache import ModularNodeCache
def _digest(value: str) -> str:
return hashlib.sha256(value.encode()).hexdigest()
def _module(
module_id: str,
group: str,
requires: tuple[str, ...],
provides: tuple[str, ...],
*,
optional_inputs: tuple[str, ...] = (),
state_policy: str = "stateless",
) -> ModuleSpec:
return ModuleSpec(
module_id=module_id,
label=module_id,
group=group,
image_sha256=_digest(module_id + "-image"),
implementation_sha256=_digest(module_id + "-code"),
model_sha256=(
_digest(module_id + "-model") if group in {"segmentation", "detection"} else None
),
contract_sha256=_digest(module_id + "-contract"),
requires=requires,
provides=provides,
optional_inputs=optional_inputs,
parameter_choices_json=json.dumps({"cadence": [1, 2]}).encode(),
defaults_json=json.dumps({"cadence": 1}).encode(),
state_policy=state_policy,
)
def _registry() -> tuple[ModuleRegistry, ModuleSpec, ModuleSpec]:
camera = _module("camera-source", "preparation", ("source.camera",), ("camera.frames",))
ddrnet = _module("ddrnet", "segmentation", ("camera.frames",), ("segmentation.mask",))
eomt = _module("eomt", "segmentation", ("camera.frames",), ("segmentation.mask",))
detector = _module("rf-detr", "detection", ("camera.frames",), ("detections.2d",))
distance = _module(
"object-distance",
"range",
("detections.2d", "source.calibration", "source.lidar", "source.pose"),
("objects.ranged",),
)
motion = _module(
"object-motion",
"motion",
("objects.ranged",),
("objects.motion",),
state_policy="causal-reset-at-source-start",
)
policy = _module(
"policy-shadow",
"policy",
("objects.motion",),
("policy.observation",),
optional_inputs=("segmentation.mask",),
)
return ModuleRegistry((camera, ddrnet, eomt, detector, distance, motion, policy)), ddrnet, eomt
def _selection(*modules: ModuleSpec) -> dict:
return {
"schema_version": COMPOSITION_SCHEMA,
"selections": [
{
"group": module.group,
"module_id": module.module_id,
"module_sha256": module.sha256,
"parameters": {},
}
for module in modules
],
}
def test_ddrnet_and_eomt_are_independent_alternative_compositions() -> None:
registry, ddrnet, eomt = _registry()
ddr = registry.compose(_selection(ddrnet))
eom = registry.compose(_selection(eomt))
assert [node.module.module_id for node in ddr.nodes] == ["camera-source", "ddrnet"]
assert [node.module.module_id for node in eom.nodes] == ["camera-source", "eomt"]
assert ddr.sha256 != eom.sha256
assert ddr.outputs == eom.outputs == ("segmentation.mask",)
assert ddrnet.docker_name == "ndc-mission-core-ai-module-ddrnet"
assert eomt.docker_name == "ndc-mission-core-ai-module-eomt"
def test_installed_tgs_is_an_independent_point_cloud_composition() -> None:
registry = ModuleRegistry.from_file(
Path(__file__).parents[1] / "config" / "observatory-ai-modules.json"
)
tgs = next(module for module in registry.modules if module.module_id == "tgs")
composition = registry.compose(_selection(tgs))
assert [node.module.module_id for node in composition.nodes] == ["tgs"]
assert composition.source_capabilities == (
"source.calibration",
"source.lidar",
"source.pose",
)
assert composition.outputs == ("geometry.costmap", "geometry.ground")
assert tgs.docker_name == "ndc-mission-core-ai-module-tgs"
def test_installed_detection_range_and_tgs_keep_explicit_dependencies() -> None:
registry = ModuleRegistry.from_file(
Path(__file__).parents[1] / "config" / "observatory-ai-modules.json"
)
modules = {module.module_id: module for module in registry.modules}
detector = registry.compose(_selection(modules["rf-detr"]))
assert [node.module.module_id for node in detector.nodes] == ["camera-source", "rf-detr"]
assert modules["rf-detr"].docker_name == "ndc-mission-core-ai-module-rf-detr"
with pytest.raises(CompositionError, match="select a module providing detections.2d"):
registry.compose(_selection(modules["object-distance"]))
ranged = registry.compose(_selection(modules["rf-detr"], modules["object-distance"]))
assert [node.module.module_id for node in ranged.nodes] == [
"camera-source",
"rf-detr",
"object-distance",
]
assert modules["object-distance"].docker_name == ("ndc-mission-core-ai-module-object-distance")
combined = registry.compose(
_selection(modules["tgs"], modules["rf-detr"], modules["object-distance"])
)
assert [node.module.module_id for node in combined.nodes] == [
"camera-source",
"tgs",
"rf-detr",
"object-distance",
]
assert combined.source_capabilities == (
"source.calibration",
"source.camera",
"source.lidar",
"source.pose",
)
def test_two_segmenters_and_hidden_analytical_dependency_are_rejected() -> None:
registry, ddrnet, eomt = _registry()
with pytest.raises(CompositionError, match="only one provider"):
registry.compose(_selection(ddrnet, eomt))
policy = next(module for module in registry.modules if module.module_id == "policy-shadow")
with pytest.raises(CompositionError, match="select a module providing objects.motion"):
registry.compose(_selection(policy))
def test_detector_distance_motion_policy_graph_is_topological_without_segmentation() -> None:
registry, _, _ = _registry()
chosen = [
module
for module in registry.modules
if module.module_id in {"rf-detr", "object-distance", "object-motion", "policy-shadow"}
]
graph = registry.compose(_selection(*chosen))
assert [node.module.module_id for node in graph.nodes] == [
"camera-source",
"rf-detr",
"object-distance",
"object-motion",
"policy-shadow",
]
assert "source.lidar" in graph.source_capabilities
assert "segmentation.mask" not in dict(graph.nodes[-1].inputs)
def test_node_identity_ignores_other_composition_nodes_but_binds_temporal_state() -> None:
registry, ddrnet, _ = _registry()
graph = registry.compose(_selection(ddrnet))
node = graph.nodes[-1]
identity = node_input_identity(node, {"camera.frames": _digest("prepared-camera")})
assert "job_id" not in json.dumps(identity)
assert "composition" not in json.dumps(identity)
assert identity == node_input_identity(node, {"camera.frames": _digest("prepared-camera")})
motion = next(module for module in registry.modules if module.module_id == "object-motion")
motion_graph = registry.compose(
_selection(
*(
module
for module in registry.modules
if module.module_id in {"rf-detr", "object-distance", "object-motion"}
)
)
)
motion_node = next(node for node in motion_graph.nodes if node.module is motion)
with pytest.raises(CompositionError, match="immutable digest"):
node_input_identity(motion_node, {"objects.ranged": _digest("ranges")})
def test_node_cache_reuses_exact_bytes_and_treats_corruption_as_miss(tmp_path: Path) -> None:
output = tmp_path / "output"
output.mkdir()
(output / "result.json").write_text('{"ok":true}', encoding="utf-8")
identity = {"schema_version": "test/v1", "input": _digest("source")}
cache = ModularNodeCache(tmp_path / "cache")
sealed = cache.seal(identity, output, metadata={"computed": True})
hit = cache.lookup(identity)
assert hit is not None and hit.result_sha256 == sealed.result_sha256
assert hit.root.joinpath("result.json").read_text() == '{"ok":true}'
hit.root.joinpath("result.json").chmod(0o644)
hit.root.joinpath("result.json").write_text("damaged")
assert cache.lookup(identity) is None
assert output.joinpath("result.json").read_text() == '{"ok":true}'
+84
View File
@@ -0,0 +1,84 @@
from pathlib import Path
import pytest
from k1link.observatory.domain_ontology import (
ObservatoryDomainOntology,
ObservatoryOntologyError,
)
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
def test_full_observatory_composition_projects_exact_label_and_pane_layers() -> None:
ontology = ObservatoryDomainOntology.from_file(
REPOSITORY_ROOT / "config" / "observatory-domain-ontology.json"
)
projection = ontology.project_module_ids(("object-distance", "tgs", "rf-detr", "ddrnet"))
assert projection["configuration_label"] == (
"DDRNet-39 · GOOSE · RF-DETR Large · TRAVEL TGS · Дистанция до объектов · K1 LiDAR"
)
assert [layer["layer_id"] for layer in projection["viewer_layers"]] == [
"camera.source",
"camera.ddrnet",
"camera.detections",
"spatial.source-points",
"spatial.local-slam",
"spatial.tgs",
]
entity_ids = {item["id"] for item in ontology.document["entities"]}
relation_ids = {item["id"] for item in ontology.document["relations"]}
assert {
"mission.transport-unit",
"mission.equipment-unit",
"observatory.equipment-mount",
"observatory.capture-profile",
"observatory.recorded-session",
"observatory.module-version",
"observatory.container-image",
"observatory.worker-node",
"observatory.lab-view-profile",
} <= entity_ids
assert {
"observatory.recorded-session.captured_on_transport",
"observatory.recorded-session.captured_with_equipment",
"observatory.recorded-session.uses_equipment_mount",
"observatory.recorded-session.uses_capture_profile",
"observatory.composition-run.uses_recorded_session",
"observatory.module-version.implemented_by_image",
"observatory.container-image.installed_on_worker",
"observatory.recorded-job.executes_on_worker",
"observatory.lab-projection.has_view_profile",
} <= relation_ids
def test_observatory_ontology_rejects_unprojected_module() -> None:
ontology = ObservatoryDomainOntology.from_file(
REPOSITORY_ROOT / "config" / "observatory-domain-ontology.json"
)
with pytest.raises(ObservatoryOntologyError, match="has no ontology projection"):
ontology.project_module_ids(("unknown-module",))
def test_single_modules_project_only_the_viewports_they_own() -> None:
ontology = ObservatoryDomainOntology.from_file(
REPOSITORY_ROOT / "config" / "observatory-domain-ontology.json"
)
ddrnet = ontology.project_module_ids(("ddrnet",))
assert [layer["layer_id"] for layer in ddrnet["viewer_layers"]] == [
"camera.source",
"camera.ddrnet",
]
assert {layer["pane_id"] for layer in ddrnet["viewer_layers"]} == {"camera"}
tgs = ontology.project_module_ids(("tgs",))
assert [layer["layer_id"] for layer in tgs["viewer_layers"]] == [
"spatial.source-points",
"spatial.local-slam",
"spatial.tgs",
]
assert {layer["pane_id"] for layer in tgs["viewer_layers"]} == {"spatial"}
+3 -7
View File
@@ -46,9 +46,7 @@ def test_k1_equipment_and_capture_profiles_have_exact_content_identities() -> No
assert capture.equipment == equipment
for definition in _definition_registry().definitions:
assert (
registry.compatible_profile_for_requirements(
definition.source_requirements.as_dict()
)
registry.compatible_profile_for_requirements(definition.source_requirements.as_dict())
== capture
)
@@ -111,12 +109,10 @@ def test_other_equipment_is_blocked_before_any_profile_probe() -> None:
catalog = projector.catalog(source)
assert calls == []
assert len(catalog["setups"]) == 2
assert len(catalog["setups"]) == 6
for setup in catalog["setups"]:
assert setup["source_compatibility"]["compatible"] is False
assert setup["source_compatibility"]["reason_code"] == (
"equipment-model-mismatch"
)
assert setup["source_compatibility"]["reason_code"] == ("equipment-model-mismatch")
assert setup["preflight"]["submission_allowed"] is False
@@ -0,0 +1,73 @@
"""No model/runtime side effects in installer admission tests."""
import importlib.util
import json
import sys
from pathlib import Path
import pytest
from test_observatory_source_reuse_install import FakeEngine as SourceEngine
SCRIPTS = Path(__file__).parents[1] / "experiments/perception/worker/observatory_portable"
@pytest.fixture
def installer(monkeypatch):
monkeypatch.syspath_prepend(str(SCRIPTS))
spec = importlib.util.spec_from_file_location(
"recorded_heartbeat_installer", SCRIPTS / "install_recorded_heartbeat_recovery.py"
)
module = importlib.util.module_from_spec(spec)
monkeypatch.setitem(sys.modules, spec.name, module)
spec.loader.exec_module(module)
return module
class Engine(SourceEngine):
busy = False
def execute_json(self, name, source):
if source == self.installer.probe():
return self.installer.BEFORE
readiness = super().execute_json(name, source)
if self.busy:
readiness["recorded_jobs_by_state"]["reconciliation-required"] = 1
return readiness
def test_two_file_payload_is_hashed_and_cannot_gain_paths(installer, tmp_path):
root = tmp_path / "payload"
installer.pack(Path(__file__).parents[1], root)
assert set(installer.payload_files(root)) == {"worker_agent.py", "worker_http_transport.py"}
path = root / "payload.json"
document = json.loads(path.read_bytes())
document["files"]["../outside.py"] = "0" * 64
path.write_text(json.dumps(document))
with pytest.raises(ValueError, match="file set changed"):
installer.payload_files(root)
def test_plan_and_exact_cutover_fence_preserve_compute_and_resources(installer, tmp_path):
root = tmp_path / "payload"
installer.pack(Path(__file__).parents[1], root)
engine = Engine(installer)
plan = installer.plan(engine, root)
assert plan["compute_packages_changed"] is False
with pytest.raises(ValueError, match="plan changed"):
installer.apply(engine, root, "0" * 64, tmp_path / "release")
assert engine.requests == [] and not (tmp_path / "release").exists()
target = plan["targets"][0]
installer.fence(engine, target)
engine.rows[target["name"]]["HostConfig"]["Memory"] = 999
with pytest.raises(ValueError, match="changed since plan"):
installer.fence(engine, target)
def test_quarantined_owner_blocks_agent_replacement(installer, tmp_path):
root = tmp_path / "payload"
installer.pack(Path(__file__).parents[1], root)
engine = Engine(installer)
engine.busy = True
with pytest.raises(ValueError, match="not idle"):
installer.plan(engine, root)
assert engine.requests == []
@@ -0,0 +1,158 @@
"""Transient control-plane recovery never re-executes a model or changes owner."""
from __future__ import annotations
import json
from pathlib import Path
from threading import Event, Thread
import httpx
import pytest
from test_observatory_worker_agent import (
BlockingExecutor,
FakeTransport,
_enqueue,
_heartbeat_agent,
_identity,
_queue,
)
from test_observatory_worker_http_transport import (
BEARER_TOKEN,
CLAIM_TOKEN,
JOB_ID,
_cache_claim,
_claim_response,
)
from k1link.observatory.worker_agent import (
ObservatoryWorkerClaimRejectedError,
ObservatoryWorkerTransientTransportError,
_ClaimHeartbeat,
_RecordedJobPayload,
_seal_job,
)
from k1link.observatory.worker_http_transport import (
ObservatoryWorkerHttpError,
ObservatoryWorkerHttpGateway,
ObservatoryWorkerHttpTransientError,
)
@pytest.mark.parametrize("lost_ack", [False, True])
def test_transient_loss_retries_exact_sequence_without_rerunning_compute(tmp_path, lost_ack):
queue = _queue(tmp_path)
job_id = _enqueue(queue)
attempts = []
accepted = Event()
class Transport(FakeTransport):
def renew_claim(self, **kwargs):
attempts.append(kwargs.copy())
if len(attempts) == 1:
if lost_ack:
super().renew_claim(**kwargs)
raise ObservatoryWorkerTransientTransportError("synthetic network loss")
result = super().renew_claim(**kwargs)
accepted.set()
return result
entered, release = Event(), Event()
transport = Transport(queue)
agent = _heartbeat_agent(transport, BlockingExecutor(entered=entered, release=release))
reports = []
thread = Thread(target=lambda: reports.append(agent.run_once()))
thread.start()
try:
assert entered.wait(2) and accepted.wait(2)
finally:
release.set()
thread.join(2)
assert not thread.is_alive()
assert reports[0].state == "succeeded"
assert attempts[0] == attempts[1]
assert transport.starts == [job_id]
assert len(transport.successes) == 1
assert queue.get(job_id).claim_generation == 1
@pytest.mark.parametrize("failure", ["expired", "forbidden", "stopping", "late-ack"])
def test_uncertain_lease_never_becomes_success(tmp_path, failure):
queue = _queue(tmp_path)
job_id = _enqueue(queue)
transport = FakeTransport(queue)
claim = transport.claim_next(
claimant_id="worker-006",
claim_request_id="heartbeat-budget",
supported_executor_identities=(_identity(),),
)
started = transport.start(
claimant_id="worker-006",
job_id=job_id,
claim_token=claim["claim_token"],
)
now, calls = [0.0], []
heartbeat = _ClaimHeartbeat(
transport=transport,
job=_seal_job(_RecordedJobPayload.model_validate(started)),
claim_token=claim["claim_token"],
interval_seconds=0.01,
stop_timeout_seconds=1.0,
clock=lambda: now[0],
)
class FailingTransport:
def renew_claim(self, **kwargs):
calls.append(kwargs)
if failure == "forbidden":
raise ObservatoryWorkerClaimRejectedError("changed generation")
if failure == "stopping":
heartbeat._stop.set()
else:
now[0] += 3601
if failure == "late-ack":
return transport.renew_claim(**kwargs)
raise ObservatoryWorkerTransientTransportError("timeout")
heartbeat._transport = FailingTransport()
heartbeat._run()
assert heartbeat.failed
assert len(calls) == 1
assert transport.successes == []
@pytest.mark.parametrize("status", [408, 429, 500, 502, 503, 504, "timeout", 401, 403, 409, 302])
def test_http_renewal_classifies_retryable_failures_and_bounds_io(tmp_path: Path, status):
attempts = []
def handle(request):
if not request.url.path.endswith("/lease/renew"):
return _claim_response()
attempts.append(json.loads(request.content))
assert request.extensions["timeout"] == dict.fromkeys(
("connect", "read", "write", "pool"),
5.0,
)
if status == "timeout":
raise httpx.ReadTimeout("fixture", request=request)
return httpx.Response(status, json={})
with ObservatoryWorkerHttpGateway(
base_url="http://127.0.0.1:8000",
bearer_token=BEARER_TOKEN,
work_root=tmp_path,
transport=httpx.MockTransport(handle),
) as gateway:
_cache_claim(gateway)
with pytest.raises(ObservatoryWorkerHttpError) as error:
gateway.renew_claim(
claimant_id="worker-006",
job_id=JOB_ID,
claim_token=CLAIM_TOKEN,
claim_generation=1,
heartbeat_sequence=1,
)
assert isinstance(error.value, ObservatoryWorkerHttpTransientError) == (
status in {408, 429, 500, 502, 503, 504, "timeout"}
)
# The transport itself must not replay uploads or other mutations.
assert len(attempts) == 1
@@ -258,8 +258,7 @@ def test_generic_fixed_stack_runs_topologically_and_returns_verified_package(
]
writer_launch = launches[-1]
assert any(
mount.container_path == "/missioncore/input/steps/compute-step"
and mount.read_only
mount.container_path == "/missioncore/input/steps/compute-step" and mount.read_only
for mount in writer_launch.mounts
)
assert draft.result_id == "portable-result-generic-runner"
@@ -268,6 +267,16 @@ def test_generic_fixed_stack_runs_topologically_and_returns_verified_package(
for launch in launches:
writable = [mount for mount in launch.mounts if not mount.read_only]
assert [mount.container_path for mount in writable] == ["/missioncore/output"]
plan_mount = next(
mount for mount in launch.mounts if mount.container_path == "/missioncore/plan"
)
assert plan_mount.read_only
plan_root = Path(plan_mount.engine_path)
assert plan_root.is_dir()
assert (
json.loads((plan_root / "run-plan.json").read_bytes())["runtime_plan"]["job_id"]
== plan.job_id
)
def test_generic_docker_launcher_uses_hardened_one_shot_contract() -> None:
@@ -298,10 +307,10 @@ def test_generic_docker_launcher_uses_hardened_one_shot_contract() -> None:
package_id=package.package_id,
container=package.containers[0],
mounts=(
InstalledLabDockerMount("/engine/plan.json", "/missioncore/input/run-plan.json", True),
InstalledLabDockerMount("/engine/source", "/missioncore/input/source", True),
InstalledLabDockerMount("/engine/output", "/missioncore/output", False),
InstalledLabDockerMount("/engine/asset", "/missioncore/package/assets/runner", True),
InstalledLabDockerMount("/engine/plan", "/missioncore/plan", True),
),
labels={
"com.nodedc.authority": "observation-only",
@@ -309,8 +318,10 @@ def test_generic_docker_launcher_uses_hardened_one_shot_contract() -> None:
"com.nodedc.definition-sha256": package.definition_sha256,
"com.nodedc.job-id": f"observatory-run-{'1' * 32}",
"com.nodedc.managed-by": "mission-core-worker",
"com.nodedc.module-id": package.containers[0].container_id,
"com.nodedc.package-sha256": package.package_sha256,
"com.nodedc.product": "mission-core",
"com.nodedc.role": "ai-module",
"com.nodedc.stack": "observatory",
},
name_token="0123456789abcdef",
@@ -324,6 +335,9 @@ def test_generic_docker_launcher_uses_hardened_one_shot_contract() -> None:
"POST",
"DELETE",
]
assert requests[1].url.params["name"] == (
f"ndc-mission-core-ai-module-{package.containers[0].container_id}-0123456789abcdef"
)
assert create_document["Image"] == f"sha256:{package.containers[0].image_sha256}"
assert create_document["NetworkDisabled"] is True
host = cast(dict[str, object], create_document["HostConfig"])
@@ -104,7 +104,7 @@ def test_installed_package_binds_exact_ready_definition_and_runtime() -> None:
assert document["container_io"] == {
"schema_version": "missioncore.observatory-installed-lab-container-io/v2",
"source_root": "/missioncore/input/source",
"plan_path": "/missioncore/input/run-plan.json",
"plan_path": "/missioncore/plan/run-plan.json",
"step_input_root": "/missioncore/input/steps",
"result_root": "/missioncore/output",
"work_root": "/missioncore/work",
@@ -0,0 +1,92 @@
from __future__ import annotations
import json
from pathlib import Path
import pytest
from k1link.observatory.lab_view_profiles import (
PROFILE_SCHEMA,
LabSceneProfile,
LabViewProfile,
LabViewProfileError,
LabViewProfileStore,
)
def _profile(result_id: str = "result-1") -> LabViewProfile:
return LabViewProfile(
result_id=result_id,
scene_settings=LabSceneProfile(
point_size=4.7,
accumulation_seconds=8,
color_mode="height",
palette="viridis",
show_grid=False,
show_labels=True,
show_camera_frustums=False,
),
updated_at_utc="2026-09-04T09:30:00.000Z",
)
def test_profile_store_round_trips_one_mutable_profile_per_result(tmp_path: Path) -> None:
store = LabViewProfileStore(tmp_path / "profiles")
assert store.get("result-1") is None
saved = store.save(_profile())
assert store.get("result-1") == saved
documents = list(store.root.glob("*.json"))
assert len(documents) == 1
assert documents[0].stat().st_mode & 0o777 == 0o600
assert json.loads(documents[0].read_text())["schema_version"] == PROFILE_SCHEMA
updated = LabViewProfile(
result_id="result-1",
scene_settings=LabSceneProfile(
point_size=2.0,
accumulation_seconds=3,
color_mode="distance",
palette="turbo",
show_grid=True,
show_labels=False,
show_camera_frustums=True,
),
updated_at_utc="2026-09-04T09:31:00.000Z",
)
store.save(updated)
assert store.get("result-1") == updated
assert len(list(store.root.glob("*.json"))) == 1
def test_profile_store_preserves_unbounded_operator_values(tmp_path: Path) -> None:
profile = LabViewProfile(
result_id="result-extreme",
scene_settings=LabSceneProfile(
point_size=128.5,
accumulation_seconds=50_000,
color_mode="height",
palette="turbo",
show_grid=True,
show_labels=False,
show_camera_frustums=False,
),
updated_at_utc="2026-09-04T09:32:00.000Z",
)
store = LabViewProfileStore(tmp_path / "profiles")
store.save(profile)
assert store.get("result-extreme") == profile
def test_profile_store_rejects_invalid_identity_and_tampered_document(tmp_path: Path) -> None:
store = LabViewProfileStore(tmp_path / "profiles")
with pytest.raises(LabViewProfileError, match="identity"):
store.get("../foreign")
store.save(_profile())
[document] = store.root.glob("*.json")
payload = json.loads(document.read_text())
payload["result_id"] = "different-result"
document.write_text(json.dumps(payload), encoding="utf-8")
with pytest.raises(LabViewProfileError, match="identity"):
store.get("result-1")
@@ -0,0 +1,103 @@
from __future__ import annotations
import hashlib
import json
import runpy
from pathlib import Path
import numpy as np
import pytest
_MODULE = runpy.run_path(
str(
Path(__file__).resolve().parents[1]
/ "experiments/perception/worker/observatory_portable/run_ai_module_object_distance.py"
)
)
_aligned_camera_seconds = _MODULE["_aligned_camera_seconds"]
ObjectDistanceModuleError = _MODULE["ObjectDistanceModuleError"]
_projection = _MODULE["_projection"]
_LidarPack = _MODULE["_LidarPack"]
def test_distance_joins_repaired_camera_and_lidar_by_frame_index() -> None:
seconds = _aligned_camera_seconds(
{"session_seconds": 136.002},
{"frame_index": 1299, "session_seconds": 175.131263458},
frame_index=1299,
previous_camera_seconds=175.0,
)
assert seconds == 175.131263458
def test_distance_rejects_frame_or_repaired_clock_regression() -> None:
with pytest.raises(ObjectDistanceModuleError, match="frame identities"):
_aligned_camera_seconds(
{"session_seconds": 136.002},
{"frame_index": 1298, "session_seconds": 175.131263458},
frame_index=1299,
previous_camera_seconds=175.0,
)
def test_distance_reads_calibration_from_full_e10_pack(tmp_path: Path) -> None:
pack = tmp_path / "lidar-pack.npz"
np.savez(
pack,
intrinsic_fx_fy_cx_cy=np.asarray([300.0, 301.0, 400.0, 300.0]),
distortion_kb4=np.asarray([0.1, 0.01, 0.001, 0.0001]),
t_camera_from_lidar=np.eye(4),
cloud_points_map=np.zeros((100_000, 3), dtype=np.float64),
)
assert pack.stat().st_size > 1024 * 1024
projection = _projection(pack)
assert projection.width == 800
assert projection.height == 600
assert projection.intrinsic_fx_fy_cx_cy == (300.0, 301.0, 400.0, 300.0)
def test_distance_accepts_sealed_lidar_pack_at_package_mount_name(tmp_path: Path) -> None:
root = tmp_path / "lidar-pack"
root.mkdir()
arrays = root / "lidar-replay.npz"
np.savez(
arrays,
point_offsets=np.asarray([0, 1]),
point_xyz_map=np.asarray([[1.0, 2.0, 3.0]]),
point_received_monotonic_ns=np.asarray([1], dtype=np.int64),
pose_positions_map=np.asarray([[0.0, 0.0, 0.0]]),
pose_quaternions_map_from_lidar=np.asarray([[0.0, 0.0, 0.0, 1.0]]),
pose_received_monotonic_ns=np.asarray([1], dtype=np.int64),
)
digest = hashlib.sha256(arrays.read_bytes()).hexdigest()
identity = "3" * 64
(root / "manifest.json").write_text(
json.dumps(
{
"schema_version": "missioncore.lidar-replay-pack/v2",
"pack_id": f"lidar-replay-pack-{identity}",
"identity_sha256": identity,
"artifacts": [
{
"kind": "lidar-arrays",
"path": "lidar-replay.npz",
"byte_length": arrays.stat().st_size,
"sha256": digest,
}
],
}
)
)
pack = _LidarPack(root)
try:
assert pack.pack_id == f"lidar-replay-pack-{identity}"
assert pack.point_frame_count == 1
finally:
pack.close()
with pytest.raises(ObjectDistanceModuleError, match="camera timeline"):
_aligned_camera_seconds(
{"session_seconds": 136.002},
{"frame_index": 1299, "session_seconds": 174.0},
frame_index=1299,
previous_camera_seconds=175.0,
)
+135
View File
@@ -0,0 +1,135 @@
"""Recovery against the durable queue and real sealed-package publisher.
Only source/model payloads are synthetic. No Worker, GPU, or application server
is started; reopening the same SQLite/CAS roots simulates process restart.
"""
from __future__ import annotations
from datetime import UTC, datetime, timedelta
from pathlib import Path
from typing import Any, cast
import pytest
from test_observatory_portable_result_publisher import (
NOW,
RESULT_ID,
_cache_fixture,
_retry_intent,
)
from test_observatory_recorded_jobs import _intent, _queue, _running_job
from k1link.observatory.portable_publication_reconciler import PortablePublicationReconciler
from k1link.observatory.portable_result_contract import PortableResultPublisherError
from k1link.observatory.recorded_jobs import (
ObservatoryRecordedJobQueue,
ObservatoryRecordedQueueDuplicateError,
RecordedRunDefinitionRegistry,
)
from k1link.sessions import SessionStore
def test_outbox_cursor_survives_publication_and_tied_creation_times(tmp_path: Path) -> None:
queue = _queue(tmp_path)
for index in range(7):
job, claim = _running_job(
queue,
intent=_intent(idempotency_key=f"page-{index}", source_session_id=f"source-{index}"),
claim_request_id=f"page-claim-{index}",
)
queue.complete_for_publication(
job.job_id,
claim_token=claim.claim_token,
result_id=f"result-{index}",
result_sha256="a" * 64,
)
expected = queue.pending_publications()
first = queue.pending_publications(limit=3)
assert first == expected[:3]
cursor = (first[-1].created_at_utc, first[-1].job_id)
for item in first:
queue.mark_published(item.job_id)
# Cursor need not point at a still-pending row; never use mutable offsets.
assert queue.pending_publications(limit=3, after=cursor) == expected[3:6]
assert (
queue.pending_publications(
limit=3, after=(expected[-1].created_at_utc, expected[-1].job_id)
)
== ()
)
@pytest.mark.parametrize("limit", [0, 101, True, 1.5])
def test_outbox_page_rejects_invalid_bounds(tmp_path: Path, limit: Any) -> None:
with pytest.raises(ValueError):
_queue(tmp_path).pending_publications(limit=limit)
@pytest.mark.parametrize("failure_after_publish", [False, True])
def test_publication_recovers_after_restart_without_new_compute(
tmp_path: Path,
failure_after_publish: bool,
) -> None:
cache, queue, sessions, _, registry, definition, job, publisher, package = _cache_fixture(
tmp_path,
published=False,
)
current = datetime.fromisoformat(NOW.replace("Z", "+00:00"))
class Transport:
def package_root_for_terminal(self, candidate: Any) -> Path:
assert candidate.job_id == job.job_id
assert candidate.result_sha256 == job.result_sha256
return package
class InterruptedPublisher:
def publish(self, **kwargs: Any) -> None:
if failure_after_publish:
publisher.publish(**kwargs)
raise PortableResultPublisherError("temporary publication interruption")
first = PortablePublicationReconciler(
queue=queue,
artifact_transport=cast(Any, Transport()),
result_publisher=cast(Any, InterruptedPublisher()),
clock=lambda: current,
).run_once()
assert first.failed == 1
failed = queue.get(job.job_id)
assert failed.state == "succeeded" and failed.publication_state == "failed"
assert failed.result_sha256 == job.result_sha256
with pytest.raises(ObservatoryRecordedQueueDuplicateError):
queue.submit(_retry_intent(job), reject_duplicate_computation=True)
restarted = ObservatoryRecordedJobQueue(
sessions.data_dir,
definitions=RecordedRunDefinitionRegistry(registry.ready_recorded_definitions()),
clock=lambda: (current + timedelta(minutes=1)).isoformat().replace("+00:00", "Z"),
)
second = PortablePublicationReconciler(
queue=restarted,
artifact_transport=cast(Any, Transport()),
result_publisher=publisher,
clock=lambda: current + timedelta(minutes=1),
).run_once()
assert second.published == 1
assert restarted.get(job.job_id).publication_state == "published"
assert len(restarted.list_jobs()) == 1
assert restarted.pending_publications() == ()
assert [row["result_id"] for row in cache.find(job.source_session_id, definition)] == [
RESULT_ID
]
reopened = SessionStore(sessions.repository_root, data_dir=sessions.data_dir)
assert reopened.get_lab_instance(RESULT_ID) == sessions.get_lab_instance(RESULT_ID)
# No second publication attempt once the outbox acknowledgement is durable.
assert (
PortablePublicationReconciler(
queue=restarted,
artifact_transport=cast(Any, Transport()),
result_publisher=publisher,
clock=lambda: datetime.now(UTC),
)
.run_once()
.examined
== 0
)
@@ -471,14 +471,10 @@ def test_component_adapter_accepts_only_exact_legacy_or_installed_package_layout
component="ddrnet",
expectations=expectations,
)
assert installed.request == Path(
"/missioncore/input/steps/prepare/ddrnet-request.json"
)
assert installed.camera_job_root == Path(
"/missioncore/input/steps/prepare/camera-job"
)
assert installed.request == Path("/missioncore/input/steps/prepare/ddrnet-request.json")
assert installed.camera_job_root == Path("/missioncore/input/steps/prepare/camera-job")
assert installed.output_root == Path("/missioncore/output")
assert installed.eomt_result_root == Path("/missioncore/input/steps/eomt")
assert installed.eomt_result_root == Path("/missioncore/input/steps/camera-source")
with pytest.raises(contract.ComponentAdapterError, match="accepts only"):
contract.resolve_runtime_layout(
@@ -969,10 +965,66 @@ def test_eomt_adapter_uses_only_fixed_legacy_argv_and_publishes_frames(
def test_eomt_default_disk_floor_retains_large_post_run_reserve() -> None:
assert eomt.DISK_FLOOR_BYTES == 350 * 1024**3
assert eomt.DISK_FLOOR_BYTES == 250 * 1024**3
full_record_working_set = 6_830 * 800 * 600 * 7 + 556_912_640
assert full_record_working_set < 22 * 1024**3
assert (eomt.DISK_FLOOR_BYTES + full_record_working_set) < 372 * 1024**3
assert (eomt.DISK_FLOOR_BYTES + full_record_working_set) < 272 * 1024**3
@pytest.mark.parametrize("floor", [-1, True, 250.5, eomt.DISK_FLOOR_BYTES])
def test_eomt_disk_rejection_precedes_heavy_input_and_asset_validation(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
floor: int,
) -> None:
output = tmp_path / "output"
output.mkdir()
source = SimpleNamespace(frame_count=6_830, input_byte_length=556_912_640)
reserve = source.frame_count * 800 * 600 * 7 + source.input_byte_length
monkeypatch.setattr(
eomt, "load_component_request", lambda *a, **kw: SimpleNamespace(source=source)
)
monkeypatch.setattr(eomt, "available_bytes", lambda _: eomt.DISK_FLOOR_BYTES + reserve - 1)
def heavy_work(*args: object, **kwargs: object) -> None:
pytest.fail("disk admission must precede input hashing, asset hashing and inference")
monkeypatch.setattr(eomt, "validate_camera_compute_job", heavy_work)
monkeypatch.setattr(eomt, "_validate_release_assets", heavy_work)
layout = cast(contract.RuntimeLayout, SimpleNamespace(output_root=output))
with pytest.raises(contract.ComponentAdapterError, match="disk reserve"):
eomt.execute_eomt_component(
request_path=tmp_path / "request.json",
layout=layout,
command_runner=heavy_work,
disk_floor_bytes=floor,
)
assert not list(output.iterdir())
def test_eomt_exact_disk_budget_still_requires_source_validation(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
output = tmp_path / "output"
output.mkdir()
source = SimpleNamespace(frame_count=6_830, input_byte_length=556_912_640)
reserve = source.frame_count * 800 * 600 * 7 + source.input_byte_length
monkeypatch.setattr(
eomt, "load_component_request", lambda *a, **kw: SimpleNamespace(source=source)
)
monkeypatch.setattr(eomt, "available_bytes", lambda _: eomt.DISK_FLOOR_BYTES + reserve)
def invalid_source(*args: object) -> None:
raise contract.ComponentAdapterError("source validation remains mandatory")
monkeypatch.setattr(eomt, "validate_camera_compute_job", invalid_source)
layout = cast(
contract.RuntimeLayout, SimpleNamespace(output_root=output, camera_job_root=tmp_path)
)
with pytest.raises(contract.ComponentAdapterError, match="source validation remains mandatory"):
eomt.execute_eomt_component(request_path=tmp_path / "request.json", layout=layout)
assert not list(output.iterdir())
def _effective_ddrnet_config(
@@ -308,7 +308,7 @@ def test_duplicate_definition_and_incomplete_ready_executor_are_rejected(
PortableRunDefinitionRegistry.from_file(_write(tmp_path, incomplete))
def test_production_definitions_are_both_ready_and_convertible() -> None:
def test_production_definitions_are_ready_and_convertible() -> None:
registry = _registry()
definition = registry.definitions[0]
@@ -325,6 +325,10 @@ def test_production_definitions_are_both_ready_and_convertible() -> None:
assert tuple(row.setup_id for row in ready) == (
"lab-v1-eomt-ddrnet-portable-v1",
"m49-tgs-portable-v2",
"ai-segmentation-ddrnet-v1",
"ai-segmentation-eomt-v1",
"ai-detection-rf-detr-v1",
"ai-range-object-distance-v1",
)
assert registry.to_recorded_registry().definitions == ready
@@ -140,7 +140,7 @@ class _GenericProbe:
)
def test_generic_catalog_projects_lab_v1_and_model_free_m49_independently() -> None:
def test_generic_catalog_projects_every_registered_portable_setup_independently() -> None:
registry = _registry()
catalog = PortableSetupProjector(
registry=registry,
@@ -151,6 +151,10 @@ def test_generic_catalog_projects_lab_v1_and_model_free_m49_independently() -> N
assert set(setups) == {
"lab-v1-eomt-ddrnet-portable-v1",
"m49-tgs-portable-v2",
"ai-segmentation-ddrnet-v1",
"ai-segmentation-eomt-v1",
"ai-detection-rf-detr-v1",
"ai-range-object-distance-v1",
}
m49 = setups["m49-tgs-portable-v2"]
assert m49["display_name"] == PORTABLE_M49_DISPLAY_NAME
@@ -11,6 +11,10 @@ from k1link.observatory.m49_portable_result import (
M49_PORTABLE_RESULT_CONTRACT_SHA256,
validate_m49_portable_result,
)
from k1link.observatory.modular_result import (
MODULAR_RESULT_CONTRACT_SHA256,
validate_modular_result,
)
from k1link.observatory.portable_artifact_transport import (
PortableObservatoryArtifactTransport,
)
@@ -26,6 +30,14 @@ from k1link.observatory.portable_run_definitions import (
PortableRunDefinitionRegistry,
)
from k1link.observatory.portable_setup_projection import (
AI_DDRNET_DISPLAY_NAME,
AI_DDRNET_SETUP_ID,
AI_EOMT_DISPLAY_NAME,
AI_EOMT_SETUP_ID,
AI_OBJECT_DISTANCE_DISPLAY_NAME,
AI_OBJECT_DISTANCE_SETUP_ID,
AI_RF_DETR_DISPLAY_NAME,
AI_RF_DETR_SETUP_ID,
PORTABLE_LAB_V1_DISPLAY_NAME,
PORTABLE_LAB_V1_SETUP_ID,
PORTABLE_M49_DISPLAY_NAME,
@@ -53,30 +65,28 @@ def _definitions() -> PortableRunDefinitionRegistry:
return PortableRunDefinitionRegistry.from_file(REGISTRY_PATH)
def test_exact_validator_registry_covers_both_portable_profiles() -> None:
def test_exact_validator_registry_covers_every_distinct_portable_contract() -> None:
definitions = _definitions()
validators = portable_result_validator_registry(definitions)
assert validators.resolve(PORTABLE_LAB_V1_RESULT_CONTRACT_SHA256) is validate_lab_v1_result_v2
assert validators.resolve(M49_PORTABLE_RESULT_CONTRACT_SHA256) is validate_m49_portable_result
assert validators.resolve(MODULAR_RESULT_CONTRACT_SHA256) is validate_modular_result
assert len(validators.registrations) == 3
def test_local_worker_gate_is_fail_closed_and_accepts_only_exact_one() -> None:
assert observatory_worker_local_enabled({}) is False
assert observatory_worker_local_enabled({OBSERVATORY_WORKER_LOCAL_ENABLED_ENV: ""}) is False
assert observatory_worker_local_enabled(
{OBSERVATORY_WORKER_LOCAL_ENABLED_ENV: "1"}
) is True
assert observatory_worker_local_enabled({OBSERVATORY_WORKER_LOCAL_ENABLED_ENV: "1"}) is True
for value in ("0", "true", " 1", "1 "):
with pytest.raises(
PortableWorkerIntegrationError,
match="must be exactly 1 when enabled",
):
observatory_worker_local_enabled(
{OBSERVATORY_WORKER_LOCAL_ENABLED_ENV: value}
)
observatory_worker_local_enabled({OBSERVATORY_WORKER_LOCAL_ENABLED_ENV: value})
def test_validator_registry_selects_only_contracts_present_in_definitions() -> None:
@@ -117,6 +127,10 @@ def test_server_integration_constructs_dormant_transport_and_publisher(
assert integration.supported_setup_ids == (
PORTABLE_LAB_V1_SETUP_ID,
PORTABLE_M49_SETUP_ID,
AI_DDRNET_SETUP_ID,
AI_EOMT_SETUP_ID,
AI_RF_DETR_SETUP_ID,
AI_OBJECT_DISTANCE_SETUP_ID,
)
assert isinstance(
integration.artifact_transport,
@@ -132,6 +146,10 @@ def test_server_integration_constructs_dormant_transport_and_publisher(
assert profiles == {
PORTABLE_LAB_V1_SETUP_ID: PORTABLE_LAB_V1_DISPLAY_NAME,
PORTABLE_M49_SETUP_ID: PORTABLE_M49_DISPLAY_NAME,
AI_DDRNET_SETUP_ID: AI_DDRNET_DISPLAY_NAME,
AI_EOMT_SETUP_ID: AI_EOMT_DISPLAY_NAME,
AI_RF_DETR_SETUP_ID: AI_RF_DETR_DISPLAY_NAME,
AI_OBJECT_DISTANCE_SETUP_ID: AI_OBJECT_DISTANCE_DISPLAY_NAME,
}
@@ -241,9 +259,7 @@ def test_explicit_volumes_storage_boundary_still_requires_a_mount(
PortableWorkerIntegrationError,
match="central artifact volume is not mounted: /Volumes/nodedc",
):
integration_module._require_mounted_volume(
Path("/Volumes/nodedc/mission-core")
)
integration_module._require_mounted_volume(Path("/Volumes/nodedc/mission-core"))
assert mount_checks == [Path("/Volumes/nodedc")]
@@ -53,8 +53,7 @@ def _runtime() -> PortableWorkerRuntimeRegistry:
def _blocked_candidate(candidate):
phases = tuple(
PortableWorkerRuntimePhase(phase.phase_id, "missing")
for phase in candidate.phases
PortableWorkerRuntimePhase(phase.phase_id, "missing") for phase in candidate.phases
)
blockers = ("executor-release-unsealed",)
identity = candidate.identity_document()
@@ -80,14 +79,25 @@ def _all_keys(value: object) -> set[str]:
return set()
def test_production_candidates_bind_exact_definitions_and_both_are_ready() -> None:
def test_production_candidates_bind_every_exact_definition_and_are_ready() -> None:
registry = _runtime()
assert {candidate.setup_id for candidate in registry.candidates} == {
"lab-v1-eomt-ddrnet-portable-v1",
"m49-tgs-portable-v2",
"ai-segmentation-ddrnet-v1",
"ai-segmentation-eomt-v1",
"ai-detection-rf-detr-v1",
"ai-range-object-distance-v1",
}
by_setup = {candidate.setup_id: candidate for candidate in registry.candidates}
assert all(candidate.ready for candidate in registry.candidates)
assert all(candidate.executor is not None for candidate in registry.candidates)
assert all(candidate.blockers == () for candidate in registry.candidates)
assert all(
all(phase.state == "implemented" for phase in candidate.phases)
for candidate in registry.candidates
)
lab_v1 = by_setup["lab-v1-eomt-ddrnet-portable-v1"]
assert lab_v1.ready is True
assert lab_v1.executor is not None
@@ -228,10 +238,7 @@ def test_ready_lab_candidate_still_requires_complete_local_asset_admission() ->
"ddrnet-portable-config": PortableWorkerLocalAssetBinding(
asset_id="ddrnet-portable-config",
file_path=(
REPOSITORY_ROOT
/ "config"
/ "perception"
/ "lab-v1-eomt-ddrnet-portable-v2.json"
REPOSITORY_ROOT / "config" / "perception" / "lab-v1-eomt-ddrnet-portable-v2.json"
),
),
"ddrnet-step-image": PortableWorkerLocalAssetBinding(
@@ -370,9 +377,7 @@ def test_sealed_local_tree_uses_manifest_receipt_and_member_metadata(
"model-bin": {
"relative_path": "model.bin",
"byte_length": 5,
"sha256": (
"9372c470eeadd5ec5f36cb0b9adf10545c93c5132503830bf1465fe7654b117b"
),
"sha256": ("9372c470eeadd5ec5f36cb0b9adf10545c93c5132503830bf1465fe7654b117b"),
}
},
}
@@ -473,10 +478,12 @@ def test_ready_local_adapter_composes_only_local_ports_and_exact_job(
definition_sha256=canonical_sha256(definition_identity),
)
blocked = _blocked_candidate(_runtime().resolve(
"lab-v1-eomt-ddrnet-portable-v1",
base_definition.definition_sha256,
))
blocked = _blocked_candidate(
_runtime().resolve(
"lab-v1-eomt-ddrnet-portable-v1",
base_definition.definition_sha256,
)
)
executor_seal = PortableWorkerExecutorSeal(
release_id="lab-v1-portable-executor-v1",
release_sha256="1" * 64,
@@ -5,6 +5,9 @@ from pathlib import Path
from types import SimpleNamespace
from typing import Any, cast
import pytest
from k1link.observatory.portable_artifact_transport import PortableArtifactTransportError
from k1link.observatory.portable_publication_reconciler import (
PortablePublicationReconciler,
)
@@ -18,8 +21,17 @@ class _Queue:
self.published: list[str] = []
self.failed: list[str] = []
def pending_publications(self) -> tuple[SimpleNamespace, ...]:
return self.jobs
def pending_publications(
self,
*,
limit: int,
after: tuple[str, str] | None = None,
) -> tuple[SimpleNamespace, ...]:
return tuple(
job
for job in sorted(self.jobs, key=lambda job: (job.created_at_utc, job.job_id))
if after is None or (job.created_at_utc, job.job_id) > after
)[:limit]
def mark_published(self, job_id: str) -> None:
self.published.append(job_id)
@@ -103,7 +115,67 @@ def _job(
) -> SimpleNamespace:
return SimpleNamespace(
job_id=f"observatory-run-{attempts:032x}",
created_at_utc=updated_at,
publication_state=publication_state,
publication_attempts=attempts,
updated_at_utc=updated_at,
)
@pytest.mark.parametrize("skip_kind", ["exhausted", "backoff"])
def test_old_outbox_prefix_cannot_starve_ready_results_across_pages(
tmp_path: Path,
skip_kind: str,
) -> None:
jobs = []
for index in range(70):
skipped = index < 66
job = _job(
"failed" if skipped else "pending",
attempts=(5 if skip_kind == "exhausted" else 1) if skipped else 0,
updated_at="2026-09-01T11:59:50Z" if skipped else "2026-09-01T11:00:00Z",
)
job.created_at_utc = "2026-09-01T10:00:00Z"
job.job_id = f"observatory-run-{index:032x}"
jobs.append(job)
queue = _Queue(tuple(jobs))
publisher = _Publisher()
reconciler = PortablePublicationReconciler(
queue=cast(Any, queue),
artifact_transport=cast(Any, _Transport(tmp_path)),
result_publisher=cast(Any, publisher),
clock=lambda: NOW,
)
result = reconciler.run_once(limit=2)
assert result.examined == 68
assert result.published == 2
assert getattr(result, "exhausted" if skip_kind == "exhausted" else "deferred") == 66
assert publisher.calls == [jobs[66].job_id, jobs[67].job_id]
assert queue.failed == []
def test_one_missing_package_does_not_block_the_next_publication(tmp_path: Path) -> None:
first = _job("pending", attempts=0, updated_at="2026-09-01T10:00:00Z")
second = _job("pending", attempts=0, updated_at="2026-09-01T11:00:00Z")
second.job_id = f"observatory-run-{1:032x}"
queue = _Queue((first, second))
publisher = _Publisher()
class MissingFirst(_Transport):
def package_root_for_terminal(self, job: SimpleNamespace) -> Path:
if job.job_id == first.job_id:
raise PortableArtifactTransportError("sealed package temporarily unavailable")
return super().package_root_for_terminal(job)
result = PortablePublicationReconciler(
queue=cast(Any, queue),
artifact_transport=cast(Any, MissingFirst(tmp_path)),
result_publisher=cast(Any, publisher),
clock=lambda: NOW,
).run_once(limit=2)
assert result.published == 1 and result.failed == 1
assert queue.failed == [first.job_id]
assert publisher.calls == [second.job_id]
+153 -115
View File
@@ -99,6 +99,7 @@ def _queue(
) -> ObservatoryRecordedJobQueue:
preemptor = None
if with_non_checkpointable_preemptor:
def preemptor(request):
return ObservatoryNonCheckpointableCancellationReceipt.sealed(
request,
@@ -154,9 +155,7 @@ def _running_job(
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
)
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"
@@ -207,7 +206,8 @@ def test_list_filters_definition_before_page_limit(tmp_path: Path) -> None:
current = replace(previous, definition_version=2, definition_sha256="0" * 64)
now = NOW
queue = ObservatoryRecordedJobQueue(
tmp_path, definitions=RecordedRunDefinitionRegistry((previous, current)),
tmp_path,
definitions=RecordedRunDefinitionRegistry((previous, current)),
clock=lambda: now,
)
first, _ = queue.submit(_intent())
@@ -220,8 +220,10 @@ def test_list_filters_definition_before_page_limit(tmp_path: Path) -> None:
)
assert queue.list_jobs(limit=1)[0].definition_sha256 == current.definition_sha256
assert queue.list_jobs(
source_session_id=first.source_session_id, setup_id=previous.setup_id,
definition_sha256=previous.definition_sha256, limit=1,
source_session_id=first.source_session_id,
setup_id=previous.setup_id,
definition_sha256=previous.definition_sha256,
limit=1,
) == (first,)
with pytest.raises(ValueError):
queue.list_jobs(definition_sha256="not-a-digest")
@@ -266,9 +268,6 @@ def test_portable_duplicate_guard_preserves_original_request(
"field",
[
"source_session_id",
"source_catalog_sha256",
"source_bundle_sha256",
"source_capability_manifest_sha256",
"setup_id",
],
)
@@ -280,9 +279,6 @@ def test_duplicate_guard_keeps_distinct_sources_and_profiles(
queue.submit(_intent(), reject_duplicate_computation=True)
values = {
"source_session_id": "another-source",
"source_catalog_sha256": "0" * 64,
"source_bundle_sha256": "0" * 64,
"source_capability_manifest_sha256": "0" * 64,
"setup_id": "legacy-monolith-v1",
}
changes = {field: values[field]}
@@ -293,6 +289,28 @@ def test_duplicate_guard_keeps_distinct_sources_and_profiles(
assert created and len(queue.list_jobs()) == 2
@pytest.mark.parametrize(
"field",
["source_catalog_sha256", "source_bundle_sha256", "source_capability_manifest_sha256"],
)
def test_duplicate_guard_does_not_recalculate_same_record_after_source_repair(
tmp_path: Path,
field: str,
) -> None:
queue = _queue(tmp_path)
first, _ = queue.submit(_intent(), reject_duplicate_computation=True)
with pytest.raises(ObservatoryRecordedQueueDuplicateError) as duplicate:
queue.submit(
replace(
_intent(idempotency_key=f"repaired-{field}"),
**{field: "0" * 64},
),
reject_duplicate_computation=True,
)
assert duplicate.value.job_id == first.job_id
assert len(queue.list_jobs()) == 1
@pytest.mark.parametrize("publication_failed", [False, True])
def test_duplicate_guard_never_recomputes_pending_publication(
tmp_path: Path,
@@ -321,7 +339,9 @@ def test_duplicate_guard_never_recomputes_pending_publication(
assert len(queue.list_jobs()) == 1
def test_duplicate_guard_allows_retry_after_computation_failure(tmp_path: Path) -> None:
def test_duplicate_guard_allows_a_fresh_attempt_after_computation_failure(
tmp_path: Path,
) -> None:
queue = _queue(tmp_path)
job, claim = _running_job(queue)
queue.fail(
@@ -336,6 +356,8 @@ def test_duplicate_guard_allows_retry_after_computation_failure(tmp_path: Path)
reject_duplicate_computation=True,
)
assert created and retried.job_id != job.job_id
assert retried.state == "queued"
assert len(queue.list_jobs()) == 2
def test_duplicate_guard_is_atomic_across_two_queue_instances(tmp_path: Path) -> None:
@@ -383,12 +405,14 @@ def test_duplicate_guard_keeps_new_version_of_same_setup(tmp_path: Path) -> None
def _capabilities() -> tuple[RecordedExecutorIdentity, ...]:
definition = _definitions().definitions[0]
return (RecordedExecutorIdentity(
release_sha256=definition.executor_release_sha256,
image_sha256=definition.executor_image_sha256,
model_manifest_sha256=definition.model_manifest_sha256,
resource_profile_sha256=definition.resource_profile_sha256,
),)
return (
RecordedExecutorIdentity(
release_sha256=definition.executor_release_sha256,
image_sha256=definition.executor_image_sha256,
model_manifest_sha256=definition.model_manifest_sha256,
resource_profile_sha256=definition.resource_profile_sha256,
),
)
def _claim_rows(queue: ObservatoryRecordedJobQueue, table: str) -> list[tuple]:
@@ -399,8 +423,11 @@ def _claim_rows(queue: ObservatoryRecordedJobQueue, table: str) -> list[tuple]:
def test_v3_idle_does_not_consume_grant_quota_or_modify_legacy_receipts(tmp_path: Path) -> None:
queue = ObservatoryRecordedJobQueue(
tmp_path, definitions=_definitions(), clock=lambda: NOW,
max_claim_receipts=1, max_v3_claim_receipts=1,
tmp_path,
definitions=_definitions(),
clock=lambda: NOW,
max_claim_receipts=1,
max_v3_claim_receipts=1,
)
args = {"claimant_id": "recorded-worker", "supported_executor_identities": _capabilities()}
assert queue.claim_next(**args, claim_request_id="legacy-empty") is None
@@ -411,9 +438,14 @@ def test_v3_idle_does_not_consume_grant_quota_or_modify_legacy_receipts(tmp_path
with pytest.raises(ObservatoryRecordedQueueCapacityError):
queue.claim_next(**args, claim_request_id="legacy-full")
for number in range(8):
assert queue.claim_next(
**args, claim_request_id=f"idle-v3-{number}", protocol_version=3,
) is None
assert (
queue.claim_next(
**args,
claim_request_id=f"idle-v3-{number}",
protocol_version=3,
)
is None
)
assert _claim_rows(queue, "observatory_recorded_claim_grants_v3") == []
assert _claim_rows(queue, "observatory_recorded_claim_receipts") == legacy
@@ -425,23 +457,30 @@ def test_v3_idle_does_not_consume_grant_quota_or_modify_legacy_receipts(tmp_path
assert len(_claim_rows(queue, "observatory_recorded_claim_grants_v3")) == 1
assert _claim_rows(queue, "observatory_recorded_claim_receipts") == legacy
reopened = ObservatoryRecordedJobQueue(
tmp_path, definitions=_definitions(), clock=lambda: NOW,
max_claim_receipts=1, max_v3_claim_receipts=1,
tmp_path,
definitions=_definitions(),
clock=lambda: NOW,
max_claim_receipts=1,
max_v3_claim_receipts=1,
)
assert reopened.claim_next(**args, claim_request_id="idle-v3-0", protocol_version=3) == claim
# No second grant even if a client retries a v3 grant through legacy v2.
assert reopened.claim_next(**args, claim_request_id="idle-v3-0") == claim
with pytest.raises(ObservatoryRecordedQueueConflictError):
reopened.claim_next(
claimant_id="other-worker", supported_executor_identities=_capabilities(),
claim_request_id="idle-v3-0", protocol_version=3,
claimant_id="other-worker",
supported_executor_identities=_capabilities(),
claim_request_id="idle-v3-0",
protocol_version=3,
)
assert reopened.claim_next(**args, claim_request_id="busy-v3", protocol_version=3) is None
reopened.start(job.job_id, claim_token=claim.claim_token)
reopened.succeed(
job.job_id, claim_token=claim.claim_token,
result_id="result-first", result_sha256=RESULT_SHA,
job.job_id,
claim_token=claim.claim_token,
result_id="result-first",
result_sha256=RESULT_SHA,
)
with pytest.raises(ObservatoryRecordedQueueStaleClaimError):
reopened.claim_next(**args, claim_request_id="idle-v3-0", protocol_version=3)
@@ -457,15 +496,25 @@ def test_v3_idle_does_not_consume_grant_quota_or_modify_legacy_receipts(tmp_path
def test_v3_empty_capabilities_and_live_lease_cannot_claim_work(tmp_path: Path) -> None:
queue = _queue(tmp_path)
job, _ = queue.submit(_intent(), enqueue=True)
assert queue.claim_next(
claimant_id="recorded-worker", claim_request_id="no-capabilities",
supported_executor_identities=(), protocol_version=3,
) is None
assert (
queue.claim_next(
claimant_id="recorded-worker",
claim_request_id="no-capabilities",
supported_executor_identities=(),
protocol_version=3,
)
is None
)
queue.request_live(_live_intent())
assert queue.claim_next(
claimant_id="recorded-worker", claim_request_id="live-has-priority",
supported_executor_identities=_capabilities(), protocol_version=3,
) is None
assert (
queue.claim_next(
claimant_id="recorded-worker",
claim_request_id="live-has-priority",
supported_executor_identities=_capabilities(),
protocol_version=3,
)
is None
)
assert queue.get(job.job_id).state == "queued"
assert _claim_rows(queue, "observatory_recorded_claim_grants_v3") == []
@@ -478,8 +527,10 @@ def test_v3_two_simultaneous_retries_receive_one_grant(tmp_path: Path) -> None:
def claim(index: int):
barrier.wait(timeout=5)
return queues[index].claim_next(
claimant_id="recorded-worker", claim_request_id="same-v3-id",
supported_executor_identities=_capabilities(), protocol_version=3,
claimant_id="recorded-worker",
claim_request_id="same-v3-id",
supported_executor_identities=_capabilities(),
protocol_version=3,
)
with ThreadPoolExecutor(max_workers=2) as executor:
@@ -491,16 +542,26 @@ def test_v3_two_simultaneous_retries_receive_one_grant(tmp_path: Path) -> None:
def test_v3_expired_grant_cannot_reclaim_or_change_capabilities(tmp_path: Path) -> None:
clock = [NOW]
queue = ObservatoryRecordedJobQueue(
tmp_path, definitions=_definitions(), clock=lambda: clock[0], claim_lease_seconds=10,
tmp_path,
definitions=_definitions(),
clock=lambda: clock[0],
claim_lease_seconds=10,
)
job, _ = queue.submit(_intent(), enqueue=True)
args = {"claimant_id": "recorded-worker", "supported_executor_identities": _capabilities(),
"protocol_version": 3}
args = {
"claimant_id": "recorded-worker",
"supported_executor_identities": _capabilities(),
"protocol_version": 3,
}
first = queue.claim_next(**args, claim_request_id="original-v3")
assert first is not None
with pytest.raises(ObservatoryRecordedQueueConflictError):
queue.claim_next(claimant_id="recorded-worker", claim_request_id="original-v3",
supported_executor_identities=(), protocol_version=3)
queue.claim_next(
claimant_id="recorded-worker",
claim_request_id="original-v3",
supported_executor_identities=(),
protocol_version=3,
)
clock[0] = "2026-08-30T21:00:11.000Z"
with pytest.raises(ObservatoryRecordedQueueStaleClaimError):
queue.claim_next(**args, claim_request_id="original-v3")
@@ -515,8 +576,12 @@ def test_v3_expired_grant_cannot_reclaim_or_change_capabilities(tmp_path: Path)
@pytest.mark.parametrize("version", [True, False, 1, 4, "3"])
def test_claim_protocol_rejects_ambiguous_versions(tmp_path: Path, version) -> None:
with pytest.raises(ValueError, match="protocol version"):
_queue(tmp_path).claim_next(claimant_id="recorded-worker", claim_request_id="invalid",
supported_executor_identities=(), protocol_version=version)
_queue(tmp_path).claim_next(
claimant_id="recorded-worker",
claim_request_id="invalid",
supported_executor_identities=(),
protocol_version=version,
)
def test_submission_is_durable_exactly_idempotent_and_path_free(tmp_path: Path) -> None:
@@ -548,15 +613,11 @@ def test_submission_is_durable_exactly_idempotent_and_path_free(tmp_path: Path)
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["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 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
@@ -684,34 +745,23 @@ def test_success_requires_running_and_cannot_publish_during_preemption(
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"
)
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
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"
)
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"
)
queue.claim_next(claimant_id="another-worker", claim_request_id="non-empty-poll-001")
def test_capability_aware_claim_skips_incompatible_queued_job(tmp_path: Path) -> None:
@@ -764,11 +814,14 @@ def test_capability_claim_identity_binds_snapshot_and_empty_snapshot_claims_noth
queue = _queue(tmp_path)
job, _ = queue.submit(_intent(), enqueue=True)
assert queue.claim_next(
claimant_id="recorded-worker",
claim_request_id="capability-empty-poll",
supported_executor_identities=(),
) is None
assert (
queue.claim_next(
claimant_id="recorded-worker",
claim_request_id="capability-empty-poll",
supported_executor_identities=(),
)
is None
)
capability = RecordedExecutorIdentity(
release_sha256=EXECUTOR_RELEASE_SHA,
image_sha256=EXECUTOR_IMAGE_SHA,
@@ -1037,9 +1090,7 @@ def test_operator_reconciliation_is_durable_idempotent_and_unblocks_queue(
assert restored == receipt
assert restored_queue.get_reconciliation(running.job_id) == receipt
assert receipt.resource_release_attestation.resources_released is True
assert receipt.resource_release_attestation.evidence_sha256 == (
RESOURCE_RELEASE_EVIDENCE_SHA
)
assert receipt.resource_release_attestation.evidence_sha256 == (RESOURCE_RELEASE_EVIDENCE_SHA)
assert receipt.expected_terminal_code == "claim-lease-expired"
assert receipt.quarantined_terminal_message == (
"Worker claim lease expired after execution started; "
@@ -1089,14 +1140,19 @@ def test_cache_indexes_follow_legacy_publication_column_migration(tmp_path: Path
connection.execute("DROP INDEX observatory_recorded_jobs_published_source")
connection.execute("DROP INDEX observatory_recorded_jobs_computation")
for column in (
"publication_state", "publication_attempts", "publication_error", "published_at_utc",
"publication_state",
"publication_attempts",
"publication_error",
"published_at_utc",
):
connection.execute(f"ALTER TABLE observatory_recorded_jobs DROP COLUMN {column}")
migrated = _queue(tmp_path)
assert migrated.get(job.job_id) == job
assert not migrated.published_results(
source_session_id=job.source_session_id, source_catalog_sha256=job.source_catalog_sha256,
setup_id=job.setup_id, definition_sha256=job.definition_sha256,
source_session_id=job.source_session_id,
source_catalog_sha256=job.source_catalog_sha256,
setup_id=job.setup_id,
definition_sha256=job.definition_sha256,
)
with sqlite3.connect(queue.database_path) as connection:
names = {
@@ -1123,9 +1179,7 @@ def test_legacy_sqlite_claim_schema_migrates_without_reusing_old_token(
"claim_heartbeat_at_utc",
"claim_renewal_count",
):
connection.execute(
f"ALTER TABLE observatory_recorded_jobs DROP COLUMN {column}"
)
connection.execute(f"ALTER TABLE observatory_recorded_jobs DROP COLUMN {column}")
connection.commit()
migrated_queue = _queue(tmp_path)
@@ -1140,9 +1194,7 @@ def test_legacy_sqlite_claim_schema_migrates_without_reusing_old_token(
with sqlite3.connect(migrated_queue.database_path) as connection:
columns = {
row[1]
for row in connection.execute(
"PRAGMA table_info(observatory_recorded_jobs)"
).fetchall()
for row in connection.execute("PRAGMA table_info(observatory_recorded_jobs)").fetchall()
}
assert {
"claimed_at_utc",
@@ -1171,9 +1223,7 @@ def test_legacy_sqlite_running_owner_migrates_to_reconciliation(
"claim_heartbeat_at_utc",
"claim_renewal_count",
):
connection.execute(
f"ALTER TABLE observatory_recorded_jobs DROP COLUMN {column}"
)
connection.execute(f"ALTER TABLE observatory_recorded_jobs DROP COLUMN {column}")
connection.commit()
migrated_queue = _queue(tmp_path)
@@ -1201,15 +1251,10 @@ def test_single_worker_resource_has_only_one_recorded_owner(tmp_path: Path) -> N
)
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"
)
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
queue.claim_next(claimant_id="recorded-worker", claim_request_id="owner-poll-002") is None
)
completed_job_id = first_claim.job.job_id
@@ -1262,10 +1307,7 @@ def test_live_lease_cooperatively_pauses_and_resumes_recorded_job(tmp_path: Path
)
queue.enqueue(another.job_id)
assert (
queue.claim_next(
claimant_id="recorded-worker", claim_request_id="blocked-poll-001"
)
is None
queue.claim_next(claimant_id="recorded-worker", claim_request_id="blocked-poll-001") is None
)
completed = queue.finish_live(
@@ -1319,9 +1361,7 @@ def test_live_request_pauses_claimed_job_before_execution(tmp_path: Path) -> Non
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"
)
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())
@@ -1377,9 +1417,7 @@ def test_non_checkpointable_job_is_cancelled_and_restarts_from_zero_for_live(
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"
)
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
@@ -1520,15 +1558,16 @@ def test_reconciliation_required_quarantines_recorded_and_live_ownership(
reason_code="worker-outcome-unknown",
message="Worker ownership cannot be proven released.",
)
waiting, _ = queue.submit(
_intent(idempotency_key="recorded-request-002")
)
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
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)
@@ -1558,8 +1597,7 @@ def test_queue_detects_mutated_immutable_identity(tmp_path: Path) -> None:
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 = ?",
"UPDATE observatory_recorded_jobs SET source_catalog_sha256 = ? WHERE job_id = ?",
("0" * 64, job.job_id),
)
connection.commit()
@@ -0,0 +1,109 @@
"""Source-reuse installation admission only; no Docker/model runs."""
import importlib.util
import json
import sys
from pathlib import Path
import pytest
SCRIPTS = Path(__file__).parents[1] / "experiments/perception/worker/observatory_portable"
@pytest.fixture
def installer(monkeypatch):
monkeypatch.syspath_prepend(str(SCRIPTS))
spec = importlib.util.spec_from_file_location(
"recorded_source_reuse_installer", SCRIPTS / "install_recorded_source_reuse.py"
)
module = importlib.util.module_from_spec(spec)
monkeypatch.setitem(sys.modules, spec.name, module)
spec.loader.exec_module(module)
return module
def test_pack_preserves_producer_and_rejects_payload_changes(installer, tmp_path):
output = tmp_path / "payload"
installer.pack(Path(__file__).parents[1], output)
assert installer.PRODUCER not in installer.payload_files(output)
assert len(installer.payload_files(output)) == 5
path = output / "observatory/worker_source_cache.py"
path.write_bytes(path.read_bytes() + b"\n# changed\n")
with pytest.raises(ValueError, match="payload changed"):
installer.payload_files(output)
def test_source_reuse_manifest_cannot_add_paths(installer, tmp_path):
output = tmp_path / "payload"
installer.pack(Path(__file__).parents[1], output)
path = output / "payload.json"
manifest = json.loads(path.read_bytes())
manifest["files"]["../../outside.py"] = "0" * 64
path.write_text(json.dumps(manifest))
with pytest.raises(ValueError, match="file set changed"):
installer.payload_files(output)
class FakeEngine:
def __init__(self, installer):
self.installer = installer
self.requests = []
self.volumes = []
self.rows = {
name: {
"Id": str(index) * 64,
"Name": "/" + name,
"Image": "sha256:" + image,
"State": {"Running": True},
"Mounts": [],
"HostConfig": {"ReadonlyRootfs": True, "NetworkMode": "bridge"},
"Config": {"Labels": {"com.nodedc.authority": "observation-only"}, "Env": []},
}
for index, (name, image) in enumerate(installer.TARGETS.items(), 1)
}
def inspect(self, name):
return self.rows[name]
def request(self, method, path, body=None):
self.requests.append((method, path))
assert method == "GET"
return {"Volumes": self.volumes}
def execute_json(self, name, source):
if source == self.installer.probe():
return {self.installer.PRODUCER: self.installer.PRODUCER_SHA, **self.installer.BEFORE}
return {
"schema_version": "missioncore.observatory-claim-readiness/v1",
"open_live_lease_count": 0,
"recorded_jobs_by_state": {"succeeded": 2},
"protocols": [{}, {"grant_capacity_available": True}],
}
def test_plan_is_read_only_and_wrong_hash_cannot_cut_over(installer, tmp_path):
output = tmp_path / "payload"
installer.pack(Path(__file__).parents[1], output)
engine = FakeEngine(installer)
plan = installer.plan(engine, output)
assert plan["compute_packages_changed"] is False
assert plan["shared_cache"]["name"] == "ndc-observatory-source-cas-v1"
assert plan["producer_sha256"] == installer.PRODUCER_SHA
with pytest.raises(ValueError, match="plan changed"):
installer.apply(engine, output, "0" * 64, tmp_path / "evidence")
assert not (tmp_path / "evidence").exists()
assert all(method == "GET" for method, _ in engine.requests)
def test_fence_and_existing_volume_ownership_are_required(installer):
engine = FakeEngine(installer)
name = next(iter(installer.TARGETS))
row = engine.inspect(name)
target = {"name": name, "id": row["Id"], "create_sha256": installer.create_hash(row)}
installer.fence(engine, target)
row["HostConfig"]["Memory"] = 1024
with pytest.raises(ValueError, match="changed since plan"):
installer.fence(engine, target)
engine.volumes = [{"Name": installer.VOLUME, "Driver": "local", "Labels": {}}]
with pytest.raises(ValueError, match="another owner"):
installer.volume_state(engine)
+188
View File
@@ -0,0 +1,188 @@
from __future__ import annotations
import json
from pathlib import Path
import pytest
import rerun as rr
from k1link.artifact_gateway import CentralArtifactStore
from k1link.observatory.portable_object_replay import load_object_data, log_objects
from k1link.observatory.portable_tgs_replay import PortableReplayError
from k1link.perception.contracts import (
BoundingRegion2D,
EvidenceBasis,
EvidenceCurrentness,
MetricGeometry,
ObjectProposal2D,
ObstacleObservation,
)
BUNDLE = "b" * 64
def _fixture(tmp_path: Path, module_id: str, damage: str | None = None):
store = CentralArtifactStore(tmp_path / "store", create=True)
members: list[dict[str, object]] = []
def member(role: str, payload: object, media: str = "application/json") -> dict[str, object]:
path = tmp_path / role
encoded = payload if isinstance(payload, bytes) else json.dumps(payload).encode()
path.write_bytes(encoded)
published = store.publish_file(path)
row = {
"role": role,
"sha256": published.sha256,
"byte_length": published.byte_length,
"media_type": media,
}
members.append(row)
return row
proposals = []
for index in range(2):
proposal = ObjectProposal2D(
proposal_id=f"proposal-{index}",
source_id="recorded-k1",
frame_id=f"frame-{index + 1:06d}",
region=BoundingRegion2D(10.0, 20.0, 110.0, 220.0),
objectness=0.9,
provider_id="rf-detr-native",
model_id="rf-detr-large",
preprocess_id="kb4-native",
semantic_hint="car",
)
proposals.append(proposal)
detection_rows = [
{
"schema_version": "missioncore.observatory-ai-module-rf-detr-frame/v1",
"frame_index": index,
"session_seconds": 10.0 + index + (0.25 if damage == "clock" else 0),
"proposals": [proposal.to_dict()],
}
for index, proposal in enumerate(proposals)
]
detections = member(
"rf-detr-frame-detections",
b"".join(json.dumps(row).encode() + b"\n" for row in detection_rows),
"application/x-ndjson",
)
rf = member(
"rf-detr-result-document",
{
"schema_version": "missioncore.observatory-ai-module-rf-detr-result/v1",
"module_id": "rf-detr",
"source": {"session_id": "source", "source_id": "recorded-k1"},
"frame_count": 2,
"detections_sha256": detections["sha256"],
},
)
component = rf
if module_id == "object-distance":
observations = []
for index, proposal in enumerate(proposals):
observation = ObstacleObservation(
observation_id=f"observation-{index}",
occupancy_key=f"occupancy-{index}",
source_id="recorded-k1",
frame_id=proposal.frame_id,
evidence_time_ns=(10 + index) * 1_000_000_000,
basis=EvidenceBasis.FUSED,
currentness=EvidenceCurrentness.CURRENT,
occupied_support=True,
source_point_ids=(index + 1,),
metric_geometry=MetricGeometry(
"camera", (0.0, 0.0, 12.5 + index), 12.5 + index, (0.1, 0.1, 0.1)
),
proposal_ids=(proposal.proposal_id,),
semantic_hint="car",
reason_codes=("qualified",),
)
observations.append(
{
"schema_version": "missioncore.observatory-ai-module-object-distance-frame/v1",
"frame_index": index,
"session_seconds": 10.0 + index,
"observations": [observation.to_dict()],
}
)
distance_rows = member(
"object-distance-frame-observations",
b"".join(json.dumps(row).encode() + b"\n" for row in observations),
"application/x-ndjson",
)
component = member(
"object-distance-result-document",
{
"schema_version": "missioncore.observatory-ai-module-object-distance-result/v1",
"module_id": "object-distance",
"source_session_id": "source",
"frame_count": 2,
"object_distances_sha256": distance_rows["sha256"],
},
)
result_id = f"ai-layer-{module_id}-{'a' * 64}"
view = {
"result_id": result_id,
"source_session_id": "source",
"artifacts": members,
"result_document": {
"schema_version": "missioncore.recorded-ai-layer-review/v1",
"result_id": result_id,
"source": {
"session_id": "source",
"bundle_sha256": BUNDLE,
"frame_count": 2,
"timeline_start_seconds": 10.0,
"timeline_end_seconds": 12.0,
},
"module": {
"module_id": module_id,
"component_result_sha256": component["sha256"],
},
},
}
return view, store
@pytest.mark.parametrize("module_id", ["rf-detr", "object-distance"])
def test_object_replay_projects_boxes_and_optional_ranges(tmp_path: Path, module_id: str) -> None:
view, store = _fixture(tmp_path, module_id)
data = load_object_data(
view, store, source_bundle_sha256=BUNDLE, starts=[10.0, 11.0], end_seconds=12.0
)
assert data.include_ranges is (module_id == "object-distance")
assert data.frames[0].ranges_m == (
{"proposal-0": 12.5} if module_id == "object-distance" else {}
)
path = tmp_path / "objects.rrd"
recording = rr.RecordingStream("nodedc_mission_core_recorded", recording_id=module_id)
recording.set_sinks(rr.FileSink(path, write_footer=True))
calls: list[tuple[str, object]] = []
native_log = recording.log
def capture(entity: str, value: object, **kwargs: object) -> None:
calls.append((entity, value))
native_log(entity, value, **kwargs)
recording.log = capture
log_objects(recording, data)
recording.flush()
recording.disconnect()
assert path.read_bytes().startswith(b"RRF2")
assert sum(isinstance(value, rr.Boxes2D) for _, value in calls) == 2
assert isinstance(calls[-1][1], rr.Clear)
assert {entity for entity, _ in calls} == {"/perception/camera/detections"}
@pytest.mark.parametrize("damage", ["clock", "bundle"])
def test_object_replay_rejects_changed_source_or_clock(tmp_path: Path, damage: str) -> None:
view, store = _fixture(tmp_path, "object-distance", damage)
with pytest.raises(PortableReplayError):
load_object_data(
view,
store,
source_bundle_sha256="c" * 64 if damage == "bundle" else BUNDLE,
starts=[10.0, 11.0],
end_seconds=12.0,
)
+4 -2
View File
@@ -210,7 +210,8 @@ def test_camera_proxy_keeps_demux_time_base_and_reuses_its_own_sealed_cache(tmp_
assert len(calls) == 1
def test_replay_api_get_never_prepares_and_range_is_generation_fenced(tmp_path):
@pytest.mark.parametrize("prefix", ["m49-tgs-portable-review", "lab-v1-eomt-ddrnet"])
def test_replay_api_get_never_prepares_and_range_is_generation_fenced(tmp_path, prefix):
path = tmp_path / "replay.rrd"
path.write_bytes(b"RRF2test")
sha = hashlib.sha256(path.read_bytes()).hexdigest()
@@ -228,7 +229,8 @@ def test_replay_api_get_never_prepares_and_range_is_generation_fenced(tmp_path):
)
)
with TestClient(app) as client:
url = f"/api/v1/observatory/portable-results/{RESULT}/replays/{BASE}/recording.rrd"
result_id = prefix + "-" + "a" * 64
url = f"/api/v1/observatory/portable-results/{result_id}/replays/{BASE}/recording.rrd"
assert client.get(url, params={"generation": sha}).status_code == 409
assert not prepared
response = client.head(url)
+263
View File
@@ -0,0 +1,263 @@
from __future__ import annotations
import hashlib
import io
import json
import tarfile
import zipfile
from pathlib import Path
import numpy as np
import pytest
import rerun as rr
from PIL import Image
from k1link.artifact_gateway import CentralArtifactStore
from k1link.observatory.portable_semantic_replay import (
EOMT_LABELS,
EOMT_PROFILE_SHA,
RESULT_SCHEMA,
load_semantic_data,
log_semantics,
)
from k1link.observatory.portable_tgs_replay import PortableReplayError
RESULT = "lab-v1-eomt-ddrnet-" + "a" * 64
BUNDLE = "b" * 64
def fixture(tmp_path: Path, damage: str | None = None):
store = CentralArtifactStore(tmp_path / "store", create=True)
members = []
def member(role, payload, media="application/json"):
path = tmp_path / role
path.write_bytes(payload if isinstance(payload, bytes) else json.dumps(payload).encode())
published = store.publish_file(path)
value = {
"role": role,
"sha256": published.sha256,
"byte_length": published.byte_length,
"media_type": media,
}
members.append(value)
return value
png = io.BytesIO()
Image.fromarray(np.ones((600, 800), dtype=np.uint8)).save(png, format="PNG")
archive = io.BytesIO()
with tarfile.open(fileobj=archive, mode="w:gz") as stream:
for index in (1, 0):
if damage == "missing-mask" and index == 0:
continue
name = f"semantic-masks/frame-{index + 1:06d}.png"
if damage == "unsafe-mask" and index == 0:
name = "../frame-000001.png"
if damage == "wrong-suffix" and index == 0:
name = "semantic-masks/frame-000001Xpng"
item = tarfile.TarInfo(name)
item.size = len(png.getvalue())
if damage == "oversized-mask" and index == 0:
item.size = 1024 * 1024 + 1
stream.addfile(item, io.BytesIO(b"x" * item.size))
else:
stream.addfile(item, io.BytesIO(png.getvalue()))
member("eomt-panoptic-mask-archive", archive.getvalue(), "application/gzip")
archive = io.BytesIO()
with zipfile.ZipFile(archive, "w") as stream:
for index in range(2):
stream.writestr(f"masks/frame-{index + 1:06d}.png", png.getvalue())
ddr_archive = member("ddrnet-semantic-mask-archive", archive.getvalue(), "application/zip")
eomt = member(
"eomt-result-document",
{
"result_id": "eomt-result",
"frames_processed": 2,
"session_id": "source",
"input_sha256": "camera",
"timestamp_basis": "session-time-seconds",
"identity": {
"configuration": {
"profile_sha256": "d" * 64 if damage == "profile" else EOMT_PROFILE_SHA
}
},
},
)
ddrnet = member(
"ddrnet-result-document",
{
"result_id": "ddrnet-result",
"video_semantics": {
"mask_archive": {
**ddr_archive,
"frame_count": 2,
"width": 800,
"height": 600,
"encoding": "uint8-class-id-png",
},
"taxonomy": {
"schema_version": "missioncore.lab-v1-vegetation-taxonomy/v1",
"classes": [
{"class_id": i, "label": str(i), "color_rgb": [64, 128, 192]}
for i in range(64)
],
},
},
},
)
frames = [
{
"frame_index": i,
"sequence": i + 1,
"session_seconds": 10 + i + (0.1 if damage == "clock" else 0),
"semantic_classes": [{"id": 1, "label": "car" if damage == "label" else "person"}],
}
for i in range(2)
]
if damage == "extra-frame":
frames.append(frames[-1])
member(
"eomt-panoptic-frame-metadata",
b"".join(json.dumps(row).encode() + b"\n" for row in frames),
"application/x-ndjson",
)
view = {
"result_id": RESULT,
"source_session_id": "source",
"artifacts": members,
"result_document": {
"schema_version": RESULT_SCHEMA,
"result_id": RESULT,
"source": {
"session_id": "source",
"bundle_sha256": BUNDLE,
"camera_input_sha256": "camera",
"frame_count": 2,
"timeline_start_seconds": 10.0,
"timeline_end_seconds": 12.0,
},
"components": {
"eomt": {
"result_id": "eomt-result",
"frames_processed": 2,
"result_document_sha256": eomt["sha256"],
},
"ddrnet": {
"result_id": "ddrnet-result",
"frames_processed": 2,
"result_document_sha256": ddrnet["sha256"],
},
},
},
}
return view, store
def test_target_labels_match_the_exact_checked_in_producer_profile():
path = Path(__file__).parents[1] / "experiments/perception/worker/e3_k1_camera1_profile.json"
payload = path.read_bytes()
assert hashlib.sha256(payload).hexdigest() == EOMT_PROFILE_SHA
assert tuple(json.loads(payload)["target_taxonomy"].values()) == EOMT_LABELS
@pytest.mark.parametrize("damage", ["clock", "profile", "label", "extra-frame", "bundle"])
def test_mask_review_rejects_foreign_profile_source_or_clock(tmp_path, damage):
view, store = fixture(tmp_path, damage)
with pytest.raises(PortableReplayError):
load_semantic_data(
view,
store,
starts=[10.0, 11.0],
end_seconds=12.0,
source_bundle_sha256="c" * 64 if damage == "bundle" else BUNDLE,
)
@pytest.mark.parametrize(
"damage", [None, "missing-mask", "unsafe-mask", "oversized-mask", "wrong-suffix"]
)
def test_native_projection_is_complete_compressed_and_cleared_at_coverage_end(tmp_path, damage):
view, store = fixture(tmp_path, damage)
data = load_semantic_data(
view, store, starts=[10.0, 11.0], end_seconds=12.0, source_bundle_sha256=BUNDLE
)
path = tmp_path / "recording.rrd"
recording = rr.RecordingStream("nodedc_mission_core_recorded", recording_id="fixture")
recording.set_sinks(rr.FileSink(path, write_footer=True))
calls, times = [], []
original_log, original_time = recording.log, recording.set_time
def log(entity, value, **kwargs):
calls.append((entity, value))
original_log(entity, value, **kwargs)
def timestamp(name, *, duration):
times.append(int(duration.astype("timedelta64[ns]").astype(np.int64)))
original_time(name, duration=duration)
recording.log, recording.set_time = log, timestamp
try:
if damage:
with pytest.raises(PortableReplayError):
log_semantics(recording, data)
else:
log_semantics(recording, data)
recording.flush()
assert path.read_bytes().startswith(b"RRF2")
assert sum(isinstance(value, rr.EncodedImage) for _, value in calls) == 4
assert times == [
11_000_000_000,
10_000_000_000,
10_000_000_000,
11_000_000_000,
12_000_000_000,
]
assert isinstance(calls[-1][1], rr.Clear)
assert all(entity.startswith("/perception/camera/segmentation") for entity, _ in calls)
finally:
recording.disconnect()
@pytest.mark.parametrize(
("module_id", "result_role", "expected_layers"),
[
("eomt", "eomt-result-document", {"city"}),
("ddrnet", "ddrnet-result-document", {"vegetation"}),
],
)
def test_independent_ai_module_result_opens_only_its_own_semantic_layer(
tmp_path: Path,
module_id: str,
result_role: str,
expected_layers: set[str],
) -> None:
view, store = fixture(tmp_path)
result_id = f"ai-layer-{module_id}-{'c' * 64}"
result_artifact = next(item for item in view["artifacts"] if item["role"] == result_role)
view["result_id"] = result_id
view["result_document"] = {
"schema_version": "missioncore.recorded-ai-layer-review/v1",
"result_id": result_id,
"source": {
"session_id": "source",
"bundle_sha256": BUNDLE,
"camera_input_sha256": "camera",
"frame_count": 2,
"timeline_start_seconds": 10.0,
"timeline_end_seconds": 12.0,
},
"module": {
"module_id": module_id,
"component_result_sha256": result_artifact["sha256"],
},
}
data = load_semantic_data(
view,
store,
starts=[10.0, 11.0],
end_seconds=12.0,
source_bundle_sha256=BUNDLE,
)
assert set(data.classes) == expected_layers
assert (data.city_masks is not None) is (module_id == "eomt")
assert (data.vegetation_masks is not None) is (module_id == "ddrnet")
+116
View File
@@ -0,0 +1,116 @@
from __future__ import annotations
import gc
import threading
import weakref
import pytest
from k1link.viewer.recorded_blueprint_lifecycle import (
BlueprintSessionReleased,
RecordedBlueprintSessions,
)
KEY = ("app", "recording", "a" * 32)
class Resource:
def __init__(self) -> None:
self.closes = 0
def close(self) -> None:
self.closes += 1
def test_release_drops_resource_and_fences_late_updates() -> None:
sessions = RecordedBlueprintSessions[Resource]()
resource = Resource()
reference = weakref.ref(resource)
assert (
sessions.use(KEY, 0, lambda resource=resource: resource, lambda _: b"result") == b"result"
)
sessions.release(KEY)
sessions.release(KEY)
assert resource.closes == 1
assert not sessions.renew(KEY)
with pytest.raises(BlueprintSessionReleased):
sessions.use(KEY, 0, Resource, lambda _: b"late")
del resource
gc.collect()
assert reference() is None
def test_renew_keeps_paused_viewport_but_abandoned_owner_expires() -> None:
now = [0.0]
sessions = RecordedBlueprintSessions[Resource](clock=lambda: now[0], ttl_seconds=300)
resource = Resource()
sessions.use(KEY, 0, lambda: resource, lambda _: None)
for timestamp in [200, 400, 600]:
now[0] = timestamp
assert sessions.renew(KEY)
sessions.expire()
assert resource.closes == 0
now[0] = 901
sessions.expire()
assert resource.closes == 1
assert not sessions.renew(KEY)
# An expired blueprint is rebuildable; this is not a recording/live stop.
sessions.use(KEY, 0, Resource, lambda _: None)
sessions.close()
def test_failed_render_releases_partial_resource_and_can_retry() -> None:
sessions = RecordedBlueprintSessions[Resource]()
resource = Resource()
def fail(_: Resource) -> None:
raise ValueError("failed serialization")
with pytest.raises(ValueError, match="failed serialization"):
sessions.use(KEY, 0, lambda: resource, fail)
assert resource.closes == 1
assert not sessions.renew(KEY)
sessions.use(KEY, 0, Resource, lambda _: None)
sessions.close()
def test_reset_lru_and_shutdown_release_resources_exactly_once() -> None:
sessions = RecordedBlueprintSessions[Resource](max_entries=1)
first, replacement, other = Resource(), Resource(), Resource()
sessions.use(KEY, 0, lambda: first, lambda _: None)
sessions.use(KEY, 1, lambda: replacement, lambda _: None)
assert first.closes == 1
sessions.use(("app", "other", "b" * 32), 0, lambda: other, lambda _: None)
assert replacement.closes == 1
sessions.close()
sessions.close()
assert other.closes == 1
def test_release_waits_for_render_and_prevents_resurrection() -> None:
sessions = RecordedBlueprintSessions[Resource]()
resource = Resource()
started, finish, released = threading.Event(), threading.Event(), threading.Event()
def render(_: Resource) -> None:
started.set()
assert finish.wait(2)
assert resource.closes == 0
def release() -> None:
sessions.release(KEY)
released.set()
worker = threading.Thread(target=lambda: sessions.use(KEY, 0, lambda: resource, render))
worker.start()
assert started.wait(2)
closer = threading.Thread(target=release)
closer.start()
assert not released.is_set()
finish.set()
worker.join(2)
closer.join(2)
assert not worker.is_alive() and not closer.is_alive()
assert released.is_set() and resource.closes == 1
with pytest.raises(BlueprintSessionReleased):
sessions.use(KEY, 0, Resource, lambda _: None)
+86
View File
@@ -0,0 +1,86 @@
from pathlib import Path
import numpy as np
import pytest
import rerun as rr
import k1link.viewer.recorded_camera_bounds as camera_bounds
from k1link.viewer.recorded_camera_bounds import recorded_orbital_radius_limit
def _recording(path: Path) -> None:
recording = rr.RecordingStream("camera-bounds-test", recording_id="camera-bounds")
recording.set_sinks(rr.FileSink(path, write_footer=True))
try:
recording.set_time("session_time", duration=np.timedelta64(10, "s"))
recording.log("/world/points", rr.Points3D([[0, 0, 0], [3, 4, 0]]))
recording.set_time("session_time", duration=np.timedelta64(20, "s"))
recording.log("/world/points", rr.Points3D([[10, 0, 0], [10, 0, 12]]))
recording.flush(timeout_sec=5)
finally:
recording.disconnect()
def test_recorded_orbital_radius_uses_visible_accumulation_window(tmp_path: Path) -> None:
path = tmp_path / "recording.rrd"
_recording(path)
accumulated = recorded_orbital_radius_limit(
path,
current_time_ns=20_000_000_000,
accumulation_seconds=15,
show_points=True,
show_trajectory=False,
)
latest = recorded_orbital_radius_limit(
path,
current_time_ns=20_000_000_000,
accumulation_seconds=0,
show_points=True,
show_trajectory=False,
)
assert accumulated == pytest.approx(5 * np.sqrt(10**2 + 4**2 + 12**2), rel=1e-6)
assert latest == pytest.approx(60, rel=1e-6)
def test_recorded_orbital_radius_is_absent_without_mapping_layers(tmp_path: Path) -> None:
path = tmp_path / "recording.rrd"
_recording(path)
assert (
recorded_orbital_radius_limit(
path,
current_time_ns=20_000_000_000,
accumulation_seconds=15,
show_points=False,
show_trajectory=False,
)
is None
)
def test_recorded_camera_queries_reuse_the_generation_bounds_index(tmp_path: Path) -> None:
path = tmp_path / "recording.rrd"
_recording(path)
camera_bounds._recorded_spatial_bounds_index.cache_clear()
first = recorded_orbital_radius_limit(
path,
current_time_ns=10_000_000_000,
accumulation_seconds=0,
show_points=True,
show_trajectory=False,
)
second = recorded_orbital_radius_limit(
path,
current_time_ns=20_000_000_000,
accumulation_seconds=0,
show_points=True,
show_trajectory=False,
)
cache = camera_bounds._recorded_spatial_bounds_index.cache_info()
assert first == pytest.approx(25, rel=1e-6)
assert second == pytest.approx(60, rel=1e-6)
assert cache.misses == 1
assert cache.hits == 1
+32 -4
View File
@@ -499,6 +499,30 @@ def test_recorded_follow_mode_tracks_sensor_pose_with_the_same_orbital_eye() ->
}
def test_recorded_layer_blueprint_can_carry_the_operator_eye() -> None:
blueprint = viewer_recorded_blueprint(
RerunSceneSettings(),
include_initial_playback_state=False,
unified_perception=True,
update_eye_controls=False,
eye_position=(3.0, 4.0, 5.0),
eye_look_target=(1.0, 2.0, 0.0),
eye_up=(0.0, 0.0, 1.0),
)
eye = blueprint.root_container.contents[1].properties["EyeControls3D"]
components = {
str(batch.component_descriptor()): batch.as_arrow_array().to_pylist()
for batch in eye.as_component_batches()
}
assert components == {
"EyeControls3D:kind": [2],
"EyeControls3D:position": [[3.0, 4.0, 5.0]],
"EyeControls3D:look_target": [[1.0, 2.0, 0.0]],
"EyeControls3D:eye_up": [[0.0, 0.0, 1.0]],
"EyeControls3D:tracking_entity": [""],
}
@pytest.mark.parametrize("reactivate_updates", [False, True])
def test_recorded_blueprint_updates_reuse_source_store_and_opt_in_to_activation(
monkeypatch: pytest.MonkeyPatch,
@@ -601,9 +625,10 @@ def test_recorded_blueprint_updates_reuse_source_store_and_opt_in_to_activation(
assert len(reset_store_ids) == 1
assert reset_store_ids.isdisjoint(initial_store_ids)
assert eye_control_updates == [True, False, True, False, True]
assert explicit_presets == [
False, reactivate_updates, reactivate_updates, reactivate_updates, False
]
# Reactivating a blueprint clone is required for visible layer changes, but
# those changes must not write position/look-target/eye-up and reset the
# operator's camera. Only the follow transition writes a spatial preset.
assert explicit_presets == [False, False, True, False, False]
assert [activation[1:] for activation in activations] == [
(True, False),
(reactivate_updates, False),
@@ -644,18 +669,21 @@ def test_viewer_blueprint_unifies_original_video_and_independent_ai_layers() ->
RerunSceneSettings(accumulation_seconds=12.0),
include_initial_playback_state=False,
unified_perception=True,
unified_camera_share=0.73,
show_camera_image=False,
show_detections_2d=True,
show_segmentation=True,
show_cuboids_3d=False,
)
assert type(blueprint.root_container).__name__ == "Horizontal"
assert blueprint.root_container.column_shares == pytest.approx([0.73, 0.27])
camera_view, spatial_view = blueprint.root_container.contents
assert camera_view.origin == "/perception/camera"
assert spatial_view.origin == "/world"
assert camera_view.visualizer_overrides[
"/perception/camera/image"
].visible.as_arrow_array().to_pylist() == [True]
].visible.as_arrow_array().to_pylist() == [False]
assert camera_view.visualizer_overrides[
"/perception/camera/detections"
].visible.as_arrow_array().to_pylist() == [True]
+227 -42
View File
@@ -9,6 +9,7 @@ import time
from collections.abc import Callable
from dataclasses import replace
from pathlib import Path
from types import SimpleNamespace
from typing import Any
import pytest
@@ -41,6 +42,7 @@ from k1link.web.camera_archive import CameraArchiveWriter
from k1link.web.observatory_api import build_observatory_router
from k1link.web.session_api import (
LayoutPutRequest,
RecordedBlueprintLifecycleRequest,
RecordedBlueprintRequest,
RecordedPerceptionRequest,
RecordedPointColorsRequest,
@@ -441,20 +443,15 @@ def test_session_router_versions_capability_and_calculation_profile_projections(
)["items"]
v3_by_id = {item["id"]: item for item in v3_labs}
assert v3_by_id[legacy.session_id]["lab"]["calculation_profile"] is None
assert (
v3_by_id[canonical.session_id]["lab"]["calculation_profile"]
== calculation_profile
)
assert (
v3_by_id[canonical.session_id]["lab"]["replay_capability"]
== capability.as_dict()
)
assert v3_by_id[canonical.session_id]["lab"]["calculation_profile"] == calculation_profile
assert v3_by_id[canonical.session_id]["lab"]["replay_capability"] == capability.as_dict()
application = FastAPI()
application.include_router(router)
assert TestClient(application).get(
"/api/v1/observation-sessions?lab_contract=v4"
).status_code == 422
assert (
TestClient(application).get("/api/v1/observation-sessions?lab_contract=v4").status_code
== 422
)
def test_observatory_projection_api_renames_alias_and_deletes_only_projection(
@@ -508,28 +505,37 @@ def test_observatory_projection_api_renames_alias_and_deletes_only_projection(
client = TestClient(application)
url = f"/api/v1/observatory/lab-projections/{projection.session_id}"
assert client.patch(
url,
json={
"schema_version": "missioncore.observatory-lab-projection-rename/v0",
"display_name": "Неверная версия",
},
).status_code == 422
assert client.patch(
url,
json={
"schema_version": "missioncore.observatory-lab-projection-rename/v1",
"display_name": "Новый разбор",
"unexpected": True,
},
).status_code == 422
assert client.patch(
url,
json={
"schema_version": "missioncore.observatory-lab-projection-rename/v1",
"display_name": " ",
},
).status_code == 422
assert (
client.patch(
url,
json={
"schema_version": "missioncore.observatory-lab-projection-rename/v0",
"display_name": "Неверная версия",
},
).status_code
== 422
)
assert (
client.patch(
url,
json={
"schema_version": "missioncore.observatory-lab-projection-rename/v1",
"display_name": "Новый разбор",
"unexpected": True,
},
).status_code
== 422
)
assert (
client.patch(
url,
json={
"schema_version": "missioncore.observatory-lab-projection-rename/v1",
"display_name": " ",
},
).status_code
== 422
)
renamed = client.patch(
url,
@@ -785,16 +791,16 @@ def test_canonical_lab_spatial_frame_uses_ready_immutable_recording(
"GET",
)
try:
response = asyncio.run(spatial_route(
session_id=session.name,
generation=generation,
time_ns=500_000_000,
profile="source-paced-ground-v3",
))
assert json.loads(response.body) == expected
assert response.headers["etag"] == (
f'"{generation}:source-paced-ground-v3:499000000"'
response = asyncio.run(
spatial_route(
session_id=session.name,
generation=generation,
time_ns=500_000_000,
profile="source-paced-ground-v3",
)
)
assert json.loads(response.body) == expected
assert response.headers["etag"] == (f'"{generation}:source-paced-ground-v3:499000000"')
assert response.headers["cache-control"].endswith("immutable")
finally:
manager.close()
@@ -1561,6 +1567,89 @@ def test_production_router_never_materializes_recording_inline(tmp_path: Path) -
assert calls == 0
def test_blueprint_lifecycle_releases_without_repreparing_source(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
calls: list[tuple[str, tuple[str, str, str]]] = []
class Sessions:
def release(self, key: tuple[str, str, str]) -> None:
calls.append(("release", key))
def renew(self, key: tuple[str, str, str]) -> bool:
calls.append(("renew", key))
return True
def forbidden_prepare(*args: object, **kwargs: object) -> None:
pytest.fail("lifecycle must not reprepare a recording")
monkeypatch.setattr("k1link.web.session_api.recorded_blueprint_sessions", Sessions())
monkeypatch.setattr("k1link.web.session_api._prepare_replay", forbidden_prepare)
store = SessionStore(tmp_path / "repo", data_dir=tmp_path / "data")
route = endpoint(
build_session_router(store),
"/api/v1/observation-sessions/{session_id}/blueprint-lifecycle",
"POST",
)
for action in ("renew", "release"):
request = RecordedBlueprintLifecycleRequest.model_validate(
{
"action": action,
"application_id": "nodedc_mission_core_recorded",
"recording_id": "recording",
"blueprint_session_id": "f" * 32,
}
)
response = asyncio.run(route(session_id="removed-source", request=request))
assert response.status_code == 204
assert response.headers["cache-control"] == "no-store"
assert calls == [
(action, ("nodedc_mission_core_recorded", "recording", "f" * 32))
for action in ("renew", "release")
]
with pytest.raises(HTTPException) as error:
asyncio.run(route(session_id="../unsafe", request=request))
assert error.value.status_code == 422
with pytest.raises(ValueError):
RecordedBlueprintLifecycleRequest.model_validate({**request.model_dump(), "path": "/tmp"})
def test_released_blueprint_update_returns_gone(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
from k1link.viewer.recorded_blueprint_lifecycle import BlueprintSessionReleased
def released(*args: object, **kwargs: object) -> bytes:
raise BlueprintSessionReleased("closed")
monkeypatch.setattr("k1link.web.session_api._prepare_replay", lambda *args: None)
monkeypatch.setattr("k1link.web.session_api.recorded_blueprint_rrd", released)
store = SessionStore(tmp_path / "repo", data_dir=tmp_path / "data")
route = endpoint(
build_session_router(store),
"/api/v1/observation-sessions/{session_id}/blueprint.rrd",
"POST",
)
with pytest.raises(HTTPException) as error:
asyncio.run(
route(
session_id="source",
request=RecordedBlueprintRequest(
application_id="nodedc_mission_core_recorded",
recording_id="recording",
blueprint_session_id="f" * 32,
accumulation_seconds=0.0,
show_points=True,
show_trajectory=False,
show_grid=True,
),
)
)
assert error.value.status_code == 410
def test_recorded_blueprint_endpoint_is_small_strict_and_session_scoped(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
@@ -1607,10 +1696,14 @@ def test_recorded_blueprint_endpoint_is_small_strict_and_session_scoped(
active_view="perception",
view_reset_generation=1,
unified_perception=True,
unified_camera_share=0.73,
show_detections_2d=True,
show_segmentation=True,
show_cuboids_3d=True,
follow_trajectory=True,
eye_position=(3.0, 4.0, 5.0),
eye_look_target=(1.0, 2.0, 0.0),
eye_up=(0.0, 0.0, 1.0),
),
)
)
@@ -1629,10 +1722,14 @@ def test_recorded_blueprint_endpoint_is_small_strict_and_session_scoped(
assert observed_kwargs[0]["active_view"] == "perception"
assert observed_kwargs[0]["view_reset_generation"] == 1
assert observed_kwargs[0]["unified_perception"] is True
assert observed_kwargs[0]["unified_camera_share"] == pytest.approx(0.73)
assert observed_kwargs[0]["show_detections_2d"] is True
assert observed_kwargs[0]["show_segmentation"] is True
assert observed_kwargs[0]["show_cuboids_3d"] is True
assert observed_kwargs[0]["follow_trajectory"] is True
assert observed_kwargs[0]["eye_position"] == (3.0, 4.0, 5.0)
assert observed_kwargs[0]["eye_look_target"] == (1.0, 2.0, 0.0)
assert observed_kwargs[0]["eye_up"] == (0.0, 0.0, 1.0)
with pytest.raises(ValueError):
RecordedBlueprintRequest.model_validate(
@@ -1644,6 +1741,32 @@ def test_recorded_blueprint_endpoint_is_small_strict_and_session_scoped(
"source_url": "https://outside.invalid/recording.rrd",
}
)
with pytest.raises(ValueError):
RecordedBlueprintRequest.model_validate(
{
"application_id": "nodedc_mission_core_recorded",
"recording_id": "recording-001",
"blueprint_session_id": "a" * 32,
"accumulation_seconds": 12.0,
"show_points": True,
"show_trajectory": True,
"show_grid": True,
"unified_camera_share": 1.2,
}
)
with pytest.raises(ValueError):
RecordedBlueprintRequest.model_validate(
{
"application_id": "nodedc_mission_core_recorded",
"recording_id": "recording-001",
"blueprint_session_id": "a" * 32,
"accumulation_seconds": 12.0,
"show_points": True,
"show_trajectory": True,
"show_grid": True,
"eye_position": [3.0, 4.0, 5.0],
}
)
with pytest.raises(HTTPException) as missing:
asyncio.run(
blueprint_route(
@@ -1662,6 +1785,68 @@ def test_recorded_blueprint_endpoint_is_small_strict_and_session_scoped(
assert missing.value.status_code == 404
def test_recorded_blueprint_restores_camera_bounds_after_base_launch_lease_expires(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
recording_path = tmp_path / "published.rrd"
calls: list[tuple[Path, dict[str, object]]] = []
class PublishedMaterializer:
def restore_published(self, command: ReplayCommand) -> object:
assert command.session_id == session.name
return SimpleNamespace(path=recording_path)
def camera_bounds(path: Path, **kwargs: object) -> float:
calls.append((path, kwargs))
return 123.5
monkeypatch.setattr(
"k1link.web.session_api.recorded_blueprint_rrd",
lambda *_args, **_kwargs: b"RRF2",
)
monkeypatch.setattr("k1link.web.session_api.recorded_orbital_radius_limit", camera_bounds)
route = endpoint(
build_session_router(store, recording_materializer=PublishedMaterializer()), # type: ignore[arg-type]
"/api/v1/observation-sessions/{session_id}/blueprint.rrd",
"POST",
)
response = asyncio.run(
route(
session_id=session.name,
request=RecordedBlueprintRequest(
application_id="nodedc_mission_core_recorded",
recording_id="recording-camera-bounds",
blueprint_session_id="b" * 32,
accumulation_seconds=305.0,
show_points=True,
show_trajectory=False,
show_grid=True,
current_time_ns=39_215_000_000,
),
)
)
assert response.headers["x-missioncore-camera-max-orbital-radius"] == "123.5"
assert calls == [
(
recording_path,
{
"current_time_ns": 39_215_000_000,
"accumulation_seconds": 305.0,
"show_points": True,
"show_trajectory": False,
},
)
]
def test_recorded_perception_endpoint_returns_one_complete_optional_overlay(
tmp_path: Path,
) -> None:
+76
View File
@@ -0,0 +1,76 @@
from pathlib import Path
from fastapi import FastAPI
from fastapi.testclient import TestClient
from test_session_api import lab_method, make_legacy_session
from k1link.device_plugins.xgrids_k1 import xgrids_k1_archive_source
from k1link.sessions import SessionStore
from k1link.web.session_api import build_session_router
def test_cursor_pages_keep_legacy_response_and_all_catalog_records(tmp_path: Path) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
for index in range(102):
make_legacy_session(sessions, f"source-{index:03}_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
for index in range(102):
store.publish_lab_instance(
session_id=f"lab-{index:03}",
source_session_id="source-000_viewer_live",
display_name=f"Historical LAB {index}",
lab_id=f"LAB E{index}",
result_kind="historical-evidence",
result_id=f"result-{index:03}",
run_created_at_utc="2026-07-23T15:55:15.548Z",
provenance={"method": lab_method()},
)
app = FastAPI()
app.include_router(build_session_router(store))
with TestClient(app) as client:
for scope in ("source", "laboratory"):
base = {"scope": scope, "limit": 100, "lab_contract": "v3"}
legacy = client.get("/api/v1/observation-sessions", params=base)
assert legacy.status_code == 200
assert set(legacy.json()) == {"items"}
first = client.get(
"/api/v1/observation-sessions", params={**base, "pagination": "cursor-v1"}
).json()
assert first["schema_version"] == "missioncore.observation-session-page/v1"
assert first["items"] == legacy.json()["items"]
assert first["next_cursor"] == first["items"][-1]["id"]
second = client.get(
"/api/v1/observation-sessions",
params={**base, "pagination": "cursor-v1", "cursor": first["next_cursor"]},
).json()
assert len(second["items"]) == 2
assert second["next_cursor"] is None
assert len({item["id"] for item in first["items"] + second["items"]}) == 102
# Neither paging nor the Observatory projection mutates the Legacy endpoint.
assert client.get("/api/v1/observation-sessions", params=base).json() == legacy.json()
assert client.get("/api/v1/observation-sessions?pagination=cursor-v2").status_code == 422
assert (
client.get(
"/api/v1/observation-sessions?scope=source&pagination=cursor-v1&cursor=lab-099"
).status_code
== 404
)
def test_empty_and_exact_pages_end_explicitly(tmp_path: Path) -> None:
repository = tmp_path / "repo"
store = SessionStore(repository, data_dir=tmp_path / "data")
app = FastAPI()
app.include_router(build_session_router(store))
with TestClient(app) as client:
url = "/api/v1/observation-sessions?scope=source&pagination=cursor-v1&limit=1"
empty = client.get(url).json()
assert empty["items"] == []
assert empty["next_cursor"] is None
make_legacy_session(repository / "sessions", "source-one_viewer_live")
store.reconcile_archive(xgrids_k1_archive_source(repository / "sessions"))
full = client.get(url).json()
assert len(full["items"]) == 1
assert full["next_cursor"] is None
+38 -24
View File
@@ -21,6 +21,7 @@ import k1link.laboratory.vegetation_shadow_lab as vegetation_lab_module
import k1link.web.vegetation_shadow_lab_api as vegetation_api_module
from k1link.laboratory import LaboratoryEvidenceRegistry
from k1link.laboratory.canonical_rerun_overlay import (
CANONICAL_REPLAY_RESULT_ID,
CanonicalLabOverlayArtifact,
CanonicalLabReplayArtifact,
_artifact_is_regular,
@@ -69,10 +70,13 @@ def test_canonical_overlay_localizes_current_taxonomies() -> None:
def test_canonical_video_references_hold_only_missing_source_samples() -> None:
session_times = np.arange(10, dtype=np.int64) * 100_000_000 + 39_000_000_000
video_times = np.array(
[0, 100, 200, 300, 400, 500, 600, 700, 900],
dtype=np.int64,
) * 1_000_000
video_times = (
np.array(
[0, 100, 200, 300, 400, 500, 600, 700, 900],
dtype=np.int64,
)
* 1_000_000
)
references = _video_reference_timestamps(video_times, session_times)
@@ -219,6 +223,22 @@ def test_canonical_replay_merges_base_and_overlay_once(
assert calls == 1
def test_canonical_replay_accepts_each_published_portable_result_family() -> None:
suffix = "a" * 64
accepted = {
"lab-v1-vegetation-shadow",
"m49-tgs-portable-review",
"lab-v1-eomt-ddrnet",
"ai-layer-ddrnet",
"ai-layer-eomt",
"ai-layer-rf-detr",
"ai-layer-object-distance",
}
assert all(CANONICAL_REPLAY_RESULT_ID.fullmatch(f"{prefix}-{suffix}") for prefix in accepted)
assert CANONICAL_REPLAY_RESULT_ID.fullmatch(f"ai-layer-unknown-{suffix}") is None
def test_canonical_overlay_get_is_generation_bound_and_range_streamable(
tmp_path: Path,
monkeypatch,
@@ -341,9 +361,7 @@ def test_canonical_overlay_get_is_generation_bound_and_range_streamable(
)
assert stale_overlay.status_code == 412
replay_endpoint = (
f"/api/v1/laboratory/vegetation-shadow/{result_id}/canonical-replay.rrd"
)
replay_endpoint = f"/api/v1/laboratory/vegetation-shadow/{result_id}/canonical-replay.rrd"
replay_descriptor = client.head(
replay_endpoint,
params={"base_generation": generation},
@@ -496,7 +514,9 @@ def _worker_result(root: Path, *, candidate: str, mode: str, vegetation_iou: flo
"truth_pixels": 16384,
"truth_fraction": 0.0625,
"stratum_rank": index + 1,
} if mode == "goose" else None,
}
if mode == "goose"
else None,
"files": files,
}
)
@@ -726,8 +746,7 @@ def test_vegetation_shadow_lab_seals_autonomous_visual_evidence(
},
]
(full_root / "result.json").write_text(
json.dumps(full_manifest, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
+ "\n",
json.dumps(full_manifest, ensure_ascii=False, sort_keys=True, separators=(",", ":")) + "\n",
encoding="utf-8",
)
for layer, sequence, expected in (
@@ -735,27 +754,24 @@ def test_vegetation_shadow_lab_seals_autonomous_visual_evidence(
("vegetation", 1, full_archive_payloads[1]),
):
response = client.get(
f"/api/v1/laboratory/vegetation-shadow/{full_result_id}"
f"/route-masks/{layer}/{sequence}"
f"/api/v1/laboratory/vegetation-shadow/{full_result_id}/route-masks/{layer}/{sequence}"
)
assert response.status_code == 200
assert response.content == expected
assert response.headers["cache-control"].endswith("immutable")
assert client.get(
f"/api/v1/laboratory/vegetation-shadow/{full_result_id}/route-masks/city/2"
).status_code == 404
timeline = client.get(
f"/api/v1/laboratory/vegetation-shadow/{full_result_id}/route-timeline"
assert (
client.get(
f"/api/v1/laboratory/vegetation-shadow/{full_result_id}/route-masks/city/2"
).status_code
== 404
)
timeline = client.get(f"/api/v1/laboratory/vegetation-shadow/{full_result_id}/route-timeline")
assert timeline.status_code == 200
assert timeline.content == full_timeline_payload
assert timeline.headers["cache-control"].endswith("immutable")
(result_root / asset_path).write_bytes(b"tampered")
assert (
client.get(f"/api/v1/laboratory/vegetation-shadow/{result_root.name}").status_code
== 503
)
assert client.get(f"/api/v1/laboratory/vegetation-shadow/{result_root.name}").status_code == 503
def test_policy_review_reuses_sealed_video_and_links_yolox_tgs(
@@ -837,9 +853,7 @@ def test_policy_review_reuses_sealed_video_and_links_yolox_tgs(
assert route["linked_tgs_result_id"] == tgs_result_id
assert route["fusion"]["pixel_raster_fusion"] is False
assert route["fusion"]["camera_semantic_temporal_filter"] == "none"
assert route["taxonomy"]["schema_version"] == (
"missioncore.lab-v1-terrain-policy-taxonomy/v1"
)
assert route["taxonomy"]["schema_version"] == ("missioncore.lab-v1-terrain-policy-taxonomy/v1")
assert len(route["taxonomy"]["classes"]) == 10
assert route["valid_fov"]["outside_valid_fov_class_id"] == 9
assert len(manifest["artifacts"]) == 80
+129
View File
@@ -0,0 +1,129 @@
from __future__ import annotations
import errno
import hashlib
import os
from pathlib import Path
import pytest
from k1link.observatory.worker_source_cache import WorkerSourceCache, WorkerSourceCacheError
def test_cache_reuses_read_only_bytes_without_copy_or_retained_buffers(tmp_path: Path) -> None:
source = tmp_path / "first-job-input"
source.write_bytes(b"source-bytes")
digest = hashlib.sha256(source.read_bytes()).hexdigest()
cache = WorkerSourceCache(tmp_path / "source-cache")
assert cache.retain(source, sha256=digest, byte_length=12)
destination = tmp_path / "second-job-input"
assert cache.restore(destination, sha256=digest, byte_length=12)
assert destination.read_bytes() == b"source-bytes"
assert destination.stat().st_ino == source.stat().st_ino
assert destination.stat().st_mode & 0o222 == 0
assert set(vars(cache)) == {"root"}
assert not list(tmp_path.rglob(".source-cache-*"))
def test_bad_cache_is_miss_and_original_evidence_is_preserved(tmp_path: Path) -> None:
good = b"good-source"
digest = hashlib.sha256(good).hexdigest()
cache = WorkerSourceCache(tmp_path / "source-cache")
damaged = cache.root / digest
damaged.write_bytes(b"bad--source")
assert not cache.restore(tmp_path / "new-job", sha256=digest, byte_length=len(good))
new_source = tmp_path / "fresh-download"
new_source.write_bytes(good)
assert not cache.retain(new_source, sha256=digest, byte_length=len(good))
assert damaged.read_bytes() == b"bad--source"
assert new_source.read_bytes() == good
assert not (tmp_path / "new-job").exists()
assert not list(tmp_path.rglob(".source-cache-*"))
@pytest.mark.parametrize("kind", ["symlink", "fifo", "directory"])
def test_unsafe_cached_object_is_not_read(tmp_path: Path, kind: str) -> None:
digest = hashlib.sha256(b"data").hexdigest()
cache = WorkerSourceCache(tmp_path / "cache")
cached = cache.root / digest
if kind == "symlink":
private = tmp_path / "not-source-evidence"
private.write_bytes(b"data")
cached.symlink_to(private)
elif kind == "fifo":
os.mkfifo(cached)
else:
cached.mkdir()
assert not cache.restore(tmp_path / "new-job", sha256=digest, byte_length=4)
assert not (tmp_path / "new-job").exists()
def test_wrong_source_bytes_do_not_enter_cache(tmp_path: Path) -> None:
source = tmp_path / "download"
source.write_bytes(b"bad")
cache = WorkerSourceCache(tmp_path / "cache")
digest = hashlib.sha256(b"yes").hexdigest()
assert not cache.retain(source, sha256=digest, byte_length=3)
assert list(cache.root.iterdir()) == []
assert source.read_bytes() == b"bad"
def test_existing_destination_and_unsafe_identity_are_rejected(tmp_path: Path) -> None:
source = tmp_path / "download"
source.write_bytes(b"data")
cache = WorkerSourceCache(tmp_path / "cache")
digest = hashlib.sha256(b"data").hexdigest()
cache.retain(source, sha256=digest, byte_length=4)
with pytest.raises(WorkerSourceCacheError, match="not empty"):
cache.restore(source, sha256=digest, byte_length=4)
with pytest.raises(WorkerSourceCacheError, match="identity is invalid"):
cache.restore(tmp_path / "other", sha256="../outside", byte_length=4)
def test_unsupported_hardlinks_are_only_cache_misses(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
source = tmp_path / "download"
source.write_bytes(b"data")
cache = WorkerSourceCache(tmp_path / "cache")
def unsupported(*args: object, **kwargs: object) -> None:
raise OSError("different filesystem")
monkeypatch.setattr(os, "link", unsupported)
assert not cache.retain(source, sha256=hashlib.sha256(b"data").hexdigest(), byte_length=4)
assert list(cache.root.iterdir()) == []
@pytest.mark.parametrize("corrupt", [False, True])
def test_shared_cache_across_filesystems_copies_bounded_and_cleans_failures(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
corrupt: bool,
) -> None:
source = tmp_path / "download"
payload = b"abcd" * 300_000
source.write_bytes(payload)
cache = WorkerSourceCache(tmp_path / "shared-cache")
real_link = os.link
real_read = os.read
read_sizes = []
def link(src: Path, dst: Path, **kwargs: object) -> None:
if Path(src) == source:
raise OSError(errno.EXDEV, "separate agent mount")
real_link(src, dst, **kwargs)
def read(fd: int, size: int) -> bytes:
read_sizes.append(size)
return real_read(fd, size)
monkeypatch.setattr(os, "link", link)
monkeypatch.setattr(os, "read", read)
digest = hashlib.sha256(b"other" if corrupt else payload).hexdigest()
assert cache.retain(source, sha256=digest, byte_length=len(payload)) is not corrupt
assert max(read_sizes) <= 1024 * 1024
assert not list(tmp_path.rglob(".source-cache-*"))
if not corrupt:
assert (cache.root / digest).read_bytes() == payload
assert (cache.root / digest).stat().st_ino != source.stat().st_ino
+147
View File
@@ -0,0 +1,147 @@
from __future__ import annotations
import hashlib
import json
from dataclasses import replace
from pathlib import Path
import httpx
import pytest
from test_observatory_worker_http_transport import (
BEARER_TOKEN,
CLAIM_TOKEN,
_cache_claim,
_job,
_source_contract,
)
from k1link.observatory.worker_http_transport import (
ObservatoryWorkerHttpError,
ObservatoryWorkerHttpGateway,
)
@pytest.mark.parametrize(
"next_run", ["new-generation", "different-profile", "changed-camera", "bad-cache"]
)
def test_other_claim_reuses_bytes_but_obtains_its_own_manifest(
tmp_path: Path, next_run: str
) -> None:
first = _job(
bundle_sha256=hashlib.sha256(b"source-bundle").hexdigest(),
capability_sha256=hashlib.sha256(b"source-capability").hexdigest(),
)
if next_run == "new-generation":
second = replace(first, claim_generation=2)
else:
second = replace(
first,
job_id="observatory-run-" + "8" * 32,
identity_sha256="9" * 64,
setup_id="another-profile",
definition_sha256="0" * 64,
)
requests: list[str] = []
active = first
def handler(request: httpx.Request) -> httpx.Response:
manifest, payloads = _source_contract(
active,
camera_segment_count=2 if active == second and next_run == "changed-camera" else 1,
)
if request.url.path.endswith("/claims"):
return httpx.Response(
200,
json={
"claim_token": CLAIM_TOKEN,
"job": {
"job_id": active.job_id,
"claim_generation": active.claim_generation,
},
},
)
assert request.headers["x-mission-core-claim-generation"] == str(active.claim_generation)
assert active.job_id in request.url.path
requests.append(request.url.path)
if request.url.path.endswith("/source-materialization"):
return httpx.Response(200, json=manifest)
if request.url.path.endswith("/source-camera-epoch-archive"):
return httpx.Response(404)
payload = payloads[request.url.path.rsplit("/", 1)[-1]]
return httpx.Response(
200,
content=payload,
headers={
"X-Mission-Core-Content-Sha256": hashlib.sha256(payload).hexdigest(),
},
)
arguments = dict(
base_url="http://127.0.0.1:18080",
bearer_token=BEARER_TOKEN,
work_root=tmp_path / "worker",
source_cache_root=tmp_path / "shared-source-cache",
transport=httpx.MockTransport(handler),
)
with ObservatoryWorkerHttpGateway(**arguments) as gateway:
_cache_claim(gateway)
first_stage = gateway.materialize(first)
if next_run == "bad-cache":
cached = (
tmp_path / "shared-source-cache" / hashlib.sha256(b"sealed-camera-segment").hexdigest()
)
cached.chmod(0o600)
cached.write_bytes(b"corrupt-cache")
requests.clear()
active = second
arguments["work_root"] = tmp_path / "other-agent-work"
# Fresh process/client instance: no in-memory cache or claim is inherited.
with ObservatoryWorkerHttpGateway(**arguments) as gateway:
with pytest.raises(ObservatoryWorkerHttpError):
gateway.materialize(second)
_cache_claim(gateway)
second_stage = gateway.materialize(second)
assert second_stage.root != first_stage.root
assert requests[0].endswith("/source-materialization")
assert len(requests) == (2 if next_run in {"changed-camera", "bad-cache"} else 1)
assert not any(path.endswith("/source-camera-epoch-archive") for path in requests)
persisted = json.loads((second_stage.root / "materialization-manifest.json").read_bytes())
assert persisted["job_id"] == second.job_id
assert persisted["job_identity_sha256"] == second.identity_sha256
assert persisted["claim_generation"] == second.claim_generation
assert (
second_stage.root / "camera/epoch-1/segments/1.m4s"
).read_bytes() == b"sealed-camera-segment"
assert not list(tmp_path.rglob(".source-cache-*"))
def test_replayed_old_manifest_cannot_use_cached_source(tmp_path: Path) -> None:
first = _job(bundle_sha256="1" * 64, capability_sha256="2" * 64)
manifest, _ = _source_contract(first)
second = replace(first, claim_generation=2)
def handler(request: httpx.Request) -> httpx.Response:
if request.url.path.endswith("/claims"):
return httpx.Response(
200,
json={
"claim_token": CLAIM_TOKEN,
"job": {
"job_id": second.job_id,
"claim_generation": 2,
},
},
)
assert request.url.path.endswith("/source-materialization")
return httpx.Response(200, json=manifest)
with ObservatoryWorkerHttpGateway(
base_url="http://127.0.0.1:18080",
bearer_token=BEARER_TOKEN,
work_root=tmp_path / "worker",
transport=httpx.MockTransport(handler),
) as gateway:
_cache_claim(gateway)
with pytest.raises(ObservatoryWorkerHttpError):
gateway.materialize(second)
assert not (tmp_path / "worker/sources").exists()