diff --git a/backend/package.json b/backend/package.json index f5cdd2a3..d9414cce 100644 --- a/backend/package.json +++ b/backend/package.json @@ -37,7 +37,7 @@ "test:ospf-optimizer": "tsx src/services/ospf-optimizer-identity.test.ts && tsx src/services/ospf-optimizer-metrics.test.ts && tsx src/services/ospf-optimizer-plan.test.ts && tsx src/routes/ospf-optimizer.test.ts", "test:containers": "tsx src/services/containers-state.test.ts && tsx src/routes/containers-state.test.ts", "test:certificates": "tsx src/services/certificate-issue-execution.test.ts && tsx src/services/certificate-issue-request.test.ts && tsx src/routes/certificates-issue.test.ts && tsx src/services/certificate-issue-dto.test.ts && tsx src/services/certificate-parse.test.ts && tsx src/services/certificates-state.test.ts && tsx src/routes/certificates-state.test.ts", - "test:system-database": "tsx src/services/traffic-flow-worker-shutdown.test.ts && tsx src/services/system-database-restore-execution.test.ts && tsx src/services/system-database-operation.test.ts && tsx src/routes/system-database.test.ts && tsx src/plugins/auth.system-database.test.ts" + "test:system-database": "tsx src/services/database-request-gate.test.ts && tsx src/services/traffic-flow-worker-shutdown.test.ts && tsx src/services/system-database-restore-execution.test.ts && tsx src/services/system-database-operation.test.ts && tsx src/routes/system-database.test.ts && tsx src/plugins/auth.system-database.test.ts" }, "dependencies": { "@aws-sdk/client-s3": "^3.888.0", diff --git a/backend/src/index.ts b/backend/src/index.ts index eac81e44..fac53695 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -6,6 +6,7 @@ import { env } from "./config.js" import { initDatabase } from "./db/bootstrap.js" import { closePool } from "./db/index.js" import authPlugin, { requireAuth } from "./plugins/auth.js" +import { installDatabaseRequestGate } from "./plugins/database-request-gate.js" import serversRoutes from "./routes/servers.js" import bgpRoutes from "./routes/bgp.js" import ospfRoutes from "./routes/ospf.js" @@ -116,6 +117,7 @@ export async function buildApp(opts?: { }) } + installDatabaseRequestGate(app) await app.register(serversRoutes, { prefix: "/api/servers" }) await app.register(bgpRoutes, { prefix: "/api" }) await app.register(ospfRoutes, { prefix: "/api" }) diff --git a/backend/src/plugins/database-request-gate.ts b/backend/src/plugins/database-request-gate.ts new file mode 100644 index 00000000..d2f1617e --- /dev/null +++ b/backend/src/plugins/database-request-gate.ts @@ -0,0 +1,31 @@ +import type { FastifyInstance, FastifyRequest } from "fastify" +import { DatabaseRequestGate } from "../services/database-request-gate.js" +const streams = new WeakMap void }>() + +export function registerDatabaseStreamAbort(request: FastifyRequest, abort: () => void): void { + const state = streams.get(request) + if (!state) return + state.abort = abort + if (state.cancelled) abort() +} + +export function installDatabaseRequestGate(app: FastifyInstance): void { + const gate = new DatabaseRequestGate() + app.addHook("onRoute", options => { + const path = options.url + if (!path.startsWith("/api/") || path === "/api/auth/config") return + const original = options.handler + const operation = path === "/api/system/database/backup" || path === "/api/system/database/restore" + const stream = path === "/api/traffic/flow/live" || /^\/api\/traffic\/servers\/:id\/live$/.test(path) + options.handler = async function (request, reply) { + const run = async () => await original.call(this, request, reply) + if (operation) return gate.exclusive(run) + if (!stream) return gate.request(run) + const state = { cancelled: false, abort: undefined as (() => void) | undefined } + streams.set(request, state) + try { + return await gate.request(run, () => { state.cancelled = true; state.abort?.() }) + } finally { streams.delete(request) } + } + }) +} diff --git a/backend/src/routes/traffic-flow.ts b/backend/src/routes/traffic-flow.ts index f2cc8da0..e3e2c63c 100644 --- a/backend/src/routes/traffic-flow.ts +++ b/backend/src/routes/traffic-flow.ts @@ -1,4 +1,5 @@ import type { FastifyPluginAsyncZod } from "@fastify/type-provider-zod" +import { registerDatabaseStreamAbort } from "../plugins/database-request-gate.js" import type { FastifyReply, FastifyRequest } from "fastify" import { env } from "../config.js" import { @@ -249,6 +250,7 @@ const trafficFlowRoutes: FastifyPluginAsyncZod = async (app) => { } const abort = new AbortController() const onClose = () => abort.abort() + registerDatabaseStreamAbort(req, onClose) req.raw.on("close", onClose) reply.hijack() diff --git a/backend/src/routes/traffic.ts b/backend/src/routes/traffic.ts index eb70a36c..a8c71995 100644 --- a/backend/src/routes/traffic.ts +++ b/backend/src/routes/traffic.ts @@ -1,4 +1,5 @@ import { env } from "../config.js" +import { registerDatabaseStreamAbort } from "../plugins/database-request-gate.js" import { desc, eq } from "drizzle-orm" import type { FastifyPluginAsyncZod } from "@fastify/type-provider-zod" import { db } from "../db/index.js" @@ -322,6 +323,7 @@ const trafficRoutes: FastifyPluginAsyncZod = async (app) => { const onlyInterface = q.iface && q.iface !== "__all__" ? q.iface : undefined const abort = new AbortController() const onClose = () => abort.abort() + registerDatabaseStreamAbort(req, onClose) req.raw.on("close", onClose) reply.hijack() diff --git a/backend/src/services/database-request-gate.test.ts b/backend/src/services/database-request-gate.test.ts new file mode 100644 index 00000000..6c9309a1 --- /dev/null +++ b/backend/src/services/database-request-gate.test.ts @@ -0,0 +1,74 @@ +import assert from "node:assert/strict" +import { test } from "node:test" +import Fastify from "fastify" +import { DatabaseRequestGate } from "./database-request-gate.js" +import { installDatabaseRequestGate, registerDatabaseStreamAbort } from "../plugins/database-request-gate.js" + +function deferred() { + let resolve!: () => void + const promise = new Promise(r => { resolve = r }) + return { promise, resolve } +} + +test("exclusive operation waits for handler settlement and blocks new requests", async () => { + const gate = new DatabaseRequestGate(), pending = deferred(), entered = deferred() + const request = gate.request(async () => { entered.resolve(); await pending.promise }) + await entered.promise + let commands = 0 + const operation = gate.exclusive(async () => { commands++ }) + await assert.rejects(gate.request(async () => {}), /повторите запрос позже/) + await assert.rejects(gate.exclusive(async () => {}), /уже выполняется/) + assert.equal(commands, 0) + pending.resolve(); await request; await operation + assert.equal(commands, 1) + await gate.request(async () => {}) +}) + +test("drain timeout cancels command without declaring an active handler finished", async () => { + const gate = new DatabaseRequestGate(), pending = deferred() + const request = gate.request(() => pending.promise) + let commands = 0 + await assert.rejects(gate.exclusive(async () => { commands++ }, 10), /операция с базой отменена/) + assert.equal(commands, 0) + const retry = gate.exclusive(async () => { commands++ }) + await Promise.resolve(); assert.equal(commands, 0) + pending.resolve(); await request; await retry + assert.equal(commands, 1) +}) + +test("stream cancellation waits for its asynchronous cleanup", async () => { + const gate = new DatabaseRequestGate(), aborted = deferred(), cleanup = deferred() + const stream = gate.request(async () => { await aborted.promise; await cleanup.promise }, aborted.resolve) + let commands = 0 + const operation = gate.exclusive(async () => { commands++ }) + await aborted.promise; assert.equal(commands, 0) + cleanup.resolve(); await stream; await operation; assert.equal(commands, 1) +}) + +test("Fastify gate covers GET, keeps health available and drains late stream registration", async () => { + const app = Fastify(); installDatabaseRequestGate(app) + const entered = deferred(), read = deferred(), cleanup = deferred(), command = deferred(), leave = deferred() + app.get('/health', async () => ({ ok: true })) + await app.register(async child => { + child.get('/traffic/flow/live', async request => { + entered.resolve(); await read.promise + const aborted = deferred(); registerDatabaseStreamAbort(request, aborted.resolve) + await aborted.promise; await cleanup.promise + return { closed: true } + }) + child.get('/servers', async () => ({ servers: [] })) + child.post('/system/database/restore', async () => { command.resolve(); await leave.promise; return { ok: true } }) + }, { prefix: '/api' }) + try { + const stream = app.inject('/api/traffic/flow/live'); await entered.promise + const operation = app.inject({ method: 'POST', url: '/api/system/database/restore' }) + // inject schedules asynchronously: wait for gate to reject a subsequent GET. + await new Promise(r => setTimeout(r, 10)) + assert.equal((await app.inject('/api/servers')).statusCode, 409) + assert.equal((await app.inject('/health')).statusCode, 200) + read.resolve(); cleanup.resolve(); await stream; await command.promise + assert.equal((await app.inject({ method: 'POST', url: '/api/system/database/restore' })).statusCode, 409) + leave.resolve(); assert.equal((await operation).statusCode, 200) + assert.equal((await app.inject('/api/servers')).statusCode, 200) + } finally { read.resolve(); cleanup.resolve(); leave.resolve(); await app.close() } +}) diff --git a/backend/src/services/database-request-gate.ts b/backend/src/services/database-request-gate.ts new file mode 100644 index 00000000..4ec77dc8 --- /dev/null +++ b/backend/src/services/database-request-gate.ts @@ -0,0 +1,39 @@ +function conflict(message: string): Error & { statusCode: number } { + return Object.assign(new Error(message), { statusCode: 409 }) +} + +/** Handler lifetime, not socket lifetime: disconnect does not finish an active write. */ +export class DatabaseRequestGate { + private paused = false + private active = new Map void) | undefined>() + private idle: (() => void) | undefined + + async request(run: () => Promise, cancelStream?: () => void): Promise { + if (this.paused) throw conflict("Выполняется операция с базой приложения; повторите запрос позже") + const lease = Symbol() + this.active.set(lease, cancelStream) + try { return await run() } + finally { + this.active.delete(lease) + if (!this.active.size) this.idle?.() + } + } + + async exclusive(run: () => Promise, timeoutMs = 30_000): Promise { + if (this.paused) throw conflict("Операция с базой приложения уже выполняется") + this.paused = true + let timer: ReturnType | undefined + try { + for (const cancel of this.active.values()) cancel?.() + if (this.active.size) await new Promise((resolve, reject) => { + this.idle = resolve + timer = setTimeout(() => reject(conflict("Не удалось дождаться завершения API-запросов; операция с базой отменена")), timeoutMs) + }) + return await run() + } finally { + if (timer) clearTimeout(timer) + this.idle = undefined + this.paused = false + } + } +} diff --git a/docs/ux-ui-external-acceptance.md b/docs/ux-ui-external-acceptance.md index ef4d3549..a391e948 100644 --- a/docs/ux-ui-external-acceptance.md +++ b/docs/ux-ui-external-acceptance.md @@ -25,7 +25,7 @@ Приложение использует pg_dump -Fc и pg_restore --single-transaction --clean --if-exists. Транзакционный режим описан в [документации PostgreSQL](https://www.postgresql.org/docs/current/app-pgrestore.html). Проверка пяти байтов PGDMP проверяет заголовок, но не полноту и не доверие к архиву. Для rollback нужен сценарий с ошибкой исполнения restore, а не только отказ HTTP до его начала. -В локальном блоке12.5.8 добавлено ожидание текущих scheduler jobs с закрытием admission, но этот код ещё не развёрнут на стенде673e5ba. Блок12.5.9 добавляет отдельный graceful shutdown worker для database operation, serial flush, pool.end и обязательные acknowledgement/exit0. Локальные protocol tests и настоящий isolated collector/UDP/blocked SQL drain12.5.10 прошли: [evidence](ux-ui/acceptance/netflow-worker-2026-10-07.json). Основной код на стенде ещё не обновлён; app-wide quiescence этим не подтверждена. Ручные API writers, вспомогательные writers и экземпляры на других хостах также требуют отдельного контроля. PG-6 остаётся открытым до наблюдаемого подтверждения; восстановление стенда выполняется после его получения. Реальные тесты backend, способные очищать таблицы, запускаются только против выделенной тестовой БД. +В локальном блоке12.5.8 добавлено ожидание текущих scheduler jobs с закрытием admission, но этот код ещё не развёрнут на стенде673e5ba. Блок12.5.9 добавляет отдельный graceful shutdown worker для database operation, serial flush, pool.end и обязательные acknowledgement/exit0. Локальные protocol tests и настоящий isolated collector/UDP/blocked SQL drain12.5.10 прошли: [evidence](ux-ui/acceptance/netflow-worker-2026-10-07.json). Основной код на стенде ещё не обновлён; app-wide quiescence этим не подтверждена. Блок12.5.11 локально добавляет admission/drain всех API handlers иtraffic live streams; новые tests/build passed, на стенде patch не развёрнут. Detached backup jobs/audit иRIPE background persistence ещё неохвачены и требуют12.5.12. Другие экземпляры/хосты требуют отдельного контроля. PG-6 остаётся открытым до наблюдаемого подтверждения; восстановление стенда выполняется после его получения. Реальные тесты backend, способные очищать таблицы, запускаются только против выделенной тестовой БД. После bootstrap достаточно следующих запросов чтения для начальной проверки, без вывода секретов: diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index a0d5d3df..499343ff 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -1,6 +1,6 @@ # План реализации UX/UI по пятичасовым окнам -Актуальное продолжение 2026-10-07:12.5.10 завершён в границах настоящего isolated NetFlow collector: UDP→SQL, ожидание blocked write, освобождение socket до drain, ack/exit0, delta1500bytes и0worker connections подтверждены. Основной runtime остаётся673e5ba. Следующий12.5.11 — admission/drain ручных API и аудит вспомогательных writers перед restore. PG-6 целиком ещё открыт. +Актуальное продолжение 2026-10-07:12.5.11 завершён локально для HTTP handlers — backup/restore закрывают API admission, ждут завершения обработчиков и останавливают traffic live-подписки с ожиданиемcleanup. Четыре новых теста,15lifecycle/database tests,HTTP/JWT scripts иbuild passed. Основной runtime673e5ba не обновлён. Следующий12.5.12 — detached backup jobs/audit иRIPE persistence; PG-6/restore остаются открытыми. Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-07. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат. diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index 84ff3bf0..6297f80a 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -1,6 +1,6 @@ # UX/UI: журнал реализации -Актуальное продолжение 2026-10-07:12.5.10 завершён в границах настоящего isolated NetFlow collector: UDP→SQL, ожидание blocked write, освобождение socket до drain, ack/exit0, delta1500bytes и0worker connections подтверждены. Основной runtime остаётся673e5ba. Следующий12.5.11 — admission/drain ручных API и аудит вспомогательных writers перед restore. PG-6 целиком ещё открыт. +Актуальное продолжение 2026-10-07:12.5.11 завершён локально для HTTP handlers — backup/restore закрывают API admission, ждут завершения обработчиков и останавливают traffic live-подписки с ожиданиемcleanup. Четыре новых теста,15lifecycle/database tests,HTTP/JWT scripts иbuild passed. Основной runtime673e5ba не обновлён. Следующий12.5.12 — detached backup jobs/audit иRIPE persistence; PG-6/restore остаются открытыми. Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md). @@ -34,6 +34,18 @@ | 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 | | 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в описанных границах:12.3.83,main1186/1186,context221/221. Этап остаётся в работе: стенд12.5.1–3 развёрнут,bootstrap/smoke проверены;остальные сценарии12.5 не выполнены. | +## 12.5.11 — ожидание ручных API-запросов и закрытие live-подписок (2026-10-07) + +Добавлен DatabaseRequestGate и root onRoute hook перед регистрацией API. Каждый API handler получает lease до выполнения и освобождает его вfinally после фактического завершения, а не onResponse/socket close. ВключеныGET: некоторые чтения имеют lazy/cache side effects. /health и /api/auth/config остаются доступными. Backup/restore захватывают exclusive gate на весь handler, включая audit event; новый API-запрос получает409 с понятной причиной, конкурирующая database operation409. Ожидание30сек; timeout отменяет команду, не объявляет незавершённые запросы завершёнными; при повторной попытке они остаются в active registry. + +Traffic flow live и server traffic live регистрируют abort callback. Exclusive gate останавливает их циклы и ждёт завершения asynchronous cleanup; не подменяет закрытие socket завершением обработчика. Отмена запоминается и приходит сразу при поздней регистрации callback, если ещё выполнялось первоначальное чтение сервера. Прочие длительные запросы не прерываются принудительно: при превышении30сек database operation отменяется. Gate локален одному Fastify instance; другие процессы/экземпляры не охвачены. + +Аудит выявил detached writers: routes/backups.ts запускает event promise иqueueMicrotask/processBackupJob послеHTTP202; traffic-flow-ripe.ts запускает background resolver/loadPg и fire-and-forget persist/persistAsn. HTTP lease эти задачи не покрывает. Их нельзя считать завершёнными только поHTTP-ответу; PG-6 остаётся открытым, API restore не выполнялся. Код на сервере не пересобирался/не развёртывался, основной source673e5ba сохраняется. + +Проверки:4новых gate tests passed — ожидание активного handler/отказ новых иконкурирующегоexclusive, timeout с нулёмdatabase commands иправильнымповторнымdrain, stream cancellation сожиданиемcleanup, Fastify child routes/GET409/health200/поздняярегистрацияabort/возобновлениеAPI. Полный test:system-database15pure/protocol tests иHTTP/JWT scripts passed; contracts/backend build passed; diff-check passed. Новые проверки включены вpackage script. Изменены index.ts, traffic.ts, traffic-flow.ts, добавлены gate service/plugin/test. RouterOS иданныестенда не менялись. + +Следующий12.5.12 — зарегистрировать/дождаться detached backup jobs иaudit, закрыть admission идождатьсяRIPE resolver/persistence; послеэтого сборка/развёртывание ивнешняяPG-6/PG-3/4 проверка. Локальныйкоммит безpush. + ## 12.5.10 — настоящий NetFlow collector: UDP и ожидание SQL перед выходом (2026-10-07) На161.104.46.222 запущен временный docker compose run --rm --no-deps backend с актуальным compiled dist изsourcef8da3e0; основной backend/frontend не пересоздавались. DATABASE_URL в памяти тестового процесса/унаследованного worker ограничен отдельной mmapp_acceptance_20261007. Приложение не подключено к этой БД. [Очищенный отчёт](ux-ui/acceptance/netflow-worker-2026-10-07.json), SHA256 архива и worker JS совпали с локальными файлами. Секреты/дампы не передавались в репозиторий. diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index d3477f18..adec1d14 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -1,6 +1,6 @@ # Этап 12: итоговая приёмка UX/UI -Актуальное продолжение 2026-10-07:12.5.10 завершён в границах настоящего isolated NetFlow collector: UDP→SQL, ожидание blocked write, освобождение socket до drain, ack/exit0, delta1500bytes и0worker connections подтверждены. Основной runtime остаётся673e5ba. Следующий12.5.11 — admission/drain ручных API и аудит вспомогательных writers перед restore. PG-6 целиком ещё открыт. +Актуальное продолжение 2026-10-07:12.5.11 завершён локально для HTTP handlers — backup/restore закрывают API admission, ждут завершения обработчиков и останавливают traffic live-подписки с ожиданиемcleanup. Четыре новых теста,15lifecycle/database tests,HTTP/JWT scripts иbuild passed. Основной runtime673e5ba не обновлён. Следующий12.5.12 — detached backup jobs/audit иRIPE persistence; PG-6/restore остаются открытыми. План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push. @@ -1042,3 +1042,15 @@ PG-6 ещё открыт: тест с реальным collector/UDP и длит Проверочная БД теперь содержит синтетические flow rows и изменённые collector counters; прежнее сравнение12.5.7 описывает состояние на момент backup, не нынешнюю идентичность БД. Временный контейнер удалён штатным--rm, worker остановлен. Основной /health ok/version1.0.0-test.673e5ba/alivefalse/boundfalse, оба serverID/IP иuptime retention14 сохранены. КонфигурацияCHR/traffic-flow на маршрутизаторах не менялась. Принята частьPG-6 для isolated collector/drain, не весь app-wide сценарий. Ручные API writers, вспомогательные writers, параллельные экземпляры и API restore не проверены; новый код ещё не развёрнут в основном приложении. Следующий12.5.11 — admission/drain ручных запросов и аудит остальных писателей, затем deployment и полнаяPG-6/PG-3/4 проверка. Локальный коммит безpush. + +## 12.5.11 — ожидание ручных API-запросов и закрытие live-подписок (2026-10-07) + +Добавлен DatabaseRequestGate и root onRoute hook перед регистрацией API. Каждый API handler получает lease до выполнения и освобождает его вfinally после фактического завершения, а не onResponse/socket close. ВключеныGET: некоторые чтения имеют lazy/cache side effects. /health и /api/auth/config остаются доступными. Backup/restore захватывают exclusive gate на весь handler, включая audit event; новый API-запрос получает409 с понятной причиной, конкурирующая database operation409. Ожидание30сек; timeout отменяет команду, не объявляет незавершённые запросы завершёнными; при повторной попытке они остаются в active registry. + +Traffic flow live и server traffic live регистрируют abort callback. Exclusive gate останавливает их циклы и ждёт завершения asynchronous cleanup; не подменяет закрытие socket завершением обработчика. Отмена запоминается и приходит сразу при поздней регистрации callback, если ещё выполнялось первоначальное чтение сервера. Прочие длительные запросы не прерываются принудительно: при превышении30сек database operation отменяется. Gate локален одному Fastify instance; другие процессы/экземпляры не охвачены. + +Аудит выявил detached writers: routes/backups.ts запускает event promise иqueueMicrotask/processBackupJob послеHTTP202; traffic-flow-ripe.ts запускает background resolver/loadPg и fire-and-forget persist/persistAsn. HTTP lease эти задачи не покрывает. Их нельзя считать завершёнными только поHTTP-ответу; PG-6 остаётся открытым, API restore не выполнялся. Код на сервере не пересобирался/не развёртывался, основной source673e5ba сохраняется. + +Проверки:4новых gate tests passed — ожидание активного handler/отказ новых иконкурирующегоexclusive, timeout с нулёмdatabase commands иправильнымповторнымdrain, stream cancellation сожиданиемcleanup, Fastify child routes/GET409/health200/поздняярегистрацияabort/возобновлениеAPI. Полный test:system-database15pure/protocol tests иHTTP/JWT scripts passed; contracts/backend build passed; diff-check passed. Новые проверки включены вpackage script. Изменены index.ts, traffic.ts, traffic-flow.ts, добавлены gate service/plugin/test. RouterOS иданныестенда не менялись. + +Следующий12.5.12 — зарегистрировать/дождаться detached backup jobs иaudit, закрыть admission идождатьсяRIPE resolver/persistence; послеэтого сборка/развёртывание ивнешняяPG-6/PG-3/4 проверка. Локальныйкоммит безpush. diff --git a/docs/ux-ui-test-server.md b/docs/ux-ui-test-server.md index ec124b2b..8de9871e 100644 --- a/docs/ux-ui-test-server.md +++ b/docs/ux-ui-test-server.md @@ -85,3 +85,7 @@ ssh -N -L 3000:127.0.0.1:3000 -L 8000:127.0.0.1:8000 root@161.104.46.222 ## Настоящий isolated collector — 2026-10-07 [12.5.10 evidence](ux-ui/acceptance/netflow-worker-2026-10-07.json): актуальныйworker изf8da3e0 в отдельном временном контейнере, выделеннаяmmapp_acceptance_20261007, UDP4749 только внутриконтейнера. Shutdown дождался blocked SQL/освободилsocket, ack+exit0, delta1500bytes,0worker connections. Контейнер удалён--rm. ПроверочнаяБД теперь содержит синтетические flow rows/counters; архив12.5.7 остаётся исходным snapshot. Основной runtime673e5ba/healthok/serverID1–2/retention14 сохранён. App-wide PG-6/ручные API writers/restore ещё открыты; следующий12.5.11 — аудит и admission остальных писателей. + +## Локальный HTTP drain patch — 2026-10-07 + +12.5.11 добавляет instance-local gate для всех API handlers/GET, exclusive backup/restore иabort/drain traffic live streams.4gate tests,15lifecycle/database tests,HTTP/JWT scripts иbuild passed. Основнойruntime673e5ba остаётсяактивным. Detached backup jobs/audit иRIPE persistence ещёнеохвачены; следующий12.5.12 закрываетэтотпробел. PG-6/restore не приняты.