fix(api): завершать запрос при обрыве ответа RouterOS

This commit is contained in:
Denozordec committed 2026-10-10 02:06:56 +07:00
1 parent 5944e5e751
commit 7f0c2aad0f
13 files changed
+1005 -31

No files matched your search

+1 -1
View File
@@ -25,7 +25,7 @@
"test:pg": "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",
"test:recursive-routes": "tsx src/routes/recursive-routes.test.ts && tsx src/services/recursive-route-catalog.test.ts && tsx src/services/recursive-apply.test.ts",
"test:config-sync": "tsx src/services/config-apply-plan.test.ts && tsx src/services/entity-snapshots.test.ts",
"test:ros-file": "tsx src/services/ros-file-contents.test.ts",
"test:ros-file": "tsx src/services/ros-file-contents.test.ts && tsx src/services/mikrotik-response.test.ts",
"test:scheduler": "tsx src/services/scheduler-running.test.ts && tsx src/services/scheduler-recorded-run.test.ts && tsx src/routes/scheduler.test.ts",
"test:backups": "tsx src/services/backup-scheduler-run.test.ts && tsx src/services/backup-restore.test.ts && tsx src/services/backup-deletion.test.ts && tsx src/services/backup-dto.test.ts && tsx src/services/backup-storage-update.test.ts && tsx src/services/backup-job-execution.test.ts && tsx src/routes/backups-operations.test.ts && tsx src/services/s3-backup-client.test.ts",
"test:ipsec": "tsx src/services/ipsec-config.test.ts && tsx src/services/ipsec-state.test.ts && tsx src/routes/ipsec-history.test.ts && tsx src/services/ipsec-restore.test.ts && tsx src/services/ipsec-operation.test.ts && tsx src/routes/ipsec-mutation.test.ts && tsx src/routes/ipsec-nat.test.ts && tsx src/routes/ipsec-group.test.ts",
@@ -0,0 +1,61 @@
import { test } from "node:test"
import assert from "node:assert/strict"
import { createServer } from "node:http"
import { MikrotikClient } from "./mikrotik.js"
const methods = ["get", "post", "put", "patch", "delete", "file"] as const
for (const method of methods) {
test(`RouterOS ${method}: interrupted response rejects promptly without a successful receipt`, async () => {
let requests = 0
const server = createServer((req, res) => {
req.resume()
req.on("end", () => {
requests++
res.writeHead(200, { "content-type": "application/json", "content-length": "1000" })
res.write('[{"status":')
setTimeout(() => res.destroy(), 20)
})
})
await new Promise<void>(resolve => server.listen(0, "127.0.0.1", resolve))
const address = server.address()
assert.ok(address && typeof address !== "string")
const client = new MikrotikClient({ host: "127.0.0.1", port: address.port, useSsl: false, verifySsl: false, username: "fixture", password: "fixture" })
let deadline: ReturnType<typeof setTimeout> | undefined
try {
const operation = method === "file" ? client.downloadFile({ id: "*A", name: "", size: 8 }, 500)
: method === "get" || method === "delete" ? client[method]("/fixture", 500)
: client[method]("/fixture", {}, 500)
await assert.rejects(Promise.race([operation, new Promise<never>((_, reject) => {
deadline = setTimeout(() => reject(Error("Request did not settle after interrupted response")), 1200)
})]), /ECONNRESET/)
assert.equal(requests, method === "file" ? 2 : 1, "Writes must not retry; file reads keep two existing compatibility strategies")
} finally {
clearTimeout(deadline)
await new Promise<void>((resolve, reject) => server.close(error => error ? reject(error) : resolve()))
}
})
}
test("RouterOS complete response: success, explicit HTTP rejection and body survive", async () => {
const server = createServer((req, res) => {
req.resume()
req.on("end", () => {
res.writeHead(req.url?.endsWith("/denied") ? 401 : 200, { "content-type": "application/json" })
res.end(req.url?.endsWith("/denied") ? '{"message":"Unauthorized"}' : '{"ack":"confirmed"}')
})
})
await new Promise<void>(resolve => server.listen(0, "127.0.0.1", resolve))
const address = server.address()
assert.ok(address && typeof address !== "string")
const client = new MikrotikClient({ host: "127.0.0.1", port: address.port, useSsl: false, verifySsl: false, username: "fixture", password: "fixture" })
try {
for (const method of ["get", "post", "put", "patch"] as const) {
assert.deepEqual(await (method === "get" ? client.get("/fixture", 500) : client[method]("/fixture", {}, 500)), { ack: "confirmed" })
await assert.rejects(method === "get" ? client.get("/denied", 500) : client[method]("/denied", {}, 500), /401.*Unauthorized/)
}
await client.delete("/fixture", 500)
await assert.rejects(client.delete("/denied", 500), /401.*Unauthorized/)
} finally {
await new Promise<void>((resolve, reject) => server.close(error => error ? reject(error) : resolve()))
}
})
+12
View File
@@ -15,6 +15,12 @@ import type {
const MAX_ROS_BODY_BYTES = 8 * 1024 * 1024
function watchRosResponseFailure(res: http.IncomingMessage, reject: (error: Error) => void): void {
res.once("aborted", () => reject(new Error("RouterOS response interrupted (ECONNRESET)")))
// IncomingMessage errors are separate from ClientRequest errors after headers arrive.
res.once("error", reject)
}
function appendRosBody(body: string, chunk: string, req?: http.ClientRequest): string {
if (body.length + chunk.length > MAX_ROS_BODY_BYTES) {
req?.destroy(new Error("RouterOS: ответ больше 8 МиБ"))
@@ -62,6 +68,7 @@ function rosRequest(
}, timeoutMs)
const req = lib.request(options, (res) => {
watchRosResponseFailure(res, error => { clearTimeout(timer); reject(error) })
let body = ""
res.setEncoding("utf8")
res.on("data", (chunk: string) => { body = appendRosBody(body, chunk, req) })
@@ -145,6 +152,7 @@ function rosPost(
externalSignal?.addEventListener("abort", onExternalAbort)
req = lib.request(options, (res) => {
watchRosResponseFailure(res, error => settle(() => reject(error)))
let buf = ""
res.setEncoding("utf8")
res.on("data", (chunk: string) => { buf = appendRosBody(buf, chunk, req) })
@@ -199,6 +207,7 @@ function rosPut(
}, timeoutMs)
const req = lib.request(options, (res) => {
watchRosResponseFailure(res, error => { clearTimeout(timer); reject(error) })
let buf = ""
res.setEncoding("utf8")
res.on("data", (chunk: string) => { buf = appendRosBody(buf, chunk, req) })
@@ -249,6 +258,7 @@ function rosDelete(
}, timeoutMs)
const req = lib.request(options, (res) => {
watchRosResponseFailure(res, error => { clearTimeout(timer); reject(error) })
let body = ""
res.setEncoding("utf8")
res.on("data", (chunk: string) => { body = appendRosBody(body, chunk, req) })
@@ -296,6 +306,7 @@ function rosRawRequest(
const lib = params.useSsl ? https : http
const req = lib.request(options, (res) => {
watchRosResponseFailure(res, error => { clearTimeout(timer); reject(error) })
const chunks: Buffer[] = []
res.on("data", (chunk: Buffer) => { chunks.push(chunk) })
res.on("end", () => {
@@ -351,6 +362,7 @@ function rosPatch(
}, timeoutMs)
const req = lib.request(options, (res) => {
watchRosResponseFailure(res, error => { clearTimeout(timer); reject(error) })
let buf = ""
res.setEncoding("utf8")
res.on("data", (chunk: string) => { buf = appendRosBody(buf, chunk, req) })
+19 -5
View File
@@ -1,6 +1,6 @@
# UX/UI: протокол внешней приёмки 12.5
Актуальное продолжение 2026-10-10: завершены12.5.93–98. На стенде backend ux-speed98/frontend ux-ospf91; неизвестный native результат при transport loss теперь явно объяснён одинаково в API/history/UI, live2/2. Собственные fixtures очищены, credentials не менялись. Следующий12.5.99 — общая backend/main/context регрессия; backend/main уже запущены и пока не объявлены прошедшими. Актуальная внешняя матрица docs/ux-ui-remaining-acceptance.md; этап12.5 открыт.
Актуальное продолжение 2026-10-10: завершены12.5.93–98 и100;99 general main UI regression в работе. Backend ux-response100/frontend ux-ospf91: partial RouterOS response теперь prompt ECONNRESET/unknown receipt,7/7 transport и общая backend регрессия24 groups/171 node cases passed, live2/2/cleanup. 30-route live обзор нашёл нарушения контраста/keyboard;101 исправить после main99, затем context. Актуальная матрица docs/ux-ui-remaining-acceptance.md; этап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).
@@ -138,10 +138,6 @@ BGP-prefix evaluator, отдельное live GRE расписание и recove
12.5.21: отдельная строка unknown/runId, сохранение snapshot и сверка по идентификатору при чтении истории. 13 браузерных тестов с HTTP-моками/typecheck/ESLint прошли; это не приёмка real browser/PG. Основной backend4c12c55/frontend673e5ba прежний. Следующий 12.5.22 — сборка и проверка обновлённых образов. Полный 12.5 открыт.
## Локальный unknown UX — 2026-10-08
12.5.21: отдельная строка unknown/runId, сохранение snapshot и сверка по идентификатору при чтении истории. 13 браузерных тестов с HTTP-моками/typecheck/ESLint прошли; это не приёмка real browser/PG. Основной backend4c12c55/frontend673e5ba прежний. Следующий 12.5.22 — сборка и проверка обновлённых образов. Полный 12.5 открыт.
## Real browser unknown и deployment — 2026-10-08
12.5.22: [real browser speed unknown](ux-ui/acceptance/browser-speed-unknown-2026-10-08.json) подтвердил snapshot42, видимый unknown/runId, reconciliation к SQL history done, снятие предупреждения, native1/POST1/JS0/320fits. HTTP/RouterOS-моков нет; COMMIT acknowledgement fault введён на node-pg boundary, физический network disconnect не проверен. [Оба образа8782054 развёрнуты](ux-ui/acceptance/deployed-8782054-2026-10-08.json), [пять экранов](ux-ui/acceptance/deploy-browser-8782054-2026-10-08.json) прошли. Основная БД не восстанавливалась, контроль сохранён. Следующий12.5.23 — ROS-8/9: preflight/late-context changes на реальном PG/native. Полный12.5 открыт.
@@ -935,3 +931,21 @@ API502, history error/TXRX/Pingnull, probe error с прежними42/17/15/0;
[Native/API/PG](ux-ui/acceptance/speed-message-2026-10-10.json) и [browser](ux-ui/acceptance/browser-speed-message-2026-10-10.json): повторён только изменённый receipt path, одна capped10Mbit/s TCP3s команда, реальное закрытие socket после передачи, native independently done, API/history содержат новое сообщение; прежние синтетические42/17/15/0 сохранены, native1/noauto retry. Live1280/3202/2, сообщение видно в probe card, reload/document fits/pageerror0/UI command POST0. Свои disabled probe/history удалены, исходные IDs совпали, только3 compose containers/backend/PG healthy; RouterOS/credentials не менялись.
При согласовании указателей94–97 обнаружено, что массовая замена также затронула исторический абзац77 в плане/протоколе. Восстановлен точный абзац из40f730b, история/дата2026-10-09 сохранены; следующие обновления ограничены текущими указателями. Следующий99 — одна общая backend/main/context регрессия на текущем source, с восстановлением исходных доказательств, затем итоговая сводка. General backend/main runs уже запущены, их результат этим блоком не заявлен. Квота23% использовано; этап12.5 открыт. Один локальный commit без push.
## 12.5.99 — общая регрессия и live обзор (2026-10-10)
**В работе.** Backend98 npm test24 groups/164 node:test cases/exit0 прошёл с недоступным fixture DB URL; это не real-PG приёмка. Main UI regression запущена на3100 с BACKEND_INTERNAL_URL=http://127.0.0.1:1, API responses контролируются test fixtures. JSON reporter выдаёт итог после завершения; main/context пока не объявлены прошедшими. UI production source не меняется во время main run.493 исходных evidence files защищены snapshot, включая чужие изменённые GRE/VXLAN screenshots; восстановление — после main/context.
[Live обзор](ux-ui/acceptance/live-shell-a11y-2026-10-10.json):30 routes×1280/320=60, HTTP200/route errors0/pageerror0/document overflow0. Два автоматических read-only OSPF preview POST, apply/mutations0. Default dark theme/scoped axe выявил10 нарушений: alerts empty text, certificate badge/pre keyboard, containers hint, uptime hint; filters320 contrast не воспроизвёлся в устойчивом900ms повторе. Дополнительная светлая тема выявила sidebar subtitles и HR badge. Полная WCAG/все интеграции этим обзором не приняты; исправление101 после завершения main run.
Согласованность журналов: у всех numbered records есть даты, найден и удалён один точный дубликат12.5.21 изprogress/stage12/external; первая запись и дата2026-10-08 сохранены. Другие исторические данные не удалялись. В процессе внешнего HTTP контроля выявлен общий partial-response hang; исправление и отдельная фиксация100 ниже. Следующий шаг99 — дождаться main result, затем context; не перезапускать уже успешные unchanged suites.
## 12.5.100 — RouterOS: обрыв тела HTTP ответа больше не зависает (2026-10-10)
У ClientRequest и IncomingMessage разные error events. После получения headers частичный ответ не вызывал request error/end; запрос оставался pending даже после500ms timeout. Изолированная проверка воспроизвела6 failures по deadline1200ms для GET/POST/PUT/PATCH/DELETE/raw file reading; полныйsuccess/401 проходил. Добавлен общий watchRosResponseFailure на aborted/error во всех6 paths, очистка timer и существующий settle/AbortSignal cleanup дляPOST. Partial response отклоняется как ECONNRESET, без выдуманного ACK и повторных writes. Две прежние read-only file compatibility стратегии сохранены.
[Протокол](ux-ui/acceptance/routeros-response-transport-2026-10-10.json): после изменения7/7 включая complete success/401, probes.cancel/actual HTTP AbortSignal и byte file parser passed. Тест включён в test:ros-file. Локальный production build, новый test ESLint и Docker build прошли. Исходные2 prefer-const errors mikrotik.ts сохраняются, новые ошибки не добавлены; whole-file lint clean не заявлен. Общая backend регрессия повторена именно из-за изменения общей основы:24 npm groups/171 node:test cases/exit0. Console assertion scripts дополнительно входят в npm suite,171 не заявляется числом всех их assertions.
**Live.** [Native/API/PG](ux-ui/acceptance/speed-response-body-2026-10-10.json), [browser](ux-ui/acceptance/browser-speed-response-body-2026-10-10.json). Relay сначала получил actual RouterOS done testing, затем передал headers/первые16 bytes настоящего JSON и оборвал body. Теперь API502/понятный неизвестный результат вместо pending, история/прежний synthetic snapshot42/17/15/0 согласованы. Один capped10Mbit/s TCP3s test, native1/noauto retry, PC/3202/2/new message/reload/document fits/pageerror0. Свои disabled probe/history удалены, original IDs совпали, credentials/configuration RouterOS не менялись. Это controlled HTTP socket loss после headers, не independent WAN failure/cancelled native command.
**Runtime.** Backend ux-response100/version1.0.0-test.response100/image797979da82d6…; одна сборка изspeed98 +client/test/package-script files, source archive SHA в отчёте. Entry/node --check/pg_restore18.6/compose --wait passed, приватный .env backup; обновлён только BACKEND_IMAGE_TAG, frontend ux-ospf91/dependencies/migrations прежние. Label response100 — имя блока, не Git SHA. Backend/PG healthy,3 compose containers. Одна локальная фиксация без push; main UI99 всё ещё выполняется на неизменённом frontend source, его итог не заявлен. Квота37% использовано. Следующий101 — подтверждённые нарушения контраста/keyboard code после main99, затем context/финальная сводка.
+2 -2
View File
@@ -1,12 +1,12 @@
# План реализации UX/UI по пятичасовым окнам
Актуальное продолжение 2026-10-10: завершены12.5.93–98. На стенде backend ux-speed98/frontend ux-ospf91; неизвестный native результат при transport loss теперь явно объяснён одинаково в API/history/UI, live2/2. Собственные fixtures очищены, credentials не менялись. Следующий12.5.99 — общая backend/main/context регрессия; backend/main уже запущены и пока не объявлены прошедшими. Актуальная внешняя матрица docs/ux-ui-remaining-acceptance.md; этап12.5 открыт.
Актуальное продолжение 2026-10-10: завершены12.5.93–98 и100;99 general main UI regression в работе. Backend ux-response100/frontend ux-ospf91: partial RouterOS response теперь prompt ECONNRESET/unknown receipt,7/7 transport и общая backend регрессия24 groups/171 node cases passed, live2/2/cleanup. 30-route live обзор нашёл нарушения контраста/keyboard;101 исправить после main99, затем context. Актуальная матрица docs/ux-ui-remaining-acceptance.md; этап12.5 открыт.
Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-10. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат.
## Актуальная точка продолжения — 2026-10-10
Актуальное продолжение 2026-10-10: завершены12.5.93–98. На стенде backend ux-speed98/frontend ux-ospf91; неизвестный native результат при transport loss теперь явно объяснён одинаково в API/history/UI, live2/2. Собственные fixtures очищены, credentials не менялись. Следующий12.5.99 — общая backend/main/context регрессия; backend/main уже запущены и пока не объявлены прошедшими. Актуальная внешняя матрица docs/ux-ui-remaining-acceptance.md; этап12.5 открыт.
Актуальное продолжение 2026-10-10: завершены12.5.93–98 и100;99 general main UI regression в работе. Backend ux-response100/frontend ux-ospf91: partial RouterOS response теперь prompt ECONNRESET/unknown receipt,7/7 transport и общая backend регрессия24 groups/171 node cases passed, live2/2/cleanup. 30-route live обзор нашёл нарушения контраста/keyboard;101 исправить после main99, затем context. Актуальная матрица docs/ux-ui-remaining-acceptance.md; этап12.5 открыт.
Этапы0–11 завершены в локальных границах журнала. История выполненных внешних блоков и доказательства сохранены в [журнале](ux-ui-stage-12.md) и [протоколе](ux-ui-external-acceptance.md). Реальная Telegram доставка, Portal SSO, multi-instance и оставшиеся профильные сетевые ограничения открыты.
+20 -10
View File
@@ -1,6 +1,6 @@
# UX/UI: журнал реализации
Актуальное продолжение 2026-10-10: завершены12.5.93–98. На стенде backend ux-speed98/frontend ux-ospf91; неизвестный native результат при transport loss теперь явно объяснён одинаково в API/history/UI, live2/2. Собственные fixtures очищены, credentials не менялись. Следующий12.5.99 — общая backend/main/context регрессия; backend/main уже запущены и пока не объявлены прошедшими. Актуальная внешняя матрица docs/ux-ui-remaining-acceptance.md; этап12.5 открыт.
Актуальное продолжение 2026-10-10: завершены12.5.93–98 и100;99 general main UI regression в работе. Backend ux-response100/frontend ux-ospf91: partial RouterOS response теперь prompt ECONNRESET/unknown receipt,7/7 transport и общая backend регрессия24 groups/171 node cases passed, live2/2/cleanup. 30-route live обзор нашёл нарушения контраста/keyboard;101 исправить после main99, затем context. Актуальная матрица docs/ux-ui-remaining-acceptance.md; этап12.5 открыт.
Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md).
@@ -36,7 +36,25 @@
| 10 | Маршрутизация, диагностика, карта и терминал | Завершён в границах локальной приёмки 2026-10-01; блоки 10.1–10.71, внешние ограничения в 10.71 |
| 11А | Пользователи, контейнеры, сертификаты и резервные копии | Завершён в границах локальной приёмки 2026-10-01; 11А.1–11А.33, внешние ограничения в 11А.33 |
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 |
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–98 и ограничения ниже. Runtime backend ux-diagnostics89/frontend ux-ospf91. |
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–98 и100;99 в работе и ограничения ниже. Runtime backend ux-diagnostics89/frontend ux-ospf91. |
## 12.5.100 — RouterOS: обрыв тела HTTP ответа больше не зависает (2026-10-10)
У ClientRequest и IncomingMessage разные error events. После получения headers частичный ответ не вызывал request error/end; запрос оставался pending даже после500ms timeout. Изолированная проверка воспроизвела6 failures по deadline1200ms для GET/POST/PUT/PATCH/DELETE/raw file reading; полныйsuccess/401 проходил. Добавлен общий watchRosResponseFailure на aborted/error во всех6 paths, очистка timer и существующий settle/AbortSignal cleanup дляPOST. Partial response отклоняется как ECONNRESET, без выдуманного ACK и повторных writes. Две прежние read-only file compatibility стратегии сохранены.
[Протокол](ux-ui/acceptance/routeros-response-transport-2026-10-10.json): после изменения7/7 включая complete success/401, probes.cancel/actual HTTP AbortSignal и byte file parser passed. Тест включён в test:ros-file. Локальный production build, новый test ESLint и Docker build прошли. Исходные2 prefer-const errors mikrotik.ts сохраняются, новые ошибки не добавлены; whole-file lint clean не заявлен. Общая backend регрессия повторена именно из-за изменения общей основы:24 npm groups/171 node:test cases/exit0. Console assertion scripts дополнительно входят в npm suite,171 не заявляется числом всех их assertions.
**Live.** [Native/API/PG](ux-ui/acceptance/speed-response-body-2026-10-10.json), [browser](ux-ui/acceptance/browser-speed-response-body-2026-10-10.json). Relay сначала получил actual RouterOS done testing, затем передал headers/первые16 bytes настоящего JSON и оборвал body. Теперь API502/понятный неизвестный результат вместо pending, история/прежний synthetic snapshot42/17/15/0 согласованы. Один capped10Mbit/s TCP3s test, native1/noauto retry, PC/3202/2/new message/reload/document fits/pageerror0. Свои disabled probe/history удалены, original IDs совпали, credentials/configuration RouterOS не менялись. Это controlled HTTP socket loss после headers, не independent WAN failure/cancelled native command.
**Runtime.** Backend ux-response100/version1.0.0-test.response100/image797979da82d6…; одна сборка изspeed98 +client/test/package-script files, source archive SHA в отчёте. Entry/node --check/pg_restore18.6/compose --wait passed, приватный .env backup; обновлён только BACKEND_IMAGE_TAG, frontend ux-ospf91/dependencies/migrations прежние. Label response100 — имя блока, не Git SHA. Backend/PG healthy,3 compose containers. Одна локальная фиксация без push; main UI99 всё ещё выполняется на неизменённом frontend source, его итог не заявлен. Квота37% использовано. Следующий101 — подтверждённые нарушения контраста/keyboard code после main99, затем context/финальная сводка.
## 12.5.99 — общая регрессия и live обзор (2026-10-10)
**В работе.** Backend98 npm test24 groups/164 node:test cases/exit0 прошёл с недоступным fixture DB URL; это не real-PG приёмка. Main UI regression запущена на3100 с BACKEND_INTERNAL_URL=http://127.0.0.1:1, API responses контролируются test fixtures. JSON reporter выдаёт итог после завершения; main/context пока не объявлены прошедшими. UI production source не меняется во время main run.493 исходных evidence files защищены snapshot, включая чужие изменённые GRE/VXLAN screenshots; восстановление — после main/context.
[Live обзор](ux-ui/acceptance/live-shell-a11y-2026-10-10.json):30 routes×1280/320=60, HTTP200/route errors0/pageerror0/document overflow0. Два автоматических read-only OSPF preview POST, apply/mutations0. Default dark theme/scoped axe выявил10 нарушений: alerts empty text, certificate badge/pre keyboard, containers hint, uptime hint; filters320 contrast не воспроизвёлся в устойчивом900ms повторе. Дополнительная светлая тема выявила sidebar subtitles и HR badge. Полная WCAG/все интеграции этим обзором не приняты; исправление101 после завершения main run.
Согласованность журналов: у всех numbered records есть даты, найден и удалён один точный дубликат12.5.21 изprogress/stage12/external; первая запись и дата2026-10-08 сохранены. Другие исторические данные не удалялись. В процессе внешнего HTTP контроля выявлен общий partial-response hang; исправление и отдельная фиксация100 ниже. Следующий шаг99 — дождаться main result, затем context; не перезапускать уже успешные unchanged suites.
## 12.5.98 — понятный неизвестный результат теста скорости (2026-10-10)
@@ -1130,14 +1148,6 @@ PG-8 browser unknown/reconciliation принят в этих границах. P
Основной backend4c12c55/frontend673e5ba не изменён. Новые backend/frontend исправления ещё не развёрнуты. Полный 12.5 открыт. Следующий 12.5.22 — сборка обновлённых образов и приёмка на стенде перед deployment. Локальный коммит без push.
## 12.5.21 — неопределённый исход speed-запуска и сверка с историей (2026-10-08)
В локальной строке запуска введён status unknown: «Сохранение не подтверждено», с видимым runId; это не новый статус серверного DTO (история API по-прежнему принимает только done/error). При обновлении истории незавершённая или неподтверждённая локальная строка сохраняется, если её идентификатора нет в ответе. Подтверждённая строка с тем же runId заменяет локальную, предупреждение снимается. Unknown не подменяет последний подтверждённый snapshot карточки пробы; предупреждение показано без destructive-оформления. Автоматического повторного сетевого теста нет. Ручной новый запуск остаётся доступен; предупреждение просит проверить историю перед повтором. Состояние локальной строки не сохраняется после перезагрузки страницы.
Проверки: 13/13 Playwright (uptime-speed-data и uptime-speed-unknown) за 27.7s, включая unknown → пустая история → done/error, сохранение TX42, один POST и адаптацию 320px. Это live-ветка UI с HTTP-моками, не real PG/browser proof. Scoped frontend typecheck (tmp/tsconfig-speed-check.json, исключён старый ignored tmp build context) и ESLint изменённых файлов прошли. Существующие null/zero, malformed DTO, свежесть, независимые источники и Ping-состояния сохранены.
Основной backend4c12c55/frontend673e5ba не изменён. Новые backend/frontend исправления ещё не развёрнуты. Полный 12.5 открыт. Следующий 12.5.22 — сборка обновлённых образов и приёмка на стенде перед deployment. Локальный коммит без push.
## 12.5.20 — сохранение подтверждённого speed snapshot при unknown receipt (2026-10-08)
Приостатке5%окнавыполненнебольшойлокальныйблокпередdeployment. Frontendcatch раньше ставилlastStatuserror/новыйlastRunAt любойошибкеPOSTspeed-test,включаяunknowncommit. Теперьподтверждённыйsnapshot пробысохраняется,еслиApiClientError502 содержитoutcomeunknown иrunId текущегозапроса. ЧужойrunId/неправильныйpayload/403/обычнаяошибка нераспознаютсякакэтотисход. Существующееservererrorсообщениепоказывается;новыекомпоненты/композицияReUIне менялись. Временнаястрокасамогозапросапо-прежнемуимеетstatuserror; полныйunknown-state/browserUX ещёнужнопроверить.
+3 -3
View File
@@ -1,8 +1,8 @@
# Оставшаяся внешняя приёмка — 2026-10-10
Это актуальная матрица после 12.5.98. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
Это актуальная матрица после 12.5.100 (99 main/context в работе). Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-speed98`, frontend `ux-ospf91`; production lab использует AUTH_REQUIRED=false. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты последнего сценария удалены, исходные11 коллекций×2 восстановлены; пароли не менялись.
Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-response100`, frontend `ux-ospf91`; production lab использует AUTH_REQUIRED=false. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты последнего сценария удалены, исходные11 коллекций×2 восстановлены; пароли не менялись.
| Раздел | Подтверждено | Осталось / условие завершения |
| --- | --- | --- |
@@ -25,7 +25,7 @@
## Следующая последовательность
1. **12.5.95 завершён:** [source/context/compiled audit](ux-ui/acceptance/deployment-source-audit-2026-10-10.json), production source drift не выявлен; нормализация и исключения явно указаны.
2. **12.5.96 timezone завершён:** [6 live UI cases](ux-ui/acceptance/browser-speed-timezone-2026-10-10.json). 97 HTTP receipt disconnect принят: [протокол](ux-ui/acceptance/speed-disconnect-2026-10-10.json). 98 сообщение неизвестного native результата исправлено/развёрнуто. Следующий99 — общая регрессия.
2. **12.5.96 timezone завершён:** [6 live UI cases](ux-ui/acceptance/browser-speed-timezone-2026-10-10.json). 97 HTTP receipt disconnect принят: [протокол](ux-ui/acceptance/speed-disconnect-2026-10-10.json). 98 сообщение неизвестного native результата исправлено/развёрнуто. 99 main/context в работе;100 partial-response hang исправлен/развёрнут. 101 — реальные a11y нарушения после main.
3. **Заключительный профильный контроль:** после последнего исправления собрать только затронутую часть, проверить её live и обновить матрицу. Общую регрессию выполнить один раз на согласованном runtime.
4. **Внешние зависимости:** Telegram/Portal/client OS/multi-WAN/scale проходят отдельную приёмку при наличии нужных аккаунтов, устройств и разрешений. Лабораторный результат двух CHR не закрывает их автоматически.
+18 -8
View File
@@ -1163,14 +1163,6 @@ SchedulerSnapshotError раньшеигнорировалruns[].okfalse уuptime
Основной backend4c12c55/frontend673e5ba не изменён. Новые backend/frontend исправления ещё не развёрнуты. Полный 12.5 открыт. Следующий 12.5.22 — сборка обновлённых образов и приёмка на стенде перед deployment. Локальный коммит без push.
## 12.5.21 — неопределённый исход speed-запуска и сверка с историей (2026-10-08)
В локальной строке запуска введён status unknown: «Сохранение не подтверждено», с видимым runId; это не новый статус серверного DTO (история API по-прежнему принимает только done/error). При обновлении истории незавершённая или неподтверждённая локальная строка сохраняется, если её идентификатора нет в ответе. Подтверждённая строка с тем же runId заменяет локальную, предупреждение снимается. Unknown не подменяет последний подтверждённый snapshot карточки пробы; предупреждение показано без destructive-оформления. Автоматического повторного сетевого теста нет. Ручной новый запуск остаётся доступен; предупреждение просит проверить историю перед повтором. Состояние локальной строки не сохраняется после перезагрузки страницы.
Проверки: 13/13 Playwright (uptime-speed-data и uptime-speed-unknown) за 27.7s, включая unknown → пустая история → done/error, сохранение TX42, один POST и адаптацию 320px. Это live-ветка UI с HTTP-моками, не real PG/browser proof. Scoped frontend typecheck (tmp/tsconfig-speed-check.json, исключён старый ignored tmp build context) и ESLint изменённых файлов прошли. Существующие null/zero, malformed DTO, свежесть, независимые источники и Ping-состояния сохранены.
Основной backend4c12c55/frontend673e5ba не изменён. Новые backend/frontend исправления ещё не развёрнуты. Полный 12.5 открыт. Следующий 12.5.22 — сборка обновлённых образов и приёмка на стенде перед deployment. Локальный коммит без push.
## 12.5.22 — новые Docker-образы и real browser speed unknown (2026-10-08)
Из зафиксированного source8782054078304f36cc611876af2d17f87b857f60 подготовлены отдельные frontend/backend contexts и последовательно собраны production-образы на Debian-стенде. Frontend production build и проверка TypeScript внутри Docker прошли; backend contracts/TypeScript build прошёл. Незакоммиченные изменения, .env, ключи и локальные node_modules в сборку не передавались. Образы не публиковались.
@@ -2244,3 +2236,21 @@ API502, history error/TXRX/Pingnull, probe error с прежними42/17/15/0;
[Native/API/PG](ux-ui/acceptance/speed-message-2026-10-10.json) и [browser](ux-ui/acceptance/browser-speed-message-2026-10-10.json): повторён только изменённый receipt path, одна capped10Mbit/s TCP3s команда, реальное закрытие socket после передачи, native independently done, API/history содержат новое сообщение; прежние синтетические42/17/15/0 сохранены, native1/noauto retry. Live1280/3202/2, сообщение видно в probe card, reload/document fits/pageerror0/UI command POST0. Свои disabled probe/history удалены, исходные IDs совпали, только3 compose containers/backend/PG healthy; RouterOS/credentials не менялись.
При согласовании указателей94–97 обнаружено, что массовая замена также затронула исторический абзац77 в плане/протоколе. Восстановлен точный абзац из40f730b, история/дата2026-10-09 сохранены; следующие обновления ограничены текущими указателями. Следующий99 — одна общая backend/main/context регрессия на текущем source, с восстановлением исходных доказательств, затем итоговая сводка. General backend/main runs уже запущены, их результат этим блоком не заявлен. Квота23% использовано; этап12.5 открыт. Один локальный commit без push.
## 12.5.99 — общая регрессия и live обзор (2026-10-10)
**В работе.** Backend98 npm test24 groups/164 node:test cases/exit0 прошёл с недоступным fixture DB URL; это не real-PG приёмка. Main UI regression запущена на3100 с BACKEND_INTERNAL_URL=http://127.0.0.1:1, API responses контролируются test fixtures. JSON reporter выдаёт итог после завершения; main/context пока не объявлены прошедшими. UI production source не меняется во время main run.493 исходных evidence files защищены snapshot, включая чужие изменённые GRE/VXLAN screenshots; восстановление — после main/context.
[Live обзор](ux-ui/acceptance/live-shell-a11y-2026-10-10.json):30 routes×1280/320=60, HTTP200/route errors0/pageerror0/document overflow0. Два автоматических read-only OSPF preview POST, apply/mutations0. Default dark theme/scoped axe выявил10 нарушений: alerts empty text, certificate badge/pre keyboard, containers hint, uptime hint; filters320 contrast не воспроизвёлся в устойчивом900ms повторе. Дополнительная светлая тема выявила sidebar subtitles и HR badge. Полная WCAG/все интеграции этим обзором не приняты; исправление101 после завершения main run.
Согласованность журналов: у всех numbered records есть даты, найден и удалён один точный дубликат12.5.21 изprogress/stage12/external; первая запись и дата2026-10-08 сохранены. Другие исторические данные не удалялись. В процессе внешнего HTTP контроля выявлен общий partial-response hang; исправление и отдельная фиксация100 ниже. Следующий шаг99 — дождаться main result, затем context; не перезапускать уже успешные unchanged suites.
## 12.5.100 — RouterOS: обрыв тела HTTP ответа больше не зависает (2026-10-10)
У ClientRequest и IncomingMessage разные error events. После получения headers частичный ответ не вызывал request error/end; запрос оставался pending даже после500ms timeout. Изолированная проверка воспроизвела6 failures по deadline1200ms для GET/POST/PUT/PATCH/DELETE/raw file reading; полныйsuccess/401 проходил. Добавлен общий watchRosResponseFailure на aborted/error во всех6 paths, очистка timer и существующий settle/AbortSignal cleanup дляPOST. Partial response отклоняется как ECONNRESET, без выдуманного ACK и повторных writes. Две прежние read-only file compatibility стратегии сохранены.
[Протокол](ux-ui/acceptance/routeros-response-transport-2026-10-10.json): после изменения7/7 включая complete success/401, probes.cancel/actual HTTP AbortSignal и byte file parser passed. Тест включён в test:ros-file. Локальный production build, новый test ESLint и Docker build прошли. Исходные2 prefer-const errors mikrotik.ts сохраняются, новые ошибки не добавлены; whole-file lint clean не заявлен. Общая backend регрессия повторена именно из-за изменения общей основы:24 npm groups/171 node:test cases/exit0. Console assertion scripts дополнительно входят в npm suite,171 не заявляется числом всех их assertions.
**Live.** [Native/API/PG](ux-ui/acceptance/speed-response-body-2026-10-10.json), [browser](ux-ui/acceptance/browser-speed-response-body-2026-10-10.json). Relay сначала получил actual RouterOS done testing, затем передал headers/первые16 bytes настоящего JSON и оборвал body. Теперь API502/понятный неизвестный результат вместо pending, история/прежний synthetic snapshot42/17/15/0 согласованы. Один capped10Mbit/s TCP3s test, native1/noauto retry, PC/3202/2/new message/reload/document fits/pageerror0. Свои disabled probe/history удалены, original IDs совпали, credentials/configuration RouterOS не менялись. Это controlled HTTP socket loss после headers, не independent WAN failure/cancelled native command.
**Runtime.** Backend ux-response100/version1.0.0-test.response100/image797979da82d6…; одна сборка изspeed98 +client/test/package-script files, source archive SHA в отчёте. Entry/node --check/pg_restore18.6/compose --wait passed, приватный .env backup; обновлён только BACKEND_IMAGE_TAG, frontend ux-ospf91/dependencies/migrations прежние. Label response100 — имя блока, не Git SHA. Backend/PG healthy,3 compose containers. Одна локальная фиксация без push; main UI99 всё ещё выполняется на неизменённом frontend source, его итог не заявлен. Квота37% использовано. Следующий101 — подтверждённые нарушения контраста/keyboard code после main99, затем context/финальная сводка.
+12 -2
View File
@@ -1,6 +1,6 @@
# Тестовый сервер UX/UI — 2026-10-10
Сервер161.104.46.222 предоставлен для временного тестирования. Backend ux-speed98/version1.0.0-test.speed98, frontend ux-ospf91/version1.0.0-test.ospf91; backend/PG healthy, frontend HTTP/browser passed. Основной блок IPsec77 принят в границах журнала, own fixtures/fake DB rows удалены; каталог flash CHR2 сохранён из-за неподтверждённого ownership. SSH/оба CHR доступны, пароли прежние.
Сервер161.104.46.222 предоставлен для временного тестирования. Backend ux-response100/version1.0.0-test.response100, frontend ux-ospf91/version1.0.0-test.ospf91; backend/PG healthy, frontend HTTP/browser passed. Основной блок IPsec77 принят в границах журнала, own fixtures/fake DB rows удалены; каталог flash CHR2 сохранён из-за неподтверждённого ownership. SSH/оба CHR доступны, пароли прежние.
| Параметр | Проверенное состояние |
| --- | --- |
@@ -14,7 +14,7 @@
| PostgreSQL | postgres:18-alpine,18.6;mmapp-test-postgres healthy;SELECT version() passed |
| Данные | Отдельный Compose project mmapp-test,том mmapp-test_mmapp-pgdata и каталог ./data |
| Доступ | PostgreSQL не публикуется;frontend3000/backend8000 привязаны к127.0.0.1 после запуска |
| Образы приложения | mmapp-test/backend:ux-speed98 и mmapp-test/frontend:ux-ospf91; запущены |
| Образы приложения | mmapp-test/backend:ux-response100 и mmapp-test/frontend:ux-ospf91; запущены |
Compose SHA256:3052bd6ab5d9865f97605cebfb5ff27a4410cd3357b81153a65a87fb7c92582f. OptionalBACKEND_IMAGE_TAG позволяетобновлятьbackendотдельно. Локальный и удалённый файлы совпали. Шаблоны: [Compose](../deploy/docker-compose.test.yml), [env без секретов](../deploy/env.test.example).
@@ -757,3 +757,13 @@ API502, history error/TXRX/Pingnull, probe error с прежними42/17/15/0;
[Native/API/PG](ux-ui/acceptance/speed-message-2026-10-10.json) и [browser](ux-ui/acceptance/browser-speed-message-2026-10-10.json): повторён только изменённый receipt path, одна capped10Mbit/s TCP3s команда, реальное закрытие socket после передачи, native independently done, API/history содержат новое сообщение; прежние синтетические42/17/15/0 сохранены, native1/noauto retry. Live1280/3202/2, сообщение видно в probe card, reload/document fits/pageerror0/UI command POST0. Свои disabled probe/history удалены, исходные IDs совпали, только3 compose containers/backend/PG healthy; RouterOS/credentials не менялись.
При согласовании указателей94–97 обнаружено, что массовая замена также затронула исторический абзац77 в плане/протоколе. Восстановлен точный абзац из40f730b, история/дата2026-10-09 сохранены; следующие обновления ограничены текущими указателями. Следующий99 — одна общая backend/main/context регрессия на текущем source, с восстановлением исходных доказательств, затем итоговая сводка. General backend/main runs уже запущены, их результат этим блоком не заявлен. Квота23% использовано; этап12.5 открыт. Один локальный commit без push.
## 12.5.100 — RouterOS: обрыв тела HTTP ответа больше не зависает (2026-10-10)
У ClientRequest и IncomingMessage разные error events. После получения headers частичный ответ не вызывал request error/end; запрос оставался pending даже после500ms timeout. Изолированная проверка воспроизвела6 failures по deadline1200ms для GET/POST/PUT/PATCH/DELETE/raw file reading; полныйsuccess/401 проходил. Добавлен общий watchRosResponseFailure на aborted/error во всех6 paths, очистка timer и существующий settle/AbortSignal cleanup дляPOST. Partial response отклоняется как ECONNRESET, без выдуманного ACK и повторных writes. Две прежние read-only file compatibility стратегии сохранены.
[Протокол](ux-ui/acceptance/routeros-response-transport-2026-10-10.json): после изменения7/7 включая complete success/401, probes.cancel/actual HTTP AbortSignal и byte file parser passed. Тест включён в test:ros-file. Локальный production build, новый test ESLint и Docker build прошли. Исходные2 prefer-const errors mikrotik.ts сохраняются, новые ошибки не добавлены; whole-file lint clean не заявлен. Общая backend регрессия повторена именно из-за изменения общей основы:24 npm groups/171 node:test cases/exit0. Console assertion scripts дополнительно входят в npm suite,171 не заявляется числом всех их assertions.
**Live.** [Native/API/PG](ux-ui/acceptance/speed-response-body-2026-10-10.json), [browser](ux-ui/acceptance/browser-speed-response-body-2026-10-10.json). Relay сначала получил actual RouterOS done testing, затем передал headers/первые16 bytes настоящего JSON и оборвал body. Теперь API502/понятный неизвестный результат вместо pending, история/прежний synthetic snapshot42/17/15/0 согласованы. Один capped10Mbit/s TCP3s test, native1/noauto retry, PC/3202/2/new message/reload/document fits/pageerror0. Свои disabled probe/history удалены, original IDs совпали, credentials/configuration RouterOS не менялись. Это controlled HTTP socket loss после headers, не independent WAN failure/cancelled native command.
**Runtime.** Backend ux-response100/version1.0.0-test.response100/image797979da82d6…; одна сборка изspeed98 +client/test/package-script files, source archive SHA в отчёте. Entry/node --check/pg_restore18.6/compose --wait passed, приватный .env backup; обновлён только BACKEND_IMAGE_TAG, frontend ux-ospf91/dependencies/migrations прежние. Label response100 — имя блока, не Git SHA. Backend/PG healthy,3 compose containers. Одна локальная фиксация без push; main UI99 всё ещё выполняется на неизменённом frontend source, его итог не заявлен. Квота37% использовано. Следующий101 — подтверждённые нарушения контраста/keyboard code после main99, затем context/финальная сводка.
@@ -0,0 +1,22 @@
{
"date": "2026-10-10",
"cases": [
{
"width": 1280,
"errorHistoryVisible": true,
"previousSnapshot42and17Preserved": true,
"reload": true,
"documentFits": true
},
{
"width": 320,
"errorHistoryVisible": true,
"previousSnapshot42and17Preserved": true,
"reload": true,
"documentFits": true
}
],
"errors": [],
"httpMocks": false,
"uiNativePosts": 0
}
@@ -0,0 +1,703 @@
{
"date": "2026-10-10",
"runtime": {
"backend": "ux-speed98",
"frontend": "ux-ospf91"
},
"httpMocks": false,
"readOnly": true,
"cases": [
{
"route": "alerts",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [
{
"id": "color-contrast",
"impact": "serious",
"description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds",
"targets": [
[
".py-12 > p"
]
]
}
],
"apiFailures": []
},
{
"route": "alerts",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [
{
"id": "color-contrast",
"impact": "serious",
"description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds",
"targets": [
[
".py-12 > .text-sm"
]
]
}
],
"apiFailures": []
},
{
"route": "asns",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "asns",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "backups",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "backups",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "bgp",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "bgp",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "certificates",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [
{
"id": "color-contrast",
"impact": "serious",
"description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds",
"targets": [
[
".text-success-foreground"
]
]
},
{
"id": "scrollable-region-focusable",
"impact": "serious",
"description": "Ensure elements that have scrollable content are accessible by keyboard in Safari",
"targets": [
[
"div:nth-child(1) > pre"
],
[
"div:nth-child(3) > pre"
]
]
}
],
"apiFailures": []
},
{
"route": "certificates",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [
{
"id": "color-contrast",
"impact": "serious",
"description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds",
"targets": [
[
".text-success-foreground"
]
]
},
{
"id": "scrollable-region-focusable",
"impact": "serious",
"description": "Ensure elements that have scrollable content are accessible by keyboard in Safari",
"targets": [
[
"div:nth-child(3) > pre"
]
]
}
],
"apiFailures": []
},
{
"route": "communities",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "communities",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "containers",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [
{
"id": "color-contrast",
"impact": "serious",
"description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds",
"targets": [
[
".bg-info\\/5 > div > .text-info"
]
]
}
],
"apiFailures": []
},
{
"route": "containers",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [
{
"id": "color-contrast",
"impact": "serious",
"description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds",
"targets": [
[
".bg-info\\/5 > div > .text-info"
]
]
}
],
"apiFailures": []
},
{
"route": "dashboard",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "dashboard",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "data-collection",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "data-collection",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "domains",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "domains",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "filters",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "filters",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [
{
"id": "color-contrast",
"impact": "serious",
"description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds",
"targets": [
[
".bg-primary"
]
]
}
],
"apiFailures": []
},
{
"route": "firewall",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "firewall",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "gre",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "gre",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "ip-ranges",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "ip-ranges",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "ipsec",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "ipsec",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "network-map",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "network-map",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "ospf",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "ospf",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "probes",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "probes",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "recursive-routes",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "recursive-routes",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "releases",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "releases",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "route-optimizer",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "route-optimizer",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "servers",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "servers",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "settings",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "settings",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "statistics",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "statistics",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "terminal",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "terminal",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "traffic",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": [
{
"path": "/api/traffic/servers/srv1/interfaces",
"status": 400
},
{
"path": "/api/traffic/servers/srv1",
"status": 400
}
]
},
{
"route": "traffic",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": [
{
"path": "/api/traffic/servers/srv1/interfaces",
"status": 400
},
{
"path": "/api/traffic/servers/srv1",
"status": 400
}
]
},
{
"route": "uptime",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [
{
"id": "color-contrast",
"impact": "serious",
"description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds",
"targets": [
[
".text-muted-foreground\\/80"
]
]
}
],
"apiFailures": []
},
{
"route": "uptime",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "users",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "users",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "vxlan",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "vxlan",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "wireguard",
"width": 1280,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
},
{
"route": "wireguard",
"width": 320,
"httpStatus": 200,
"fits": true,
"title": "MikrotikManager",
"violations": [],
"apiFailures": []
}
],
"errors": [],
"writes": [
{
"method": "POST",
"path": "/api/servers/1/ospf/optimize/preview"
},
{
"method": "POST",
"path": "/api/servers/1/ospf/optimize/preview"
}
],
"automaticReadOnlyRequests": [
{
"method": "POST",
"path": "/api/servers/1/ospf/optimize/preview"
},
{
"method": "POST",
"path": "/api/servers/1/ospf/optimize/preview"
}
],
"mutations": [],
"note": "Two automatic OSPF optimizer preview POSTs only; no apply/mutation requests. Scoped axe at700ms after fonts ready; not full WCAG or functional acceptance of all integrations."
}
@@ -0,0 +1,78 @@
{
"date": "2026-10-10",
"runtime": {
"backend": "ux-response100",
"version": "1.0.0-test.response100",
"imageId": "sha256:797979da82d6bd10147bd57557c65cc28a61b1e583abed13ed7326635d4e5adf",
"frontend": "ux-ospf91"
},
"source": {
"base": "build-speed98",
"archiveSha256": "08e9b4bb3a70c5d9d20e0ffef387d9bbba7bcc51f80968be740c7c64982dc5da",
"workingTreeOverrides": [
"backend/src/services/mikrotik.ts",
"backend/src/services/mikrotik-response.test.ts",
"backend/package.json"
],
"revisionLabel": "response100 (stage label, not Git SHA)"
},
"before": {
"boundedTests": 7,
"passed": 1,
"failed": 6,
"reason": "Incomplete IncomingMessage left promise pending past500ms timeout; test1200ms deadline rejected"
},
"after": {
"boundedTests": 7,
"passed": 7,
"failed": 0,
"methods": [
"GET",
"POST",
"PUT",
"PATCH",
"DELETE",
"raw file POST"
],
"completeSuccessAnd401": true,
"mutationRetries": 0,
"fileCompatibilityReadStrategies": 2
},
"checks": {
"abortSignalAndProbeCancellation": true,
"fileByteParser": true,
"localProductionBuild": true,
"dockerProductionBuild": true,
"entryNodeCheck": true,
"pgRestore": "18.6",
"composeHealthy": true,
"backendNpmTest": {
"groups": 24,
"nodeTestCases": 171,
"reportedFailures": 0,
"exitCode": 0,
"database": "Deliberately unavailable fixture URL; no external DB writes"
},
"lintNewTest": true,
"existingMikrotikPreferConstErrors": 2
},
"live": {
"nativeReport": "speed-response-body-2026-10-10.json",
"browserReport": "browser-speed-response-body-2026-10-10.json",
"nativeCommands": 1,
"durationSec": 3,
"rateLimitBps": 10000000,
"body": "First16 bytes of actual completed native JSON delivered; relay closed socket before remaining body",
"api502": true,
"unknownMessage": true,
"priorSnapshotPreserved": true,
"automaticRetry": false,
"uiCases": 2,
"cleanup": true
},
"limits": [
"HTTP localhost partial-response tests plus real RouterOS command through controlled relay; not independent physical WAN loss",
"Complete malformed PATCH JSON semantics, general response size limits and durable cancellation not changed",
"Main/context UI matrix still pending; frontend unchanged"
]
}
@@ -0,0 +1,54 @@
{
"date": "2026-10-10",
"name": "mm-body100-1791572367426",
"rateLimitBps": 10000000,
"durationSec": 3,
"syntheticPreviousSnapshot": {
"txAvgMbps": 42,
"rxAvgMbps": 17,
"pingRttMs": 15,
"pingLossPct": 0
},
"httpMocks": false,
"routerOsMocks": false,
"nativeCompletion": {
"status": "done testing",
"responseRows": 5,
"httpStatus": 200
},
"receiptPhase": "partial-response-body",
"receiptSocketDestroyed": true,
"httpStatus": 502,
"receipt": {
"error": "Результат теста скорости неизвестен: ответ устройства не получен. Проверьте его состояние перед повторным запуском."
},
"nativeCount": 1,
"probe": {
"id": "mm-body100-1791572367426",
"last_status": "error",
"last_tx_avg_mbps": 42,
"last_rx_avg_mbps": 17,
"last_ping_rtt_ms": 15,
"last_ping_loss_pct": 0,
"last_error": "Результат теста скорости неизвестен: ответ устройства не получен. Проверьте его состояние перед повторным запуском."
},
"history": [
{
"id": "mm-body100-1791572367426",
"status": "error",
"tx_avg_mbps": null,
"rx_avg_mbps": null,
"ping_rtt_ms": null,
"ping_loss_pct": null,
"error": "Результат теста скорости неизвестен: ответ устройства не получен. Проверьте его состояние перед повторным запуском."
}
],
"automaticRetry": false,
"limits": [
"Real Node HTTP client socket closed after request forwarded; RouterOS independently completed. Not physical WAN loss or cancelled native command.",
"Previous metrics deliberately seeded on own disabled probe, not new real measurements.",
"History error means application did not obtain confirmed test result; native completion was observed only by independent relay."
],
"cleanupHistory": true,
"cleanupProbes": true
}