From 8ffb8b5f00942270bfe954ee27ed5b2191955a34 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Mon, 12 Oct 2026 14:14:45 +0700 Subject: [PATCH] =?UTF-8?q?fix(alerts):=20=D0=B8=D1=81=D0=BA=D0=BB=D1=8E?= =?UTF-8?q?=D1=87=D0=B8=D1=82=D1=8C=20=D0=BD=D0=B5=D0=BE=D0=B4=D0=BD=D0=BE?= =?UTF-8?q?=D0=B7=D0=BD=D0=B0=D1=87=D0=BD=D1=8B=D0=B5=20=D0=B8=20=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=BB=D1=8E=D1=87=D1=91=D0=BD=D0=BD=D1=8B=D0=B5=20?= =?UTF-8?q?=D1=83=D1=81=D1=82=D1=80=D0=BE=D0=B9=D1=81=D1=82=D0=B2=D0=B0=20?= =?UTF-8?q?=D0=B8=D0=B7=20=D1=81=D0=B8=D0=B3=D0=BD=D0=B0=D0=BB=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/package.json | 2 +- .../server-signal-identity.test.ts | 28 +++++++++++++++ .../alert-engine/server-signal-identity.ts | 36 +++++++++++++++++++ .../alert-engine/signals-from-collectors.ts | 10 ++++-- docs/ux-ui-external-acceptance.md | 14 ++++++-- docs/ux-ui-plan.md | 6 ++-- docs/ux-ui-progress.md | 12 ++++++- docs/ux-ui-remaining-acceptance.md | 14 ++++++-- docs/ux-ui-stage-12.md | 12 ++++++- docs/ux-ui-test-server.md | 12 ++++++- .../alert-server-identity-2026-10-12.json | 32 +++++++++++++++++ 11 files changed, 164 insertions(+), 14 deletions(-) create mode 100644 backend/src/services/alert-engine/server-signal-identity.test.ts create mode 100644 backend/src/services/alert-engine/server-signal-identity.ts create mode 100644 docs/ux-ui/acceptance/alert-server-identity-2026-10-12.json diff --git a/backend/package.json b/backend/package.json index 8ad8a362..1764e4ed 100644 --- a/backend/package.json +++ b/backend/package.json @@ -8,7 +8,7 @@ "dev": "tsx watch src/index.ts", "build": "npm run build -w @mmapp/contracts && tsc", "test:alerts-telegram": "tsx src/services/alerts-outbox-retry.test.ts && tsx src/services/alerts-outbox-overview.test.ts && tsx src/services/alert-engine/outbox-execution.test.ts && tsx src/services/alerts-telegram-delivery.test.ts && tsx src/services/alerts-revision.test.ts && tsx src/services/alerts-overview.test.ts && tsx ../packages/contracts/src/alerts-normalization.test.ts && tsx src/services/alerts-config-execution.test.ts && tsx src/services/alerts-telegram-settings.test.ts && tsx src/services/alerts-telegram-send.test.ts && tsx src/routes/alerts.test.ts && tsx src/plugins/auth.alerts.test.ts", - "test:alert-engine": "tsx src/services/alert-engine/groups.test.ts && tsx src/services/alert-engine/evaluate-rule.test.ts && tsx src/services/gre-bgp-snapshot-collector.test.ts && tsx src/services/gre-bgp-snapshot-history.test.ts && tsx src/services/gre-bgp-snapshot-sql.test.ts && tsx src/services/alert-engine/confirm-stability.test.ts", + "test:alert-engine": "tsx src/services/alert-engine/server-signal-identity.test.ts && tsx src/services/alert-engine/groups.test.ts && tsx src/services/alert-engine/evaluate-rule.test.ts && tsx src/services/gre-bgp-snapshot-collector.test.ts && tsx src/services/gre-bgp-snapshot-history.test.ts && tsx src/services/gre-bgp-snapshot-sql.test.ts && tsx src/services/alert-engine/confirm-stability.test.ts", "start": "node dist/index.js", "db:generate": "drizzle-kit generate", "db:migrate": "drizzle-kit migrate", diff --git a/backend/src/services/alert-engine/server-signal-identity.test.ts b/backend/src/services/alert-engine/server-signal-identity.test.ts new file mode 100644 index 00000000..428b0919 --- /dev/null +++ b/backend/src/services/alert-engine/server-signal-identity.test.ts @@ -0,0 +1,28 @@ +import assert from "node:assert/strict" +import { createServerSignalIdentityGuard, type AlertServerIdentity } from "./server-signal-identity.js" + +const server = (id: number, name = "CHR", enabled = true): AlertServerIdentity => ({ id, name, host: `host-${id}`, enabled }) +const sample = (id: number, name = "CHR") => ({ serverId: id, name, host: `host-${id}`, status: "offline", sumRxMbps: 100 }) + +// A valid, uniquely named current device survives; disabled/deleted devices do not. +const guard = createServerSignalIdentityGuard([server(1), server(2, "disabled", false)]) +assert.deepEqual(guard([sample(1), sample(2, "disabled"), sample(3, "deleted")]), [sample(1)]) +// Case/whitespace aliases of two enabled devices cannot mix history or traffic. +for (const inventory of [[server(1), server(2, " chr ")], [server(2, " chr "), server(1)]]) { + assert.deepEqual(createServerSignalIdentityGuard(inventory)([sample(1), sample(2, " chr ")]), []) +} +// Disabling the duplicate makes the remaining active device unambiguous. +assert.deepEqual(createServerSignalIdentityGuard([server(1), server(2, " chr ", false)])([sample(1), sample(2, " chr ")]), [sample(1)]) +// Rename, host change and id reuse must not reassign old observations by display name. +assert.deepEqual(guard([sample(3), { ...sample(1), host: "old-host" }, { ...sample(1), name: "old-name" }]), []) +assert.deepEqual(createServerSignalIdentityGuard([server(1, "renamed")])([sample(1)]), []) +assert.deepEqual(guard([sample(1), sample(1)]), []) +assert.deepEqual(guard([{ ...sample(1), name: " chr " }]), [{ ...sample(1), name: " chr " }]) +assert.deepEqual(createServerSignalIdentityGuard([{ ...server(1), id: "1" }])([sample(1)]), [sample(1)]) +assert.deepEqual(createServerSignalIdentityGuard([{ ...server(1), id: "9007199254740993" }])([sample(1)]), []) +// Filtering is immutable, and checks every run against the same current inventory. +const rows = [sample(1), sample(2)] +const before = JSON.stringify(rows) +guard(rows) +assert.equal(JSON.stringify(rows), before) +console.log("server signal identity: current/disabled/deleted/ambiguous/renamed/host/duplicate/id/immutability passed") diff --git a/backend/src/services/alert-engine/server-signal-identity.ts b/backend/src/services/alert-engine/server-signal-identity.ts new file mode 100644 index 00000000..94014ce7 --- /dev/null +++ b/backend/src/services/alert-engine/server-signal-identity.ts @@ -0,0 +1,36 @@ +export interface AlertServerIdentity { + id: number | string + name: string + host: string + enabled: boolean +} + +interface SnapshotServerIdentity { serverId: number; name: string; host: string } + +const nameKey = (name: string) => name.trim().toLowerCase() + +/** Rules currently address servers by name. Ambiguous names must never select an arbitrary device. */ +export function createServerSignalIdentityGuard(servers: AlertServerIdentity[]) { + const active = servers.filter(s => s.enabled === true) + const nameCounts = new Map() + for (const server of active) { + const key = nameKey(server.name) + nameCounts.set(key, (nameCounts.get(key) ?? 0) + 1) + } + const eligible = new Map() + for (const server of active) { + const id = Number(server.id) + const key = nameKey(server.name) + if (Number.isSafeInteger(id) && id > 0 && key && nameCounts.get(key) === 1) eligible.set(id, server) + } + return (rows: T[]): T[] => { + const counts = new Map() + for (const row of rows) if (row) counts.set(row.serverId, (counts.get(row.serverId) ?? 0) + 1) + return rows.filter(row => { + if (!row || counts.get(row.serverId) !== 1) return false + const server = eligible.get(row.serverId) + return server != null && typeof row.name === "string" && typeof row.host === "string" + && nameKey(row.name) === nameKey(server.name) && row.host.trim() === server.host.trim() + }) + } +} diff --git a/backend/src/services/alert-engine/signals-from-collectors.ts b/backend/src/services/alert-engine/signals-from-collectors.ts index d8f0105c..e0413118 100644 --- a/backend/src/services/alert-engine/signals-from-collectors.ts +++ b/backend/src/services/alert-engine/signals-from-collectors.ts @@ -2,6 +2,7 @@ import { dbAll } from "../../db/index.js" import { collectBgpSignalsFromRuns } from "./bgp-signals.js" import { collectGreSignalsFromRuns } from "./gre-signals.js" import { readGreBgpRunSnapshots } from "../gre-bgp-snapshot-history.js" +import { createServerSignalIdentityGuard, type AlertServerIdentity } from "./server-signal-identity.js" import type { PingRunSnapshot, ResourcesRunSnapshot, @@ -187,14 +188,17 @@ function collectTrafficSignalsFromRuns(runs: TrafficRunSnapshot[]): TrafficServe /** Собирает сигналы alert_engine напрямую из результатов collector jobs (`scheduler_runs.result_json`). */ export async function buildSignalSnapshotFromCollectors(): Promise { const sampledAt = new Date().toISOString() + // Read only identity/configuration; credentials are not needed by the alert engine. + const currentServers = await dbAll("SELECT id, name, host, enabled FROM servers") + const guardServerRows = createServerSignalIdentityGuard(currentServers) const resourceRuns = await readLatestOkRunSnapshots("uptime_resources", 16) as ResourcesRunSnapshot[] const restRuns = await readLatestOkRunSnapshots("servers_rest_ping", 16) as ServersRestPingRunSnapshot[] const pingRuns = await readLatestOkRunSnapshots("uptime_ping", 24) as PingRunSnapshot[] const trafficRuns = await readLatestOkRunSnapshots("traffic", 8) as TrafficRunSnapshot[] const greBgpRuns = await readGreBgpRunSnapshots() - const resourceByKey = collectServerSignalsFromResourceRuns(resourceRuns) - const restByKey = collectServerSignalsFromRestRuns(restRuns) + const resourceByKey = collectServerSignalsFromResourceRuns(resourceRuns.map(run => ({ ...run, servers: guardServerRows(run.servers ?? []) }))) + const restByKey = collectServerSignalsFromRestRuns(restRuns.map(run => ({ ...run, servers: guardServerRows(run.servers ?? []) }))) const serverKeys = new Set([...resourceByKey.keys(), ...restByKey.keys()]) const servers: ServerSignal[] = [] for (const key of serverKeys) { @@ -216,7 +220,7 @@ export async function buildSignalSnapshotFromCollectors(): Promise ({ ...run, servers: guardServerRows(run.servers ?? []) }))), greTunnels: collectGreSignalsFromRuns(greBgpRuns), bgpSessions: collectBgpSignalsFromRuns(greBgpRuns), } diff --git a/docs/ux-ui-external-acceptance.md b/docs/ux-ui-external-acceptance.md index 783b4225..9b499404 100644 --- a/docs/ux-ui-external-acceptance.md +++ b/docs/ux-ui-external-acceptance.md @@ -1,6 +1,6 @@ # UX/UI: протокол внешней приёмки 12.5 -Актуальное продолжение 2026-10-12: завершён 12.5.168 — readonly audit остальных alert sources. На стенде resources15s/traffic30s/REST disabled120s/active probes0;2 enabled CHR с одинаковым normalized name сворачиваются в1 server signal. Source code не проверяет возраст/disabled config этих families; ошибки фильтруются как only-ok history. Это audit, исправление не заявляется. Runtime ux-fresh167/ux-bfd158 healthy. Следующий169 — единый блок current/identity/config/freshness guards для server/probe/traffic signals с учётом per-probe intervals, owned PG fixtures и profile tests;12.5 открыт. +Актуальное продолжение 2026-10-12: завершён локальный блок 12.5.169 — identity/config guards серверных и traffic signals. Проверяются текущие id/name/host/enabled; неоднозначные имена enabled устройств исключаются. Пробы, enabled источников, свежесть и latest-error barriers ещё не исправлены. Profile/build/ESLint passed; SQL skipped, deployment отсутствует. Стенд остаётся ux-fresh167/ux-bfd158. Следующий170 — завершить source freshness/current/per-probe guards, owned PG приёмку и общий backend deploy;12.5 открыт. IPsec78: реальный PSK responder established, mode-config198.18.78.10 и online=true подтверждены API; точные собственные объекты удалены. Сертификатный/DN online, late failure и history restore остаются открытыми. [Отчёт](ux-ui/acceptance/ipsec-mode-config-2026-10-09.json), [журнал](ux-ui-progress.md). @@ -696,7 +696,7 @@ flowchart LR ## 12.5.77 — основной блок IPsec (2026-10-09) -Актуальное продолжение 2026-10-12: завершён 12.5.168 — readonly audit остальных alert sources. На стенде resources15s/traffic30s/REST disabled120s/active probes0;2 enabled CHR с одинаковым normalized name сворачиваются в1 server signal. Source code не проверяет возраст/disabled config этих families; ошибки фильтруются как only-ok history. Это audit, исправление не заявляется. Runtime ux-fresh167/ux-bfd158 healthy. Следующий169 — единый блок current/identity/config/freshness guards для server/probe/traffic signals с учётом per-probe intervals, owned PG fixtures и profile tests;12.5 открыт. +Актуальное продолжение 2026-10-12: завершён локальный блок 12.5.169 — identity/config guards серверных и traffic signals. Проверяются текущие id/name/host/enabled; неоднозначные имена enabled устройств исключаются. Пробы, enabled источников, свежесть и latest-error barriers ещё не исправлены. Profile/build/ESLint passed; SQL skipped, deployment отсутствует. Стенд остаётся ux-fresh167/ux-bfd158. Следующий170 — завершить source freshness/current/per-probe guards, owned PG приёмку и общий backend deploy;12.5 открыт. Подробный объём, протоколы, схема и ограничения: [12.5.77 в журнале](ux-ui-progress.md). @@ -1880,3 +1880,13 @@ flowchart LR ~~~ Протокол: [readonly audit168](ux-ui/acceptance/alert-source-audit-2026-10-12.json). Source/build/runtime не менялись после167, новые тесты и исправления не заявляются. Следующий169 — выполнить этот единый блок, сохранить profile/owned SQL результаты и локальный коммит без push. Реальные Telegram/Portal/внешние accounts, client OS/physical WAN/multi-instance остаются отдельными условиями12.5. + +## 12.5.169 — идентификация устройств в сигналах оповещений (2026-10-12) + +По поручению пользователя выполнить небольшой объём при оставшихся7% лимита. Исходный единый объём169 ограничен самостоятельной локальной частью; полная приёмка источников не заявляется. Добавлен server-signal-identity.ts, builder читает только id/name/host/enabled из текущего servers. До name-based timeline и traffic reducer каждая строка resource/REST/traffic сопоставляется с текущим активным устройством по ID, имени и адресу. Сигналы отключённых/удалённых устройств, старого имени/адреса и повторяющегося ID внутри одного run исключаются. Если несколько enabled устройств имеют одинаковое имя после trim/lowercase, их сигналы не выдаются: существующие правила адресуют цель по имени и не позволяют безопасно выбрать конкретное устройство. Имена/настройки самих CHR не менялись. На текущем стенде оба CHR имеют одинаковое имя; после будущего развёртывания такие цели будут подавлены до явного устранения неоднозначности или миграции правил на ID. Это консервативное отсутствие подтверждённого события, не offline и не нулевой traffic. + +Новый постоянный тест добавлен в test:alert-engine: уникальная цель; отключённая/удалённая; два одноимённых устройства и перестановка каталога; отключение дубликата; изменение имени/адреса; другой ID; повторные строки; строковый безопасный PG ID и небезопасный ID; неизменность входа. Прошли доступные проверки alert-engine (identity/groups/evaluate-rule/collector/history), backend production build и ESLint трёх изменённых TS файлов. GRE/BGP SQL пропущен без явно выделенной own DB; confirm-stability SQL пропущен из-за PostgreSQL connect EACCES127.0.0.1:5432. Это не положительная SQL-приёмка. Полная UI/context regression не запускалась: frontend не менялся. + +Ограничения сохранены явно: only-ok history всё ещё может поднимать старый факт, disabled flags самих jobs и sampledAt age не применяются; resource unknown и traffic отсутствующее значение требуют отдельного исправления; probe identity/enabled/individual interval остаются в170. Duplicate row guard не заменяет latest-current/error barrier. SQL/live fixtures и source/runtime parity для169 отсутствуют; удалённый backend/frontend не развёртывались, RouterOS writes/messages0, новые сетевые объекты не создавались. Доступность серверов в этом локальном блоке не перепроверялась. Runtime остаётся ux-fresh167/ux-bfd158; изменений169 в браузере пока нет. + +Протокол: [локальная проверка169](ux-ui/acceptance/alert-server-identity-2026-10-12.json). Следующий170 — source enabled/freshness/current known guards, per-probe timing и identity с owned SQL positive/negative fixtures, затем coherent backend build/deploy/live и локальный коммит без push. Этап12.5 остаётся открытым. diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index a2463ab7..62a85b72 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -1,12 +1,12 @@ # План реализации UX/UI по пятичасовым окнам -Актуальное продолжение 2026-10-12: завершён 12.5.168 — readonly audit остальных alert sources. На стенде resources15s/traffic30s/REST disabled120s/active probes0;2 enabled CHR с одинаковым normalized name сворачиваются в1 server signal. Source code не проверяет возраст/disabled config этих families; ошибки фильтруются как only-ok history. Это audit, исправление не заявляется. Runtime ux-fresh167/ux-bfd158 healthy. Следующий169 — единый блок current/identity/config/freshness guards для server/probe/traffic signals с учётом per-probe intervals, owned PG fixtures и profile tests;12.5 открыт. +Актуальное продолжение 2026-10-12: завершён локальный блок 12.5.169 — identity/config guards серверных и traffic signals. Проверяются текущие id/name/host/enabled; неоднозначные имена enabled устройств исключаются. Пробы, enabled источников, свежесть и latest-error barriers ещё не исправлены. Profile/build/ESLint passed; SQL skipped, deployment отсутствует. Стенд остаётся ux-fresh167/ux-bfd158. Следующий170 — завершить source freshness/current/per-probe guards, owned PG приёмку и общий backend deploy;12.5 открыт. Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-12. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат. ## Актуальная точка продолжения — 2026-10-12 -Актуальное продолжение 2026-10-12: завершён 12.5.168 — readonly audit остальных alert sources. На стенде resources15s/traffic30s/REST disabled120s/active probes0;2 enabled CHR с одинаковым normalized name сворачиваются в1 server signal. Source code не проверяет возраст/disabled config этих families; ошибки фильтруются как only-ok history. Это audit, исправление не заявляется. Runtime ux-fresh167/ux-bfd158 healthy. Следующий169 — единый блок current/identity/config/freshness guards для server/probe/traffic signals с учётом per-probe intervals, owned PG fixtures и profile tests;12.5 открыт. +Актуальное продолжение 2026-10-12: завершён локальный блок 12.5.169 — identity/config guards серверных и traffic signals. Проверяются текущие id/name/host/enabled; неоднозначные имена enabled устройств исключаются. Пробы, enabled источников, свежесть и latest-error barriers ещё не исправлены. Profile/build/ESLint passed; SQL skipped, deployment отсутствует. Стенд остаётся ux-fresh167/ux-bfd158. Следующий170 — завершить source freshness/current/per-probe guards, owned PG приёмку и общий backend deploy;12.5 открыт. Этапы0–11 завершены в локальных границах журнала. История выполненных внешних блоков и доказательства сохранены в [журнале](ux-ui-stage-12.md) и [протоколе](ux-ui-external-acceptance.md). Реальная Telegram доставка, Portal SSO, multi-instance и оставшиеся профильные сетевые ограничения открыты. @@ -371,6 +371,6 @@ flowchart LR ## 12.5.77 — основной блок IPsec (2026-10-09) -Актуальное продолжение 2026-10-12: завершён 12.5.168 — readonly audit остальных alert sources. На стенде resources15s/traffic30s/REST disabled120s/active probes0;2 enabled CHR с одинаковым normalized name сворачиваются в1 server signal. Source code не проверяет возраст/disabled config этих families; ошибки фильтруются как only-ok history. Это audit, исправление не заявляется. Runtime ux-fresh167/ux-bfd158 healthy. Следующий169 — единый блок current/identity/config/freshness guards для server/probe/traffic signals с учётом per-probe intervals, owned PG fixtures и profile tests;12.5 открыт. +Актуальное продолжение 2026-10-12: завершён локальный блок 12.5.169 — identity/config guards серверных и traffic signals. Проверяются текущие id/name/host/enabled; неоднозначные имена enabled устройств исключаются. Пробы, enabled источников, свежесть и latest-error barriers ещё не исправлены. Profile/build/ESLint passed; SQL skipped, deployment отсутствует. Стенд остаётся ux-fresh167/ux-bfd158. Следующий170 — завершить source freshness/current/per-probe guards, owned PG приёмку и общий backend deploy;12.5 открыт. Подробный объём, протоколы, схема и ограничения: [12.5.77 в журнале](ux-ui-progress.md). diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index fec89ac4..e7eb779a 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -1,6 +1,6 @@ # UX/UI: журнал реализации -Актуальное продолжение 2026-10-12: завершён 12.5.168 — readonly audit остальных alert sources. На стенде resources15s/traffic30s/REST disabled120s/active probes0;2 enabled CHR с одинаковым normalized name сворачиваются в1 server signal. Source code не проверяет возраст/disabled config этих families; ошибки фильтруются как only-ok history. Это audit, исправление не заявляется. Runtime ux-fresh167/ux-bfd158 healthy. Следующий169 — единый блок current/identity/config/freshness guards для server/probe/traffic signals с учётом per-probe intervals, owned PG fixtures и profile tests;12.5 открыт. +Актуальное продолжение 2026-10-12: завершён локальный блок 12.5.169 — identity/config guards серверных и traffic signals. Проверяются текущие id/name/host/enabled; неоднозначные имена enabled устройств исключаются. Пробы, enabled источников, свежесть и latest-error barriers ещё не исправлены. Profile/build/ESLint passed; SQL skipped, deployment отсутствует. Стенд остаётся ux-fresh167/ux-bfd158. Следующий170 — завершить source freshness/current/per-probe guards, owned PG приёмку и общий backend deploy;12.5 открыт. Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md). @@ -38,6 +38,16 @@ | 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 | | 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–168 и ограничения ниже. Runtime backend ux-month149/frontend ux-sessions150. | +## 12.5.169 — идентификация устройств в сигналах оповещений (2026-10-12) + +По поручению пользователя выполнить небольшой объём при оставшихся7% лимита. Исходный единый объём169 ограничен самостоятельной локальной частью; полная приёмка источников не заявляется. Добавлен server-signal-identity.ts, builder читает только id/name/host/enabled из текущего servers. До name-based timeline и traffic reducer каждая строка resource/REST/traffic сопоставляется с текущим активным устройством по ID, имени и адресу. Сигналы отключённых/удалённых устройств, старого имени/адреса и повторяющегося ID внутри одного run исключаются. Если несколько enabled устройств имеют одинаковое имя после trim/lowercase, их сигналы не выдаются: существующие правила адресуют цель по имени и не позволяют безопасно выбрать конкретное устройство. Имена/настройки самих CHR не менялись. На текущем стенде оба CHR имеют одинаковое имя; после будущего развёртывания такие цели будут подавлены до явного устранения неоднозначности или миграции правил на ID. Это консервативное отсутствие подтверждённого события, не offline и не нулевой traffic. + +Новый постоянный тест добавлен в test:alert-engine: уникальная цель; отключённая/удалённая; два одноимённых устройства и перестановка каталога; отключение дубликата; изменение имени/адреса; другой ID; повторные строки; строковый безопасный PG ID и небезопасный ID; неизменность входа. Прошли доступные проверки alert-engine (identity/groups/evaluate-rule/collector/history), backend production build и ESLint трёх изменённых TS файлов. GRE/BGP SQL пропущен без явно выделенной own DB; confirm-stability SQL пропущен из-за PostgreSQL connect EACCES127.0.0.1:5432. Это не положительная SQL-приёмка. Полная UI/context regression не запускалась: frontend не менялся. + +Ограничения сохранены явно: only-ok history всё ещё может поднимать старый факт, disabled flags самих jobs и sampledAt age не применяются; resource unknown и traffic отсутствующее значение требуют отдельного исправления; probe identity/enabled/individual interval остаются в170. Duplicate row guard не заменяет latest-current/error barrier. SQL/live fixtures и source/runtime parity для169 отсутствуют; удалённый backend/frontend не развёртывались, RouterOS writes/messages0, новые сетевые объекты не создавались. Доступность серверов в этом локальном блоке не перепроверялась. Runtime остаётся ux-fresh167/ux-bfd158; изменений169 в браузере пока нет. + +Протокол: [локальная проверка169](ux-ui/acceptance/alert-server-identity-2026-10-12.json). Следующий170 — source enabled/freshness/current known guards, per-probe timing и identity с owned SQL positive/negative fixtures, затем coherent backend build/deploy/live и локальный коммит без push. Этап12.5 остаётся открытым. + ## 12.5.168 — карта оставшихся источников оповещений (2026-10-12) Выполнен source audit и чтение метаданных production PG с default_transaction_read_only=on. Source/database settings не менялись, default-seeding getters не вызывались, bootstrap/RouterOS/messages отсутствовали. Реальные текущие настройки: resources enabled/15s, traffic enabled/30s, REST disabled/120s, active Ping probes0. Builder в момент audit выдавал1 server signal (max age3.197s), traffic1 (max age18.24s), probes0. Это возраст наблюдаемого снимка, не controlled stale fixture. Одновременно обнаружена1 группа одинаковых имён у enabled servers: оба CHR называются одинаково,2 devices сворачиваются в1 name-based signal. Audit не исправляет это поведение. diff --git a/docs/ux-ui-remaining-acceptance.md b/docs/ux-ui-remaining-acceptance.md index 04876aa1..14410ed3 100644 --- a/docs/ux-ui-remaining-acceptance.md +++ b/docs/ux-ui-remaining-acceptance.md @@ -1,6 +1,6 @@ # Оставшаяся внешняя приёмка — 2026-10-12 -Это актуальная матрица после 12.5.168. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии. +Это актуальная матрица после 12.5.169. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии. Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-fresh167`, frontend `ux-bfd158`; production lab использует AUTH_REQUIRED=false. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись. @@ -18,7 +18,7 @@ | Recursive routes | Production PUT/change/own history restore88, disabled guards, partial/unknown/applied outcomes, live формы/контекст | Автоматический rollback/retry, durable receipts и несколько экземпляров отсутствуют; PUT не является idempotent noop. Полная произвольная legacy catalog миграция не принята | | Diagnostics/terminal | Allowlist/native reads, Ping/trace/route/MTU/DNS, actual avg-rtt, disabled guards, JWT/live PC/mobile89; real default memory tail/log limits/API/UI133 | DNS использует backend resolver; MTU — ограниченная оценка, не гарантированный максимум. Stop ожидания не доказывает остановку RouterOS команды; arbitrary CLI и physical disconnect не приняты | | Portal/интеграции | Локальные и isolated JWT guards; UI/API error states в границах журналов | Настоящий Portal issuer/login/отзыв прав; RIPE SDK two-IP/no-fetch passed140, native IPv6 ULA flow pipeline passed145 (IPv4 transport), public IPv6 WAN/local licensed MaxMind/S3/container/certificate jobs и Gitea delivery требуют отдельных тестовых endpoints/аккаунтов. Не отправлять сообщения и не публиковать релиз без соответствующего поручения | -| Общая приёмка | Локальная12.3.83; UI1244/context221/compiled984 parity139; профильные build/E2E/live проверки последующих блоков | Traffic initial live mock-id gate passed141; IPv6 special ranges/cache passed142; unknown RIPE coordinates passed143; IPv6 synthetic UDP/worker/SQL passed144; native CHR ULA/IPFIX/API/UI passed145; ICMPv6/IPv6 endpoint labels passed146; HTTP DTO validation passed147; scoped flow catalog/serverId guards passed148; strict monthly calendar passed149; Sessions labels passed150; application GRE static IPv6 lifecycle151 passed; WireGuard static IPv6 lifecycle152 и disabled SQL/API gates passed; OSPF/BFD boundary/warning153 passed; OSPFv3 routes154 accepted in recorded bounds; BGP IPv6 basic155 passed; bigint ID/SQL guards156 passed; общая UI1260/context221/backend24 regression157 passed; IPv6 BFD correlation158 profile/deployed passed; native OSPFv3/BFDv6 boundary/recovery159 passed; BGP IPv6/BFD withdrawal/recovery160 passed; selected BGP/alert unknown guards161 replay/profiles/deployed passed (SQL stability skipped); private4-byte ASN162 native/API/UI passed; duplicate BGP alert keys163 replay/profiles/deployed passed; nullable selected BGP164 replay/profiles/API/UI passed; collector165 parallel/current guards/read-only PG passed; positive partial/error SQL166/full24 with PG passed (optional network SQL not repeated); GRE/BGP freshness167/SQL8 passed; other source audit168 выявил pending identity/disabled/freshness gaps, fix169 pending; последующие изменения проверять отдельно; scoped axe не означает полную WCAG сертификацию | +| Общая приёмка | Локальная12.3.83; UI1244/context221/compiled984 parity139; профильные build/E2E/live проверки последующих блоков | Traffic initial live mock-id gate passed141; IPv6 special ranges/cache passed142; unknown RIPE coordinates passed143; IPv6 synthetic UDP/worker/SQL passed144; native CHR ULA/IPFIX/API/UI passed145; ICMPv6/IPv6 endpoint labels passed146; HTTP DTO validation passed147; scoped flow catalog/serverId guards passed148; strict monthly calendar passed149; Sessions labels passed150; application GRE static IPv6 lifecycle151 passed; WireGuard static IPv6 lifecycle152 и disabled SQL/API gates passed; OSPF/BFD boundary/warning153 passed; OSPFv3 routes154 accepted in recorded bounds; BGP IPv6 basic155 passed; bigint ID/SQL guards156 passed; общая UI1260/context221/backend24 regression157 passed; IPv6 BFD correlation158 profile/deployed passed; native OSPFv3/BFDv6 boundary/recovery159 passed; BGP IPv6/BFD withdrawal/recovery160 passed; selected BGP/alert unknown guards161 replay/profiles/deployed passed (SQL stability skipped); private4-byte ASN162 native/API/UI passed; duplicate BGP alert keys163 replay/profiles/deployed passed; nullable selected BGP164 replay/profiles/API/UI passed; collector165 parallel/current guards/read-only PG passed; positive partial/error SQL166/full24 with PG passed (optional network SQL not repeated); GRE/BGP freshness167/SQL8 passed; other source audit168 выявил identity/disabled/freshness gaps; локальный server/traffic identity guard169 passed без SQL/deploy, source/per-probe/freshness fix170 pending; последующие изменения проверять отдельно; scoped axe не означает полную WCAG сертификацию | Доказательства и точные ограничения: [полная история этапа12](ux-ui-stage-12.md), [внешний протокол](ux-ui-external-acceptance.md), [BGP93](ux-ui/acceptance/bgp-failover-2026-10-10.json), [паспорт стенда](ux-ui-test-server.md). Невыполненные пункты не являются все блокерами данного лабораторного сценария: для выпуска необходимо явно определить требуемые конфигурации и принять их либо согласовать ограничение. @@ -795,3 +795,13 @@ flowchart LR ~~~ Протокол: [readonly audit168](ux-ui/acceptance/alert-source-audit-2026-10-12.json). Source/build/runtime не менялись после167, новые тесты и исправления не заявляются. Следующий169 — выполнить этот единый блок, сохранить profile/owned SQL результаты и локальный коммит без push. Реальные Telegram/Portal/внешние accounts, client OS/physical WAN/multi-instance остаются отдельными условиями12.5. + +## 12.5.169 — идентификация устройств в сигналах оповещений (2026-10-12) + +По поручению пользователя выполнить небольшой объём при оставшихся7% лимита. Исходный единый объём169 ограничен самостоятельной локальной частью; полная приёмка источников не заявляется. Добавлен server-signal-identity.ts, builder читает только id/name/host/enabled из текущего servers. До name-based timeline и traffic reducer каждая строка resource/REST/traffic сопоставляется с текущим активным устройством по ID, имени и адресу. Сигналы отключённых/удалённых устройств, старого имени/адреса и повторяющегося ID внутри одного run исключаются. Если несколько enabled устройств имеют одинаковое имя после trim/lowercase, их сигналы не выдаются: существующие правила адресуют цель по имени и не позволяют безопасно выбрать конкретное устройство. Имена/настройки самих CHR не менялись. На текущем стенде оба CHR имеют одинаковое имя; после будущего развёртывания такие цели будут подавлены до явного устранения неоднозначности или миграции правил на ID. Это консервативное отсутствие подтверждённого события, не offline и не нулевой traffic. + +Новый постоянный тест добавлен в test:alert-engine: уникальная цель; отключённая/удалённая; два одноимённых устройства и перестановка каталога; отключение дубликата; изменение имени/адреса; другой ID; повторные строки; строковый безопасный PG ID и небезопасный ID; неизменность входа. Прошли доступные проверки alert-engine (identity/groups/evaluate-rule/collector/history), backend production build и ESLint трёх изменённых TS файлов. GRE/BGP SQL пропущен без явно выделенной own DB; confirm-stability SQL пропущен из-за PostgreSQL connect EACCES127.0.0.1:5432. Это не положительная SQL-приёмка. Полная UI/context regression не запускалась: frontend не менялся. + +Ограничения сохранены явно: only-ok history всё ещё может поднимать старый факт, disabled flags самих jobs и sampledAt age не применяются; resource unknown и traffic отсутствующее значение требуют отдельного исправления; probe identity/enabled/individual interval остаются в170. Duplicate row guard не заменяет latest-current/error barrier. SQL/live fixtures и source/runtime parity для169 отсутствуют; удалённый backend/frontend не развёртывались, RouterOS writes/messages0, новые сетевые объекты не создавались. Доступность серверов в этом локальном блоке не перепроверялась. Runtime остаётся ux-fresh167/ux-bfd158; изменений169 в браузере пока нет. + +Протокол: [локальная проверка169](ux-ui/acceptance/alert-server-identity-2026-10-12.json). Следующий170 — source enabled/freshness/current known guards, per-probe timing и identity с owned SQL positive/negative fixtures, затем coherent backend build/deploy/live и локальный коммит без push. Этап12.5 остаётся открытым. diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index b4218ccd..d7461b7b 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -1,6 +1,6 @@ # Этап 12: итоговая приёмка UX/UI -Актуальное продолжение 2026-10-12: завершён 12.5.168 — readonly audit остальных alert sources. На стенде resources15s/traffic30s/REST disabled120s/active probes0;2 enabled CHR с одинаковым normalized name сворачиваются в1 server signal. Source code не проверяет возраст/disabled config этих families; ошибки фильтруются как only-ok history. Это audit, исправление не заявляется. Runtime ux-fresh167/ux-bfd158 healthy. Следующий169 — единый блок current/identity/config/freshness guards для server/probe/traffic signals с учётом per-probe intervals, owned PG fixtures и profile tests;12.5 открыт. +Актуальное продолжение 2026-10-12: завершён локальный блок 12.5.169 — identity/config guards серверных и traffic signals. Проверяются текущие id/name/host/enabled; неоднозначные имена enabled устройств исключаются. Пробы, enabled источников, свежесть и latest-error barriers ещё не исправлены. Profile/build/ESLint passed; SQL skipped, deployment отсутствует. Стенд остаётся ux-fresh167/ux-bfd158. Следующий170 — завершить source freshness/current/per-probe guards, owned PG приёмку и общий backend deploy;12.5 открыт. План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push. @@ -3185,3 +3185,13 @@ flowchart LR ~~~ Протокол: [readonly audit168](ux-ui/acceptance/alert-source-audit-2026-10-12.json). Source/build/runtime не менялись после167, новые тесты и исправления не заявляются. Следующий169 — выполнить этот единый блок, сохранить profile/owned SQL результаты и локальный коммит без push. Реальные Telegram/Portal/внешние accounts, client OS/physical WAN/multi-instance остаются отдельными условиями12.5. + +## 12.5.169 — идентификация устройств в сигналах оповещений (2026-10-12) + +По поручению пользователя выполнить небольшой объём при оставшихся7% лимита. Исходный единый объём169 ограничен самостоятельной локальной частью; полная приёмка источников не заявляется. Добавлен server-signal-identity.ts, builder читает только id/name/host/enabled из текущего servers. До name-based timeline и traffic reducer каждая строка resource/REST/traffic сопоставляется с текущим активным устройством по ID, имени и адресу. Сигналы отключённых/удалённых устройств, старого имени/адреса и повторяющегося ID внутри одного run исключаются. Если несколько enabled устройств имеют одинаковое имя после trim/lowercase, их сигналы не выдаются: существующие правила адресуют цель по имени и не позволяют безопасно выбрать конкретное устройство. Имена/настройки самих CHR не менялись. На текущем стенде оба CHR имеют одинаковое имя; после будущего развёртывания такие цели будут подавлены до явного устранения неоднозначности или миграции правил на ID. Это консервативное отсутствие подтверждённого события, не offline и не нулевой traffic. + +Новый постоянный тест добавлен в test:alert-engine: уникальная цель; отключённая/удалённая; два одноимённых устройства и перестановка каталога; отключение дубликата; изменение имени/адреса; другой ID; повторные строки; строковый безопасный PG ID и небезопасный ID; неизменность входа. Прошли доступные проверки alert-engine (identity/groups/evaluate-rule/collector/history), backend production build и ESLint трёх изменённых TS файлов. GRE/BGP SQL пропущен без явно выделенной own DB; confirm-stability SQL пропущен из-за PostgreSQL connect EACCES127.0.0.1:5432. Это не положительная SQL-приёмка. Полная UI/context regression не запускалась: frontend не менялся. + +Ограничения сохранены явно: only-ok history всё ещё может поднимать старый факт, disabled flags самих jobs и sampledAt age не применяются; resource unknown и traffic отсутствующее значение требуют отдельного исправления; probe identity/enabled/individual interval остаются в170. Duplicate row guard не заменяет latest-current/error barrier. SQL/live fixtures и source/runtime parity для169 отсутствуют; удалённый backend/frontend не развёртывались, RouterOS writes/messages0, новые сетевые объекты не создавались. Доступность серверов в этом локальном блоке не перепроверялась. Runtime остаётся ux-fresh167/ux-bfd158; изменений169 в браузере пока нет. + +Протокол: [локальная проверка169](ux-ui/acceptance/alert-server-identity-2026-10-12.json). Следующий170 — source enabled/freshness/current known guards, per-probe timing и identity с owned SQL positive/negative fixtures, затем coherent backend build/deploy/live и локальный коммит без push. Этап12.5 остаётся открытым. diff --git a/docs/ux-ui-test-server.md b/docs/ux-ui-test-server.md index 1d1c32f9..a49f97f6 100644 --- a/docs/ux-ui-test-server.md +++ b/docs/ux-ui-test-server.md @@ -1,6 +1,6 @@ # Тестовый сервер UX/UI — 2026-10-10 -Сервер161.104.46.222: завершён 12.5.168 — readonly audit остальных alert sources. На стенде resources15s/traffic30s/REST disabled120s/active probes0;2 enabled CHR с одинаковым normalized name сворачиваются в1 server signal. Source code не проверяет возраст/disabled config этих families; ошибки фильтруются как only-ok history. Это audit, исправление не заявляется. Runtime ux-fresh167/ux-bfd158 healthy. Следующий169 — единый блок current/identity/config/freshness guards для server/probe/traffic signals с учётом per-probe intervals, owned PG fixtures и profile tests;12.5 открыт. +Сервер161.104.46.222: завершён локальный блок 12.5.169 — identity/config guards серверных и traffic signals. Проверяются текущие id/name/host/enabled; неоднозначные имена enabled устройств исключаются. Пробы, enabled источников, свежесть и latest-error barriers ещё не исправлены. Profile/build/ESLint passed; SQL skipped, deployment отсутствует. Стенд остаётся ux-fresh167/ux-bfd158. Следующий170 — завершить source freshness/current/per-probe guards, owned PG приёмку и общий backend deploy;12.5 открыт. | Параметр | Проверенное состояние | | --- | --- | @@ -1611,3 +1611,13 @@ flowchart LR ~~~ Протокол: [readonly audit168](ux-ui/acceptance/alert-source-audit-2026-10-12.json). Source/build/runtime не менялись после167, новые тесты и исправления не заявляются. Следующий169 — выполнить этот единый блок, сохранить profile/owned SQL результаты и локальный коммит без push. Реальные Telegram/Portal/внешние accounts, client OS/physical WAN/multi-instance остаются отдельными условиями12.5. + +## 12.5.169 — идентификация устройств в сигналах оповещений (2026-10-12) + +По поручению пользователя выполнить небольшой объём при оставшихся7% лимита. Исходный единый объём169 ограничен самостоятельной локальной частью; полная приёмка источников не заявляется. Добавлен server-signal-identity.ts, builder читает только id/name/host/enabled из текущего servers. До name-based timeline и traffic reducer каждая строка resource/REST/traffic сопоставляется с текущим активным устройством по ID, имени и адресу. Сигналы отключённых/удалённых устройств, старого имени/адреса и повторяющегося ID внутри одного run исключаются. Если несколько enabled устройств имеют одинаковое имя после trim/lowercase, их сигналы не выдаются: существующие правила адресуют цель по имени и не позволяют безопасно выбрать конкретное устройство. Имена/настройки самих CHR не менялись. На текущем стенде оба CHR имеют одинаковое имя; после будущего развёртывания такие цели будут подавлены до явного устранения неоднозначности или миграции правил на ID. Это консервативное отсутствие подтверждённого события, не offline и не нулевой traffic. + +Новый постоянный тест добавлен в test:alert-engine: уникальная цель; отключённая/удалённая; два одноимённых устройства и перестановка каталога; отключение дубликата; изменение имени/адреса; другой ID; повторные строки; строковый безопасный PG ID и небезопасный ID; неизменность входа. Прошли доступные проверки alert-engine (identity/groups/evaluate-rule/collector/history), backend production build и ESLint трёх изменённых TS файлов. GRE/BGP SQL пропущен без явно выделенной own DB; confirm-stability SQL пропущен из-за PostgreSQL connect EACCES127.0.0.1:5432. Это не положительная SQL-приёмка. Полная UI/context regression не запускалась: frontend не менялся. + +Ограничения сохранены явно: only-ok history всё ещё может поднимать старый факт, disabled flags самих jobs и sampledAt age не применяются; resource unknown и traffic отсутствующее значение требуют отдельного исправления; probe identity/enabled/individual interval остаются в170. Duplicate row guard не заменяет latest-current/error barrier. SQL/live fixtures и source/runtime parity для169 отсутствуют; удалённый backend/frontend не развёртывались, RouterOS writes/messages0, новые сетевые объекты не создавались. Доступность серверов в этом локальном блоке не перепроверялась. Runtime остаётся ux-fresh167/ux-bfd158; изменений169 в браузере пока нет. + +Протокол: [локальная проверка169](ux-ui/acceptance/alert-server-identity-2026-10-12.json). Следующий170 — source enabled/freshness/current known guards, per-probe timing и identity с owned SQL positive/negative fixtures, затем coherent backend build/deploy/live и локальный коммит без push. Этап12.5 остаётся открытым. diff --git a/docs/ux-ui/acceptance/alert-server-identity-2026-10-12.json b/docs/ux-ui/acceptance/alert-server-identity-2026-10-12.json new file mode 100644 index 00000000..d85914d2 --- /dev/null +++ b/docs/ux-ui/acceptance/alert-server-identity-2026-10-12.json @@ -0,0 +1,32 @@ +{ + "block": "12.5.169", + "date": "2026-10-12", + "scope": "local server/traffic identity guards only", + "checks": { + "identity": "passed", + "groups": "passed", + "evaluateRule": "passed", + "collector": "passed", + "history": "passed", + "backendBuild": "passed", + "eslint": "passed", + "sql": "skipped: no owned DB and local PG connect EACCES", + "live": "not run", + "deploy": "not run" + }, + "runtimeUnchanged": { + "backend": "ux-fresh167", + "frontend": "ux-bfd158" + }, + "pending": [ + "source enabled flags", + "freshness", + "latest error/current known state", + "probe identity and individual intervals", + "owned PG fixtures", + "deployment and live acceptance" + ], + "routerOsWrites": 0, + "messagesSent": 0, + "push": false +}