Harden RF node control and agent updates

This commit is contained in:
LittleSam129
2026-09-30 11:42:19 +03:00
parent a94f2db4ee
commit 0d6fff224e
34 changed files with 14010 additions and 0 deletions

View File

@@ -0,0 +1,133 @@
"""Run one finite RF-001 RX child and persist start/final evidence."""
from __future__ import annotations
import argparse
from datetime import datetime, timezone
import hashlib
import json
import os
from pathlib import Path
import subprocess
from typing import Mapping
def utc_now() -> str:
return datetime.now(timezone.utc).isoformat()
def atomic_json(path: Path, value: Mapping[str, object]) -> None:
temporary = path.with_suffix(path.suffix + ".tmp")
temporary.write_text(
json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
os.replace(temporary, path)
def sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
while block := stream.read(1024 * 1024):
digest.update(block)
return digest.hexdigest()
def run_capture(
executable: Path,
arguments_path: Path,
capture_path: Path,
stdout_path: Path,
stderr_path: Path,
started_path: Path,
result_path: Path,
) -> int:
arguments_value = json.loads(arguments_path.read_text(encoding="utf-8-sig"))
if not isinstance(arguments_value, list) or not all(
isinstance(item, str) for item in arguments_value
):
raise ValueError("arguments JSON must be an array of strings")
started_at = utc_now()
child: subprocess.Popen[bytes] | None = None
exit_code: int | None = None
runner_error: str | None = None
try:
with stdout_path.open("xb") as stdout_stream, stderr_path.open(
"xb"
) as stderr_stream:
child = subprocess.Popen(
[str(executable), *arguments_value],
stdin=subprocess.DEVNULL,
stdout=stdout_stream,
stderr=stderr_stream,
creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
close_fds=True,
)
atomic_json(
started_path,
{
"state": "RUNNING",
"pid": int(child.pid),
"started_at": started_at,
"capture_path": str(capture_path),
"rf_tx": 0,
},
)
exit_code = int(child.wait())
except Exception as error: # persisted at the process boundary
runner_error = f"{type(error).__name__}: {error}"
finished_at = utc_now()
capture_size = capture_path.stat().st_size if capture_path.is_file() else 0
capture_hash: str | None = None
if capture_path.is_file():
try:
capture_hash = sha256_file(capture_path)
except OSError as error:
detail = f"{type(error).__name__}: {error}"
runner_error = detail if runner_error is None else f"{runner_error}; {detail}"
duration = (
datetime.fromisoformat(finished_at) - datetime.fromisoformat(started_at)
).total_seconds()
atomic_json(
result_path,
{
"pid": int(child.pid) if child is not None else None,
"started_at": started_at,
"finished_at": finished_at,
"duration": duration,
"exit_code": exit_code,
"capture_path": str(capture_path),
"capture_size": capture_size,
"sha256": capture_hash,
"runner_error": runner_error,
"rf_tx": 0,
},
)
return 255 if exit_code is None else exit_code
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--executable", required=True, type=Path)
parser.add_argument("--arguments-json", required=True, type=Path)
parser.add_argument("--capture-path", required=True, type=Path)
parser.add_argument("--stdout-path", required=True, type=Path)
parser.add_argument("--stderr-path", required=True, type=Path)
parser.add_argument("--started-status", required=True, type=Path)
parser.add_argument("--result-status", required=True, type=Path)
arguments = parser.parse_args()
raise SystemExit(
run_capture(
arguments.executable,
arguments.arguments_json,
arguments.capture_path,
arguments.stdout_path,
arguments.stderr_path,
arguments.started_status,
arguments.result_status,
)
)
if __name__ == "__main__":
main()