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
+60 -23
View File
@@ -13,7 +13,7 @@ from types import SimpleNamespace
import pytest
import k1link.sessions.recording as recording_module
from k1link.sessions.models import ReplayCommand
from k1link.sessions.models import ReplayArtifact, ReplayCommand
from k1link.sessions.recording import (
CACHE_SCHEMA,
RERUN_RECORDING_MEDIA_TYPE,
@@ -57,17 +57,46 @@ def _command(tmp_path: Path) -> ReplayCommand:
metadata.write_text('{"record_type":"message","sequence":1}\n', encoding="utf-8")
return ReplayCommand(
session_id="20260716T205632Z_viewer_live",
source_path=source,
plugin_id="test.recording-plugin",
allowed_root=tmp_path,
session_root=tmp_path,
replay_byte_length=source.stat().st_size,
metadata_byte_length=metadata.stat().st_size,
expected_source_sha256=None,
primary_artifact_id="primary",
artifacts=(
ReplayArtifact(
artifact_id="primary",
path=source,
media_type="application/x-test-recording",
file_byte_length=source.stat().st_size,
replay_byte_length=source.stat().st_size,
expected_sha256=None,
),
ReplayArtifact(
artifact_id="index",
path=metadata,
media_type="application/x-ndjson",
file_byte_length=metadata.stat().st_size,
replay_byte_length=metadata.stat().st_size,
expected_sha256=None,
),
),
timeline_origin_epoch_ns=0,
timeline_origin_monotonic_ns=0,
speed=1.0,
loop=False,
)
def _replace_primary(command: ReplayCommand, **changes: object) -> ReplayCommand:
replacement = replace(command.primary_artifact, **changes)
return replace(
command,
artifacts=tuple(
replacement if artifact.artifact_id == command.primary_artifact_id else artifact
for artifact in command.artifacts
),
)
def test_materializer_reuses_only_a_digest_validated_private_cache(tmp_path: Path) -> None:
command = _command(tmp_path)
exporter = FakeExporter()
@@ -137,8 +166,12 @@ def test_failed_rebuild_preserves_previously_published_cache(tmp_path: Path) ->
published_bytes = first.path.read_bytes()
sidecar = first.path.with_name("scene.rrd.cache.json")
published_sidecar = sidecar.read_bytes()
command.source_path.write_bytes(b"new-source-that-requires-rebuild")
changed = replace(command, replay_byte_length=command.source_path.stat().st_size)
command.primary_artifact.path.write_bytes(b"new-source-that-requires-rebuild")
changed = _replace_primary(
command,
file_byte_length=command.primary_artifact.path.stat().st_size,
replay_byte_length=command.primary_artifact.path.stat().st_size,
)
def fail_export(_source: Path, destination: Path) -> dict[str, object]:
destination.write_bytes(b"incomplete-candidate")
@@ -173,11 +206,15 @@ def test_materializer_rebuilds_when_source_or_recording_changes(tmp_path: Path)
assert repaired.sha256 == _sha256(repaired.path)
assert repaired.sha256 != hashlib.sha256(corrupted).hexdigest()
command.source_path.write_bytes(b"new-native-source")
command = replace(command, replay_byte_length=command.source_path.stat().st_size)
command.primary_artifact.path.write_bytes(b"new-native-source")
command = _replace_primary(
command,
file_byte_length=command.primary_artifact.path.stat().st_size,
replay_byte_length=command.primary_artifact.path.stat().st_size,
)
updated = materializer.materialize(command)
assert exporter.calls == 3
assert updated.source_sha256 == _sha256(command.source_path)
assert updated.source_sha256 == _sha256(command.primary_artifact.path)
def test_concurrent_materialization_exports_one_recording(tmp_path: Path) -> None:
@@ -227,9 +264,9 @@ def test_materializer_revalidates_prepared_source_without_following_replaced_sym
exporter = FakeExporter()
materializer = SessionRecordingMaterializer(tmp_path / "private", exporter=exporter)
outside = tmp_path / "outside.k1mqtt"
outside.write_bytes(command.source_path.read_bytes())
command.source_path.unlink()
command.source_path.symlink_to(outside)
outside.write_bytes(command.primary_artifact.path.read_bytes())
command.primary_artifact.path.unlink()
command.primary_artifact.path.symlink_to(outside)
with pytest.raises(RecordingMaterializationError, match="missing or unsafe"):
materializer.materialize(command)
@@ -242,11 +279,11 @@ def test_materializer_exports_only_validated_prefix_before_crash_tail(
tmp_path: Path,
) -> None:
command = _command(tmp_path / "session")
committed_raw_bytes = command.replay_byte_length
committed_metadata_bytes = command.metadata_byte_length
with command.source_path.open("ab") as stream:
committed_raw_bytes = command.primary_artifact.replay_byte_length
committed_metadata_bytes = command.artifacts[1].replay_byte_length
with command.primary_artifact.path.open("ab") as stream:
stream.write(b"uncommitted-raw-tail")
metadata = command.source_path.with_name("mqtt.metadata.jsonl")
metadata = command.primary_artifact.path.with_name("mqtt.metadata.jsonl")
with metadata.open("ab") as stream:
stream.write(b'{"record_type":"message"')
observed: list[tuple[int, int]] = []
@@ -265,7 +302,7 @@ def test_materializer_exports_only_validated_prefix_before_crash_tail(
assert observed == [(committed_raw_bytes, committed_metadata_bytes)]
assert recording.source_sha256 == hashlib.sha256(b"native-source-recording").hexdigest()
assert command.source_path.read_bytes().endswith(b"uncommitted-raw-tail")
assert command.primary_artifact.path.read_bytes().endswith(b"uncommitted-raw-tail")
def test_global_singleflight_bounds_exports_across_different_sessions(tmp_path: Path) -> None:
@@ -447,14 +484,14 @@ def test_cache_quota_evicts_lru_derived_recording_without_deleting_raw(
free_space_reserve_bytes=0,
)
first_recording = materializer.materialize(first)
first_source_bytes = first.source_path.read_bytes()
first_source_bytes = first.primary_artifact.path.read_bytes()
second_recording = materializer.materialize(second)
assert not first_recording.path.exists()
assert second_recording.path.exists()
assert first.source_path.read_bytes() == first_source_bytes
assert second.source_path.exists()
assert first.primary_artifact.path.read_bytes() == first_source_bytes
assert second.primary_artifact.path.exists()
def test_pinned_cache_entry_survives_eviction_until_release(tmp_path: Path) -> None:
@@ -503,7 +540,7 @@ def test_cache_free_space_reserve_refuses_export_without_touching_raw(
monkeypatch: pytest.MonkeyPatch,
) -> None:
command = _command(tmp_path / "session")
source_bytes = command.source_path.read_bytes()
source_bytes = command.primary_artifact.path.read_bytes()
materializer = SessionRecordingMaterializer(
tmp_path / "private",
exporter=FakeExporter(),
@@ -519,4 +556,4 @@ def test_cache_free_space_reserve_refuses_export_without_touching_raw(
with pytest.raises(RecordingMaterializationError, match="free-space reserve"):
materializer.materialize(command)
assert command.source_path.read_bytes() == source_bytes
assert command.primary_artifact.path.read_bytes() == source_bytes