feat(plugins): isolate device integrations

This commit is contained in:
DCCONSTRUCTIONS
2026-07-17 19:29:32 +03:00
parent f9ffb7bd1c
commit 24a47318f2
122 changed files with 3304 additions and 1892 deletions
+63 -26
View File
@@ -8,7 +8,12 @@ from pathlib import Path
import pytest
from k1link.mqtt.capture import FRAME_HEADER, RAW_MAGIC, iter_capture_frames
from k1link.device_plugins.xgrids_k1 import xgrids_k1_archive_source
from k1link.device_plugins.xgrids_k1.mqtt.capture import (
FRAME_HEADER,
RAW_MAGIC,
iter_capture_frames,
)
from k1link.sessions import (
LayoutConflictError,
SessionIntegrityError,
@@ -115,10 +120,10 @@ def test_catalog_reconciles_sessions_removed_from_one_evidence_root(tmp_path: Pa
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
assert store.import_legacy_viewer_live(sessions) == (session.name,)
assert store.reconcile_archive(xgrids_k1_archive_source(sessions)) == (session.name,)
shutil.rmtree(session)
assert store.import_legacy_viewer_live(sessions) == ()
assert store.reconcile_archive(xgrids_k1_archive_source(sessions)) == ()
assert store.list_recent().items == ()
@@ -207,8 +212,8 @@ def test_store_uses_private_wal_database_and_idempotently_imports_legacy_session
make_legacy_session(sessions, "20260716T205632Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
first = store.import_legacy_viewer_live(sessions)
second = store.import_legacy_viewer_live(sessions)
first = store.reconcile_archive(xgrids_k1_archive_source(sessions))
second = store.reconcile_archive(xgrids_k1_archive_source(sessions))
assert first == second == ("20260716T205632Z_viewer_live",)
page = store.list_recent()
@@ -229,7 +234,7 @@ def test_store_uses_private_wal_database_and_idempotently_imports_legacy_session
assert all(source.seekable for source in detail.sources)
assert str(repository) not in serialized(detail.as_dict())
command = store.prepare_replay(summary.session_id, speed=2.0, loop=True)
assert command.source_path.name == "mqtt.raw.k1mqtt"
assert command.primary_artifact.path.name == "mqtt.raw.k1mqtt"
assert command.speed == 2.0
assert command.loop is True
@@ -238,6 +243,35 @@ def test_store_uses_private_wal_database_and_idempotently_imports_legacy_session
assert store.database_path.stat().st_mode & 0o777 == 0o600
def test_reconcile_claims_pre_plugin_catalog_row_without_changing_session_identity(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
source = xgrids_k1_archive_source(sessions)
store.reconcile_archive(source)
with sqlite3.connect(store.database_path) as connection:
connection.execute(
"UPDATE observation_sessions SET plugin_id = '', archive_id = '', "
"primary_replay_artifact_id = NULL, timeline_origin_epoch_ns = NULL, "
"timeline_origin_monotonic_ns = NULL WHERE session_id = ?",
(session.name,),
)
connection.execute(
"UPDATE observation_session_artifacts SET replay_byte_length = 0 "
"WHERE session_id = ?",
(session.name,),
)
connection.commit()
assert store.reconcile_archive(source) == (session.name,)
command = store.prepare_replay(session.name)
assert command.plugin_id == source.plugin_id
assert command.primary_artifact.replay_byte_length > 0
def test_recent_sessions_are_sorted_and_cursor_paginated(tmp_path: Path) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
@@ -252,7 +286,7 @@ def test_recent_sessions_are_sorted_and_cursor_paginated(tmp_path: Path) -> None
created_at="2026-07-16T20:56:32.699Z",
)
store = SessionStore(repository, data_dir=tmp_path / "data")
store.import_legacy_viewer_live(sessions)
store.reconcile_archive(xgrids_k1_archive_source(sessions))
first = store.list_recent(limit=1)
assert first.items[0].session_id == "20260716T205632Z_viewer_live"
@@ -271,7 +305,7 @@ def test_legacy_import_adds_video_only_for_validated_recording_tree(tmp_path: Pa
make_recorded_camera_source(incomplete, complete=False)
store = SessionStore(repository, data_dir=tmp_path / "data")
store.import_legacy_viewer_live(sessions)
store.reconcile_archive(xgrids_k1_archive_source(sessions))
complete_detail = store.get_session(complete.name)
assert complete_detail.summary.modalities == ("point-cloud", "trajectory", "video")
@@ -305,7 +339,7 @@ def test_interrupted_capture_is_recovered_from_aligned_raw_and_metadata_prefix(
replace_summary_with_recovery_metadata(session, corrupt_trailing_line=True)
store = SessionStore(repository, data_dir=tmp_path / "data")
store.import_legacy_viewer_live(sessions)
store.reconcile_archive(xgrids_k1_archive_source(sessions))
detail = store.get_session(session.name)
assert detail.summary.status == "interrupted"
@@ -315,7 +349,7 @@ def test_interrupted_capture_is_recovered_from_aligned_raw_and_metadata_prefix(
assert detail.summary.completed_at_utc == "2026-07-16T20:56:35.199Z"
assert detail.summary.duration_seconds == 2.5
assert detail.artifacts[0].integrity_status == "validated-prefix"
assert store.prepare_replay(session.name).source_path.name == "mqtt.raw.k1mqtt"
assert store.prepare_replay(session.name).primary_artifact.path.name == "mqtt.raw.k1mqtt"
def test_catalog_upsert_promotes_recovered_session_after_summary_is_completed(
@@ -331,12 +365,12 @@ def test_catalog_upsert_promotes_recovered_session_after_summary_is_completed(
replace_summary_with_recovery_metadata(session)
store = SessionStore(repository, data_dir=tmp_path / "data")
store.import_legacy_viewer_live(sessions)
store.reconcile_archive(xgrids_k1_archive_source(sessions))
assert store.get_session(session.name).summary.status == "interrupted"
summary_path.write_text(completed_summary, encoding="utf-8")
metadata_path.write_text(completed_metadata, encoding="utf-8")
store.import_legacy_viewer_live(sessions)
store.reconcile_archive(xgrids_k1_archive_source(sessions))
promoted = store.get_session(session.name).summary
assert promoted.status == "ready"
@@ -356,7 +390,7 @@ def test_interrupted_capture_does_not_trust_metadata_outside_session(tmp_path: P
metadata.symlink_to(outside)
store = SessionStore(repository, data_dir=tmp_path / "data")
store.import_legacy_viewer_live(sessions)
store.reconcile_archive(xgrids_k1_archive_source(sessions))
detail = store.get_session(session.name)
assert detail.summary.status == "failed"
@@ -376,7 +410,7 @@ def test_interrupted_capture_rejects_newline_terminated_metadata_corruption(
stream.write(b"{corrupt\n")
store = SessionStore(repository, data_dir=tmp_path / "data")
store.import_legacy_viewer_live(sessions)
store.reconcile_archive(xgrids_k1_archive_source(sessions))
detail = store.get_session(session.name)
assert detail.summary.status == "failed"
@@ -388,7 +422,7 @@ def test_replay_resolution_rejects_artifact_symlink_escape(tmp_path: Path) -> No
sessions = repository / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
store.import_legacy_viewer_live(sessions)
store.reconcile_archive(xgrids_k1_archive_source(sessions))
raw = session / "captures" / "mqtt_live" / "mqtt.raw.k1mqtt"
outside = tmp_path / "outside.k1mqtt"
outside.write_bytes(raw.read_bytes())
@@ -411,7 +445,7 @@ def test_completed_capture_is_not_replayable_when_declared_integrity_fails(
raw.write_bytes(corrupted)
store = SessionStore(repository, data_dir=tmp_path / "data")
store.import_legacy_viewer_live(sessions)
store.reconcile_archive(xgrids_k1_archive_source(sessions))
detail = store.get_session(session.name)
assert detail.summary.status == "failed"
@@ -431,7 +465,7 @@ def test_completed_capture_is_not_replayable_when_declared_count_is_wrong(
summary_path.write_text(json.dumps(summary), encoding="utf-8")
store = SessionStore(repository, data_dir=tmp_path / "data")
store.import_legacy_viewer_live(sessions)
store.reconcile_archive(xgrids_k1_archive_source(sessions))
assert store.get_session(session.name).summary.replayable is False
@@ -446,13 +480,13 @@ def test_current_session_marker_keeps_active_capture_out_of_replay(tmp_path: Pat
)
store = SessionStore(repository, data_dir=tmp_path / "data")
store.import_legacy_viewer_live(sessions)
store.reconcile_archive(xgrids_k1_archive_source(sessions))
with pytest.raises(SessionNotFoundError):
store.get_session(session.name)
(sessions / ".current_session").unlink()
store.import_legacy_viewer_live(sessions)
store.reconcile_archive(xgrids_k1_archive_source(sessions))
assert store.get_session(session.name).summary.replayable is True
@@ -469,14 +503,17 @@ def test_interrupted_capture_replays_only_committed_prefix_before_partial_raw_ta
stream.write(FRAME_HEADER.pack(12, 100)[:7])
store = SessionStore(repository, data_dir=tmp_path / "data")
store.import_legacy_viewer_live(sessions)
store.reconcile_archive(xgrids_k1_archive_source(sessions))
detail = store.get_session(session.name)
command = store.prepare_replay(session.name)
assert detail.summary.status == "interrupted"
assert detail.summary.replayable is True
assert command.replay_byte_length == committed_bytes
assert command.replay_byte_length < command.source_path.stat().st_size
assert command.primary_artifact.replay_byte_length == committed_bytes
assert (
command.primary_artifact.replay_byte_length
< command.primary_artifact.path.stat().st_size
)
def test_interrupted_capture_rejects_non_frame_raw_tail(tmp_path: Path) -> None:
@@ -489,7 +526,7 @@ def test_interrupted_capture_rejects_non_frame_raw_tail(tmp_path: Path) -> None:
stream.write(FRAME_HEADER.pack(0, 0))
store = SessionStore(repository, data_dir=tmp_path / "data")
store.import_legacy_viewer_live(sessions)
store.reconcile_archive(xgrids_k1_archive_source(sessions))
assert store.get_session(session.name).summary.replayable is False
@@ -511,11 +548,11 @@ def test_interrupted_capture_tolerates_bounded_group_commit_raw_tail(
stream.write(payload)
store = SessionStore(repository, data_dir=tmp_path / "data")
store.import_legacy_viewer_live(sessions)
store.reconcile_archive(xgrids_k1_archive_source(sessions))
command = store.prepare_replay(session.name)
assert store.get_session(session.name).summary.status == "interrupted"
assert command.replay_byte_length == committed_bytes
assert command.primary_artifact.replay_byte_length == committed_bytes
def test_failed_final_summary_falls_back_to_last_committed_prefix(tmp_path: Path) -> None:
@@ -532,7 +569,7 @@ def test_failed_final_summary_falls_back_to_last_committed_prefix(tmp_path: Path
summary_path.write_text(json.dumps(summary), encoding="utf-8")
store = SessionStore(repository, data_dir=tmp_path / "data")
store.import_legacy_viewer_live(sessions)
store.reconcile_archive(xgrids_k1_archive_source(sessions))
detail = store.get_session(session.name)
assert detail.summary.status == "interrupted"