diff --git a/agents/inbox/2026-09-02-A60-update-completes.md b/agents/inbox/2026-09-02-A60-update-completes.md index 61bae1d..67bfdde 100644 --- a/agents/inbox/2026-09-02-A60-update-completes.md +++ b/agents/inbox/2026-09-02-A60-update-completes.md @@ -18,7 +18,7 @@ git checkout -b antigravity/a60-update-completes origin/main ## Порядок исполнения -Два прохода: **Flash** реализует, **Pro** проводит аудит. Пункт **P0-5** написан +Два прохода: **Flash** реализует, **Pro** проводит аудит. Пункт **P0-6** написан для аудитора. Зона: применение обновления и перезапуск. Показа и загрузки не касается — там всё @@ -63,20 +63,35 @@ subprocess.run(["bash", installer], timeout=600) ЖДЁТ здесь schedule_restart() сюда управление не доходит ``` -**Linux.** `installer/install-linux.sh:53` в своём `stop_running_hub` берёт -`pgrep -u $(id -u) -f "antigravity_provider.router.web|hermes_hub_web_entry"` и не -исключает никого. Под шаблон попадает тот самый питон, который висит на -`subprocess.run`. Установщик снимает его, `schedule_restart` не выполняется -никогда, окно прогресса умирает вместе с процессом. Скрипт при этом доживает -сиротой и файлы обновляет — поэтому со стороны выглядит как «поставилось, но не -запустилось». +**Linux — проверено исполнением, цепочка целиком.** -**Windows.** `installer/HermesHubSetup.cs:280` (`StopOwnedRuntime`) выглядит -аккуратнее: строит `$protected` — цепочку собственных предков, чтобы не снять +``` +1. хаб: subprocess.run(["bash", installer], capture_output=True) + stdout установщика — труба, единственный читатель которой сам хаб +2. install-linux.sh, шаг [0/6]: stop_running_hub снимает хаб + pgrep -u $(id -u) -f "antigravity_provider.router.web|hermes_hub_web_entry" + никого не исключает, под шаблон попадает тот, кто запустил установщик +3. хаб мёртв -> у трубы не осталось читателя +4. следующий echo установщика -> SIGPIPE -> установщик умирает на шаге [1/6] +5. не установлено ничего; перезапускать нечего +``` + +**Установщик не доживает до конца — он умирает раньше, чем что-либо поставит.** +Это не «поставилось, но не запустилось»: манифест и код остаются на прежней +сборке. Проверено контрольным опытом — тот же скрипт, та же смерть родителя: с +выводом в файл доходит до конца, с `capture_output=True` умирает. + +Отсюда следует, что одной перестановкой `schedule_restart` делу не помочь: пока +установщик пишет в трубу убитого им процесса, он не доживёт до установки при +любом порядке вызовов. + +**Windows — прочитано по коду, живьём не проверялось.** +`installer/HermesHubSetup.cs:280` (`StopOwnedRuntime`) выглядит аккуратнее: строит `$protected` — цепочку собственных предков, чтобы не снять того, кто его запустил. Но проверка `-notin $protected` стоит **только на дочерних** процессах внутри `Stop-HubBranch`. Сам процесс-цель снимается безусловно, а хаб под шаблон `antigravity_provider\.router\.web` подходит. -Прочитано по коду — **подтвердить исполнением, а не поверить на слово.** +Труба там та же: `proc.wait(timeout=600)` при `Popen` без перенаправления вывода. +**Подтвердить исполнением, а не поверить на слово.** Отката на путях установщика нет вообще: он есть только для `.zip` (`apply_update_sync`). Ни `.sh`, ни `.exe` при срыве на середине ничего не @@ -96,9 +111,13 @@ schedule_restart() сюда управлени **выходит сам**, освободив порт. 2. **Помощник**: дожидается освобождения порта → запускает установщик → поднимает хаб → завершается. -3. **Помощник не наследует** ни stdout хаба, ни его рабочий каталог: хаб исчезнет +3. **Ни одна труба помощника не должна вести в хаб.** Это то самое место, где всё + ломается сейчас: `capture_output=True` делает читателем вывода тот процесс, + который установщик собирается снять. Вывод установщика — сразу в файл, а не в + `PIPE`, и не через процесс, которому предстоит умереть. +4. **Помощник не наследует** ни stdout хаба, ни его рабочий каталог: хаб исчезнет раньше, чем помощник закончит. -4. **Помощник пишет свой ход в файл** `~/.hermes/updates/apply-<время>.log`, чтобы +5. **Помощник пишет свой ход в файл** `~/.hermes/updates/apply-<время>.log`, чтобы после неудачи было что показать. Пустой отказ без причины — уже было в A59, второй раз не проходит. @@ -138,7 +157,28 @@ schedule_restart() сюда управлени что хаб жив. 6. **Проверить, что старый процесс не остался** и порт занят новым. -## P0-5. Аудит вторым проходом +## P0-5. Обновление вообще предлагается + +Найдено тем же прогоном, до того как дело дошло до установки. + +Хаб на `v0.1.2-b7` при живом релизе `v0.1.3-b1` ответил: +`«Установлена сборка новее опубликованного релиза (4fa9939 от 2026-09-02)»`, +`update_available: false`. Обновиться было нельзя вообще. + +Причина: `deployed_at` в `deployment_manifest.json` пишется установщиком в момент +**установки**, а не сборки, и сравнивается с `published_at` релиза. Поставил +старую сборку сегодня — она «новее» любого релиза, и обновление не предложат +больше никогда. Владелец, переставивший сборку руками, выпадает из обновлений +молча. + +1. **Сравнивать сборки, а не дату установки.** Дата установки не говорит о том, + какой код внутри. +2. **Если сравнить нечем — предложить обновление, а не промолчать.** Молчание + здесь дороже лишнего окна: владелец не узнает, что отстал. +3. **Причину решения показывать.** «Установлена сборка новее релиза» — вывод, а + не факт; рядом должно стоять, из чего он сделан. + +## P0-6. Аудит вторым проходом 1. **Пройти обновление целиком на обеих системах.** 2. **Проверить, что помощник не снимает чужого** — только процессы хаба своего @@ -173,8 +213,10 @@ schedule_restart() сюда управлени 4. Сорвавшаяся установка откатывается, хаб остаётся работоспособным. 5. Старый процесс не остался, порт занят новым. 6. Журнал помощника пишется, и при отказе на него указывают. -7. `ruff check .` чисто; релизный гейт пройден; тестов не меньше **740**. -8. Отчёт: `START_HEAD`, `FINAL_HEAD`, `origin/main`, `git status`, +7. Обновление предлагается по сравнению сборок, а не по дате установки; + переустановка старой сборки не выключает обновления навсегда. +8. `ruff check .` чисто; релизный гейт пройден; тестов не меньше **740**. +9. Отчёт: `START_HEAD`, `FINAL_HEAD`, `origin/main`, `git status`, `X passed / Y skipped / Z failed`, тайминги, скриншоты. ## Главное