Show egress/ingress levels in the registry; freeze checks at the verdict

Registry: the "last result" column now also shows, per level (egress,
ingress), how many of the recorded checks of the latest cycle succeeded, split
by check family (tcp-22 and tcp-443 are both "tcp"). One grouped query per
chunk of addresses; new fields last_cycle_id, egress, ingress in
GET /admin/registry; the dashboard renders them under the verdict.

Verdict integrity (migration 0010):
- the prober is handed an address once per site and attempt, not on every
  poll, so results are no longer overwritten by later probe rounds;
- UpsertCheckIfOpen refuses writes once the address is aggregating or has its
  verdict, or for an older attempt; senders get {"ok":true,"ignored":N} and a
  result_dropped event is recorded;
- the checking window counts from checking_started_at, not from assigned_at;
- checks.recorded_at (server clock) and checks.after_verdict (flag for rows
  written after the verdict in existing data);
- the verdict rule is a pure function (computeVerdict) and the aggregated
  event carries the egress/ingress check counts.

Rebuilt bin/control-api and bin/admin-dashboard to match. Plans and summaries
are in docs/changes; README, API, USAGE, DASHBOARD and DIAGRAMS are updated.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ayurishchevandClaude Sonnet 5.5 committed 2026-10-03 17:59:52 +03:00
1 parent db73409e8f
commit 864208238f
34 files changed
+1570 -72

No files matched your search

+43 -5
View File
@@ -218,6 +218,19 @@ self-check способом `control_api` (`self_check.methods` в
Ответ: `{"ok": true}`.
### Результаты после вердикта
Проверки фиксируются в момент вердикта: `POST /agents/{id}/results` и
`POST /probers/{site_id}/results` принимают результат только пока адрес
проверяется (состояния до `aggregating`) и только для его текущей попытки.
Результат, пришедший после начала агрегации или после вердикта, а также
результат прежней попытки не сохраняется и не меняет сохранённые проверки.
Ответ остаётся `200`, чтобы отправитель не повторял запрос:
`{"ok": true, "ignored": N}`, где `N` — число отброшенных проверок. На каждый
такой запрос по адресу пишется событие `result_dropped`
(`{"source": "egress"|"inbound-site-N", "dropped": N}`). Благодаря этому вердикт
всегда совпадает с сохранёнными проверками и пересчитывается из них.
### `POST /api/v1/agents/{id}/results`
Отчёт о результатах исходящих (egress) проверок. Можно отправлять по
@@ -286,13 +299,17 @@ USAGE.md про состояния площадки).
Запрос: тело не требуется.
Ответ: `{"ok": true}`. `404`, если `site_id` не сконфигурирован.
Ответ: `{"ok": true}`, либо `{"ok": true, "ignored": N}`, если часть результатов
отброшена (см. «Результаты после вердикта» ниже). `404`, если `site_id` не сконфигурирован.
### `GET /api/v1/probers/{site_id}/assignments`
Список всех IP, которые сейчас находятся в состоянии `checking` — то есть
всё, что нужно прозондировать на этом цикле опроса (валидаторов может
работать несколько параллельно, поэтому список, а не один IP).
Список IP в состоянии `checking`, которые эта площадка ещё не зондировала
в текущей попытке (валидаторов может работать несколько параллельно,
поэтому список, а не один IP). Адрес выдаётся площадке, пока она не
пришлёт для него результат с `complete: true`; после этого этой площадке он
больше не выдаётся (другим площадкам выдаётся). Новая попытка (повтор после
сбоя) выдаёт адрес снова. Так каждая площадка зондирует адрес один раз за попытку.
Ответ:
```json
@@ -659,7 +676,16 @@ curl -s -X POST http://<control-api>:8080/api/v1/admin/auto-cycle/stop
"last_result": "pass",
"last_checked_at": "2026-02-01T09:05:00Z",
"in_queue": true,
"current_state": "done"
"current_state": "done",
"last_cycle_id": 4,
"egress": {"total": 5, "ok": 5, "by_type": [
{"type": "https", "total": 3, "ok": 3},
{"type": "icmp", "total": 2, "ok": 2}
]},
"ingress": {"total": 4, "ok": 3, "by_type": [
{"type": "icmp", "total": 1, "ok": 1},
{"type": "tcp", "total": 3, "ok": 2}
]}
}
]
```
@@ -667,6 +693,18 @@ curl -s -X POST http://<control-api>:8080/api/v1/admin/auto-cycle/stop
`in_queue`/`current_state` отражают, есть ли у адреса сейчас живая строка в
`ip_queue`, а не только в реестре.
**Уровни `egress` и `ingress`.** Результат последнего цикла (`last_cycle_id` —
наибольший цикл с записанными проверками, `0` — проверок нет), разделённый
на выходные проверки валидатора (`egress`) и входные проверки пробера со всех
площадок (`ingress`). В каждом уровне: `total` — сколько проверок записано,
`ok` — сколько успешных, `by_type` — то же по типам, по алфавиту. Тип — это
`check_type` до первого дефиса: `tcp-22` и `tcp-443` дают `tcp`, `tls-443` —
`tls`; `https`, `icmp`, `ssh` остаются как есть, новый тип проверки появляется
в `by_type` сам. Без проверок на уровне: `{"total": 0, "ok": 0, "by_type": []}`.
Счёт идёт по записанным проверкам, а `last_result` учитывает ещё и недостающие
результаты, поэтому при неполном наборе вердикт может быть хуже, чем «`ok` из
`total`».
### `GET /api/v1/admin/registry/{ip}`
Реестровая запись по одному адресу плюс вся сохранённая история проверок