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

@@ -23,6 +23,8 @@ if str(ROOT) not in sys.path:
from tools.rf_handoff_channel import ( # noqa: E402
EXPECTED_RX_TASK_ID,
GitResultChannel,
READY_STATE,
RxHandoffResult,
load_result,
)
from tools.tx_handoff_channel import ( # noqa: E402
@@ -571,6 +573,7 @@ def publish_result(arguments: argparse.Namespace) -> int:
task = parse_task((repository / DEFAULT_TASK_PATH).read_text(encoding="utf-8-sig"))
state = load_state(arguments.state_file.resolve())
result = load_result(arguments.publish_result.resolve())
require_live_ready_window(result)
if task.task_id != result.task_id:
raise ValueError("result TASK_ID disagrees with current RX task")
if task.allow_push != "RX_RESULT_ONLY" or task.rf_tx:
@@ -613,10 +616,55 @@ def publish_result(arguments: argparse.Namespace) -> int:
return 0
def require_live_ready_window(
result: RxHandoffResult,
*,
now: datetime | None = None,
) -> None:
"""Запретить READY после окончания конечного окна захвата."""
if result.state != READY_STATE:
return
current = (now or datetime.now(timezone.utc)).astimezone(timezone.utc)
expected_end = datetime.fromisoformat(
result.expected_capture_end.replace("Z", "+00:00")
).astimezone(timezone.utc)
if current >= expected_end:
raise ValueError("READY capture window has already ended")
def verify_result_channel_write_access(
repository: Path,
*,
remote: str = "origin",
result_branch: str = "rx-node",
output_function: Callable[[str], None] = print,
) -> str:
"""Проверить право публикации результата RX без изменения ветки."""
channel = GitResultChannel(
repository,
remote=remote,
branch=result_branch,
)
remote_head = channel.verify_write_access()
output_function("RX-NODE WRITE PREFLIGHT = PASS")
output_function(f"RX-NODE REMOTE HEAD = {remote_head}")
output_function("RF TX = 0")
return remote_head
def run_watcher(arguments: argparse.Namespace) -> int:
state_path = arguments.state_file.resolve()
if arguments.publish_result is not None:
return publish_result(arguments)
if arguments.check_result_write:
verify_result_channel_write_access(
arguments.repository.resolve(),
remote=arguments.remote,
result_branch=arguments.result_branch,
)
return 0
if arguments.request_stop:
stopped = request_stop(state_path, arguments.stop_timeout)
print("WATCHER STOPPED" if stopped else "WATCHER STOP TIMEOUT")
@@ -670,6 +718,12 @@ def run_watcher(arguments: argparse.Namespace) -> int:
task_text, source_head = source.read()
task = parse_task(task_text)
if task.task_id == EXPECTED_RX_TASK_ID and not arguments.dry_run:
if is_new_task(task, state):
verify_result_channel_write_access(
arguments.repository.resolve(),
remote=arguments.remote,
result_branch=arguments.result_branch,
)
confirm_main_channel_test(
arguments.repository.resolve(),
state,
@@ -726,6 +780,7 @@ def build_parser() -> argparse.ArgumentParser:
parser.add_argument("--stop-timeout", type=float, default=35.0)
parser.add_argument("--mark-completed")
parser.add_argument("--publish-result", type=Path)
parser.add_argument("--check-result-write", action="store_true")
parser.add_argument("--result-branch", default="rx-node")
parser.add_argument("--acknowledgement-branch", default="tx-node")
return parser