Files
SDR-Rover/docs/handoff/RF001_LAUNCHER_HEALTH_DIAGNOSTIC.md
2026-09-30 11:42:19 +03:00

16 KiB
Raw Blame History

RF001 launcher/health diagnostic

Дата проверки: 29 сентября 2026 года.

Ограничения проверки

  • Производственный RX-агент не останавливался и не изменялся.
  • Задание Планировщика Windows не изменялось.
  • Диагностический процесс не обращался к HTTP-очереди.
  • Доступ к SDR не выполнялся: hardware_accessed=false, RF RX=0, RF TX=0.
  • Bootstrap rf001-bootstrap-2026.09.29.5 не создавался; .4 повторно не запускался.
  • Clean release A rf001-agent-2026.09.29.1 использовался только для чтения кода health writer и проверки READY.json/manifest.

Безопасное состояние после проверки

  • Активный производственный агент: 2026.09.25.1; heartbeat на MAIN свежий, active task отсутствует.
  • Незавершённых задач: 0. Незавершённых аппаратных задач: 0.
  • Задание SDR Rover RX Agent: Running; его действие по-прежнему запускает legacy agent, а не release A.
  • Производственная цепочка legacy agent на RX: venv wrapper PID 7980 -> базовый Python PID 17568.
  • hackrf_transfer.exe: 0 процессов. Процессов RX capture: 0.
  • Диагностических процессов: 0.
  • Clean release A: 10 файлов из 10 совпадают с versions/rf001-agent-2026.09.29.1 внутри подписанного bootstrap .4 по относительному пути, размеру и SHA256.
  • Conflict quarantine сохранён. SHA256 его diagnostic manifest: b92f15dd4b7cffef375fd108f454a41623ed7328220c73c3f2b847dc59e956bc.
  • rf001-bootstrap-2026.09.29.5.zip отсутствует.

Доказанная первопричина

Два последовательных изолированных запуска через настоящий start_release() дали одинаковый результат. Поля release_id, agent_version, launch_nonce и state совпали. Единственным несовпавшим полем стало health.pid == Popen.pid.

На проверенном Windows/venv способе запуска Popen.pid принадлежит процессу venv\\Scripts\\pythonw.exe, а фактический интерпретатор, который исполняет health writer, является его единственным прямым дочерним процессом с образом базового Python313\\python.exe. Связь установлена не по именам: сверены PID, PPID, время создания, полный путь образа, командная строка и одноразовый launch_nonce.

Запуск 1

  • Launcher процесса диагностики: PID 7848.
  • Popen.pid: 10284, образ C:\\Users\\Professional\\Documents\\Codex\\SDR_Rover_Video\\.venv\\Scripts\\pythonw.exe, PPID 7848, создан 2026-09-29T13:04:44.696889Z.
  • Health writer: PID 8452, образ C:\\Users\\Professional\\AppData\\Local\\Programs\\Python\\Python313\\python.exe, PPID 10284, создан 2026-09-29T13:04:44.702070Z.
  • Health обновлён 2026-09-29T16:04:44.866156+03:00.
  • Wrapper уже наблюдался 0,167845 с и отсутствовал к завершению прогона через 2,589228 с после создания.
  • Agent child уже наблюдался 0,164086 с и отсутствовал к завершению прогона через 2,585469 с после создания.
  • Точный момент завершения каждого процесса не опрашивался, поэтому приведены проверенные нижняя и верхняя границы, а не предполагаемая длительность.

Запуск 2

  • Launcher процесса диагностики: PID 7848.
  • Popen.pid: 3504, образ C:\\Users\\Professional\\Documents\\Codex\\SDR_Rover_Video\\.venv\\Scripts\\pythonw.exe, PPID 7848, создан 2026-09-29T13:04:47.290751Z.
  • Health writer: PID 14616, образ C:\\Users\\Professional\\AppData\\Local\\Programs\\Python\\Python313\\python.exe, PPID 3504, создан 2026-09-29T13:04:47.295668Z.
  • Health обновлён 2026-09-29T16:04:47.461326+03:00.
  • Wrapper уже наблюдался 0,169277 с и отсутствовал к завершению прогона через 2,574777 с после создания.
  • Agent child уже наблюдался 0,165658 с и отсутствовал к завершению прогона через 2,571158 с после создания.
  • Точный момент завершения каждого процесса не опрашивался.

Оба launch_nonce различались:

  • 96acc3af17150f42d393238bcbdad3ff;
  • e4a618547e4bcda4b6914bf81bbd7963.

В обоих случаях штатный start_release() завершился fail-closed:

RuntimeError: health file does not match the launched process

После каждого прогона процессов, принадлежащих диагностическому запуску, не осталось. Исходные health-файлы и снимки процессов сохранены на RX в:

C:\Users\Professional\Downloads\SDR_Rover_Diagnostics\launcher_health_20260929T130000Z\evidence

Сравнение полей health

Поле Запуск 1 Запуск 2
release_id == rf001-agent-2026.09.29.1 PASS PASS
agent_version == 2026.09.29.1 PASS PASS
launch_nonce PASS PASS
state == RUNNING PASS PASS
health.pid == Popen.pid FAIL: 8452 != 10284 FAIL: 14616 != 3504

Следовательно, Popen.pid соответствует варианту C: это родитель фактического процесса агента. Он не является произвольным или посторонним процессом; это venv-обёртка с ожидаемой командной строкой, которая существует одновременно с прямым дочерним health writer.

Строгая модель идентичности

Основным вариантом по-прежнему должен быть запуск, при котором Popen.pid принадлежит фактическому долгоживущему агенту. Для проверенной Windows/venv топологии, где это условие не выполняется, допустима только следующая строгая альтернатива:

  1. Сохранить точные проверки release_id, agent_version, одноразового launch_nonce и state=RUNNING.
  2. Снять идентичность процесса Popen сразу после запуска: PID, путь образа, командную строку и время создания.
  3. Если health PID отличается, принять только один живой прямой дочерний процесс этого конкретного wrapper.
  4. Для дочернего процесса проверить время создания после начала данного запуска, точную команду с тем же agent entrypoint и nonce, ожидаемый путь интерпретатора и наличие процесса в момент принятия health.
  5. Не принимать внука, произвольного потомка, самый новый python.exe, совпадение только по имени или один числовой PID.
  6. При завершении или откате повторно сверить PID и время создания перед остановкой wrapper и строго связанных с ним потомков. Это закрывает повторное использование PID.

Минимальное производственное исправление можно ограничить rf001_agent_launcher.py: добавить проверку уникального прямого потомка и такую же строгую очистку потомков при ошибке. Формат health менять не требуется, поскольку в нём уже есть nonce и собственный PID. Если получение надёжного времени создания и пути образа без дополнительного компонента окажется невозможно, health writer следует расширить полями process identity; отключать проверку PID нельзя.

Конечный автомат launcher

Названия ниже описывают фактические фазы кода. Не все они сейчас записываются как отдельное значение в launcher_status.json.

Фаза Допустимый вход Процессы Canonical release / active pointer Scheduled Task Health Действие при ошибке
LEGACY_ACTIVE Действующий агент здоров и idle Legacy agent запущен Указатель на legacy Launcher не меняет определение задания Legacy RUNNING Запрос обновления принимается только при safe_to_switch=true, совпадающем from_release_id и idle health
TARGET_VERIFIED Подписанный target прошёл validate_ready_release() Новый процесс ещё не создан Target уже существует в canonical releases; указатель ещё legacy Без изменений Legacy health Отказ до остановки legacy
STARTING_TARGET Target проверен Legacy останавливается; создаётся wrapper target Указатель атомарно меняется на target; status STARTING_UPDATE Без изменений Старый health больше не считается подтверждением Переход в TARGET_FAILED
WAITING_FOR_HEALTH Popen создан, nonce уникален Wrapper и возможный прямой child Указатель target Без изменений Ожидаются свежие точные поля и строгая process identity Timeout, exit или mismatch завершают target fail-closed
TARGET_HEALTHY Все health-поля и identity прошли Target продолжает работу Указатель target; status RUNNING Без изменений Target RUNNING Дальнейший выход ведёт в RESTARTING
TARGET_FAILED Startup exit, timeout или mismatch Target должен быть остановлен без orphan Target ещё был активным указателем Без изменений Ошибочный health не принимается Начать rollback
ROLLING_BACK Target не прошёл health gate Target очищается, запускается legacy Указатель возвращается на legacy Без изменений Для legacy требуется новый nonce и новый health Ошибка rollback должна оставаться явной, а не считаться успехом
LEGACY_RESTORED Legacy снова прошёл health gate Legacy работает Указатель legacy; status ROLLED_BACK Без изменений Свежий legacy RUNNING Архивировать запрос и продолжить legacy

Существенный пробел текущего production-кода: TARGET_FAILED останавливает только объект Popen; общая строгая очистка потомка в launcher пока не реализована. В двух диагностических запусках orphan не осталось, но это наблюдение не заменяет будущую явную очистку по identity.

Проверенные гонки и регрессии

Локальный набор pytest проверяет:

  • точное совпадение процесса: PASS;
  • неверные nonce, release, version и state: FAIL;
  • stale health: FAIL;
  • посторонний PID: FAIL;
  • единственный ожидаемый прямой child: PASS только со строгой lineage, nonce, командой и временем создания;
  • неожиданный grandchild: FAIL;
  • повторное использование PID с другим creation time: FAIL;
  • target умер до health: FAIL;
  • target записал health, но умер: FAIL;
  • health в состоянии STARTING, до полной готовности: FAIL;
  • rollback не принимает health с nonce прошлого запуска: FAIL;
  • выбор процессов для cleanup не затрагивает посторонний процесс;
  • cleanup останавливает только строго принадлежащие wrapper и child;
  • два успешных логических запуска детерминированы и имеют разные nonce;
  • health JSON заменяется атомарно.

Команда проверки:

python -m pytest tests/test_rf001_launcher_health_diagnostic.py tests/test_rf001_agent_launcher.py tests/test_rf001_rx_http_agent.py tests/test_rf001_agent_update.py -q

Результат: 48 passed, предупреждение только о недоступности .pytest_cache в текущей песочнице.

Windows integration test использовал реальный RX venv, установленный launcher source, canonical release A и настоящий agent health writer. Выполнено два последовательных изолированных запуска. HTTP polling, SDR и аппаратные команды в этом режиме отсутствовали.

Файлы и следующий этап

В этой диагностической фазе добавлены только:

  • tools/rf001_launcher_health_diagnostic.py;
  • tests/test_rf001_launcher_health_diagnostic.py;
  • этот отчёт.

Для будущего производственного исправления потребуются как минимум:

  • tools/rf001_agent_launcher.py;
  • tests/test_rf001_agent_launcher.py;
  • тесты упаковки, подтверждающие неизменность подписанного состава и fail-closed rollback.

tools/rf001_rx_http_agent.py потребуется менять только при выборе расширенного health process identity; доказанная минимальная схема может обойтись без изменения агента.

После отдельного согласования исправления понадобится новый уникальный bootstrap, поскольку launcher на RX должен быть заменён проверенным пакетом. Bootstrap .5 в рамках этой фазы не создавался.