diff --git a/backend/package.json b/backend/package.json index 996a7ecb..4217a6d3 100644 --- a/backend/package.json +++ b/backend/package.json @@ -37,7 +37,7 @@ "test:geoip": "tsx src/services/geoip-download.test.ts && tsx src/services/traffic-flow-geoip.test.ts && tsx src/plugins/auth.geoip.test.ts", "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/services/ospf-effective-disabled.test.ts && tsx src/routes/ospf-optimizer.test.ts", "test:containers": "tsx src/services/containers-live.test.ts && tsx src/services/containers-state.test.ts && tsx src/services/container-removal.test.ts && tsx src/routes/containers-state.test.ts", - "test:certificates": "tsx src/services/certificate-renew-collector.test.ts && 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:certificates": "tsx src/services/certificate-installation.test.ts && tsx src/services/certificate-renew-collector.test.ts && 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/system-database-partition-restore.test.ts && tsx src/services/system-database-cache.test.ts && tsx src/services/database-background-work.test.ts && 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", "test:all": "npm run test:probes && npm run test:exec && npm run test:alert-engine && npm run test:alerts-telegram && npm run test:auth && npm run test:wireguard && npm run test:ipsec && npm run test:traffic-rate && npm run test:traffic-flow && npm run test:users && npm run test:pg && npm run test:backups && npm run test:scheduler && npm run test:live-maps && npm run test:ospf-optimizer && npm run test:recursive-routes && npm run test:config-sync && npm run test:ros-file && npm run test:containers && npm run test:certificates && npm run test:netflow-settings && npm run test:netflow-purge && npm run test:evolution && npm run test:system-database && npm run test:server-privacy", "test:server-privacy": "tsx src/routes/server-secrets-sql.test.ts" diff --git a/backend/src/services/acme-cloudflare.ts b/backend/src/services/acme-cloudflare.ts index 82f56db0..bc69d906 100644 --- a/backend/src/services/acme-cloudflare.ts +++ b/backend/src/services/acme-cloudflare.ts @@ -1,4 +1,6 @@ import { lookup } from "node:dns/promises" +import { X509Certificate } from "node:crypto" +import { applyVerifiedCertificateToServices } from "./certificate-installation.js" import * as acme from "acme-client" import type { Server } from "../db/schema.js" import { @@ -357,7 +359,11 @@ export async function issueCertificateWithCloudflareDns(params: { }) } - await clientRos.applyCertificateToServices(params.certName) + await applyVerifiedCertificateToServices({ + certificates: () => clientRos.get("/certificate"), + apply: () => clientRos.applyCertificateToServices(params.certName), + services: () => clientRos.get("/ip/service"), + }, params.certName, new X509Certificate(leafPem).fingerprint256) } finally { params.onStep?.("cleanup") for (const item of txtCleanups) { diff --git a/backend/src/services/certificate-installation.test.ts b/backend/src/services/certificate-installation.test.ts new file mode 100644 index 00000000..85dbafe7 --- /dev/null +++ b/backend/src/services/certificate-installation.test.ts @@ -0,0 +1,22 @@ +import assert from "node:assert/strict" +import { applyVerifiedCertificateToServices } from "./certificate-installation.js" +const fresh={name:"own",fingerprint:"b".repeat(64),"private-key":"true"} +const services=[{name:"www-ssl",certificate:"own",disabled:"false"},{name:"api-ssl",certificate:"own",disabled:"false"}] +for(const value of [{},[],[fresh,fresh],[{...fresh,fingerprint:"a".repeat(64)}],[{...fresh,"private-key":undefined}],[{...fresh,"private-key":"false",flags:"K"}]]) { + let applied=0 + await assert.rejects(()=>applyVerifiedCertificateToServices({certificates:async()=>value,apply:async()=>{applied++},services:async()=>services},"own",fresh.fingerprint)) + assert.equal(applied,0) +} +for(const value of [[],[services[0]],services.map(row=>({...row,disabled:"true"})),services.map(row=>({...row,certificate:"old"}))]) { + await assert.rejects(()=>applyVerifiedCertificateToServices({certificates:async()=>[fresh],apply:async()=>{},services:async()=>value},"own",fresh.fingerprint)) +} +{ + let reads=0 + await assert.rejects(()=>applyVerifiedCertificateToServices({certificates:async()=>++reads===1?[fresh]:[fresh,fresh],apply:async()=>{},services:async()=>services},"own",fresh.fingerprint)) +} +{ + let applied=0 + await applyVerifiedCertificateToServices({certificates:async()=>[fresh,{name:"ca",fingerprint:"a".repeat(64)}],apply:async()=>{applied++},services:async()=>[...services,{name:"www-ssl",dynamic:"true"}]},"own",fresh.fingerprint) + assert.equal(applied,1) +} +console.log("Certificate installation: 6 invalid imports never apply; 4 service mismatches and post-apply ambiguity never succeed; confirmed import/apply succeeds") diff --git a/backend/src/services/certificate-installation.ts b/backend/src/services/certificate-installation.ts new file mode 100644 index 00000000..fa403bcb --- /dev/null +++ b/backend/src/services/certificate-installation.ts @@ -0,0 +1,33 @@ +type Row = Record +function rows(value: unknown): Row[] { + if (!Array.isArray(value) || value.some(row => !row || typeof row !== "object" || Array.isArray(row))) throw new Error("Некорректный ответ проверки сертификата") + return value as Row[] +} +function fingerprint(value: unknown): string | null { + if (typeof value !== "string") return null + const normalized = value.replaceAll(":", "").toLowerCase() + return /^[a-f0-9]{64}$/.test(normalized) ? normalized : null +} +export function assertImportedCertificate(value: unknown, name: string, expectedFingerprint: string): void { + const matches = rows(value).filter(row => row.name === name) + if (matches.length !== 1) throw new Error("Импорт не подтверждён: имя сертификата отсутствует или неоднозначно. Назначение сервисам не подтверждено.") + const row = matches[0]! + const expected = fingerprint(expectedFingerprint) + if (!expected || fingerprint(row.fingerprint) !== expected) throw new Error("Импорт не подтверждён: отпечаток сертификата отличается от выданного") + const privateKey = row["private-key"] === "true" || row["private-key"] === "yes" || row["private-key"] === undefined && typeof row.flags === "string" && row.flags.toUpperCase().includes("K") + if (!privateKey) throw new Error("Импорт не подтверждён: закрытый ключ сертификата не обнаружен") +} +export async function applyVerifiedCertificateToServices(deps: { + certificates: () => Promise + apply: () => Promise + services: () => Promise +}, name: string, expectedFingerprint: string): Promise { + assertImportedCertificate(await deps.certificates(), name, expectedFingerprint) + await deps.apply() + assertImportedCertificate(await deps.certificates(), name, expectedFingerprint) + const services = rows(await deps.services()).filter(row => row.dynamic !== "true" && row.dynamic !== "yes") + for (const service of ["www-ssl", "api-ssl"]) { + const matching = services.filter(row => row.name === service) + if (matching.length !== 1 || matching[0]!.certificate !== name || !["false", "no"].includes(String(matching[0]!.disabled))) throw new Error(`Назначение сертификата сервису ${service} не подтверждено; проверьте фактические настройки устройства`) + } +} diff --git a/backend/src/services/mikrotik-response.test.ts b/backend/src/services/mikrotik-response.test.ts index 7da668df..ebc47d90 100644 --- a/backend/src/services/mikrotik-response.test.ts +++ b/backend/src/services/mikrotik-response.test.ts @@ -3,6 +3,33 @@ import assert from "node:assert/strict" import { createServer } from "node:http" import { MikrotikClient } from "./mikrotik.js" +test("RouterOS shared REST pool bounds logins across client instances and closes idle sockets", async () => { + let active=0,peak=0,requests=0 + const connections=new Set() + const server=createServer((req,res)=>{requests++;active++;peak=Math.max(peak,active);req.resume();setTimeout(()=>{res.end('{"ok":true}');active--},20)}) + server.on("connection",socket=>{connections.add(socket);socket.on("close",()=>connections.delete(socket))}) + await new Promise(resolve=>server.listen(0,"127.0.0.1",resolve)) + const address=server.address();assert(address&&typeof address!=="string") + const clients=Array.from({length:3},()=>new MikrotikClient({host:"127.0.0.1",port:address.port,useSsl:false,verifySsl:false,username:"fixture",password:"fixture"})) + try { + await Promise.all(Array.from({length:12},(_,i)=>i%2?clients[i%3]!.post("/own",{},2000):clients[i%3]!.get("/own",2000))) + assert.equal(requests,12);assert.equal(peak,2) + await new Promise(resolve=>setTimeout(resolve,30));assert.equal(connections.size,0) + }finally{await new Promise((resolve,reject)=>server.close(error=>error?reject(error):resolve()))} +}) + +test("RouterOS queued mutation that times out never reaches the device later", async () => { + let writes=0 + const server=createServer((req)=>{req.resume();if(req.method!=="GET")writes++}) + await new Promise(resolve=>server.listen(0,"127.0.0.1",resolve)) + const address=server.address();assert(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 { + const first=client.get("/hold1",300),second=client.get("/hold2",300),write=client.put("/own",{},40) + const results=await Promise.allSettled([first,second,write]);assert(results.every(result=>result.status==="rejected"));assert.equal(writes,0) + }finally{server.closeAllConnections();await new Promise((resolve,reject)=>server.close(error=>error?reject(error):resolve()))} +}) + 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 () => { diff --git a/backend/src/services/mikrotik.ts b/backend/src/services/mikrotik.ts index a6bddb54..176cdf59 100644 --- a/backend/src/services/mikrotik.ts +++ b/backend/src/services/mikrotik.ts @@ -15,6 +15,10 @@ import type { } from "../types/server.js" const MAX_ROS_BODY_BYTES = 8 * 1024 * 1024 +// Shared pools bound concurrent REST logins across client instances. Close idle +// connections so RouterOS does not keep scarce authenticated sessions occupied. +const rosHttpAgent = new http.Agent({ keepAlive: false, maxSockets: 2 }) +const rosHttpsAgent = new https.Agent({ keepAlive: false, maxSockets: 2 }) function watchRosResponseFailure(res: http.IncomingMessage, reject: (error: Error) => void): void { res.once("aborted", () => reject(new Error("RouterOS response interrupted (ECONNRESET)"))) @@ -63,6 +67,7 @@ function rosRequest( } const lib = params.useSsl ? https : http + options.agent = params.useSsl ? rosHttpsAgent : rosHttpAgent const timer = setTimeout(() => { req.destroy(new Error(`Connection to ${params.host}:${params.port} timed out after ${timeoutMs / 1000}s`)) @@ -122,6 +127,7 @@ function rosPost( } const lib = params.useSsl ? https : http + options.agent = params.useSsl ? rosHttpsAgent : rosHttpAgent // An already-aborted signal can settle before the request is assigned. // eslint-disable-next-line prefer-const @@ -205,6 +211,7 @@ function rosPut( } const lib = params.useSsl ? https : http + options.agent = params.useSsl ? rosHttpsAgent : rosHttpAgent const timer = setTimeout(() => { req.destroy(new Error(`Connection to ${params.host}:${params.port} timed out after ${timeoutMs / 1000}s`)) }, timeoutMs) @@ -256,6 +263,7 @@ function rosDelete( } const lib = params.useSsl ? https : http + options.agent = params.useSsl ? rosHttpsAgent : rosHttpAgent const timer = setTimeout(() => { req.destroy(new Error(`Connection to ${params.host}:${params.port} timed out after ${timeoutMs / 1000}s`)) }, timeoutMs) @@ -307,6 +315,7 @@ function rosRawRequest( } const lib = params.useSsl ? https : http + options.agent = params.useSsl ? rosHttpsAgent : rosHttpAgent const req = lib.request(options, (res) => { watchRosResponseFailure(res, error => { clearTimeout(timer); reject(error) }) @@ -360,6 +369,7 @@ function rosPatch( } const lib = params.useSsl ? https : http + options.agent = params.useSsl ? rosHttpsAgent : rosHttpAgent const timer = setTimeout(() => { req.destroy(new Error(`Connection to ${params.host}:${params.port} timed out after ${timeoutMs / 1000}s`)) }, timeoutMs) diff --git a/docs/ux-ui-external-acceptance.md b/docs/ux-ui-external-acceptance.md index 77141878..c62fefa4 100644 --- a/docs/ux-ui-external-acceptance.md +++ b/docs/ux-ui-external-acceptance.md @@ -1,6 +1,6 @@ # UX/UI: протокол внешней приёмки 12.5 -Актуальное продолжение 2026-10-15: 256 — collector renewal использует подтверждённые dates без выдуманного revocation status; профиль/build/lint прошли, backend ux-certificates256/source528/dist1080 healthy. Реальное scheduler renewal выполнилось, served TLS SHA-256 сменился и совпал с новым native leaf, но импорт создал2 одинаковых имени: общая приёмка не завершена. Все own native/DNS/jobs/settings очищены/восстановлены; оба CHR доступны. Следующий257 — запрет ложного success при неоднозначном импорте и стратегия уникальной замены; отдельно лимит REST logins6. Этап12.5 открыт. +Актуальное продолжение 2026-10-15: 257 — post-import guard проверяет unique name/X509 SHA-256/private-key и оба SSL assignments; REST pools ограничивают запросы/закрывают idle sessions. Профили9 certificates/2 ros-file и общая backend regression прошли; backend ux-certificates257/source530/dist1084 healthy. Actual duplicate payload256 rejected applyCalls0. Controlled live parallel read12 на2CHR без login errors. Следующий258 — безопасная unique replacement без duplicate logical names; реальная повторная выдача после guard ещё не принята. Этап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). @@ -18,6 +18,22 @@ OSPF84: real GRE Full adjacency/private routes/Ping3/3 и4 live PC/320px URL/bro BFD85: Up/Full/own route/Ping,4 live UI и loss/withdrawal/recovery приняты; exact own cleanup8 коллекций×2 passed. Withdrawal5,245ms не подтверждает sub-second BFD timing; этот вопрос открыт. Runtime routing83 без новой сборки. [Отчёт](ux-ui/acceptance/ospf-bfd-loss-2026-10-09.json). +## 12.5.257 — достоверный импорт сертификата и ограничение REST-сессий (2026-10-15) + +После реального256 ACME issuer больше не считает HTTP ACK достаточным. applyVerifiedCertificateToServices перед назначением читает raw /certificate, требует unique logical name, SHA-256 ожидаемого выданного X509 leaf и private-key; после применения повторяет проверку leaf и читает configured /ip/service, подтверждая www-ssl/api-ssl enabled с нужным сертификатом. Missing/ambiguous/wrong fingerprint/private-key и неизвестное/ошибочное назначение приводят к failed через existing runner, не к done. Auto-delete импортированных объектов или rollback неизвестной частичной операции не добавлялся; operator видит ошибку и должен учитывать фактические изменения. + +Новый certificate-installation.test:6 invalid imports never apply,4 service mismatches и post-apply ambiguity не дают успеха, positive подтверждённого apply проходит. Certificate profile теперь9 scripts. Production compiled helper проверен на точном captured REST JSON256 с2 одинаковыми именами: rejected/applyCalls0/networkCalls0. Это проверка настоящего payload, не новый RouterOS import/CA order. [Captured guard](ux-ui/acceptance/certificates257-captured-duplicate.json). + +Initial intermediate postguard deployment ещё давал ошибки устройств в live assertion из-за известного login cap6. Общий transport теперь использует отдельные shared HTTP/HTTPS agents с keepAlivefalse/maxSockets2: ограничение на pool/origin в каждом протоколе, idle connections не занимают login slots. GET/POST/PUT/PATCH/DELETE/file HTTP пути используют эти pools; TLS rejectUnauthorized остаётся исходным verifySsl, credentials/HTTP ошибки/abort/deadlines/replay rules не менялись. Не заявляем глобальный cap между backend instances, hostname aliases или другими SSH/WinBox клиентами. HTTP/HTTPS pools отдельные; нагрузочная live проверка относится к HTTP REST на двух CHR. + +Transport тесты:12 запросов из3 разных client instances peak2, idle sockets0; queued PUT deadline истёк до отправки и позже не дошёл до устройства. Старые interrupted/malformed/explicit rejection/no write replay случаи сохранены. test:ros-file (2scripts, response suite10 cases),9 certificate scripts, contracts/backend build и scoped ESLint passed. Initial TCP tests внутри sandbox были отвергнуты окружением, тот же suite с разрешённым локальным TCP passed. Ошибка ignored packaging replace256→257 затронула слово sha256 и остановилась до upload/build; corrected script сохранил алгоритм SHA-256, final manifest подтверждён. + +Из-за изменения общей транспортной основы выполнена полная backend profile regression на собственной SQL БД/ограниченной роли, не production DSN. Все25 profiles passed; private logs остаются вне Git. Пропущенные специальные SQL/UDP ветки перечислены в report, не объявляются выполненными: их наличие не скрывается общим passed. Own test DB/role/container удалены и отсутствие подтверждено. [Full regression](ux-ui/acceptance/certificates257-full-regression.json). + +Финальные production/acceptance images собраны; source530/compiled1084 matched и backend ux-certificates257 healthy. Frontend ux-certificates251/DB/Portal сохранены. После полной регрессии backend перезапущен на final image; [deployment](ux-ui/acceptance/certificates257-deployment.json). Настоящий Portal/read1440/390: оба CHR без ошибок, overflow/JS errors/writes0. [Live UI](ux-ui/acceptance/certificates257-live-ui.json). Дополнительно12 параллельных state reads через реальный API,2CHR в каждом ответе, все200/errornull, без RouterOS login ошибок [live load](ux-ui/acceptance/certificates257-live-load.json). Это конкретный controlled burst, не длительная нагрузочная/многопроцессная приёмка. + +Новых ACME/DNS/native объектов257 не создавали; cleanup256 сохранён, passwords unchanged. Guard исправляет ложный success при duplicate import, но ещё не реализует безопасную уникальную замену: при следующем старом алгоритме duplicate name будет честно failed. Следующий258 — стратегия unique imported generation, verified rebinding и сохранение старого working leaf до подтверждения нового, затем реальная приёмка с fingerprint/TLS и полным откатом в разрешённом scope. Без этого пункт renewal остаётся открытым. + ## 12.5.256 — реальный scheduler renewal, дубликаты native имён и полный откат (2026-10-15) Начат2026-10-14, завершён2026-10-15 по Новосибирску. Пятичасовой лимит сбросился, недельный остался ограниченным. По действующему разрешению254 снят новый native/SQL baseline, own jobs/certificates/DNS0, token/zone/account проверены. Первый UI issue exact mmt253-8e62a941.pngs.top/manual →done, trusted TLS/private-key/native SHA-256 подтверждены. [UI](ux-ui/acceptance/acme256-live-ui.json), [before](ux-ui/acceptance/acme256-tls-before.json). diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index c088545e..32fa1e07 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -1,9 +1,25 @@ # План реализации UX/UI по пятичасовым окнам -Актуальное продолжение 2026-10-15: 256 — collector renewal использует подтверждённые dates без выдуманного revocation status; профиль/build/lint прошли, backend ux-certificates256/source528/dist1080 healthy. Реальное scheduler renewal выполнилось, served TLS SHA-256 сменился и совпал с новым native leaf, но импорт создал2 одинаковых имени: общая приёмка не завершена. Все own native/DNS/jobs/settings очищены/восстановлены; оба CHR доступны. Следующий257 — запрет ложного success при неоднозначном импорте и стратегия уникальной замены; отдельно лимит REST logins6. Этап12.5 открыт. +Актуальное продолжение 2026-10-15: 257 — post-import guard проверяет unique name/X509 SHA-256/private-key и оба SSL assignments; REST pools ограничивают запросы/закрывают idle sessions. Профили9 certificates/2 ros-file и общая backend regression прошли; backend ux-certificates257/source530/dist1084 healthy. Actual duplicate payload256 rejected applyCalls0. Controlled live parallel read12 на2CHR без login errors. Следующий258 — безопасная unique replacement без duplicate logical names; реальная повторная выдача после guard ещё не принята. Этап12.5 открыт. Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-14. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат. +## 12.5.257 — достоверный импорт сертификата и ограничение REST-сессий (2026-10-15) + +После реального256 ACME issuer больше не считает HTTP ACK достаточным. applyVerifiedCertificateToServices перед назначением читает raw /certificate, требует unique logical name, SHA-256 ожидаемого выданного X509 leaf и private-key; после применения повторяет проверку leaf и читает configured /ip/service, подтверждая www-ssl/api-ssl enabled с нужным сертификатом. Missing/ambiguous/wrong fingerprint/private-key и неизвестное/ошибочное назначение приводят к failed через existing runner, не к done. Auto-delete импортированных объектов или rollback неизвестной частичной операции не добавлялся; operator видит ошибку и должен учитывать фактические изменения. + +Новый certificate-installation.test:6 invalid imports never apply,4 service mismatches и post-apply ambiguity не дают успеха, positive подтверждённого apply проходит. Certificate profile теперь9 scripts. Production compiled helper проверен на точном captured REST JSON256 с2 одинаковыми именами: rejected/applyCalls0/networkCalls0. Это проверка настоящего payload, не новый RouterOS import/CA order. [Captured guard](ux-ui/acceptance/certificates257-captured-duplicate.json). + +Initial intermediate postguard deployment ещё давал ошибки устройств в live assertion из-за известного login cap6. Общий transport теперь использует отдельные shared HTTP/HTTPS agents с keepAlivefalse/maxSockets2: ограничение на pool/origin в каждом протоколе, idle connections не занимают login slots. GET/POST/PUT/PATCH/DELETE/file HTTP пути используют эти pools; TLS rejectUnauthorized остаётся исходным verifySsl, credentials/HTTP ошибки/abort/deadlines/replay rules не менялись. Не заявляем глобальный cap между backend instances, hostname aliases или другими SSH/WinBox клиентами. HTTP/HTTPS pools отдельные; нагрузочная live проверка относится к HTTP REST на двух CHR. + +Transport тесты:12 запросов из3 разных client instances peak2, idle sockets0; queued PUT deadline истёк до отправки и позже не дошёл до устройства. Старые interrupted/malformed/explicit rejection/no write replay случаи сохранены. test:ros-file (2scripts, response suite10 cases),9 certificate scripts, contracts/backend build и scoped ESLint passed. Initial TCP tests внутри sandbox были отвергнуты окружением, тот же suite с разрешённым локальным TCP passed. Ошибка ignored packaging replace256→257 затронула слово sha256 и остановилась до upload/build; corrected script сохранил алгоритм SHA-256, final manifest подтверждён. + +Из-за изменения общей транспортной основы выполнена полная backend profile regression на собственной SQL БД/ограниченной роли, не production DSN. Все25 profiles passed; private logs остаются вне Git. Пропущенные специальные SQL/UDP ветки перечислены в report, не объявляются выполненными: их наличие не скрывается общим passed. Own test DB/role/container удалены и отсутствие подтверждено. [Full regression](ux-ui/acceptance/certificates257-full-regression.json). + +Финальные production/acceptance images собраны; source530/compiled1084 matched и backend ux-certificates257 healthy. Frontend ux-certificates251/DB/Portal сохранены. После полной регрессии backend перезапущен на final image; [deployment](ux-ui/acceptance/certificates257-deployment.json). Настоящий Portal/read1440/390: оба CHR без ошибок, overflow/JS errors/writes0. [Live UI](ux-ui/acceptance/certificates257-live-ui.json). Дополнительно12 параллельных state reads через реальный API,2CHR в каждом ответе, все200/errornull, без RouterOS login ошибок [live load](ux-ui/acceptance/certificates257-live-load.json). Это конкретный controlled burst, не длительная нагрузочная/многопроцессная приёмка. + +Новых ACME/DNS/native объектов257 не создавали; cleanup256 сохранён, passwords unchanged. Guard исправляет ложный success при duplicate import, но ещё не реализует безопасную уникальную замену: при следующем старом алгоритме duplicate name будет честно failed. Следующий258 — стратегия unique imported generation, verified rebinding и сохранение старого working leaf до подтверждения нового, затем реальная приёмка с fingerprint/TLS и полным откатом в разрешённом scope. Без этого пункт renewal остаётся открытым. + ## 12.5.256 — реальный scheduler renewal, дубликаты native имён и полный откат (2026-10-15) Начат2026-10-14, завершён2026-10-15 по Новосибирску. Пятичасовой лимит сбросился, недельный остался ограниченным. По действующему разрешению254 снят новый native/SQL baseline, own jobs/certificates/DNS0, token/zone/account проверены. Первый UI issue exact mmt253-8e62a941.pngs.top/manual →done, trusted TLS/private-key/native SHA-256 подтверждены. [UI](ux-ui/acceptance/acme256-live-ui.json), [before](ux-ui/acceptance/acme256-tls-before.json). diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index b3c051d5..3bd4442a 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -1,6 +1,6 @@ # UX/UI: журнал реализации -Актуальное продолжение 2026-10-15: 256 — collector renewal использует подтверждённые dates без выдуманного revocation status; профиль/build/lint прошли, backend ux-certificates256/source528/dist1080 healthy. Реальное scheduler renewal выполнилось, served TLS SHA-256 сменился и совпал с новым native leaf, но импорт создал2 одинаковых имени: общая приёмка не завершена. Все own native/DNS/jobs/settings очищены/восстановлены; оба CHR доступны. Следующий257 — запрет ложного success при неоднозначном импорте и стратегия уникальной замены; отдельно лимит REST logins6. Этап12.5 открыт. +Актуальное продолжение 2026-10-15: 257 — post-import guard проверяет unique name/X509 SHA-256/private-key и оба SSL assignments; REST pools ограничивают запросы/закрывают idle sessions. Профили9 certificates/2 ros-file и общая backend regression прошли; backend ux-certificates257/source530/dist1084 healthy. Actual duplicate payload256 rejected applyCalls0. Controlled live parallel read12 на2CHR без login errors. Следующий258 — безопасная unique replacement без duplicate logical names; реальная повторная выдача после guard ещё не принята. Этап12.5 открыт. Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md). @@ -36,7 +36,23 @@ | 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–256 в записанных scopes; real renewal/TLS256 выполнен, дубликаты native имён и REST concurrency остаются открыты в записанных scopes. Runtime backend ux-certificates256/frontend ux-certificates251. Общая регрессия249 и профиль сертификатов252 прошли; реальный renewal остаётся открытым. | +| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная12.3.83; внешняя12.5 открыта, блоки1–257 в записанных scopes; guard/REST pool257 проверены, unique replacement258 остаётся открытой в записанных scopes. Runtime backend ux-certificates257/frontend ux-certificates251. Общая регрессия257 и профиль сертификатов252 прошли; реальный renewal остаётся открытым. | + +## 12.5.257 — достоверный импорт сертификата и ограничение REST-сессий (2026-10-15) + +После реального256 ACME issuer больше не считает HTTP ACK достаточным. applyVerifiedCertificateToServices перед назначением читает raw /certificate, требует unique logical name, SHA-256 ожидаемого выданного X509 leaf и private-key; после применения повторяет проверку leaf и читает configured /ip/service, подтверждая www-ssl/api-ssl enabled с нужным сертификатом. Missing/ambiguous/wrong fingerprint/private-key и неизвестное/ошибочное назначение приводят к failed через existing runner, не к done. Auto-delete импортированных объектов или rollback неизвестной частичной операции не добавлялся; operator видит ошибку и должен учитывать фактические изменения. + +Новый certificate-installation.test:6 invalid imports never apply,4 service mismatches и post-apply ambiguity не дают успеха, positive подтверждённого apply проходит. Certificate profile теперь9 scripts. Production compiled helper проверен на точном captured REST JSON256 с2 одинаковыми именами: rejected/applyCalls0/networkCalls0. Это проверка настоящего payload, не новый RouterOS import/CA order. [Captured guard](ux-ui/acceptance/certificates257-captured-duplicate.json). + +Initial intermediate postguard deployment ещё давал ошибки устройств в live assertion из-за известного login cap6. Общий transport теперь использует отдельные shared HTTP/HTTPS agents с keepAlivefalse/maxSockets2: ограничение на pool/origin в каждом протоколе, idle connections не занимают login slots. GET/POST/PUT/PATCH/DELETE/file HTTP пути используют эти pools; TLS rejectUnauthorized остаётся исходным verifySsl, credentials/HTTP ошибки/abort/deadlines/replay rules не менялись. Не заявляем глобальный cap между backend instances, hostname aliases или другими SSH/WinBox клиентами. HTTP/HTTPS pools отдельные; нагрузочная live проверка относится к HTTP REST на двух CHR. + +Transport тесты:12 запросов из3 разных client instances peak2, idle sockets0; queued PUT deadline истёк до отправки и позже не дошёл до устройства. Старые interrupted/malformed/explicit rejection/no write replay случаи сохранены. test:ros-file (2scripts, response suite10 cases),9 certificate scripts, contracts/backend build и scoped ESLint passed. Initial TCP tests внутри sandbox были отвергнуты окружением, тот же suite с разрешённым локальным TCP passed. Ошибка ignored packaging replace256→257 затронула слово sha256 и остановилась до upload/build; corrected script сохранил алгоритм SHA-256, final manifest подтверждён. + +Из-за изменения общей транспортной основы выполнена полная backend profile regression на собственной SQL БД/ограниченной роли, не production DSN. Все25 profiles passed; private logs остаются вне Git. Пропущенные специальные SQL/UDP ветки перечислены в report, не объявляются выполненными: их наличие не скрывается общим passed. Own test DB/role/container удалены и отсутствие подтверждено. [Full regression](ux-ui/acceptance/certificates257-full-regression.json). + +Финальные production/acceptance images собраны; source530/compiled1084 matched и backend ux-certificates257 healthy. Frontend ux-certificates251/DB/Portal сохранены. После полной регрессии backend перезапущен на final image; [deployment](ux-ui/acceptance/certificates257-deployment.json). Настоящий Portal/read1440/390: оба CHR без ошибок, overflow/JS errors/writes0. [Live UI](ux-ui/acceptance/certificates257-live-ui.json). Дополнительно12 параллельных state reads через реальный API,2CHR в каждом ответе, все200/errornull, без RouterOS login ошибок [live load](ux-ui/acceptance/certificates257-live-load.json). Это конкретный controlled burst, не длительная нагрузочная/многопроцессная приёмка. + +Новых ACME/DNS/native объектов257 не создавали; cleanup256 сохранён, passwords unchanged. Guard исправляет ложный success при duplicate import, но ещё не реализует безопасную уникальную замену: при следующем старом алгоритме duplicate name будет честно failed. Следующий258 — стратегия unique imported generation, verified rebinding и сохранение старого working leaf до подтверждения нового, затем реальная приёмка с fingerprint/TLS и полным откатом в разрешённом scope. Без этого пункт renewal остаётся открытым. ## 12.5.256 — реальный scheduler renewal, дубликаты native имён и полный откат (2026-10-15) diff --git a/docs/ux-ui-remaining-acceptance.md b/docs/ux-ui-remaining-acceptance.md index 53049fe5..0801c506 100644 --- a/docs/ux-ui-remaining-acceptance.md +++ b/docs/ux-ui-remaining-acceptance.md @@ -1,10 +1,26 @@ # Оставшаяся внешняя приёмка — 2026-10-14 -Актуальное продолжение 2026-10-15: 256 — collector renewal использует подтверждённые dates без выдуманного revocation status; профиль/build/lint прошли, backend ux-certificates256/source528/dist1080 healthy. Реальное scheduler renewal выполнилось, served TLS SHA-256 сменился и совпал с новым native leaf, но импорт создал2 одинаковых имени: общая приёмка не завершена. Все own native/DNS/jobs/settings очищены/восстановлены; оба CHR доступны. Следующий257 — запрет ложного success при неоднозначном импорте и стратегия уникальной замены; отдельно лимит REST logins6. Этап12.5 открыт. +Актуальное продолжение 2026-10-15: 257 — post-import guard проверяет unique name/X509 SHA-256/private-key и оба SSL assignments; REST pools ограничивают запросы/закрывают idle sessions. Профили9 certificates/2 ros-file и общая backend regression прошли; backend ux-certificates257/source530/dist1084 healthy. Actual duplicate payload256 rejected applyCalls0. Controlled live parallel read12 на2CHR без login errors. Следующий258 — безопасная unique replacement без duplicate logical names; реальная повторная выдача после guard ещё не принята. Этап12.5 открыт. -Это актуальная матрица после 12.5.256. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии. +Это актуальная матрица после 12.5.257. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии. -Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-certificates256`, frontend `ux-certificates251`; production lab с195 использует AUTH_REQUIRED=true и Portal1.3.1. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись. +Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-certificates257`, frontend `ux-certificates251`; production lab с195 использует AUTH_REQUIRED=true и Portal1.3.1. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись. + +## 12.5.257 — достоверный импорт сертификата и ограничение REST-сессий (2026-10-15) + +После реального256 ACME issuer больше не считает HTTP ACK достаточным. applyVerifiedCertificateToServices перед назначением читает raw /certificate, требует unique logical name, SHA-256 ожидаемого выданного X509 leaf и private-key; после применения повторяет проверку leaf и читает configured /ip/service, подтверждая www-ssl/api-ssl enabled с нужным сертификатом. Missing/ambiguous/wrong fingerprint/private-key и неизвестное/ошибочное назначение приводят к failed через existing runner, не к done. Auto-delete импортированных объектов или rollback неизвестной частичной операции не добавлялся; operator видит ошибку и должен учитывать фактические изменения. + +Новый certificate-installation.test:6 invalid imports never apply,4 service mismatches и post-apply ambiguity не дают успеха, positive подтверждённого apply проходит. Certificate profile теперь9 scripts. Production compiled helper проверен на точном captured REST JSON256 с2 одинаковыми именами: rejected/applyCalls0/networkCalls0. Это проверка настоящего payload, не новый RouterOS import/CA order. [Captured guard](ux-ui/acceptance/certificates257-captured-duplicate.json). + +Initial intermediate postguard deployment ещё давал ошибки устройств в live assertion из-за известного login cap6. Общий transport теперь использует отдельные shared HTTP/HTTPS agents с keepAlivefalse/maxSockets2: ограничение на pool/origin в каждом протоколе, idle connections не занимают login slots. GET/POST/PUT/PATCH/DELETE/file HTTP пути используют эти pools; TLS rejectUnauthorized остаётся исходным verifySsl, credentials/HTTP ошибки/abort/deadlines/replay rules не менялись. Не заявляем глобальный cap между backend instances, hostname aliases или другими SSH/WinBox клиентами. HTTP/HTTPS pools отдельные; нагрузочная live проверка относится к HTTP REST на двух CHR. + +Transport тесты:12 запросов из3 разных client instances peak2, idle sockets0; queued PUT deadline истёк до отправки и позже не дошёл до устройства. Старые interrupted/malformed/explicit rejection/no write replay случаи сохранены. test:ros-file (2scripts, response suite10 cases),9 certificate scripts, contracts/backend build и scoped ESLint passed. Initial TCP tests внутри sandbox были отвергнуты окружением, тот же suite с разрешённым локальным TCP passed. Ошибка ignored packaging replace256→257 затронула слово sha256 и остановилась до upload/build; corrected script сохранил алгоритм SHA-256, final manifest подтверждён. + +Из-за изменения общей транспортной основы выполнена полная backend profile regression на собственной SQL БД/ограниченной роли, не production DSN. Все25 profiles passed; private logs остаются вне Git. Пропущенные специальные SQL/UDP ветки перечислены в report, не объявляются выполненными: их наличие не скрывается общим passed. Own test DB/role/container удалены и отсутствие подтверждено. [Full regression](ux-ui/acceptance/certificates257-full-regression.json). + +Финальные production/acceptance images собраны; source530/compiled1084 matched и backend ux-certificates257 healthy. Frontend ux-certificates251/DB/Portal сохранены. После полной регрессии backend перезапущен на final image; [deployment](ux-ui/acceptance/certificates257-deployment.json). Настоящий Portal/read1440/390: оба CHR без ошибок, overflow/JS errors/writes0. [Live UI](ux-ui/acceptance/certificates257-live-ui.json). Дополнительно12 параллельных state reads через реальный API,2CHR в каждом ответе, все200/errornull, без RouterOS login ошибок [live load](ux-ui/acceptance/certificates257-live-load.json). Это конкретный controlled burst, не длительная нагрузочная/многопроцессная приёмка. + +Новых ACME/DNS/native объектов257 не создавали; cleanup256 сохранён, passwords unchanged. Guard исправляет ложный success при duplicate import, но ещё не реализует безопасную уникальную замену: при следующем старом алгоритме duplicate name будет честно failed. Следующий258 — стратегия unique imported generation, verified rebinding и сохранение старого working leaf до подтверждения нового, затем реальная приёмка с fingerprint/TLS и полным откатом в разрешённом scope. Без этого пункт renewal остаётся открытым. ## 12.5.256 — реальный scheduler renewal, дубликаты native имён и полный откат (2026-10-15) @@ -310,7 +326,7 @@ Portal offline JWT действует доexp после logout/role change; э ## Следующая последовательность — 2026-10-14 -1. **Сертификаты257:** real scheduler renewal256 сменил TLS, но создаёт duplicate logical names. Исправить post-import verification/unique replacement; отдельно REST max concurrent logins6. Приёмка пока открыта. +1. **Сертификаты258:** post-import verification/REST pool257 приняты в scopes; реализовать safe unique replacement и подтвердить реальное repeat renewal/TLS без duplicate logical names. Не считать защитный failed полной приёмкой renewal. 2. **Профильный контроль:** для новых изменений выполнять затронутые SQL/API/UI проверки. Общая регрессия192 выполнена; не повторять её без изменений общей основы или нового основания. 3. **Внешние зависимости:** Portal issuer/login/revoke, Telegram bot/chat/разрешённое сообщение, клиентские ОС, независимые WAN/public IPv6 и scale/multi-instance — отдельные группы по таблице выше. Ограничения текущего стенда не превращать в обещание приёмки всех конфигураций. diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index 664560be..d2b5e6af 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -1,6 +1,6 @@ # Этап 12: итоговая приёмка UX/UI -Актуальное продолжение 2026-10-15: 256 — collector renewal использует подтверждённые dates без выдуманного revocation status; профиль/build/lint прошли, backend ux-certificates256/source528/dist1080 healthy. Реальное scheduler renewal выполнилось, served TLS SHA-256 сменился и совпал с новым native leaf, но импорт создал2 одинаковых имени: общая приёмка не завершена. Все own native/DNS/jobs/settings очищены/восстановлены; оба CHR доступны. Следующий257 — запрет ложного success при неоднозначном импорте и стратегия уникальной замены; отдельно лимит REST logins6. Этап12.5 открыт. +Актуальное продолжение 2026-10-15: 257 — post-import guard проверяет unique name/X509 SHA-256/private-key и оба SSL assignments; REST pools ограничивают запросы/закрывают idle sessions. Профили9 certificates/2 ros-file и общая backend regression прошли; backend ux-certificates257/source530/dist1084 healthy. Actual duplicate payload256 rejected applyCalls0. Controlled live parallel read12 на2CHR без login errors. Следующий258 — безопасная unique replacement без duplicate logical names; реальная повторная выдача после guard ещё не принята. Этап12.5 открыт. План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push. @@ -4152,6 +4152,22 @@ Real Portal/browser API, provider local verified: создан один job ро Runtime backend ux-backup234/frontend ux-routing218 healthy; новое application image для volume-only fix не требовалось. Пароли CHR не менялись, S3 mutations0 в235, configs body/JWT не опубликованы, private local manifest удалён. Исходные10 missing local files не восстановлены и records не удалены. Force-recreate — не host reboot/power loss/fsync/DR приёмка. Далее236 — UX недоступных local files, затем version retention границы и оставшиеся7 группы матрицы; multi-instance guard не принимается. Этап12.5 открыт. +## 12.5.257 — достоверный импорт сертификата и ограничение REST-сессий (2026-10-15) + +После реального256 ACME issuer больше не считает HTTP ACK достаточным. applyVerifiedCertificateToServices перед назначением читает raw /certificate, требует unique logical name, SHA-256 ожидаемого выданного X509 leaf и private-key; после применения повторяет проверку leaf и читает configured /ip/service, подтверждая www-ssl/api-ssl enabled с нужным сертификатом. Missing/ambiguous/wrong fingerprint/private-key и неизвестное/ошибочное назначение приводят к failed через existing runner, не к done. Auto-delete импортированных объектов или rollback неизвестной частичной операции не добавлялся; operator видит ошибку и должен учитывать фактические изменения. + +Новый certificate-installation.test:6 invalid imports never apply,4 service mismatches и post-apply ambiguity не дают успеха, positive подтверждённого apply проходит. Certificate profile теперь9 scripts. Production compiled helper проверен на точном captured REST JSON256 с2 одинаковыми именами: rejected/applyCalls0/networkCalls0. Это проверка настоящего payload, не новый RouterOS import/CA order. [Captured guard](ux-ui/acceptance/certificates257-captured-duplicate.json). + +Initial intermediate postguard deployment ещё давал ошибки устройств в live assertion из-за известного login cap6. Общий transport теперь использует отдельные shared HTTP/HTTPS agents с keepAlivefalse/maxSockets2: ограничение на pool/origin в каждом протоколе, idle connections не занимают login slots. GET/POST/PUT/PATCH/DELETE/file HTTP пути используют эти pools; TLS rejectUnauthorized остаётся исходным verifySsl, credentials/HTTP ошибки/abort/deadlines/replay rules не менялись. Не заявляем глобальный cap между backend instances, hostname aliases или другими SSH/WinBox клиентами. HTTP/HTTPS pools отдельные; нагрузочная live проверка относится к HTTP REST на двух CHR. + +Transport тесты:12 запросов из3 разных client instances peak2, idle sockets0; queued PUT deadline истёк до отправки и позже не дошёл до устройства. Старые interrupted/malformed/explicit rejection/no write replay случаи сохранены. test:ros-file (2scripts, response suite10 cases),9 certificate scripts, contracts/backend build и scoped ESLint passed. Initial TCP tests внутри sandbox были отвергнуты окружением, тот же suite с разрешённым локальным TCP passed. Ошибка ignored packaging replace256→257 затронула слово sha256 и остановилась до upload/build; corrected script сохранил алгоритм SHA-256, final manifest подтверждён. + +Из-за изменения общей транспортной основы выполнена полная backend profile regression на собственной SQL БД/ограниченной роли, не production DSN. Все25 profiles passed; private logs остаются вне Git. Пропущенные специальные SQL/UDP ветки перечислены в report, не объявляются выполненными: их наличие не скрывается общим passed. Own test DB/role/container удалены и отсутствие подтверждено. [Full regression](ux-ui/acceptance/certificates257-full-regression.json). + +Финальные production/acceptance images собраны; source530/compiled1084 matched и backend ux-certificates257 healthy. Frontend ux-certificates251/DB/Portal сохранены. После полной регрессии backend перезапущен на final image; [deployment](ux-ui/acceptance/certificates257-deployment.json). Настоящий Portal/read1440/390: оба CHR без ошибок, overflow/JS errors/writes0. [Live UI](ux-ui/acceptance/certificates257-live-ui.json). Дополнительно12 параллельных state reads через реальный API,2CHR в каждом ответе, все200/errornull, без RouterOS login ошибок [live load](ux-ui/acceptance/certificates257-live-load.json). Это конкретный controlled burst, не длительная нагрузочная/многопроцессная приёмка. + +Новых ACME/DNS/native объектов257 не создавали; cleanup256 сохранён, passwords unchanged. Guard исправляет ложный success при duplicate import, но ещё не реализует безопасную уникальную замену: при следующем старом алгоритме duplicate name будет честно failed. Следующий258 — стратегия unique imported generation, verified rebinding и сохранение старого working leaf до подтверждения нового, затем реальная приёмка с fingerprint/TLS и полным откатом в разрешённом scope. Без этого пункт renewal остаётся открытым. + ## 12.5.256 — реальный scheduler renewal, дубликаты native имён и полный откат (2026-10-15) Начат2026-10-14, завершён2026-10-15 по Новосибирску. Пятичасовой лимит сбросился, недельный остался ограниченным. По действующему разрешению254 снят новый native/SQL baseline, own jobs/certificates/DNS0, token/zone/account проверены. Первый UI issue exact mmt253-8e62a941.pngs.top/manual →done, trusted TLS/private-key/native SHA-256 подтверждены. [UI](ux-ui/acceptance/acme256-live-ui.json), [before](ux-ui/acceptance/acme256-tls-before.json). diff --git a/docs/ux-ui-test-server.md b/docs/ux-ui-test-server.md index e83c656d..ed018bee 100644 --- a/docs/ux-ui-test-server.md +++ b/docs/ux-ui-test-server.md @@ -1,6 +1,6 @@ # Тестовый сервер UX/UI — 2026-10-10 -Актуальное продолжение 2026-10-15: 256 — collector renewal использует подтверждённые dates без выдуманного revocation status; профиль/build/lint прошли, backend ux-certificates256/source528/dist1080 healthy. Реальное scheduler renewal выполнилось, served TLS SHA-256 сменился и совпал с новым native leaf, но импорт создал2 одинаковых имени: общая приёмка не завершена. Все own native/DNS/jobs/settings очищены/восстановлены; оба CHR доступны. Следующий257 — запрет ложного success при неоднозначном импорте и стратегия уникальной замены; отдельно лимит REST logins6. Этап12.5 открыт. +Актуальное продолжение 2026-10-15: 257 — post-import guard проверяет unique name/X509 SHA-256/private-key и оба SSL assignments; REST pools ограничивают запросы/закрывают idle sessions. Профили9 certificates/2 ros-file и общая backend regression прошли; backend ux-certificates257/source530/dist1084 healthy. Actual duplicate payload256 rejected applyCalls0. Controlled live parallel read12 на2CHR без login errors. Следующий258 — безопасная unique replacement без duplicate logical names; реальная повторная выдача после guard ещё не принята. Этап12.5 открыт. | Параметр | Проверенное состояние | | --- | --- | @@ -34,6 +34,22 @@ OSPF84: real GRE Full adjacency/private routes/Ping3/3 и4 live PC/320px URL/bro BFD85: Up/Full/own route/Ping,4 live UI и loss/withdrawal/recovery приняты; exact own cleanup8 коллекций×2 passed. Withdrawal5,245ms не подтверждает sub-second BFD timing; этот вопрос открыт. Runtime routing83 без новой сборки. [Отчёт](ux-ui/acceptance/ospf-bfd-loss-2026-10-09.json). +## 12.5.257 — достоверный импорт сертификата и ограничение REST-сессий (2026-10-15) + +После реального256 ACME issuer больше не считает HTTP ACK достаточным. applyVerifiedCertificateToServices перед назначением читает raw /certificate, требует unique logical name, SHA-256 ожидаемого выданного X509 leaf и private-key; после применения повторяет проверку leaf и читает configured /ip/service, подтверждая www-ssl/api-ssl enabled с нужным сертификатом. Missing/ambiguous/wrong fingerprint/private-key и неизвестное/ошибочное назначение приводят к failed через existing runner, не к done. Auto-delete импортированных объектов или rollback неизвестной частичной операции не добавлялся; operator видит ошибку и должен учитывать фактические изменения. + +Новый certificate-installation.test:6 invalid imports never apply,4 service mismatches и post-apply ambiguity не дают успеха, positive подтверждённого apply проходит. Certificate profile теперь9 scripts. Production compiled helper проверен на точном captured REST JSON256 с2 одинаковыми именами: rejected/applyCalls0/networkCalls0. Это проверка настоящего payload, не новый RouterOS import/CA order. [Captured guard](ux-ui/acceptance/certificates257-captured-duplicate.json). + +Initial intermediate postguard deployment ещё давал ошибки устройств в live assertion из-за известного login cap6. Общий transport теперь использует отдельные shared HTTP/HTTPS agents с keepAlivefalse/maxSockets2: ограничение на pool/origin в каждом протоколе, idle connections не занимают login slots. GET/POST/PUT/PATCH/DELETE/file HTTP пути используют эти pools; TLS rejectUnauthorized остаётся исходным verifySsl, credentials/HTTP ошибки/abort/deadlines/replay rules не менялись. Не заявляем глобальный cap между backend instances, hostname aliases или другими SSH/WinBox клиентами. HTTP/HTTPS pools отдельные; нагрузочная live проверка относится к HTTP REST на двух CHR. + +Transport тесты:12 запросов из3 разных client instances peak2, idle sockets0; queued PUT deadline истёк до отправки и позже не дошёл до устройства. Старые interrupted/malformed/explicit rejection/no write replay случаи сохранены. test:ros-file (2scripts, response suite10 cases),9 certificate scripts, contracts/backend build и scoped ESLint passed. Initial TCP tests внутри sandbox были отвергнуты окружением, тот же suite с разрешённым локальным TCP passed. Ошибка ignored packaging replace256→257 затронула слово sha256 и остановилась до upload/build; corrected script сохранил алгоритм SHA-256, final manifest подтверждён. + +Из-за изменения общей транспортной основы выполнена полная backend profile regression на собственной SQL БД/ограниченной роли, не production DSN. Все25 profiles passed; private logs остаются вне Git. Пропущенные специальные SQL/UDP ветки перечислены в report, не объявляются выполненными: их наличие не скрывается общим passed. Own test DB/role/container удалены и отсутствие подтверждено. [Full regression](ux-ui/acceptance/certificates257-full-regression.json). + +Финальные production/acceptance images собраны; source530/compiled1084 matched и backend ux-certificates257 healthy. Frontend ux-certificates251/DB/Portal сохранены. После полной регрессии backend перезапущен на final image; [deployment](ux-ui/acceptance/certificates257-deployment.json). Настоящий Portal/read1440/390: оба CHR без ошибок, overflow/JS errors/writes0. [Live UI](ux-ui/acceptance/certificates257-live-ui.json). Дополнительно12 параллельных state reads через реальный API,2CHR в каждом ответе, все200/errornull, без RouterOS login ошибок [live load](ux-ui/acceptance/certificates257-live-load.json). Это конкретный controlled burst, не длительная нагрузочная/многопроцессная приёмка. + +Новых ACME/DNS/native объектов257 не создавали; cleanup256 сохранён, passwords unchanged. Guard исправляет ложный success при duplicate import, но ещё не реализует безопасную уникальную замену: при следующем старом алгоритме duplicate name будет честно failed. Следующий258 — стратегия unique imported generation, verified rebinding и сохранение старого working leaf до подтверждения нового, затем реальная приёмка с fingerprint/TLS и полным откатом в разрешённом scope. Без этого пункт renewal остаётся открытым. + ## 12.5.256 — реальный scheduler renewal, дубликаты native имён и полный откат (2026-10-15) Начат2026-10-14, завершён2026-10-15 по Новосибирску. Пятичасовой лимит сбросился, недельный остался ограниченным. По действующему разрешению254 снят новый native/SQL baseline, own jobs/certificates/DNS0, token/zone/account проверены. Первый UI issue exact mmt253-8e62a941.pngs.top/manual →done, trusted TLS/private-key/native SHA-256 подтверждены. [UI](ux-ui/acceptance/acme256-live-ui.json), [before](ux-ui/acceptance/acme256-tls-before.json). diff --git a/docs/ux-ui/acceptance/certificates257-captured-duplicate.json b/docs/ux-ui/acceptance/certificates257-captured-duplicate.json new file mode 100644 index 00000000..a90bbbc4 --- /dev/null +++ b/docs/ux-ui/acceptance/certificates257-captured-duplicate.json @@ -0,0 +1 @@ +{"capturedRows":2,"duplicateLogicalNames":true,"rejected":true,"applyCalls":0,"networkCalls":0} diff --git a/docs/ux-ui/acceptance/certificates257-deployment.json b/docs/ux-ui/acceptance/certificates257-deployment.json new file mode 100644 index 00000000..1114d94f --- /dev/null +++ b/docs/ux-ui/acceptance/certificates257-deployment.json @@ -0,0 +1,32 @@ +{ + "date": "2026-10-15", + "block": "12.5.257", + "backendImage": "mmapp-test/backend:ux-certificates257", + "frontendImage": "mmapp-test/frontend:ux-certificates251", + "backendHealth": "healthy", + "sourceMatched": 530, + "compiled": { + "matched": 1084, + "missing": 0, + "different": 0 + }, + "productionBuild": "passed", + "acceptanceBuild": "passed", + "profileScripts": { + "certificates": 9, + "rosFile": 2 + }, + "localTcpResponseCases": 10, + "fullBackendProfiles": 25, + "fullBackendDurationMs": 384057, + "ownedSqlCleanupConfirmed": true, + "scopedLint": "passed", + "finalDeploymentExit": 0, + "capturedDuplicateGuard": { + "rejected": true, + "applyCalls": 0, + "networkCalls": 0 + }, + "newAcmeOrders": 0, + "uniqueRenewalReplacementImplemented": false +} diff --git a/docs/ux-ui/acceptance/certificates257-full-regression.json b/docs/ux-ui/acceptance/certificates257-full-regression.json new file mode 100644 index 00000000..5f09ec5b --- /dev/null +++ b/docs/ux-ui/acceptance/certificates257-full-regression.json @@ -0,0 +1,191 @@ +{ + "date": "2026-10-15", + "block": "12.5.257", + "scope": "full backend profile list on owned isolated PostgreSQL; no production DSN", + "database": "mm_accept_166_1791998256460", + "profiles": [ + { + "profile": "test:probes", + "passed": true, + "durationMs": 28041, + "skippedLineCount": 1, + "unavailableSqlBranches": [] + }, + { + "profile": "test:exec", + "passed": true, + "durationMs": 8427, + "skippedLineCount": 1, + "unavailableSqlBranches": [] + }, + { + "profile": "test:alert-engine", + "passed": true, + "durationMs": 23141, + "skippedLineCount": 0, + "unavailableSqlBranches": [] + }, + { + "profile": "test:alerts-telegram", + "passed": true, + "durationMs": 15935, + "skippedLineCount": 10, + "unavailableSqlBranches": [] + }, + { + "profile": "test:auth", + "passed": true, + "durationMs": 11217, + "skippedLineCount": 0, + "unavailableSqlBranches": [] + }, + { + "profile": "test:wireguard", + "passed": true, + "durationMs": 13537, + "skippedLineCount": 1, + "unavailableSqlBranches": [] + }, + { + "profile": "test:ipsec", + "passed": true, + "durationMs": 16641, + "skippedLineCount": 0, + "unavailableSqlBranches": [] + }, + { + "profile": "test:traffic-rate", + "passed": true, + "durationMs": 51851, + "skippedLineCount": 1, + "unavailableSqlBranches": [] + }, + { + "profile": "test:traffic-flow", + "passed": true, + "durationMs": 53259, + "skippedLineCount": 2, + "unavailableSqlBranches": [ + "Monthly SQL: skip (requires an owned isolated database)", + "IPv6 UDP/worker/SQL: skip (requires an owned isolated database)" + ] + }, + { + "profile": "test:users", + "passed": true, + "durationMs": 7589, + "skippedLineCount": 0, + "unavailableSqlBranches": [] + }, + { + "profile": "test:pg", + "passed": true, + "durationMs": 10389, + "skippedLineCount": 0, + "unavailableSqlBranches": [] + }, + { + "profile": "test:backups", + "passed": true, + "durationMs": 12214, + "skippedLineCount": 8, + "unavailableSqlBranches": [] + }, + { + "profile": "test:scheduler", + "passed": true, + "durationMs": 6700, + "skippedLineCount": 5, + "unavailableSqlBranches": [] + }, + { + "profile": "test:live-maps", + "passed": true, + "durationMs": 28413, + "skippedLineCount": 0, + "unavailableSqlBranches": [] + }, + { + "profile": "test:ospf-optimizer", + "passed": true, + "durationMs": 6984, + "skippedLineCount": 1, + "unavailableSqlBranches": [] + }, + { + "profile": "test:recursive-routes", + "passed": true, + "durationMs": 4071, + "skippedLineCount": 0, + "unavailableSqlBranches": [] + }, + { + "profile": "test:config-sync", + "passed": true, + "durationMs": 18996, + "skippedLineCount": 1, + "unavailableSqlBranches": [ + "network-disabled-server.test.ts: invalid IDs passed; own SQL/disabled API checks skipped (MMAPP_OWN_TEST_DB required)" + ] + }, + { + "profile": "test:ros-file", + "passed": true, + "durationMs": 2076, + "skippedLineCount": 1, + "unavailableSqlBranches": [] + }, + { + "profile": "test:containers", + "passed": true, + "durationMs": 8354, + "skippedLineCount": 0, + "unavailableSqlBranches": [] + }, + { + "profile": "test:certificates", + "passed": true, + "durationMs": 16931, + "skippedLineCount": 3, + "unavailableSqlBranches": [] + }, + { + "profile": "test:netflow-settings", + "passed": true, + "durationMs": 4969, + "skippedLineCount": 0, + "unavailableSqlBranches": [] + }, + { + "profile": "test:netflow-purge", + "passed": true, + "durationMs": 2548, + "skippedLineCount": 0, + "unavailableSqlBranches": [] + }, + { + "profile": "test:evolution", + "passed": true, + "durationMs": 5874, + "skippedLineCount": 1, + "unavailableSqlBranches": [] + }, + { + "profile": "test:system-database", + "passed": true, + "durationMs": 19953, + "skippedLineCount": 9, + "unavailableSqlBranches": [] + }, + { + "profile": "test:server-privacy", + "passed": true, + "durationMs": 5947, + "skippedLineCount": 0, + "unavailableSqlBranches": [] + } + ], + "passed": true, + "ownedDatabaseAndRoleRemoved": true, + "testContainerRemoved": true +} \ No newline at end of file diff --git a/docs/ux-ui/acceptance/certificates257-live-load.json b/docs/ux-ui/acceptance/certificates257-live-load.json new file mode 100644 index 00000000..11f15e12 --- /dev/null +++ b/docs/ux-ui/acceptance/certificates257-live-load.json @@ -0,0 +1,191 @@ +{ + "date": "2026-10-15", + "block": "12.5.257", + "realPortal": true, + "noMocks": true, + "nonAuthApiWrites": 0, + "concurrentCatalogReads": 12, + "durationMs": 6763, + "samples": [ + { + "status": 200, + "servers": [ + { + "serverId": "1", + "error": null, + "certificateCount": 0 + }, + { + "serverId": "2", + "error": null, + "certificateCount": 0 + } + ] + }, + { + "status": 200, + "servers": [ + { + "serverId": "1", + "error": null, + "certificateCount": 0 + }, + { + "serverId": "2", + "error": null, + "certificateCount": 0 + } + ] + }, + { + "status": 200, + "servers": [ + { + "serverId": "1", + "error": null, + "certificateCount": 0 + }, + { + "serverId": "2", + "error": null, + "certificateCount": 0 + } + ] + }, + { + "status": 200, + "servers": [ + { + "serverId": "1", + "error": null, + "certificateCount": 0 + }, + { + "serverId": "2", + "error": null, + "certificateCount": 0 + } + ] + }, + { + "status": 200, + "servers": [ + { + "serverId": "1", + "error": null, + "certificateCount": 0 + }, + { + "serverId": "2", + "error": null, + "certificateCount": 0 + } + ] + }, + { + "status": 200, + "servers": [ + { + "serverId": "1", + "error": null, + "certificateCount": 0 + }, + { + "serverId": "2", + "error": null, + "certificateCount": 0 + } + ] + }, + { + "status": 200, + "servers": [ + { + "serverId": "1", + "error": null, + "certificateCount": 0 + }, + { + "serverId": "2", + "error": null, + "certificateCount": 0 + } + ] + }, + { + "status": 200, + "servers": [ + { + "serverId": "1", + "error": null, + "certificateCount": 0 + }, + { + "serverId": "2", + "error": null, + "certificateCount": 0 + } + ] + }, + { + "status": 200, + "servers": [ + { + "serverId": "1", + "error": null, + "certificateCount": 0 + }, + { + "serverId": "2", + "error": null, + "certificateCount": 0 + } + ] + }, + { + "status": 200, + "servers": [ + { + "serverId": "1", + "error": null, + "certificateCount": 0 + }, + { + "serverId": "2", + "error": null, + "certificateCount": 0 + } + ] + }, + { + "status": 200, + "servers": [ + { + "serverId": "1", + "error": null, + "certificateCount": 0 + }, + { + "serverId": "2", + "error": null, + "certificateCount": 0 + } + ] + }, + { + "status": 200, + "servers": [ + { + "serverId": "1", + "error": null, + "certificateCount": 0 + }, + { + "serverId": "2", + "error": null, + "certificateCount": 0 + } + ] + } + ] +} diff --git a/docs/ux-ui/acceptance/certificates257-live-ui.json b/docs/ux-ui/acceptance/certificates257-live-ui.json new file mode 100644 index 00000000..689a571f --- /dev/null +++ b/docs/ux-ui/acceptance/certificates257-live-ui.json @@ -0,0 +1,41 @@ +{ + "date": "2026-10-15", + "block": "12.5.257", + "realPortalLogin": true, + "noMocks": true, + "results": [ + { + "path": "/api/certificates/state", + "status": 200, + "servers": [ + { + "serverId": "1", + "error": null, + "certificates": 0 + }, + { + "serverId": "2", + "error": null, + "certificates": 0 + } + ] + }, + { + "path": "/api/certificates/renew-settings", + "status": 200, + "renewEnabled": true + } + ], + "views": [ + { + "width": 1440, + "overflow": false + }, + { + "width": 390, + "overflow": false + } + ], + "pageErrors": [], + "nonAuthApiWrites": 0 +}