server-monitor-manager/agents/hermes/inbox/from-smm-deliverables/Старые задачи/smm-hermes-task-b3r-2026-08-06.md
Ochenstarik 23eb3f5233 chore(agents): разбор рабочих папок с диска на 2026-08-18
Задания, отчёты и патчи, лежавшие в C:\Users\Ochenstarik\projects и в
домашней папке, перенесены в agents/. Разложено по агентам там, где имя
файла позволяло определить автора; остальное — в _salvage-2026-08-18/
и разбирается вручную.

Патчи в notes/salvage-2026-08-18/ — незакоммиченная работа из брошенных
рабочих копий: она существовала только на диске.

Тяжёлое (релизные архивы, инсталляторы, наборы данных) в репозиторий не
попало: оно лежит рядом, в Agent_projects/_archive и Agent_projects/_data.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 14:19:54 +07:00

122 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# B-3R: завершить реконсиляцию от фактического состояния
## Репозиторий и ветка
- Репозиторий: https://github.com/ochenstarik-ui/server-monitor-manager
- Рабочая копия: `C:\Users\Ochenstarik\projects\server-monitor-manager-task3-b3`
- Ветка: `hermes/task3-b3-fact-reconciliation`, **изменения не закоммичены** — 24 файла, около +1276/141
- База: `b11c277ac7f79a18670932eca4622982d9ff48e0`
- Ветки на GitHub нет; коммит и push ещё не выполнялись
Нормативные документы задания: `B3R_SPEC.md` и исходное ТЗ B-3 — оба в `C:\Users\Ochenstarik\Сюда\Panel control\Task3-B3-2026-08-04\`. Они остаются в силе целиком; ниже — только то, что осталось не сделано.
## Зачем
Проход реконсиляции стал fact-first: сначала один `link-list`, потом сверка с базой. Но верификация результата мутации осталась пер-линковой, а классификация результата неполна. Из-за этого два дефекта, оба на пути kill switch:
- удаление правила может быть засчитано без доказательства, что правило исчезло;
- результат, не попавший ни в `Converged`, ни в `Failed`, позволяет фоновому сервису потребить marker внеочередной реконсиляции при неподтверждённом исходе.
## Что уже закрыто — проверено по коду, переделывать не нужно
- **R3 полностью.** `lookup_node_ip` читает четвёртое поле `nodes.tsv` и требует `active`; проверка статуса стоит раньше проверки IP, поэтому `reserved` + валидный адрес даёт exit 80, а `active` + битый адрес даёт 78.
- **R1 на уровне отдельной мутации.** Появился `VerifyExactFactualCountAsync`, считающий записи через `ListRulesAsync`, то есть через `link-list`, а не `link-status`. Условие `if (persisted)` вокруг верификации снято — DB-less orphan теперь тоже проверяется. Синтетическое присваивание `ActualState` выполняется после доказанного нуля записей, а не вместо доказательства.
- `list_rules` разбирает только managed-comment, проверяет арность, node-паттерны, протокол и диапазон порта, подделки отбрасывает в stderr, чужие правила не выводит.
- `disconnect_rule` больше не вызывает `lookup_node_ip` — удаление правила не зависит от `nodes.tsv`. Это верно, сохранить.
## Работы
### 1. Пакетная финальная верификация
Сейчас `VerifyExactFactualCountAsync` вызывается на каждую мутацию: проход с `k` мутациями делает `1 + k` вызовов `link-list`.
Требуется: один стартовый `link-list` и, при наличии мутаций, **ровно один финальный** `link-list`, по которому сверяются все затронутые ключи разом. Проход без расхождений — один привилегированный вызов, проход с мутациями — ровно два.
Проверяется счётчиком вызовов фейкового applier и журналом вызовов в интеграционном тесте.
### 2. Класс `Deferred` и инвариант классификации
Ввести третий класс результата и инвариант:
```
Converged + Failed + Deferred == Examined
```
Инвариант проверять в коде — нарушение это `LogError` с идентификаторами политик — и отдельным тестом на смешанном наборе.
Решение, которое нужно реализовать именно так:
- `PendingActivation` — это `Deferred`, а не `Failed`. Node зарезервирован, `peer-add` не выполнен, состояние может держаться сутками и ошибкой не является.
- `Deferred` **не блокирует** потребление marker. Иначе один зарезервированный Node навсегда удержит marker и вернёт бесконечный полный проход каждые 30 секунд — ровно то, что закрывали в B-2 и B3-3.
- Marker блокирует только `Failed`, и на него уже действует ограничение в три попытки.
- `Deferred` обязан быть видимым: событие `link.pending-node-activation`, отдельная подпись в Desktop, отражение в журнале прохода. Ошибкой не называется.
`LinkFullReconciliationResult` и `LinkReconciliationResult` дополнить полем `Deferred` и списком `DeferredPolicyIds` по образцу `FailedPolicyIds`.
### 3. Source-generated сериализация orphan audit (R4)
В `ControlStore.cs` запись audit для orphan-правила сериализуется анонимным типом через reflection, тогда как остальной Control последовательно использует source-generated `JsonTypeInfo`.
Отказ произойдёт **после** мутации firewall и **после** публикации `link.orphan-removed`, дав противоречивую телеметрию: правило удалено, событие отправлено, проход помечен неуспешным.
- ввести именованный DTO, добавить его в source-generated контекст Control, сериализовать через `Context.Default.<Type>`;
- зафиксировать порядок: запись audit до публикации финального события успеха;
- проверить на **опубликованном** `linux-x64 PublishTrimmed` артефакте, прогнав orphan-путь и сверив содержимое audit-записи. Приложить вывод к отчёту.
### 4. Linux integration test на fact-first протокол (R5)
`tests/ServerMonitorManager.Control.Tests/LinkPolicyApplierIntegrationTests.cs` остался на протоколе `link-status`-first: fake helper не реализует `link-list`, ожидаемый журнал вызовов начинается с `link-status`.
На Windows тест выходит по `OperatingSystem.IsLinux() == false`, поэтому локальные прогоны его не показывают. `linux-control-agent.yml` гоняет Control-тесты на Ubuntu, где пропуска не будет.
- реализовать `link-list` в fake helper;
- переписать ожидаемый журнал на fact-first протокол;
- проверить: no-drift → ровно один `link-list`; отсутствующая таблица классифицируется прямо на `link-list` без промежуточной мутации; мутация сопровождается одной финальной верификацией; DB-less raw disconnect проходит верификацию;
- **прогнать Control suite на Linux.** Windows-прогон этот тест не выполняет и заявленные локальные результаты дефект не выявляют.
### 5. Убрать fail-open default в интерфейсе (R6)
`ILinkPolicyApplier.ListRulesAsync` имеет тело по умолчанию, возвращающее пустой список. Забытая реализация молча сообщает «firewall пуст» — худший возможный дефолт для источника истины о факте, и он уже позволил старым test doubles скомпилироваться без нового протокола.
Сделать `ListRulesAsync` и raw `ApplyDisconnectAsync(LinkRule, …)` обязательными членами интерфейса, все test doubles обновить явно.
### 6. Читаемость вложенных scope (R7)
`LinkService.cs`, участки обработки persisted-кандидатов и factual-orphans: тела `try` визуально на уровне внешнего scope, порядок блокировок `sorted node locks → per-Link gate` восстанавливается только разбором отступов. Переформатировать либо вынести обработку одного tuple и одного orphan в приватные методы без дублирования логики сходимости.
### 7. Мусор в поле статуса `nodes.tsv`
Сейчас любое не-`active` значение даёт exit 80 и уходит в `Deferred` навсегда. Разграничить: известные значения (`reserved` и подобные) → 80; значение, не проходящее валидацию формата, → 78. Повреждённое состояние — это отказ, который должен всплыть, а не ожидаемое состояние, которое можно откладывать бесконечно.
## Тесты, без которых задание не принимается
1. DB-less orphan: helper сообщает успех удаления, но правило осталось → классифицируется как `Failed`, не как успех.
2. Persisted `Disabled`, узел отсутствует в `nodes.tsv` → удаление подтверждается через `link-list`, состояние `Disabled`, не `PendingActivation`.
3. Marker не потребляется при наличии `Failed`; потребляется, если остались только `Converged` и `Deferred`.
4. Инвариант `Converged + Failed + Deferred == Examined` на наборе из шести политик со всеми тремя исходами.
5. Проход с `k` мутациями выполняет ровно два вызова `link-list`.
6. Production-shape фикстура `nodes.tsv` из четырёх полей: `reserved` + валидный IP → 80; `active` + валидный IP → успех; `active` + невалидный IP → 78; мусор в статусе → 78.
7. Зарезервированный Node на десяти последовательных проходах: каждый раз `Deferred`, marker потребляется, число проходов не превышает расписание.
## Критерий приёмки
- все семь пунктов работ закрыты;
- семь тестов выше зелёные;
- Control suite прогнан **на Linux**, а не только на Windows;
- orphan audit проверен на опубликованном trimmed-артефакте;
- все критерии исходного ТЗ B-3 по-прежнему выполняются;
- PR создан, CI зелёный — впервые для этой ветки;
- независимый review получил полный текст этого задания, `B3R_SPEC.md` и исходное ТЗ B-3.
## Границы
- не трогать файлы вне области реконсиляции Links; изменения в Desktop ограничены отображением `Deferred`;
- не менять порядок блокировок и не вводить новых классов блокировок;
- не ослаблять fail-closed поведение: неизвестная ошибка `nft` остаётся отказом, а не «правил нет»;
- `TreatWarningsAsErrors` в этой задаче не включать.
## Отчётность
Отдельно перечислить: что запущено локально, что в CI, что не запускалось и почему. Физический acceptance (`SMM_ACCEPT_RESTORE=1 SMM_ACCEPT_REBOOT=1 tests/acceptance/three-server-mesh.sh`) выполнить нельзя — не выданы topology inputs; contract- и mock-тесты за него не выдавать.
Статус после merge — `merged / verified, physical acceptance pending`, а не «выполнено».