Harden RF handoff and stream OTA decode

This commit is contained in:
LittleSam129
2026-09-22 17:43:58 +03:00
parent 168e03d9e7
commit a94f2db4ee
17 changed files with 1548 additions and 497 deletions

View File

@@ -31,6 +31,7 @@ from tools.rf_handoff_smoke import run_smoke
from tools.rx_agent_watcher import (
WatcherState as RxWatcherState,
publish_result,
require_live_ready_window,
save_state as save_rx_state,
)
from tools.tx_agent_watcher import (
@@ -59,7 +60,7 @@ def ready_result(**changes: object) -> RxHandoffResult:
capture_pid=1234,
published_at=NOW.isoformat(),
capture_started_at=NOW.isoformat(),
expected_capture_end=(NOW + timedelta(seconds=120)).isoformat(),
expected_capture_end=(NOW + timedelta(seconds=60)).isoformat(),
capture_path=r"C:\captures\rf001a_ota_capture.cs8",
common_clock_verified=True,
source_head=HEAD,
@@ -97,10 +98,10 @@ def complete_result(
rx_ready_for_tx=False,
process_alive=False,
file_growing=False,
published_at=(NOW + timedelta(seconds=121)).isoformat(),
published_at=(NOW + timedelta(seconds=61)).isoformat(),
exit_code=0,
file_size=1_920_000_000,
duration_seconds=120.01,
file_size=960_000_000,
duration_seconds=60.01,
overruns=0,
usb_errors=0,
file_errors=0,
@@ -278,7 +279,7 @@ def test_complete_result_accepts_explicit_unreported_counters() -> None:
ready_result(
published_at=(NOW - timedelta(seconds=31)).isoformat(),
capture_started_at=(NOW - timedelta(seconds=31)).isoformat(),
expected_capture_end=(NOW + timedelta(seconds=89)).isoformat(),
expected_capture_end=(NOW + timedelta(seconds=29)).isoformat(),
),
NOW,
"STALE_READY",
@@ -290,7 +291,7 @@ def test_complete_result_accepts_explicit_unreported_counters() -> None:
),
(
ready_result(
capture_started_at=(NOW - timedelta(seconds=111)).isoformat(),
capture_started_at=(NOW - timedelta(seconds=51)).isoformat(),
expected_capture_end=(NOW + timedelta(seconds=9)).isoformat(),
),
NOW,
@@ -329,7 +330,7 @@ def test_ready_evaluation_passes_with_more_than_ten_seconds() -> None:
)
assert evaluation.ready
assert evaluation.reason == "READY"
assert evaluation.seconds_remaining == pytest.approx(118.0)
assert evaluation.seconds_remaining == pytest.approx(58.0)
def test_red_approval_and_duplicate_suppression(tmp_path: Path) -> None:
@@ -378,7 +379,7 @@ def test_window_and_commit_are_rechecked_after_exact_approval(tmp_path: Path) ->
digest = result_digest(result)
source = FakeSource(result, digest)
executor = FakeExecutor(tmp_path / "fake_tx.txt")
times = iter((NOW + timedelta(seconds=2), NOW + timedelta(seconds=111)))
times = iter((NOW + timedelta(seconds=2), NOW + timedelta(seconds=51)))
status, _ = handle_result(
result,
@@ -600,7 +601,7 @@ def test_expired_historical_ready_is_observed_without_authorization(
started = NOW - timedelta(seconds=200)
expired = ready_result(
capture_started_at=started.isoformat(),
expected_capture_end=(started + timedelta(seconds=120)).isoformat(),
expected_capture_end=(started + timedelta(seconds=60)).isoformat(),
published_at=(NOW - timedelta(seconds=100)).isoformat(),
)
expired_outcome = channel.publish(expired)
@@ -796,9 +797,16 @@ Fake task used only by the local coordination test.
)
source_head = _git(["rev-parse", "HEAD"], work)
fresh_start = datetime.now(timezone.utc)
fresh_result = ready_result(
source_head=source_head,
published_at=fresh_start.isoformat(),
capture_started_at=fresh_start.isoformat(),
expected_capture_end=(fresh_start + timedelta(seconds=60)).isoformat(),
)
result_path = tmp_path / "ready.json"
result_path.write_text(
canonical_result_json(ready_result(source_head=source_head)),
canonical_result_json(fresh_result),
encoding="utf-8",
)
state_path = tmp_path / "rx_state.json"
@@ -824,4 +832,12 @@ Fake task used only by the local coordination test.
assert "RF TX = 0" in output
published, _digest, _head = GitResultChannel(work).read()
assert published == ready_result(source_head=source_head)
assert published == fresh_result
def test_rx_publisher_refuses_ready_after_capture_window() -> None:
with pytest.raises(ValueError, match="window has already ended"):
require_live_ready_window(
ready_result(),
now=NOW + timedelta(seconds=60),
)