fix(routeros): подтверждать импорт сертификата и ограничить REST-сессии
This commit is contained in:
1 parent
9178ff2ccb
commit
c683e1b2c1
17 files changed
+662
-12
No files matched your search
@@ -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"
|
||||
|
||||
@@ -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<unknown>("/certificate"),
|
||||
apply: () => clientRos.applyCertificateToServices(params.certName),
|
||||
services: () => clientRos.get<unknown>("/ip/service"),
|
||||
}, params.certName, new X509Certificate(leafPem).fingerprint256)
|
||||
} finally {
|
||||
params.onStep?.("cleanup")
|
||||
for (const item of txtCleanups) {
|
||||
|
||||
@@ -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")
|
||||
@@ -0,0 +1,33 @@
|
||||
type Row = Record<string, unknown>
|
||||
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<unknown>
|
||||
apply: () => Promise<void>
|
||||
services: () => Promise<unknown>
|
||||
}, name: string, expectedFingerprint: string): Promise<void> {
|
||||
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} не подтверждено; проверьте фактические настройки устройства`)
|
||||
}
|
||||
}
|
||||
@@ -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<import("node:net").Socket>()
|
||||
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<void>(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<void>(resolve=>setTimeout(resolve,30));assert.equal(connections.size,0)
|
||||
}finally{await new Promise<void>((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<void>(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<void>((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 () => {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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).
|
||||
|
||||
+17
-1
@@ -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).
|
||||
|
||||
+18
-2
@@ -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)
|
||||
|
||||
|
||||
@@ -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 — отдельные группы по таблице выше. Ограничения текущего стенда не превращать в обещание приёмки всех конфигураций.
|
||||
|
||||
|
||||
+17
-1
@@ -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).
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"capturedRows":2,"duplicateLogicalNames":true,"rejected":true,"applyCalls":0,"networkCalls":0}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in new issue
Block a user