feat(k1): complete primary acquisition lifecycle

This commit is contained in:
DCCONSTRUCTIONS
2026-07-17 23:03:59 +03:00
parent 9d51080d2e
commit aa3680948f
66 changed files with 6093 additions and 544 deletions
+87 -5
View File
@@ -1,8 +1,11 @@
from __future__ import annotations
import inspect
import json
import struct
import threading
import time
from collections.abc import Callable
from pathlib import Path
import lz4.block
@@ -301,11 +304,13 @@ def test_live_runtime_surfaces_preamble_failure_as_terminal_error(
monkeypatch.setattr(runtime_module, "_write_live_session_preamble", fail_preamble)
runtime = VisualizationRuntime(normalizer=normalize_k1_message)
runtime.start_live(
"192.168.1.20",
tmp_path / "unwritable-evidence",
duration_seconds=1.0,
)
with pytest.raises(RuntimeError, match="session clock"):
runtime.start_live(
"192.168.1.20",
tmp_path / "unwritable-evidence",
duration_seconds=1.0,
project_name="Preamble failure test",
)
deadline = time.monotonic() + 2.0
snapshot = runtime.snapshot()
while snapshot["phase"] == "starting_live" and time.monotonic() < deadline:
@@ -317,3 +322,80 @@ def test_live_runtime_surfaces_preamble_failure_as_terminal_error(
assert snapshot["source_ready"] is False
assert "synthetic evidence directory failure" in snapshot["message"]
runtime.close()
def test_live_start_waits_for_capture_clock_before_returning(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
entered_capture = threading.Event()
allow_clock = threading.Event()
returned = threading.Event()
failures: list[BaseException] = []
recording = FakeRecording()
def bridge_factory(**kwargs: object) -> RerunBridge:
return RerunBridge(
recording_factory=lambda _: recording, # type: ignore[arg-type]
**kwargs, # type: ignore[arg-type]
)
def delayed_capture(
_host: str,
_out_dir: Path,
*,
on_clock_established: Callable[[], None],
should_stop: Callable[[], bool],
**_kwargs: object,
) -> dict[str, object]:
entered_capture.set()
assert allow_clock.wait(timeout=2.0)
on_clock_established()
while not should_stop():
time.sleep(0.005)
return {"message_count": 0}
monkeypatch.setattr(runtime_module, "capture_mqtt", delayed_capture)
runtime = VisualizationRuntime(
bridge_factory=bridge_factory,
normalizer=normalize_k1_message,
)
def start() -> None:
try:
runtime.start_live(
"192.168.1.20",
tmp_path / "delayed-clock",
duration_seconds=30.0,
project_name="Clock barrier test",
)
except BaseException as exc:
failures.append(exc)
finally:
returned.set()
caller = threading.Thread(target=start)
caller.start()
assert entered_capture.wait(timeout=2.0)
assert not returned.wait(timeout=0.05)
allow_clock.set()
assert returned.wait(timeout=2.0)
assert failures == []
runtime.stop()
caller.join(timeout=2.0)
runtime.close()
def test_live_session_manifest_retains_project_display_name(tmp_path: Path) -> None:
out_dir = tmp_path / "evidence-session"
runtime_module._write_live_session_preamble(
out_dir,
"192.168.1.20",
60.0,
"K1 Route Alpha",
)
manifest = json.loads((out_dir / "manifest.redacted.json").read_text(encoding="utf-8"))
assert manifest["project_name"] == "K1 Route Alpha"
assert "192.168.1.20" not in json.dumps(manifest)