# План доделывания Server Monitor Manager — для облачного исполнителя (Gemini) Составлен 2026-08-04. Заменяет собой полосовое разделение: Codex приостановлен, Gemini ведёт весь объём. Базовая ревизия на GitHub: `main` @ `b11c277`. --- ## 0. Передача работ — сделать до постановки первой задачи ### 0.1. B-3R существует только локально Незавершённая работа по блоку B-3R — **24 изменённых файла, около 1276 добавленных строк** — лежит незакоммиченной в `C:\Users\Ochenstarik\projects\server-monitor-manager-task3-b3`. На GitHub этой ветки нет: последняя ветка серии на remote — `hermes/task2-b-link-reconciliation` (`1580ec7`), это ещё блок B-2. Облачный исполнитель этого не видит. Возможные решения: | Вариант | Последствия | |---|---| | **Закоммитить и запушить** ветку `hermes/task3-b3-fact-reconciliation` как есть, с честной пометкой «незавершено, см. B3R_SPEC» | Работа сохранена, Gemini продолжает с неё. Рекомендуемый вариант | | Выгрузить патчем и передать файлом | Работает, но патч быстро протухнет и его придётся накатывать вручную | | Отбросить и переделать с `b11c277` | Потеря корректной работы: R3 закрыт полностью, R1 закрыт на уровне отдельной мутации — я это проверял по коду | Пока решение не принято, ставить задачу по B-3R облачному исполнителю нельзя: он либо не найдёт контекст, либо начнёт вторую параллельную реализацию. ### 0.2. Нормативные документы тоже локальны Коммит `4de849e` (`docs/product-horizons.md`, `docs/approval-policies.md`, `docs/integration-kagent.md`, правки `roadmap.md` и `security-model.md`) находится на локальной ветке `docs/product-horizons-and-integration` и не запушен. Пока он не в репозитории, план ниже ссылается на документы, которых исполнитель не увидит. Запушить до выдачи первой задачи. --- ## 1. Что облачный исполнитель проверить не может Это самый важный раздел. Дисциплина честной отчётности была сильной стороной предыдущих пакетов — её надо сохранить, а список ограничений изменился. | Проверка | Доступно в облаке | Что делать | |---|---|---| | Control test suite на Linux | **Да** | Запускать обязательно. Именно Linux-прогон в блоке B-3 выявил, что integration-тест остался на старом протоколе: на Windows он молча пропускался по `OperatingSystem.IsLinux()` | | Тесты helper, nftables, bash-контракты | **Да** | Запускать; это естественная среда для них | | Сборка Desktop (WinUI 3) | **Нет** | Требует Windows и Windows App SDK. Полагаться на `windows-build.yml` в CI. **Никогда не заявлять локальную сборку Desktop** | | `Test-DesktopContracts.ps1` | Частично | Это сопоставление строк в XAML, идёт под `pwsh` на Linux. Указывать, что запущено под pwsh, а не на Windows | | Физический acceptance | **Нет ни у кого** | Требует `HUB_SSH_HOST`, `HUB_SSH_USER`, `SOURCE_SSH_HOST`, `SOURCE_SSH_USER`, `HOME_WG_IP`, `SECOND_WG_IP`, `SSH_IDENTITY_FILE`. Не выдавать mock- и contract-тесты за него | | Trimmed linux-x64 publish | **Да** | Полезно: проверка source-generated сериализации на реальном опубликованном артефакте | **Правило.** В `TEST_EVIDENCE.md` раздельно перечисляются: что запущено локально, что запущено в CI, что не запускалось и почему. Отсутствие проверки — не дефект отчёта; выдача непроверенного за проверенное — дефект. --- ## 2. Формат сдачи для облака Прежние пакеты складывались в локальную папку. Для облачного исполнителя: - **Основной артефакт — Pull Request.** Якорь целостности — SHA коммита, а не `SHA256SUMS`. В отчёте указывать точный SHA, который ревьюился, и SHA, который смержен, если они различаются. - `REPORT.md` и `TEST_EVIDENCE.md` — в описании PR либо отдельным коммитом в PR; содержание прежнее. - `CI_CHECKS.txt` заменяется ссылкой на прогон checks в PR. - `INDEPENDENT_REVIEW.md` — обязателен по-прежнему, и **ревьюеру передаётся полный текст задачи**, а не только диф. Три блока из четырёх дали находки класса «пропущенное требование», которые при diff-only ревью не ищутся вовсе. - Один PR — одна тема. Смешанные PR не принимаются. - Статус после merge — `merged / verified`, а не «выполнено», если критерий сформулирован через фактическое поведение и не измерялся на реальной топологии. --- ## 3. Первая задача — калибровочная Дешёвая, быстрая, с нулевым риском для существующего кода, и результат проверяется объективно. Даёт понять, справляется ли исполнитель, до того как ему доверят control plane. ### Задача A — гигиена репозитория и цепочки поставки · до одного дня Только новые файлы и `.github/**`. Ни одного файла, который трогает ветка B-3R. 1. Запинить все GitHub Actions по commit SHA. Сейчас плавающие `@v6`, `@v5`, `@v2` — содержимое шага может измениться без нашего ведома. 2. `.github/dependabot.yml` для `github-actions` и `nuget`. 3. Сузить `permissions: contents: write` в `linux-release.yml` и `windows-release.yml` до job'а публикации. 4. `SECURITY.md` — куда сообщать об уязвимости, ожидаемое время ответа, что считается уязвимостью. Для инструмента безопасности его отсутствие — само по себе дефект. 5. `CHANGELOG.md` в формате Keep a Changelog, заполнить по существующим тегам `v0.1.0-alpha.1…5`. 6. `CONTRIBUTING.md`, `CODEOWNERS`, шаблоны issue и PR. 7. SBOM (`dotnet CycloneDX`) как артефакт релиза. 8. Удалить слитые ветки `agent/remove-lightweight-server-references` и `codex/ttl-backup-acceptance`. **Как я буду проверять результат:** ни одна ссылка на action не содержит плавающего тега; `permissions` сужены именно в job'ах публикации, а не глобально; `SECURITY.md` содержит адрес и процедуру, а не заглушку; CHANGELOG соответствует реальным тегам, а не выдуман; CI зелёный; в `TEST_EVIDENCE.md` честно разделено локальное и CI; ревью получило текст задачи. Если задача выполнена аккуратно и отчёт не приукрашен — переходим к содержательным задачам. Если в отчёте появятся непроверенные утверждения, это будет видно сразу и дёшево. --- ## 4. Дальнейшая очередь — Горизонт 0 Порядок обязательный. Горизонт закрывается целиком, Горизонт 1 не начинается до этого. ### Задача B — завершить B-3R · зависит от решения по п. 0.1 Контракт: `B3R_SPEC.md` (выдан ранее, принят исполнителем как authoritative). По состоянию на 04.08 закрыто: R3 полностью (`lookup_node_ip` читает статус, `reserved` → exit 80, `active` с битым IP → 78) и R1 на уровне отдельной мутации (`VerifyExactFactualCountAsync` считает записи через `link-list`, условие `if (persisted)` вокруг верификации снято). Осталось: - пакетная финальная верификация: сейчас `1 + k` вызовов `link-list`, требуется ровно два — стартовый и один финальный на все затронутые ключи; - класс `Deferred` и инвариант `Converged + Failed + Deferred == Examined`; `PendingActivation` — это `Deferred`, а не `Failed`, и он **не** блокирует потребление marker, иначе один зарезервированный Node вернёт бесконечный цикл проходов; - R4: именованный DTO в source-generated контексте вместо анонимного типа для orphan audit, с проверкой на опубликованном trimmed-артефакте; - R5: Linux integration test на fact-first протокол, с обязательным прогоном **на Linux**; - R6: убрать fail-open default у `ILinkPolicyApplier.ListRulesAsync`; - R7: форматирование вложенных lock-scope; - дополнительно: мусор в поле статуса `nodes.tsv` должен давать 78, а не 80 — повреждённое состояние не то же самое, что ожидаемое. **Готово, когда:** PR создан, Linux CI зелёный впервые для этой ветки, merge выполнен. ### Задача C — жизненный цикл сертификатов `CertificateAuthority.cs:55` выдаёт клиентские сертификаты на год, автопродления нет. Через год после развёртывания парк отваливается одновременно, и восстановление потребует ручной перерегистрации каждого Node. - срок клиентского сертификата 30–90 дней, значение в конфигурации с валидацией; - автопродление Agent за треть срока до истечения через существующий mTLS-канал: новый CSR, выдача, атомарная замена `agent.pfx`, откат при неудаче; - продление Operator-сертификата Desktop с уведомлением пользователя; - событие `certificate.expiring` в поток событий; - документированная процедура ротации Control CA и повторной регистрации парка — сейчас её нет; - тесты: продление до истечения, недоступный Hub в момент продления, отказ при отозванном сертификате. Файлы `CertificateAuthority.cs`, `CertificateLifecycleService.cs`, `AgentClient.cs` веткой B-3R не затронуты — задачу можно вести параллельно задаче B. ### Задача D — роль Monitor в bootstrap SSH-мониторинг — функция, ради которой существует Desktop, — не имеет серверной части. В bootstrap нет `install-monitor`, forced-command скрипт не поставляется, а парсер `SshMonitorService.QueryAsync` ожидает ключи `PROTOCOL`, `CPU_COUNT`, `MEM_AVAILABLE_KB`, `DISK_INODES_TOTAL`, которых в репозитории никто не производит. - действия `install-monitor PUBLIC_KEY` и `uninstall-monitor`; - пользователь `ochenstarik-monitor`: nologin, без пароля, без sudo; - `/usr/local/libexec/ochenstarik-smm-metrics`, root-owned `0755`, выдающий ровно снимок из `docs/installer-contract.md` §7 и read-only `mesh status`; - `authorized_keys` с `command="…",restrict,no-pty,no-agent-forwarding,no-port-forwarding,no-X11-forwarding`; - идемпотентность повторного запуска и корректное удаление; - контрактный тест формата снимка, общий для скрипта и парсера Desktop. Bootstrap-скрипт затронут веткой B-3R — делать после merge задачи B. ### Задача E — подписанная поставка Bootstrap сверяет `ARCHIVE.sha256`, лежащий рядом с архивом в том же релизе, а manifest не подписан и содержит только хэш самого bootstrap. Кто может опубликовать релиз — ставит произвольный root-код на весь парк через `update-agent`. - manifest v2: хэши всех артефактов, версии Control/Agent/helper/Desktop, минимальные совместимые версии, `helper_protocol`; - подпись `cosign sign-blob` keyless либо `minisign`; публичный ключ или issuer **вшит константой** в bootstrap и Desktop, а не скачивается вместе с релизом; - действие `verify-manifest`; `verify_archive` сверяет хэш с manifest, а не с соседним файлом; старый путь только под `SMM_ALLOW_UNSIGNED=1`; - отказ `update-control` и `update-agent` при несовместимых версиях — требование `docs/installer-contract.md` §6, не выполненное до сих пор; - `PROGRAM_VERSION` из тега вместо `0.2.0-dev`; - отсутствие инструмента проверки подписи — отказ, не предупреждение; - негативные тесты в CI: изменённый байт архива, подменённый хэш в manifest, manifest без подписи. ### Задача F — инженерная оснастка - `Directory.Build.props`: `Nullable`, `TreatWarningsAsErrors`, `EnableNETAnalyzers`, `AnalysisLevel latest-recommended`, детерминированная сборка; - `Directory.Packages.props` и `RestorePackagesWithLockFile` с коммитом lock-файлов — сейчас сборки невоспроизводимы, что противоречит требованию закреплённой поставки; - разделить тесты на `Core.Tests`, `Control.Tests`, `Agent.Tests`, `Helper.Tests`: сегодня `LinuxMetricsTests`, `MetricBufferTests` и `ProvisioningHelperTests` лежат в проекте Control; - тесты round-trip сериализации контрактов: при `PublishTrimmed=true` молчаливая потеря поля не ловится ничем. `TreatWarningsAsErrors` включать после merge задачи B, иначе сломает незавершённую ветку. ### Гейт Горизонта 0 Чистый сервер ставится из подписанного релиза одной командой, регистрируется одноразовым кодом, попадает под мониторинг без ручной правки `authorized_keys`, и после перезагрузки Hub и Node фактическое состояние Links совпадает с желаемым — измеренное связностью на реальной топологии. Последнее не может закрыть ни один исполнитель. Это единственное, что зависит только от выдачи доступов, и оно дешевле любой задачи в этом плане. --- ## 5. Горизонт 1 и далее Не выдавать до закрытия гейта Горизонта 0. Состав и порядок — по `docs/product-horizons.md` и разделу «Горизонт 1» документа `smm-codex-plan-2026-08-04.md`: каркас `IProvisioningModule` первым, затем модули по одному, политики подтверждения, миграция SSH, alert engine, Telegram только на чтение, наблюдение Docker и systemd, Backup Manager. Ключевое правило переноса: **не добавлять второй provisioning-модуль копированием timezone-исполнителя.** Сначала каркас, иначе шесть копий логики backup → mutate → verify → rollback. --- ## 6. Как оценивать исполнителя По задаче A смотреть на четыре вещи, в порядке важности: 1. **Честность отчёта.** Разделены ли локальные проверки, CI и то, что не запускалось. Появление в отчёте непроверенных утверждений — худший признак из возможных, потому что обесценивает все последующие отчёты. 2. **Соответствие постановке, а не её пересказ.** Выполнены ли все восемь пунктов, а не пять удобных. 3. **Отсутствие расползания.** Не тронуты ли файлы вне области задачи. 4. **Качество ревью.** Получил ли независимый ревьюер полный текст задачи и нашёл ли что-нибудь, кроме опечаток. Если задача A пройдена — давать задачу C (сертификаты): она содержательная, файлы свободны, и результат тоже проверяется объективно. Задачу B (завершение B-3R) давать только после того, как решён вопрос 0.1 и подтверждено, что исполнитель понимает контракт: это самая тонкая часть системы, там уже дважды находили блокирующие дефекты.