docs(agents): A60 — причина разрыва установлена живым прогоном
Поставил v0.1.2-b7 в изолированную песочницу (свои HOME и HERMES_HOME, живая установка не тронута) и запустил обновление на v0.1.3-b1. Хаб умер через шесть секунд и за 150 секунд не поднялся. Установка при этом не произошла вовсе: манифест и код остались на 0.1.2. В задании было написано, что установщик доживает сиротой и файлы обновляет. Прогон это опроверг. Настоящая цепочка: хаб зовёт установщик через capture_output=True, то есть читателем его вывода становится сам хаб; установщик на шаге [0/6] снимает хаб; у трубы не остаётся читателя; следующий echo даёт SIGPIPE, и установщик умирает на шаге [1/6], не поставив ничего. Проверено контрольным опытом: тот же скрипт с выводом в файл доходит до конца, с трубой умирает. Отсюда следует, что перестановкой schedule_restart делу не помочь. Тем же прогоном найдено второе: deployed_at пишется в момент установки, а не сборки, и сравнивается с датой публикации релиза. Хаб на b7 при живом b1 ответил «установлена сборка новее опубликованного релиза» и обновление не предложил. Переустановил старую сборку — выпал из обновлений молча. Вынесено в P0-5. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EDLuenXmGjWaS2rs8E72En
This commit is contained in:
parent
c0485a3721
commit
c6981921da
1 changed files with 58 additions and 16 deletions
|
|
@ -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`, тайминги, скриншоты.
|
||||
|
||||
## Главное
|
||||
|
|
|
|||
Loading…
Reference in a new issue