# Отчёт: разбор PR #63 «Link management и Event Journal в веб-консоли»

- **Задание:** роль главного агента, подтверждена владельцем в чате 2026-08-18
- **Агент:** `claude` (в роли главного агента)
- **Дата:** 2026-08-18
- **Разбираемая работа:** PR #63, ветка `antigravity/console-links`,
  коммит `051d917`; задание —
  `agents/antigravity/inbox/from-smm-deliverables/smm-antigravity-task-console-links-2026-08-18.md`;
  отчёт исполнителя — описание PR #63.

## Что сделано

Разбор по репозиторию, а не по отчёту: диф снят от базы слияния
`7c43977`, а не от `main`.

## Как проверено

```
$ git merge-base origin/main origin/antigravity/console-links
7c43977fd137c2236f7fc100f5d972f4e5f2bcac

$ git diff --stat 7c43977 origin/antigravity/console-links
 src/ServerMonitorManager.Control/wwwroot/app.js    | 722 +++++++++++++++++----
 src/ServerMonitorManager.Control/wwwroot/index.html|  179 ++++-
 src/ServerMonitorManager.Control/wwwroot/style.css |  156 ++++-
 tests/.../WebConsoleTests.cs                       |   45 +-
 4 files changed, 976 insertions(+), 126 deletions(-)
```

Границы задания соблюдены: `deploy/**`, релизные workflow и bootstrap-тесты
не затронуты, новых эндпоинтов нет. Заявленный в описании PR состав файлов
совпадает с фактическим.

**Проверено и подтверждается:**

- **XSS не найден.** `escapeHtml` (app.js:1045) экранирует пять сущностей
  (`& < > " '`) и применён при каждой интерполяции в `innerHTML`, включая
  payload событий (app.js:479), `lastError` (338) и `data-*`-атрибуты
  (347–351). Непроэкранированных вставок пользовательских данных не найдено.
- **Разбор NDJSON корректен.** app.js:412–413 — `split('\n')` с
  возвратом хвоста в буфер через `pop()`; разрыв события на границе чанка
  не теряется и не портит разбор.
- **Переподключение не размножается.** `startEventStream` вызывается ровно
  из двух мест (app.js:89 при инициализации и 431 по таймеру), в начале
  функции прежний `AbortController` прерывается. Накопления параллельных
  таймеров нет.
- **Журнал ограничен**: `MAX_EVENTS_IN_JOURNAL = 100`, `unshift` + `pop`
  (app.js:437–439). Неограниченного роста в долгоживущей вкладке нет.

## Замечания

**1. Тесты не проверяют поведение — только строки в статическом HTML.**

Оба теста (`WebConsoleHtmlContainsRequiredUiElementsAndWarning` и новый
`WebConsoleProvidesLinkManagementAndEventsJournal`) состоят из
`Assert.Contains("id=\"…\"", html)` по разметке. Они доказывают, что в
`index.html` есть элементы с нужными идентификаторами, и ничего больше.

При этом 976 изменённых строк — почти целиком поведение на JavaScript:
создание Link, отключение, идемпотентность, разбор потока событий,
расчёт срока действия, разбор Problem Details. Ни одна из этих веток
тестом не покрыта.

В описании PR «158 пройденных тестов» приведено как доказательство работы
управления Links. Это не доказательство: пройдёт и разметка с правильными
`id` при полностью нерабочем `app.js`. Соглашение 6 из
`agents/claude/notes/2026-08-18-передача-состояния.md` — «зелёный CI не
означает работающую машину» — здесь применимо буквально.

**2. Обновление дашборда не троттлится (app.js:444–448).**

```js
const eventType = (ev.type || '').toLowerCase();
if (eventType.startsWith('link.') || eventType.startsWith('agent.')) {
  loadDashboardData(false);
}
```

Каждое событие `link.*` или `agent.*` вызывает `loadDashboardData`, а тот
делает два запроса — `/api/v1/control/agents` и `/api/v1/control/links`
(app.js:230–233). Ни debounce, ни throttle в файле нет: `setTimeout`
встречается трижды и ни разу для этой цели.

Согласование политик Links в Control работает непрерывно (`CHANGELOG.md`,
Unreleased, #11). Пачка событий согласования по флоту превращается в
двукратно умноженный поток запросов от каждой открытой вкладки консоли.
На одном узле незаметно, на флоте — усилитель нагрузки на Control.

**3. Мелкое: запасной `generateUuid` на `Math.random()` (app.js:167).**

Для ключа идемпотентности приемлемо — это не секрет. Отмечаю только чтобы
запасная ветка не была скопирована туда, где нужна криптостойкость.

## Что не сделано

- `dotnet build` и `dotnet test` не запускались: на машине нет .NET SDK
  (`dotnet: command not found` и в Git Bash, и в PowerShell). Заявленные
  в описании PR 158 пройденных тестов и зелёные прогоны CI я принимаю по
  ссылкам на прогоны, а не по собственному запуску;
- консоль в браузере не открывалась, поведение вручную не проверялось;
- PR не слит: ветка отстала от `main` (`mergeStateStatus = BEHIND`),
  и решение о слиянии за владельцем.

## Решение

Работа соответствует заданию по составу и границам, дефектов безопасности
в ней не найдено. К слиянию пригодна после обновления относительно `main`.

Замечание 1 не блокирует слияние, но должно стать отдельным заданием: без
поведенческих тестов следующая правка `app.js` не будет ничем удержана.
Замечание 2 — отдельным заданием на исправление.

## Вопросы к приёмке

- Обновлять ветку относительно `main` силами `claude` или возвращать автору?
- Выпускать ли задания по замечаниям 1 и 2 сразу, или после слияния #63.
