server-monitor-manager/agents/_salvage-2026-08-18/panel-control/Task3-B3-2026-08-04/B3R_TASK_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

13 KiB
Raw Blame History

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, а не «выполнено».