# 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: ```text RuntimeError: health file does not match the launched process ``` После каждого прогона процессов, принадлежащих диагностическому запуску, не осталось. Исходные health-файлы и снимки процессов сохранены на RX в: ```text 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 заменяется атомарно. Команда проверки: ```text 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` в рамках этой фазы не создавался.