# План доделывания 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 и подтверждено, что исполнитель понимает контракт: это самая тонкая часть системы, там уже дважды находили блокирующие дефекты.
