fix(traffic): исключить ложные нули из суммы трафика

This commit is contained in:
Denozordec committed 2026-10-12 18:06:05 +07:00
1 parent 8fb46b1552
commit 4527e5537c
12 files changed
+280 -27

No files matched your search

+1 -1
View File
@@ -19,7 +19,7 @@
"test:exec": "tsx src/services/mikrotik-logs.test.ts && tsx src/routes/exec.test.ts && tsx src/plugins/auth.exec.test.ts",
"test:auth": "tsx src/lib/permissions.test.ts && tsx src/plugins/auth.smoke.test.ts && tsx src/plugins/auth.ospf-optimizer.test.ts",
"test:wireguard": "npx tsx src/services/wireguard-config.test.ts && npx tsx src/services/wireguard-ipv6.test.ts && npx tsx --test src/services/wireguard-import.test.ts src/services/wireguard-import-apply.test.ts src/services/wireguard-import-revision.test.ts src/services/wireguard-delete.test.ts && npx tsx src/routes/wireguard-import.test.ts",
"test:traffic-rate": "tsx src/services/traffic-rate.test.ts",
"test:traffic-rate": "tsx src/services/traffic-rate.test.ts && tsx src/services/traffic-observation.test.ts",
"test:traffic-flow": "tsx src/routes/traffic-flow-query.test.ts && tsx src/services/traffic-flow-month.test.ts && tsx src/services/traffic-flow-parse.test.ts && tsx src/services/traffic-flow-ipv6-pipeline.test.ts && tsx src/services/traffic-flow-map-exporter.test.ts && tsx src/services/traffic-flow-ifaces.test.ts && tsx src/services/traffic-flow-ifindex.test.ts && tsx src/services/traffic-flow-dedup.test.ts && tsx src/services/traffic-flow-planes.test.ts && tsx src/services/traffic-flow-ip.test.ts && tsx src/services/traffic-flow-dest.test.ts && tsx src/services/traffic-flow-classify.test.ts && tsx src/services/traffic-flow-ripe.test.ts && tsx src/services/traffic-flow-brands.test.ts && tsx src/services/traffic-flow-ingest.test.ts && tsx src/services/traffic-flow-analytics.test.ts && tsx src/services/traffic-flow-map-hops.test.ts && tsx src/services/traffic-flow-purge.test.ts && tsx src/services/traffic-flow-geoip.test.ts && tsx src/services/traffic-flow-facts.test.ts && tsx src/services/traffic-flow-facts-filter.test.ts && tsx src/services/traffic-flow-facts-rebuild.test.ts && tsx src/services/statistics-aggregate.test.ts",
"test:users": "tsx src/modules/users/iface-type.test.ts && tsx src/modules/users/bindings.test.ts && tsx src/modules/users/interface-inventory.test.ts && tsx src/routes/users-catalog.test.ts",
"test:pg": "tsx src/db/pg-pool.test.ts && tsx src/db/sql-bind.test.ts && tsx src/db/sqlite-json.test.ts && tsx src/db/traffic-flags.test.ts && tsx src/db/pg-schema.test.ts && tsx src/services/config-revisions.test.ts",
+14 -16
View File
@@ -5,7 +5,8 @@ import { decodeTrafficSampleFlags, encodeTrafficFlags } from "../db/traffic-flag
import type { TrafficRunSnapshot } from "../types/scheduler-run-snapshot.js"
import { SCHEDULER_RUN_SNAPSHOT_VERSION } from "../types/scheduler-run-snapshot.js"
import { MikrotikClient } from "./mikrotik.js"
import { bpsToMbps, rateBpsFromDelta, shouldIncludeIface } from "./traffic-rate.js"
import { bpsToMbps, shouldIncludeIface } from "./traffic-rate.js"
import { observedTrafficRate, parseTrafficCounter } from "./traffic-observation.js"
import { rememberServerIfaces } from "./traffic-flow-ifindex.js"
interface RosIfaceTraffic {
@@ -40,16 +41,8 @@ function sampleRate(
rxBytes: number,
txBytes: number,
nowMs: number,
): { rxBps: number; txBps: number } {
const prev = prevWave.get(key)
const prevMs = prev ? Date.parse(prev.sampledAt) : NaN
const rxBps = prev && Number.isFinite(prevMs)
? (rateBpsFromDelta(prev.rxBytes, rxBytes, prevMs, nowMs) ?? 0)
: 0
const txBps = prev && Number.isFinite(prevMs)
? (rateBpsFromDelta(prev.txBytes, txBytes, prevMs, nowMs) ?? 0)
: 0
return { rxBps, txBps }
): { rxBps: number; txBps: number; measured: boolean } {
return observedTrafficRate(prevWave.get(key), rxBytes, txBytes, nowMs)
}
export interface TrafficCollectorState {
@@ -61,8 +54,7 @@ export interface TrafficCollectorState {
let collecting = false
function toNum(raw: unknown): number {
const n = Number.parseFloat(String(raw ?? "0"))
return Number.isFinite(n) ? Math.max(0, Math.round(n)) : 0
return parseTrafficCounter(raw)
}
async function getSettingsRow() {
@@ -139,14 +131,18 @@ export async function collectTrafficOnce(): Promise<TrafficRunSnapshot> {
const nowMs = Date.parse(now)
let sumRxMbps = 0
let sumTxMbps = 0
let includedInterfaces = 0
let totalsMeasured = true
const rows = ifaces.map((i) => {
const interfaceName = i.name ?? "unknown"
const rxBytes = toNum(i["rx-byte"])
const txBytes = toNum(i["tx-byte"])
const running = (i.running ?? "false") === "true"
const disabled = (i.disabled ?? "false") === "true"
const { rxBps, txBps } = sampleRate(prevWave, waveKey(interfaceName), rxBytes, txBytes, nowMs)
const { rxBps, txBps, measured } = sampleRate(prevWave, waveKey(interfaceName), rxBytes, txBytes, nowMs)
if (shouldIncludeIface(interfaceName, running, disabled)) {
includedInterfaces++
totalsMeasured &&= measured
sumRxMbps += bpsToMbps(rxBps)
sumTxMbps += bpsToMbps(txBps)
}
@@ -203,8 +199,10 @@ export async function collectTrafficOnce(): Promise<TrafficRunSnapshot> {
host: srv.host,
ok: true,
interfaces: ifaces.length,
sumRxMbps: Math.round(sumRxMbps * 1000) / 1000,
sumTxMbps: Math.round(sumTxMbps * 1000) / 1000,
...(includedInterfaces > 0 && totalsMeasured ? {
sumRxMbps: Math.round(sumRxMbps * 1000) / 1000,
sumTxMbps: Math.round(sumTxMbps * 1000) / 1000,
} : {}),
})
} catch (err) {
snapshot.servers.push({
@@ -0,0 +1,66 @@
import assert from "node:assert/strict"
import { observedTrafficRate, parseTrafficCounter } from "./traffic-observation.js"
import { collectSourceSignals } from "./alert-engine/collector-source-signals.js"
import fs from "node:fs"
import vm from "node:vm"
import ts from "typescript"
import { bpsToMbps, shouldIncludeIface } from "./traffic-rate.js"
const now = Date.parse("2026-10-12T12:00:00Z"), iso = new Date(now).toISOString()
const previous = { rxBytes: 1000, txBytes: 1000, sampledAt: new Date(now - 30_000).toISOString() }
for (const value of [undefined, null, "", "123bad", "1.5", "-1", -1, NaN, Infinity, Number.MAX_SAFE_INTEGER + 1, {}, " 10 "]) {
assert.throws(() => parseTrafficCounter(value), /некорректный счётчик/)
}
for (const value of [0, "0", "1000", Number.MAX_SAFE_INTEGER]) assert.equal(parseTrafficCounter(value), Number(value))
const cases = [
observedTrafficRate(undefined, 1000, 1000, now),
observedTrafficRate(previous, 10, 10, now),
observedTrafficRate(previous, 10, 1100, now),
observedTrafficRate({ ...previous, sampledAt: iso }, 1100, 1100, now),
observedTrafficRate({ ...previous, sampledAt: "invalid" }, 1100, 1100, now),
observedTrafficRate(previous, 1000, 1000, now),
observedTrafficRate(previous, 3_751_000, 3_751_000, now),
]
assert.deepEqual(cases.map(r => r.measured), [false, false, false, false, false, true, true])
assert.equal(cases[5].rxBps, 0); assert.equal(cases[6].rxBps, 1_000_000)
for (const rate of cases) {
const row = { serverId: 1, name: "audit", host: "audit.example", ok: true,
...(rate.measured ? { sumRxMbps: rate.rxBps / 1_000_000, sumTxMbps: rate.txBps / 1_000_000 } : {}) }
const signals = collectSourceSignals({ servers: [{ id: 1, name: "audit", host: "audit.example", enabled: true }], probes: [], traffic: { enabled: true, intervalSec: 30 } },
{ resources: [], rest: [], ping: [], traffic: [{ finishedAt: iso, resultJson: { v: 1, job: "traffic", sampledAt: iso, servers: [row] } }] }, now)
assert.equal(signals.trafficByServer.length, Number(rate.measured))
}
console.log("traffic observation counters/rates/source confidence tests ok")
// Execute the actual collector body with in-memory DB/HTTP boundaries, not a copied sum algorithm.
const collector = fs.readFileSync(new URL("./traffic-collector.ts", import.meta.url), "utf8")
const ast = ts.createSourceFile("traffic-collector.ts", collector, ts.ScriptTarget.Latest, true)
const functions = ast.statements.filter(n => ts.isFunctionDeclaration(n) && n.name &&
["waveKey", "sampleRate", "toNum", "collectTrafficOnce"].includes(n.name.text)).map(n => n.getText(ast)).join("\n")
assert.equal(ast.statements.filter(n => ts.isFunctionDeclaration(n) && n.name && n.name.text === "collectTrafficOnce").length, 1)
let inserted: unknown[] = [], wave = new Map<string, typeof previous>(), counters: unknown = "1000"
let included = true
const context = vm.createContext({
Date, observedTrafficRate, parseTrafficCounter, bpsToMbps, shouldIncludeIface,
SCHEDULER_RUN_SNAPSHOT_VERSION: 1, servers: {}, trafficSamples: {}, trafficSettings: {}, eq: () => ({}),
getSettingsRow: async () => ({}), readPreviousWave: async () => wave, rememberServerIfaces: () => {},
encodeTrafficFlags: () => 1,
MikrotikClient: { fromServer: () => ({ get: async (path: string) => path === "/interface" ?
[{ name: "ether1", running: String(included), disabled: "false", "rx-byte": counters, "tx-byte": counters }] : [] }) },
db: {
select: () => ({ from: () => ({ where: async () => [{ id: 1, name: "audit", host: "audit.example", enabled: true }] }) }),
insert: () => ({ values: async (rows: unknown[]) => { inserted.push(...rows) } }),
update: () => ({ set: () => ({ where: async () => {} }) }),
},
})
vm.runInContext(ts.transpileModule("let collecting=false;\n" + functions.replace("export async function", "async function") +
"\nglobalThis.run=collectTrafficOnce;", { compilerOptions: { target: ts.ScriptTarget.ES2022 } }).outputText, context)
let snapshot = await context.run()
assert.equal(snapshot.servers[0].ok, true); assert.equal(snapshot.servers[0].sumRxMbps, undefined); assert.equal(inserted.length, 1)
wave = new Map([["ether1\0", { ...previous, sampledAt: new Date(Date.now() - 30_000).toISOString() }]])
snapshot = await context.run(); assert.equal(snapshot.servers[0].sumRxMbps, 0)
counters = "10"; snapshot = await context.run(); assert.equal(snapshot.servers[0].sumRxMbps, undefined)
counters = "bad"; inserted = []; snapshot = await context.run()
assert.equal(snapshot.servers[0].ok, false); assert.equal(inserted.length, 0); assert.match(snapshot.servers[0].error, /некорректный счётчик/)
counters = "1000"; included = false; snapshot = await context.run(); assert.equal(snapshot.servers[0].sumRxMbps, undefined)
console.log("actual traffic collector first-wave/zero/reset/invalid/excluded totals tests ok")
@@ -0,0 +1,22 @@
import { rateBpsFromDelta } from "./traffic-rate.js"
/** RouterOS byte counters must be exact nonnegative integers, never fallback zero. */
export function parseTrafficCounter(raw: unknown): number {
if (typeof raw !== "number" && (typeof raw !== "string" || !/^\d+$/.test(raw))) {
throw new Error("RouterOS вернул некорректный счётчик трафика")
}
const n = Number(raw)
if (!Number.isSafeInteger(n) || n < 0) throw new Error("RouterOS вернул некорректный счётчик трафика")
return n
}
export function observedTrafficRate(
previous: { rxBytes: number; txBytes: number; sampledAt: string } | undefined,
rxBytes: number, txBytes: number, nowMs: number,
): { rxBps: number; txBps: number; measured: boolean } {
const prevMs = previous ? Date.parse(previous.sampledAt) : NaN
const rx = previous ? rateBpsFromDelta(previous.rxBytes, rxBytes, prevMs, nowMs) : null
const tx = previous ? rateBpsFromDelta(previous.txBytes, txBytes, prevMs, nowMs) : null
// Stored rates retain the legacy numeric shape; observed totals require both deltas.
return { rxBps: rx ?? 0, txBps: tx ?? 0, measured: rx !== null && tx !== null }
}
+14 -2
View File
@@ -1,6 +1,6 @@
# UX/UI: протокол внешней приёмки 12.5
Актуальное продолжение 2026-10-12: завершён12.5.173 — Ping execution error/empty payload не считается100% loss: measurement_known/error provenance, nullable unknown API/UI, scheduled/manual/after-BT, malformed/zero/inconsistent count guards.85 unique E2E initial83+delta3, ownSQL26+Ping8+GRE8/stability, source440+518/compiled1020/build passed. Migration0010 applied; оба ux-ping173 healthy/running; real CHR Ping4/4 both/0%/53ms, config/password unchanged. Own DB/role removed. Следующий174 — audit first-wave/invalid traffic rate confidence; legacy unclassified manual samples и source unconfirmed diagnostics остаются;12.5 открыт.
Актуальное продолжение 2026-10-12: завершён локальный блок12.5.174 — воспроизведены ложные нули трафика; сумма scheduler snapshot выдаётся только при достоверных дельтах всех учитываемых интерфейсов, invalid counters отклоняются. Профиль traffic-rate (включая actual collector body) и backend production build прошли. Стенд остаётся ux-ping173; SQL/HTTP-приёмка producer и deployment нового guard — следующий175. Этап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.173 — Ping execution error/empty payload не считается100% loss: measurement_known/error provenance, nullable unknown API/UI, scheduled/manual/after-BT, malformed/zero/inconsistent count guards.85 unique E2E initial83+delta3, ownSQL26+Ping8+GRE8/stability, source440+518/compiled1020/build passed. Migration0010 applied; оба ux-ping173 healthy/running; real CHR Ping4/4 both/0%/53ms, config/password unchanged. Own DB/role removed. Следующий174 — audit first-wave/invalid traffic rate confidence; legacy unclassified manual samples и source unconfirmed diagnostics остаются;12.5 открыт.
Актуальное продолжение 2026-10-12: завершён локальный блок12.5.174 — воспроизведены ложные нули трафика; сумма scheduler snapshot выдаётся только при достоверных дельтах всех учитываемых интерфейсов, invalid counters отклоняются. Профиль traffic-rate (включая actual collector body) и backend production build прошли. Стенд остаётся ux-ping173; SQL/HTTP-приёмка producer и deployment нового guard — следующий175. Этап12.5 открыт.
Подробный объём, протоколы, схема и ограничения: [12.5.77 в журнале](ux-ui-progress.md).
@@ -1973,3 +1973,15 @@ Source440 backend+518 frontend inputs/1020 emitted parity совпали SHA256.
Протоколы: [SQL/HTTP8+cleanup](ux-ui/acceptance/ping-measurement-sql-2026-10-12.json), [native4/4](ux-ui/acceptance/ping-measurement-native-2026-10-12.json), [source](ux-ui/acceptance/ping-measurement-source-2026-10-12.json), [compiled](ux-ui/acceptance/ping-measurement-compiled-2026-10-12.json), [interop](ux-ui/acceptance/ping-measurement-interop-2026-10-12.json), [runtime](ux-ui/acceptance/ping-measurement-runtime-2026-10-12.json), [live UI](ux-ui/acceptance/ping-measurement-deployed-2026-10-12.json).
Следующий174 — audit first-wave/invalid traffic rate confidence: ok request и numeric sum не всегда доказывают измеренный rate без previous wave. Не заявлять эту гипотезу как уже выполненное исправление. Source unconfirmed/missing target diagnostics, manual history provenance, unsupported/compound time formats, legacy ambiguous samples, scale/multi-instance/physical WAN/external integrations остаются отдельными условиями12.5. Полная acceptance открыта. Локальный коммит без push.
## 12.5.174 — достоверность суммы трафика: аудит и локальное исправление (2026-10-12)
На исходном8fb46b1 гипотеза подтверждена исполнением настоящих private sampleRate/toNum, извлечённых TypeScript AST, затем существующих collectSourceSignals/evaluateRule. Семь source fixtures: first-wave/reset/equal timestamps/missing/malformed counters дают0 и ложный RX<1; genuine zero даёт правильное срабатывание, positive1Mbps не срабатывает. Протокол [baseline audit](ux-ui/acceptance/traffic-confidence-audit-2026-10-12.json) намеренно fixed=false и относится к исходной версии, а не к финальному исправлению. Это локальное source воспроизведение; отправки Telegram, реальной SQL persistence и физического сброса RouterOS не было.
Добавлен pure traffic-observation: строгие safe nonnegative integer counters, без parseFloat prefix/negative clamp/missing→0; observed rate подтверждается только двумя валидными дельтами. Traffic collector оставляет numeric stored-rate shape, но публикует sumRxMbps/sumTxMbps только при наличии учитываемых интерфейсов и measured=true у всех. First wave, reset, равное/invalid время, частично неизвестный интерфейс и отсутствие учитываемых интерфейсов не становятся измеренным нулём; genuine zero сохраняется. Некорректный interface counter завершает текущий server snapshot с ok=false до insert. WG peers остаются optional: ошибка их чтения/декодирования не превращает корректный interface snapshot в failure; частичные валидные peer rows могут остаться. Это не новая гарантия полноты peer history.
Existing snapshot types уже имеют optional суммы; source decoder174 не менялся и исключает отсутствующие суммы. Таблица snapshots уже показывает — при отсутствии значения; ReUI/theme не менялись. Historical traffic builder уже различает первый sample и measured zero при includeObservations=true. Старые загрязнённые counters/rates не переписаны: ошибку прежнего missing-counter→0 нельзя надёжно реконструировать без provenance. Consumer raw numeric-rate compatibility, freshness/failed attempts history, безопасный baseline после смены объекта, unsafe integer counters и масштаб требуют отдельной SQL/HTTP-приёмки.
npm --prefix backend run test:traffic-rate прошёл: существующие формулы/observations, новый counter/rate/source guard и actual collectTrafficOnce body через AST с in-memory DB/HTTP boundaries (first wave, zero, reset, malformed/no insert, excluded interfaces). Извлечение исполняет исходный collector, не копию алгоритма; это не реальный PostgreSQL или RouterOS. npm --prefix backend run build прошёл. [Local check](ux-ui/acceptance/traffic-confidence-local-2026-10-12.json) фиксирует хеши изменённых source и границы доказательства. Full regression/remote deployment не выполнялись, поскольку изменён только локальный producer guard.
Следующий175: real MikrotikClient loopback HTTP→owned PostgreSQL→scheduler resultJson→source decoder с first-wave/next-zero/reset/malformed/mixed-interface controls; проверить очистку своих объектов и исключение stale historical samples. После profile acceptance собрать backend и развернуть guard на тестовом стенде; frontend ux-ping173 совместим с absent optional totals. Сейчас оба сервиса удалённого стенда всё ещё ux-ping173, native Ping4/4 подтверждён в173; новое исправление там ещё не доступно. Пароли, конфигурация CHR и тестовый runtime этим блоком не менялись. Локальный коммит без push.
+3 -3
View File
@@ -1,12 +1,12 @@
# План реализации UX/UI по пятичасовым окнам
Актуальное продолжение 2026-10-12: завершён12.5.173 — Ping execution error/empty payload не считается100% loss: measurement_known/error provenance, nullable unknown API/UI, scheduled/manual/after-BT, malformed/zero/inconsistent count guards.85 unique E2E initial83+delta3, ownSQL26+Ping8+GRE8/stability, source440+518/compiled1020/build passed. Migration0010 applied; оба ux-ping173 healthy/running; real CHR Ping4/4 both/0%/53ms, config/password unchanged. Own DB/role removed. Следующий174 — audit first-wave/invalid traffic rate confidence; legacy unclassified manual samples и source unconfirmed diagnostics остаются;12.5 открыт.
Актуальное продолжение 2026-10-12: завершён локальный блок12.5.174 — воспроизведены ложные нули трафика; сумма scheduler snapshot выдаётся только при достоверных дельтах всех учитываемых интерфейсов, invalid counters отклоняются. Профиль traffic-rate (включая actual collector body) и backend production build прошли. Стенд остаётся ux-ping173; SQL/HTTP-приёмка producer и deployment нового guard — следующий175. Этап12.5 открыт.
Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-12. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат.
## Актуальная точка продолжения — 2026-10-12
Актуальное продолжение 2026-10-12: завершён12.5.173 — Ping execution error/empty payload не считается100% loss: measurement_known/error provenance, nullable unknown API/UI, scheduled/manual/after-BT, malformed/zero/inconsistent count guards.85 unique E2E initial83+delta3, ownSQL26+Ping8+GRE8/stability, source440+518/compiled1020/build passed. Migration0010 applied; оба ux-ping173 healthy/running; real CHR Ping4/4 both/0%/53ms, config/password unchanged. Own DB/role removed. Следующий174 — audit first-wave/invalid traffic rate confidence; legacy unclassified manual samples и source unconfirmed diagnostics остаются;12.5 открыт.
Актуальное продолжение 2026-10-12: завершён локальный блок12.5.174 — воспроизведены ложные нули трафика; сумма scheduler snapshot выдаётся только при достоверных дельтах всех учитываемых интерфейсов, invalid counters отклоняются. Профиль traffic-rate (включая actual collector body) и backend production build прошли. Стенд остаётся ux-ping173; SQL/HTTP-приёмка producer и deployment нового guard — следующий175. Этап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.173 — Ping execution error/empty payload не считается100% loss: measurement_known/error provenance, nullable unknown API/UI, scheduled/manual/after-BT, malformed/zero/inconsistent count guards.85 unique E2E initial83+delta3, ownSQL26+Ping8+GRE8/stability, source440+518/compiled1020/build passed. Migration0010 applied; оба ux-ping173 healthy/running; real CHR Ping4/4 both/0%/53ms, config/password unchanged. Own DB/role removed. Следующий174 — audit first-wave/invalid traffic rate confidence; legacy unclassified manual samples и source unconfirmed diagnostics остаются;12.5 открыт.
Актуальное продолжение 2026-10-12: завершён локальный блок12.5.174 — воспроизведены ложные нули трафика; сумма scheduler snapshot выдаётся только при достоверных дельтах всех учитываемых интерфейсов, invalid counters отклоняются. Профиль traffic-rate (включая actual collector body) и backend production build прошли. Стенд остаётся ux-ping173; SQL/HTTP-приёмка producer и deployment нового guard — следующий175. Этап12.5 открыт.
Подробный объём, протоколы, схема и ограничения: [12.5.77 в журнале](ux-ui-progress.md).
+13 -1
View File
@@ -1,6 +1,6 @@
# UX/UI: журнал реализации
Актуальное продолжение 2026-10-12: завершён12.5.173 — Ping execution error/empty payload не считается100% loss: measurement_known/error provenance, nullable unknown API/UI, scheduled/manual/after-BT, malformed/zero/inconsistent count guards.85 unique E2E initial83+delta3, ownSQL26+Ping8+GRE8/stability, source440+518/compiled1020/build passed. Migration0010 applied; оба ux-ping173 healthy/running; real CHR Ping4/4 both/0%/53ms, config/password unchanged. Own DB/role removed. Следующий174 — audit first-wave/invalid traffic rate confidence; legacy unclassified manual samples и source unconfirmed diagnostics остаются;12.5 открыт.
Актуальное продолжение 2026-10-12: завершён локальный блок12.5.174 — воспроизведены ложные нули трафика; сумма scheduler snapshot выдаётся только при достоверных дельтах всех учитываемых интерфейсов, invalid counters отклоняются. Профиль traffic-rate (включая actual collector body) и backend production build прошли. Стенд остаётся ux-ping173; SQL/HTTP-приёмка producer и deployment нового guard — следующий175. Этап12.5 открыт.
Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md).
@@ -38,6 +38,18 @@
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 |
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–168 и ограничения ниже. Runtime backend ux-month149/frontend ux-sessions150. |
## 12.5.174 — достоверность суммы трафика: аудит и локальное исправление (2026-10-12)
На исходном8fb46b1 гипотеза подтверждена исполнением настоящих private sampleRate/toNum, извлечённых TypeScript AST, затем существующих collectSourceSignals/evaluateRule. Семь source fixtures: first-wave/reset/equal timestamps/missing/malformed counters дают0 и ложный RX<1; genuine zero даёт правильное срабатывание, positive1Mbps не срабатывает. Протокол [baseline audit](ux-ui/acceptance/traffic-confidence-audit-2026-10-12.json) намеренно fixed=false и относится к исходной версии, а не к финальному исправлению. Это локальное source воспроизведение; отправки Telegram, реальной SQL persistence и физического сброса RouterOS не было.
Добавлен pure traffic-observation: строгие safe nonnegative integer counters, без parseFloat prefix/negative clamp/missing→0; observed rate подтверждается только двумя валидными дельтами. Traffic collector оставляет numeric stored-rate shape, но публикует sumRxMbps/sumTxMbps только при наличии учитываемых интерфейсов и measured=true у всех. First wave, reset, равное/invalid время, частично неизвестный интерфейс и отсутствие учитываемых интерфейсов не становятся измеренным нулём; genuine zero сохраняется. Некорректный interface counter завершает текущий server snapshot с ok=false до insert. WG peers остаются optional: ошибка их чтения/декодирования не превращает корректный interface snapshot в failure; частичные валидные peer rows могут остаться. Это не новая гарантия полноты peer history.
Existing snapshot types уже имеют optional суммы; source decoder174 не менялся и исключает отсутствующие суммы. Таблица snapshots уже показывает — при отсутствии значения; ReUI/theme не менялись. Historical traffic builder уже различает первый sample и measured zero при includeObservations=true. Старые загрязнённые counters/rates не переписаны: ошибку прежнего missing-counter→0 нельзя надёжно реконструировать без provenance. Consumer raw numeric-rate compatibility, freshness/failed attempts history, безопасный baseline после смены объекта, unsafe integer counters и масштаб требуют отдельной SQL/HTTP-приёмки.
npm --prefix backend run test:traffic-rate прошёл: существующие формулы/observations, новый counter/rate/source guard и actual collectTrafficOnce body через AST с in-memory DB/HTTP boundaries (first wave, zero, reset, malformed/no insert, excluded interfaces). Извлечение исполняет исходный collector, не копию алгоритма; это не реальный PostgreSQL или RouterOS. npm --prefix backend run build прошёл. [Local check](ux-ui/acceptance/traffic-confidence-local-2026-10-12.json) фиксирует хеши изменённых source и границы доказательства. Full regression/remote deployment не выполнялись, поскольку изменён только локальный producer guard.
Следующий175: real MikrotikClient loopback HTTP→owned PostgreSQL→scheduler resultJson→source decoder с first-wave/next-zero/reset/malformed/mixed-interface controls; проверить очистку своих объектов и исключение stale historical samples. После profile acceptance собрать backend и развернуть guard на тестовом стенде; frontend ux-ping173 совместим с absent optional totals. Сейчас оба сервиса удалённого стенда всё ещё ux-ping173, native Ping4/4 подтверждён в173; новое исправление там ещё не доступно. Пароли, конфигурация CHR и тестовый runtime этим блоком не менялись. Локальный коммит без push.
## 12.5.173 — ошибка Ping не подменяет измеренные потери (2026-10-12)
Завершён целый раздел producer→SQL→API→UI. parsePing теперь возвращает measured: empty/unsupported replies, zero sent и противоречивые/malformed counts не подтверждают процент потерь. Подтверждённые received0/sent4 остаются100%/down с RTTnull; успешный настоящий RTT0/loss0 сохранён. Scheduled и manual collectors различают успешное измерение и ошибку REST/недостаточный ответ; after-bandwidth Ping при неизвестном результате возвращает null metrics/error, а не выдуманный outage. Alert decoder принимает только ok=true и известное состояние; retained legacy ok=false/down/100 не срабатывает как измеренная потеря. Malformed due-probe rows без ID — barrier, не разрешение восстановить старое измерение.
+14 -2
View File
@@ -1,6 +1,6 @@
# Оставшаяся внешняя приёмка — 2026-10-12
Это актуальная матрица после 12.5.173. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
Это актуальная матрица после 12.5.174. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-ping173`, frontend `ux-ping173`; 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 выявил identity/disabled/freshness gaps; локальный server/traffic identity guard169 passed без SQL/deploy, source/per-probe/freshness170/ownSQL16/compiled1008/deployed passed; source diagnostics171/42E2E/ownSQL metadata/live2 passed; monitoring history172/ownSQL26/compiled1020/deployed passed; Ping known/error173/ownHTTP-SQL8/85uniqueE2E/native4/4/deployed passed; traffic first-wave confidence174 audit 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/freshness170/ownSQL16/compiled1008/deployed passed; source diagnostics171/42E2E/ownSQL metadata/live2 passed; monitoring history172/ownSQL26/compiled1020/deployed passed; Ping known/error173/ownHTTP-SQL8/85uniqueE2E/native4/4/deployed passed; traffic confidence174 baseline reproduced/local guard passed; owned SQL/deploy175 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). Невыполненные пункты не являются все блокерами данного лабораторного сценария: для выпуска необходимо явно определить требуемые конфигурации и принять их либо согласовать ограничение.
@@ -888,3 +888,15 @@ Source440 backend+518 frontend inputs/1020 emitted parity совпали SHA256.
Протоколы: [SQL/HTTP8+cleanup](ux-ui/acceptance/ping-measurement-sql-2026-10-12.json), [native4/4](ux-ui/acceptance/ping-measurement-native-2026-10-12.json), [source](ux-ui/acceptance/ping-measurement-source-2026-10-12.json), [compiled](ux-ui/acceptance/ping-measurement-compiled-2026-10-12.json), [interop](ux-ui/acceptance/ping-measurement-interop-2026-10-12.json), [runtime](ux-ui/acceptance/ping-measurement-runtime-2026-10-12.json), [live UI](ux-ui/acceptance/ping-measurement-deployed-2026-10-12.json).
Следующий174 — audit first-wave/invalid traffic rate confidence: ok request и numeric sum не всегда доказывают измеренный rate без previous wave. Не заявлять эту гипотезу как уже выполненное исправление. Source unconfirmed/missing target diagnostics, manual history provenance, unsupported/compound time formats, legacy ambiguous samples, scale/multi-instance/physical WAN/external integrations остаются отдельными условиями12.5. Полная acceptance открыта. Локальный коммит без push.
## 12.5.174 — достоверность суммы трафика: аудит и локальное исправление (2026-10-12)
На исходном8fb46b1 гипотеза подтверждена исполнением настоящих private sampleRate/toNum, извлечённых TypeScript AST, затем существующих collectSourceSignals/evaluateRule. Семь source fixtures: first-wave/reset/equal timestamps/missing/malformed counters дают0 и ложный RX<1; genuine zero даёт правильное срабатывание, positive1Mbps не срабатывает. Протокол [baseline audit](ux-ui/acceptance/traffic-confidence-audit-2026-10-12.json) намеренно fixed=false и относится к исходной версии, а не к финальному исправлению. Это локальное source воспроизведение; отправки Telegram, реальной SQL persistence и физического сброса RouterOS не было.
Добавлен pure traffic-observation: строгие safe nonnegative integer counters, без parseFloat prefix/negative clamp/missing→0; observed rate подтверждается только двумя валидными дельтами. Traffic collector оставляет numeric stored-rate shape, но публикует sumRxMbps/sumTxMbps только при наличии учитываемых интерфейсов и measured=true у всех. First wave, reset, равное/invalid время, частично неизвестный интерфейс и отсутствие учитываемых интерфейсов не становятся измеренным нулём; genuine zero сохраняется. Некорректный interface counter завершает текущий server snapshot с ok=false до insert. WG peers остаются optional: ошибка их чтения/декодирования не превращает корректный interface snapshot в failure; частичные валидные peer rows могут остаться. Это не новая гарантия полноты peer history.
Existing snapshot types уже имеют optional суммы; source decoder174 не менялся и исключает отсутствующие суммы. Таблица snapshots уже показывает — при отсутствии значения; ReUI/theme не менялись. Historical traffic builder уже различает первый sample и measured zero при includeObservations=true. Старые загрязнённые counters/rates не переписаны: ошибку прежнего missing-counter→0 нельзя надёжно реконструировать без provenance. Consumer raw numeric-rate compatibility, freshness/failed attempts history, безопасный baseline после смены объекта, unsafe integer counters и масштаб требуют отдельной SQL/HTTP-приёмки.
npm --prefix backend run test:traffic-rate прошёл: существующие формулы/observations, новый counter/rate/source guard и actual collectTrafficOnce body через AST с in-memory DB/HTTP boundaries (first wave, zero, reset, malformed/no insert, excluded interfaces). Извлечение исполняет исходный collector, не копию алгоритма; это не реальный PostgreSQL или RouterOS. npm --prefix backend run build прошёл. [Local check](ux-ui/acceptance/traffic-confidence-local-2026-10-12.json) фиксирует хеши изменённых source и границы доказательства. Full regression/remote deployment не выполнялись, поскольку изменён только локальный producer guard.
Следующий175: real MikrotikClient loopback HTTP→owned PostgreSQL→scheduler resultJson→source decoder с first-wave/next-zero/reset/malformed/mixed-interface controls; проверить очистку своих объектов и исключение stale historical samples. После profile acceptance собрать backend и развернуть guard на тестовом стенде; frontend ux-ping173 совместим с absent optional totals. Сейчас оба сервиса удалённого стенда всё ещё ux-ping173, native Ping4/4 подтверждён в173; новое исправление там ещё не доступно. Пароли, конфигурация CHR и тестовый runtime этим блоком не менялись. Локальный коммит без push.
+13 -1
View File
@@ -1,6 +1,6 @@
# Этап 12: итоговая приёмка UX/UI
Актуальное продолжение 2026-10-12: завершён12.5.173 — Ping execution error/empty payload не считается100% loss: measurement_known/error provenance, nullable unknown API/UI, scheduled/manual/after-BT, malformed/zero/inconsistent count guards.85 unique E2E initial83+delta3, ownSQL26+Ping8+GRE8/stability, source440+518/compiled1020/build passed. Migration0010 applied; оба ux-ping173 healthy/running; real CHR Ping4/4 both/0%/53ms, config/password unchanged. Own DB/role removed. Следующий174 — audit first-wave/invalid traffic rate confidence; legacy unclassified manual samples и source unconfirmed diagnostics остаются;12.5 открыт.
Актуальное продолжение 2026-10-12: завершён локальный блок12.5.174 — воспроизведены ложные нули трафика; сумма scheduler snapshot выдаётся только при достоверных дельтах всех учитываемых интерфейсов, invalid counters отклоняются. Профиль traffic-rate (включая actual collector body) и backend production build прошли. Стенд остаётся ux-ping173; SQL/HTTP-приёмка producer и deployment нового guard — следующий175. Этап12.5 открыт.
План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push.
@@ -3278,3 +3278,15 @@ Source440 backend+518 frontend inputs/1020 emitted parity совпали SHA256.
Протоколы: [SQL/HTTP8+cleanup](ux-ui/acceptance/ping-measurement-sql-2026-10-12.json), [native4/4](ux-ui/acceptance/ping-measurement-native-2026-10-12.json), [source](ux-ui/acceptance/ping-measurement-source-2026-10-12.json), [compiled](ux-ui/acceptance/ping-measurement-compiled-2026-10-12.json), [interop](ux-ui/acceptance/ping-measurement-interop-2026-10-12.json), [runtime](ux-ui/acceptance/ping-measurement-runtime-2026-10-12.json), [live UI](ux-ui/acceptance/ping-measurement-deployed-2026-10-12.json).
Следующий174 — audit first-wave/invalid traffic rate confidence: ok request и numeric sum не всегда доказывают измеренный rate без previous wave. Не заявлять эту гипотезу как уже выполненное исправление. Source unconfirmed/missing target diagnostics, manual history provenance, unsupported/compound time formats, legacy ambiguous samples, scale/multi-instance/physical WAN/external integrations остаются отдельными условиями12.5. Полная acceptance открыта. Локальный коммит без push.
## 12.5.174 — достоверность суммы трафика: аудит и локальное исправление (2026-10-12)
На исходном8fb46b1 гипотеза подтверждена исполнением настоящих private sampleRate/toNum, извлечённых TypeScript AST, затем существующих collectSourceSignals/evaluateRule. Семь source fixtures: first-wave/reset/equal timestamps/missing/malformed counters дают0 и ложный RX<1; genuine zero даёт правильное срабатывание, positive1Mbps не срабатывает. Протокол [baseline audit](ux-ui/acceptance/traffic-confidence-audit-2026-10-12.json) намеренно fixed=false и относится к исходной версии, а не к финальному исправлению. Это локальное source воспроизведение; отправки Telegram, реальной SQL persistence и физического сброса RouterOS не было.
Добавлен pure traffic-observation: строгие safe nonnegative integer counters, без parseFloat prefix/negative clamp/missing→0; observed rate подтверждается только двумя валидными дельтами. Traffic collector оставляет numeric stored-rate shape, но публикует sumRxMbps/sumTxMbps только при наличии учитываемых интерфейсов и measured=true у всех. First wave, reset, равное/invalid время, частично неизвестный интерфейс и отсутствие учитываемых интерфейсов не становятся измеренным нулём; genuine zero сохраняется. Некорректный interface counter завершает текущий server snapshot с ok=false до insert. WG peers остаются optional: ошибка их чтения/декодирования не превращает корректный interface snapshot в failure; частичные валидные peer rows могут остаться. Это не новая гарантия полноты peer history.
Existing snapshot types уже имеют optional суммы; source decoder174 не менялся и исключает отсутствующие суммы. Таблица snapshots уже показывает — при отсутствии значения; ReUI/theme не менялись. Historical traffic builder уже различает первый sample и measured zero при includeObservations=true. Старые загрязнённые counters/rates не переписаны: ошибку прежнего missing-counter→0 нельзя надёжно реконструировать без provenance. Consumer raw numeric-rate compatibility, freshness/failed attempts history, безопасный baseline после смены объекта, unsafe integer counters и масштаб требуют отдельной SQL/HTTP-приёмки.
npm --prefix backend run test:traffic-rate прошёл: существующие формулы/observations, новый counter/rate/source guard и actual collectTrafficOnce body через AST с in-memory DB/HTTP boundaries (first wave, zero, reset, malformed/no insert, excluded interfaces). Извлечение исполняет исходный collector, не копию алгоритма; это не реальный PostgreSQL или RouterOS. npm --prefix backend run build прошёл. [Local check](ux-ui/acceptance/traffic-confidence-local-2026-10-12.json) фиксирует хеши изменённых source и границы доказательства. Full regression/remote deployment не выполнялись, поскольку изменён только локальный producer guard.
Следующий175: real MikrotikClient loopback HTTP→owned PostgreSQL→scheduler resultJson→source decoder с first-wave/next-zero/reset/malformed/mixed-interface controls; проверить очистку своих объектов и исключение stale historical samples. После profile acceptance собрать backend и развернуть guard на тестовом стенде; frontend ux-ping173 совместим с absent optional totals. Сейчас оба сервиса удалённого стенда всё ещё ux-ping173, native Ping4/4 подтверждён в173; новое исправление там ещё не доступно. Пароли, конфигурация CHR и тестовый runtime этим блоком не менялись. Локальный коммит без push.
+13 -1
View File
@@ -1,6 +1,6 @@
# Тестовый сервер UX/UI — 2026-10-10
Сервер161.104.46.222: завершён12.5.173 — Ping execution error/empty payload не считается100% loss: measurement_known/error provenance, nullable unknown API/UI, scheduled/manual/after-BT, malformed/zero/inconsistent count guards.85 unique E2E initial83+delta3, ownSQL26+Ping8+GRE8/stability, source440+518/compiled1020/build passed. Migration0010 applied; оба ux-ping173 healthy/running; real CHR Ping4/4 both/0%/53ms, config/password unchanged. Own DB/role removed. Следующий174 — audit first-wave/invalid traffic rate confidence; legacy unclassified manual samples и source unconfirmed diagnostics остаются;12.5 открыт.
Сервер161.104.46.222: завершён локальный блок12.5.174 — воспроизведены ложные нули трафика; сумма scheduler snapshot выдаётся только при достоверных дельтах всех учитываемых интерфейсов, invalid counters отклоняются. Профиль traffic-rate (включая actual collector body) и backend production build прошли. Стенд остаётся ux-ping173; SQL/HTTP-приёмка producer и deployment нового guard — следующий175. Этап12.5 открыт.
| Параметр | Проверенное состояние |
| --- | --- |
@@ -1704,3 +1704,15 @@ Source440 backend+518 frontend inputs/1020 emitted parity совпали SHA256.
Протоколы: [SQL/HTTP8+cleanup](ux-ui/acceptance/ping-measurement-sql-2026-10-12.json), [native4/4](ux-ui/acceptance/ping-measurement-native-2026-10-12.json), [source](ux-ui/acceptance/ping-measurement-source-2026-10-12.json), [compiled](ux-ui/acceptance/ping-measurement-compiled-2026-10-12.json), [interop](ux-ui/acceptance/ping-measurement-interop-2026-10-12.json), [runtime](ux-ui/acceptance/ping-measurement-runtime-2026-10-12.json), [live UI](ux-ui/acceptance/ping-measurement-deployed-2026-10-12.json).
Следующий174 — audit first-wave/invalid traffic rate confidence: ok request и numeric sum не всегда доказывают измеренный rate без previous wave. Не заявлять эту гипотезу как уже выполненное исправление. Source unconfirmed/missing target diagnostics, manual history provenance, unsupported/compound time formats, legacy ambiguous samples, scale/multi-instance/physical WAN/external integrations остаются отдельными условиями12.5. Полная acceptance открыта. Локальный коммит без push.
## 12.5.174 — достоверность суммы трафика: аудит и локальное исправление (2026-10-12)
На исходном8fb46b1 гипотеза подтверждена исполнением настоящих private sampleRate/toNum, извлечённых TypeScript AST, затем существующих collectSourceSignals/evaluateRule. Семь source fixtures: first-wave/reset/equal timestamps/missing/malformed counters дают0 и ложный RX<1; genuine zero даёт правильное срабатывание, positive1Mbps не срабатывает. Протокол [baseline audit](ux-ui/acceptance/traffic-confidence-audit-2026-10-12.json) намеренно fixed=false и относится к исходной версии, а не к финальному исправлению. Это локальное source воспроизведение; отправки Telegram, реальной SQL persistence и физического сброса RouterOS не было.
Добавлен pure traffic-observation: строгие safe nonnegative integer counters, без parseFloat prefix/negative clamp/missing→0; observed rate подтверждается только двумя валидными дельтами. Traffic collector оставляет numeric stored-rate shape, но публикует sumRxMbps/sumTxMbps только при наличии учитываемых интерфейсов и measured=true у всех. First wave, reset, равное/invalid время, частично неизвестный интерфейс и отсутствие учитываемых интерфейсов не становятся измеренным нулём; genuine zero сохраняется. Некорректный interface counter завершает текущий server snapshot с ok=false до insert. WG peers остаются optional: ошибка их чтения/декодирования не превращает корректный interface snapshot в failure; частичные валидные peer rows могут остаться. Это не новая гарантия полноты peer history.
Existing snapshot types уже имеют optional суммы; source decoder174 не менялся и исключает отсутствующие суммы. Таблица snapshots уже показывает — при отсутствии значения; ReUI/theme не менялись. Historical traffic builder уже различает первый sample и measured zero при includeObservations=true. Старые загрязнённые counters/rates не переписаны: ошибку прежнего missing-counter→0 нельзя надёжно реконструировать без provenance. Consumer raw numeric-rate compatibility, freshness/failed attempts history, безопасный baseline после смены объекта, unsafe integer counters и масштаб требуют отдельной SQL/HTTP-приёмки.
npm --prefix backend run test:traffic-rate прошёл: существующие формулы/observations, новый counter/rate/source guard и actual collectTrafficOnce body через AST с in-memory DB/HTTP boundaries (first wave, zero, reset, malformed/no insert, excluded interfaces). Извлечение исполняет исходный collector, не копию алгоритма; это не реальный PostgreSQL или RouterOS. npm --prefix backend run build прошёл. [Local check](ux-ui/acceptance/traffic-confidence-local-2026-10-12.json) фиксирует хеши изменённых source и границы доказательства. Full regression/remote deployment не выполнялись, поскольку изменён только локальный producer guard.
Следующий175: real MikrotikClient loopback HTTP→owned PostgreSQL→scheduler resultJson→source decoder с first-wave/next-zero/reset/malformed/mixed-interface controls; проверить очистку своих объектов и исключение stale historical samples. После profile acceptance собрать backend и развернуть guard на тестовом стенде; frontend ux-ping173 совместим с absent optional totals. Сейчас оба сервиса удалённого стенда всё ещё ux-ping173, native Ping4/4 подтверждён в173; новое исправление там ещё не доступно. Пароли, конфигурация CHR и тестовый runtime этим блоком не менялись. Локальный коммит без push.
@@ -0,0 +1,79 @@
{
"date": "2026-10-12",
"kind": "read-only-source-audit",
"head": "8fb46b1",
"collectorSha256": "ff16eb9c01d66501248c41335b6e34db3eeef29dfbe776d00701649d3db0445b",
"method": "AST extracts actual private sampleRate/toNum; production decoder/evaluator imported; synthetic current scheduler rows, no DB or RouterOS calls",
"cases": [
{
"name": "first-wave",
"expectedKnown": false,
"rxMbps": 0,
"txMbps": 0,
"acceptedAsObserved": true,
"lowTrafficHit": true
},
{
"name": "counter-reset",
"expectedKnown": false,
"rxMbps": 0,
"txMbps": 0,
"acceptedAsObserved": true,
"lowTrafficHit": true
},
{
"name": "equal-timestamps",
"expectedKnown": false,
"rxMbps": 0,
"txMbps": 0,
"acceptedAsObserved": true,
"lowTrafficHit": true
},
{
"name": "missing-counters",
"expectedKnown": false,
"rxMbps": 0,
"txMbps": 0,
"acceptedAsObserved": true,
"lowTrafficHit": true
},
{
"name": "malformed-counters",
"expectedKnown": false,
"rxMbps": 0,
"txMbps": 0,
"acceptedAsObserved": true,
"lowTrafficHit": true
},
{
"name": "genuine-zero",
"expectedKnown": true,
"rxMbps": 0,
"txMbps": 0,
"acceptedAsObserved": true,
"lowTrafficHit": true
},
{
"name": "genuine-positive",
"expectedKnown": true,
"rxMbps": 1,
"txMbps": 1,
"acceptedAsObserved": true,
"lowTrafficHit": false
}
],
"partialNumericParsing": {
"input": "123bad",
"acceptedAs": 123
},
"historyControls": {
"firstWaveKnown": false,
"genuineZeroKnown": true
},
"findingsConfirmed": true,
"fixed": false,
"networkRequests": 0,
"databaseWrites": 0,
"routerWrites": 0,
"messagesSent": 0
}
@@ -0,0 +1,28 @@
{
"date": "2026-10-12",
"kind": "local-producer-confidence-fix",
"passed": true,
"checks": [
"test:traffic-rate",
"backend production build"
],
"sourceHashes": {
"backend/src/services/traffic-observation.ts": "0246c9ab5c0204e16d17d03f55fbf7c341bce9bc3975e92b8ba7648f6a44effe",
"backend/src/services/traffic-collector.ts": "8cb8f0be608d851fd36ea45d37fdeea6abb5f5d38f04a16fbd2f7c018d9d5050",
"backend/src/services/traffic-observation.test.ts": "76916e354c500f810589d2ec337b07b715b3145fa0a62b0c76336273892dd766",
"backend/package.json": "f901a04b34168411fe385a92f8ef63170465814ede61f71adffda3c001aaf7e0"
},
"actualCollectorBoundary": "in-memory SQL/HTTP, actual source body executed via AST",
"ownedPostgresVerified": false,
"realRouterOSVerified": false,
"deployed": false,
"remoteImages": {
"backend": "ux-ping173",
"frontend": "ux-ping173"
},
"remaining": [
"owned SQL/HTTP producer acceptance",
"remote deployment",
"legacy invalid-counter provenance/history consumers"
]
}