16 KiB
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 топологии, где это условие не выполняется, допустима только следующая строгая альтернатива:
- Сохранить точные проверки
release_id,agent_version, одноразовогоlaunch_nonceиstate=RUNNING. - Снять идентичность процесса
Popenсразу после запуска: PID, путь образа, командную строку и время создания. - Если health PID отличается, принять только один живой прямой дочерний процесс этого конкретного wrapper.
- Для дочернего процесса проверить время создания после начала данного запуска, точную команду с тем же agent entrypoint и nonce, ожидаемый путь интерпретатора и наличие процесса в момент принятия health.
- Не принимать внука, произвольного потомка, самый новый
python.exe, совпадение только по имени или один числовой PID. - При завершении или откате повторно сверить 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 в рамках этой фазы не создавался.