diff --git a/backend/src/services/acme-cloudflare.ts b/backend/src/services/acme-cloudflare.ts index bc69d906..ce7582ec 100644 --- a/backend/src/services/acme-cloudflare.ts +++ b/backend/src/services/acme-cloudflare.ts @@ -1,6 +1,6 @@ import { lookup } from "node:dns/promises" -import { X509Certificate } from "node:crypto" -import { applyVerifiedCertificateToServices } from "./certificate-installation.js" +import { X509Certificate, randomBytes } from "node:crypto" +import { assertLogicalCertificateName, rotateCertificateGeneration } from "./certificate-installation.js" import * as acme from "acme-client" import type { Server } from "../db/schema.js" import { @@ -252,6 +252,9 @@ export async function issueCertificateWithCloudflareDns(params: { const domains = [...new Set(params.domainNames.map((d) => d.trim().toLowerCase()).filter(Boolean))] if (domains.length === 0) throw new Error("Нужен хотя бы один домен") + const clientRos = MikrotikClient.fromServer(params.server) + assertLogicalCertificateName(await clientRos.get("/certificate"),params.certName) + params.onStep?.("acme_order") const accountKey = await getOrCreateAccountKey() const client = new acme.Client({ @@ -300,7 +303,6 @@ export async function issueCertificateWithCloudflareDns(params: { const certPem = await client.getCertificate(finalized) // A-sync опционален: DNS-01 уже завершён. CNAME на CN (msk2 → msk-gw02) не должен валить импорт. - const clientRos = MikrotikClient.fromServer(params.server) try { params.onStep?.("dns_a_sync") const serverIp = await resolveServerPublicIp(params.server, clientRos) @@ -328,6 +330,9 @@ export async function issueCertificateWithCloudflareDns(params: { const safeBase = params.certName.replace(/[^a-zA-Z0-9._-]+/g, "_") const chain = splitPemCertificates(certPem) const leafPem = chain[0] ?? certPem + const expectedFingerprint = new X509Certificate(leafPem).fingerprint256 + const generationName = `${safeBase.slice(0,40)}_new_${randomBytes(6).toString("hex")}` + const archiveName = `${safeBase.slice(0,40)}_old_${randomBytes(6).toString("hex")}` const chainRest = chain.slice(1) const certFile = `${safeBase}.crt` const keyFile = `${safeBase}.key` @@ -335,13 +340,13 @@ export async function issueCertificateWithCloudflareDns(params: { const keyRouterFile = await clientRos.uploadTextFile(keyFile, privateKey.toString("utf8")) await clientRos.importCertificate({ fileName: certRouterFile, - name: params.certName, + name: generationName, trusted: true, trustStore: trustStoreCsv, }) await clientRos.importCertificate({ fileName: keyRouterFile, - name: params.certName, + name: generationName, trusted: true, trustStore: trustStoreCsv, }) @@ -359,11 +364,12 @@ export async function issueCertificateWithCloudflareDns(params: { }) } - await applyVerifiedCertificateToServices({ + await rotateCertificateGeneration({ certificates: () => clientRos.get("/certificate"), - apply: () => clientRos.applyCertificateToServices(params.certName), + apply: name => clientRos.applyCertificateToServices(name), services: () => clientRos.get("/ip/service"), - }, params.certName, new X509Certificate(leafPem).fingerprint256) + rename: (id,name) => clientRos.patch(`/certificate/${id}`,{name}).then(() => undefined), + }, params.certName, generationName, archiveName, expectedFingerprint) } 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 index 85dbafe7..93176498 100644 --- a/backend/src/services/certificate-installation.test.ts +++ b/backend/src/services/certificate-installation.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict" -import { applyVerifiedCertificateToServices } from "./certificate-installation.js" +import { applyVerifiedCertificateToServices,rotateCertificateGeneration } 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"}]]) { @@ -20,3 +20,53 @@ for(const value of [[],[services[0]],services.map(row=>({...row,disabled:"true"} 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") + +function rotationFixture(withPrevious=true) { + const original={".id":"*A",name:"own",fingerprint:"a".repeat(64),"private-key":"true"} + const candidate={...fresh,".id":"*B",name:"own_new"} + const certs:Record[]=[...(withPrevious?[original]:[]),candidate] + const bindings=services.map(row=>({...row})),calls:string[]=[] + let fail="" + const deps={certificates:async()=>certs.map(row=>({...row})),services:async()=>bindings.map(row=>({...row})), + apply:async(name:string)=>{calls.push('apply:'+name);if(fail==='apply:'+name)throw Error('apply refused');for(const row of bindings){row.certificate=name;row.disabled='false'}}, + rename:async(id:string,name:string)=>{calls.push('rename:'+name);if(fail==='rename:'+name)throw Error('rename refused');const row=certs.find(row=>row['.id']===id);assert(row);row.name=name}} + return{deps,certs,bindings,calls,original,candidate,setFail:(value:string)=>{fail=value}} +} +for(const previous of[true,false]) { + const f=rotationFixture(previous) + await rotateCertificateGeneration(f.deps,'own','own_new','own_old',fresh.fingerprint) + assert.equal(f.certs.filter(row=>row.name==='own').length,1);assert.equal(f.candidate.name,'own');assert(f.bindings.every(row=>row.certificate==='own')) + if(previous){assert.equal(f.original.name,'own_old');assert.equal(f.original['private-key'],'true');assert.equal(f.original.fingerprint,'a'.repeat(64));assert(f.calls.indexOf('apply:own_new')rotateCertificateGeneration(f.deps,'own','own_new','own_old',fresh.fingerprint)) + assert(f.certs.includes(f.original));assert.equal(f.original['private-key'],'true');assert.equal(f.original.fingerprint,'a'.repeat(64)) + if(failure==='apply:own_new')assert.equal(f.original.name,'own') +} +for(const problem of['duplicate-original','occupied-archive','missing-native-id']) { + const f=rotationFixture() + if(problem==='duplicate-original')f.certs.push({...f.original,'.id':'*C'}) + if(problem==='occupied-archive')f.certs.push({...f.original,name:'own_old','.id':'*C'}) + if(problem==='missing-native-id')delete f.candidate['.id'] + await assert.rejects(()=>rotateCertificateGeneration(f.deps,'own','own_new','own_old',fresh.fingerprint));assert.deepEqual(f.calls,[]) +} +console.log('Generation rotation: first issue/renewal keep one logical name; old key retained on success and four failure checkpoints; three conflicts never apply') + +{ + const f=rotationFixture();f.candidate['.id']=f.original['.id'] + await assert.rejects(()=>rotateCertificateGeneration(f.deps,'own','own_new','own_old',fresh.fingerprint));assert.deepEqual(f.calls,[]) +} +{ + const f=rotationFixture(),rename=f.deps.rename + f.deps.rename=async(id,name)=>{if(name==='own_old')return;await rename(id,name)} + await assert.rejects(()=>rotateCertificateGeneration(f.deps,'own','own_new','own_old',fresh.fingerprint),/не подтверждено/) + assert.equal(f.original.name,'own');assert.equal(f.candidate.name,'own_new') +} +{ + const f=rotationFixture(),read=f.deps.certificates;let count=0 + f.deps.certificates=async()=>{if(++count===4)f.original.fingerprint='c'.repeat(64);return read()} + await assert.rejects(()=>rotateCertificateGeneration(f.deps,'own','own_new','own_old',fresh.fingerprint),/изменился/) + assert.equal(f.original.name,'own') +} +console.log('Rotation rechecks native identities and archive ACK; shared IDs, external changes and false rename ACK cannot create a successful receipt') diff --git a/backend/src/services/certificate-installation.ts b/backend/src/services/certificate-installation.ts index fa403bcb..f64d48e4 100644 --- a/backend/src/services/certificate-installation.ts +++ b/backend/src/services/certificate-installation.ts @@ -8,7 +8,7 @@ function fingerprint(value: unknown): string | 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 { +export function assertImportedCertificate(value: unknown, name: string, expectedFingerprint: string): Row { const matches = rows(value).filter(row => row.name === name) if (matches.length !== 1) throw new Error("Импорт не подтверждён: имя сертификата отсутствует или неоднозначно. Назначение сервисам не подтверждено.") const row = matches[0]! @@ -16,6 +16,10 @@ export function assertImportedCertificate(value: unknown, name: string, expected 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("Импорт не подтверждён: закрытый ключ сертификата не обнаружен") + return row +} +export function assertLogicalCertificateName(value: unknown, name: string): void { + if (rows(value).filter(row => row.name === name).length > 1) throw new Error("Имя существующего сертификата неоднозначно; сначала устраните дубликаты") } export async function applyVerifiedCertificateToServices(deps: { certificates: () => Promise @@ -31,3 +35,35 @@ export async function applyVerifiedCertificateToServices(deps: { if (matching.length !== 1 || matching[0]!.certificate !== name || !["false", "no"].includes(String(matching[0]!.disabled))) throw new Error(`Назначение сертификата сервису ${service} не подтверждено; проверьте фактические настройки устройства`) } } + +export async function rotateCertificateGeneration(deps: { + certificates: () => Promise + apply: (name: string) => Promise + services: () => Promise + rename: (id: string, name: string) => Promise +}, logicalName: string, generationName: string, archiveName: string, expectedFingerprint: string): Promise { + if (new Set([logicalName,generationName,archiveName]).size!==3) throw new Error("Имена поколений сертификата должны различаться") + const initial=await deps.certificates() + assertLogicalCertificateName(initial,logicalName) + const candidate=assertImportedCertificate(initial,generationName,expectedFingerprint) + const previous=rows(initial).find(row=>row.name===logicalName) + if(rows(initial).some(row=>row.name===archiveName))throw new Error("Имя архива сертификата уже занято") + const id=(row:Row):string=>{if(typeof row[".id"]!=="string"||!/^\*[a-z0-9]+$/i.test(row[".id"]))throw new Error("Native ID сертификата не подтверждён");return row[".id"]} + const candidateId=id(candidate),previousId=previous?id(previous):null + if(candidateId===previousId)throw new Error("Native ID поколений сертификата неоднозначен") + const verified=(name:string)=>applyVerifiedCertificateToServices({certificates:deps.certificates,apply:()=>deps.apply(name),services:deps.services},name,expectedFingerprint) + // Keep the original identity and key intact until both services use the verified generation. + await verified(generationName) + const current=await deps.certificates() + assertLogicalCertificateName(current,logicalName) + const currentPrevious=rows(current).find(row=>row.name===logicalName) + if(previousId ? !currentPrevious||id(currentPrevious)!==previousId||currentPrevious.fingerprint!==previous!.fingerprint : !!currentPrevious)throw new Error("Исходный сертификат изменился во время переключения; проверьте фактические назначения") + if(previousId)await deps.rename(previousId,archiveName) + const staged=await deps.certificates() + if(rows(staged).some(row=>row.name===logicalName))throw new Error("Освобождение исходного имени сертификата не подтверждено") + if(id(assertImportedCertificate(staged,generationName,expectedFingerprint))!==candidateId)throw new Error("Native ID нового поколения изменился") + if(previousId&&!rows(staged).some(row=>row.name===archiveName&&row[".id"]===previousId&&row.fingerprint===previous!.fingerprint))throw new Error("Сохранение прежнего поколения не подтверждено") + await deps.rename(candidateId,logicalName) + await verified(logicalName) + // The archived generation is intentionally retained; other consumers may still need its key. +} diff --git a/deploy/TEST-ACME.md b/deploy/TEST-ACME.md index b54eac59..48ba9aeb 100644 --- a/deploy/TEST-ACME.md +++ b/deploy/TEST-ACME.md @@ -29,3 +29,13 @@ Для этих двух выдач и повторного назначения SSL необходимо согласие на конкретный сценарий. Разрешение250 относилось к уже завершённому тесту. До согласия выполняются только подготовка/чтение. Полная регрессия и Docker build для изменения лишь ручного acceptance helper не нужны; comparator проверяется отдельно. CA revocation и multi-instance здесь не принимаются. Нормативная справка для native полей/import: [MikroTik Certificates](https://help.mikrotik.com/docs/spaces/ROS/pages/2555969/Certificates). Документация не гарантирует успех повторного импорта в текущем приложении; именно это предстоит измерить. + +## Ротация поколений — подготовка258 (2026-10-15) + +Новый код проверяет неоднозначное исходное имя до CA order. Leaf/key импортируются под уникальным `_new_<12hex>`. После проверки отпечатка/ключа оба SSL-сервиса переключаются на это поколение. Только после подтверждения назначений прежний объект переименовывается в `_old_<12hex>`, новый получает исходное логическое имя, назначения и отпечаток проверяются повторно. Старый ключ не удаляется. API/DB requestId и certName остаются прежними; raw native IDs проверяются перед PATCH. + +Подтверждается сохранение старого native ID/fingerprint, освобождение логического имени и неизменность нового поколения. Неизвестный ACK или внешнее изменение не дают success; автоматического повторного PATCH/слепого rollback нет. При поздней ошибке нужно сверить фактические назначения и оба поколения. Это ещё не обещание отсутствия краткого TLS reload и не приёмка нескольких одновременно работающих issuers. + +Следующий реальный тест259: активировать подготовленный образ258, снять свежий baseline и выполнить согласованный first issue + scheduler renewal. Проверить ровно один leaf с логическим именем, один архив старого leaf с прежними ID/SHA-256/private-key и отсутствие незавершённых `_new_` объектов. Trusted TLS должен совпасть с новым leaf; canonical dates — с X509. Очистка должна учитывать собственные `_old_`/`_new_` имена с12hex, exact CN и записанные ID/fingerprints. Старый cleanup helper256, допускавший лишь exact logical/root имена, без адаптации не запускать. Удалять leaf поколения до собственных CA, затем проверить отсутствие и исходный export/files/services/settings. Чужие объекты не удалять. + +Образ258 подготовлен и проверен локальными service сценариями; до этого реального теста активный backend остаётся257. Повторное согласование того же уже разрешённого сценария не требуется. Продление не объявляется принятым по fixtures. diff --git a/docs/ux-ui-external-acceptance.md b/docs/ux-ui-external-acceptance.md index c62fefa4..afb0916a 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: 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 открыт. +Актуальное продолжение 2026-10-15: 258 — реализована unique generation rotation с проверкой нового leaf/SSL до архивации старого, сохранением прежнего ключа и финальным logical name. Profile9/build/lint passed, image ux-certificates258 source530/dist1084 prepared, ещё не активирован; runtime backend257/frontend251. Следующий259 — согласованный real first issue + scheduler renewal, trusted TLS, unique logical name/old archive и полный откат; разрешение сохраняется. Этап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,18 @@ 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.258 — уникальное новое поколение и сохранение старого сертификата (2026-10-15) + +После256/257 issuer проверяет duplicate logical name ещё до CA order. Leaf/key получают случайное уникальное имя _new_<12hex>, signed X509 fingerprint вычисляется до native upload. rotateCertificateGeneration требует подтверждённые разные native IDs и свободное archive name; сначала проверяет новый leaf/private-key и назначения обоих SSL сервисов на него. Только после этого прежний logical объект переносится в _old_<12hex>, новый получает прежнее logical name и повторно проверяются fingerprint/private-key/services. Старый объект/ключ не удаляется, данные jobs/requestId остаются совместимыми. + +Перед архивацией re-read обнаруживает изменение old ID/fingerprint; после ACK проверяется освобождение logical name, сохранение old ID/fingerprint в archive и неизменность new native ID. Shared IDs, occupied archive, missing ID, external modification или false rename ACK не дают successful receipt. При поздней ошибке возможен частично применённый state с services на проверенном generation; auto-delete/слепого rollback/повторного PATCH нет. Old key сохраняется для reconciliation, остальные его потребители не объявляются автоматически проверенными. Нет обещания нулевого TLS reload или защиты нескольких issuers. + +Сценарии first issue/no previous и renewal/previous оставляют ровно один logical name и old key; четыре apply/rename failure checkpoints сохраняют старый материал; initial3 conflicts не вызывают apply. Дополнительно shared native ID, external fingerprint change и false archive ACK отвергаются. Все9 certificate scripts прошли, contracts/backend build и scoped ESLint passed; общая25-profile regression257 не повторялась, транспорт не менялся. Новых CA/DNS/native операций258 не было. + +Production image ux-certificates258 собран; source530/compiled1084 matched, missing/different0. Образ подготовлен, НЕ активирован: тестовый runtime остаётся backend ux-certificates257/frontend ux-certificates251 healthy. [Prepared image](ux-ui/acceptance/certificates258-preparation.json). Реальная rename/binding семантика ещё не принята по mocks. Signed certificate name изменяем по [официальной документации](https://help.mikrotik.com/docs/spaces/ROS/pages/2555969/Certificates), но фактические native/TLS postconditions проверяются отдельно. + +[TEST-ACME](../deploy/TEST-ACME.md) дополнен поколениями и cleanup правилами. Следующий259 — активировать258 только для согласованного exact mmt253-8e62a941.pngs.top/server2, свежий baseline, real first issue + one scheduler renewal. Требовать new trusted TLS fingerprint, один logical leaf, архив с исходными native ID/fingerprint/private-key и0 unfinished new names; затем убрать все только собственные поколения/root/files/jobs/DNS и восстановить settings/services/export. Старый cleanup256 нужно адаптировать к generated names, не применять вслепую. Разрешение того же сценария уже получено254, новый вопрос не нужен. Этот внешний цикл не начат на малом остатке недельного лимита, чтобы оставить запас на обязательный откат/коммит. Real 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 видит ошибку и должен учитывать фактические изменения. diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index 32fa1e07..4706bc3d 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -1,9 +1,21 @@ # План реализации UX/UI по пятичасовым окнам -Актуальное продолжение 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 открыт. +Актуальное продолжение 2026-10-15: 258 — реализована unique generation rotation с проверкой нового leaf/SSL до архивации старого, сохранением прежнего ключа и финальным logical name. Profile9/build/lint passed, image ux-certificates258 source530/dist1084 prepared, ещё не активирован; runtime backend257/frontend251. Следующий259 — согласованный real first issue + scheduler renewal, trusted TLS, unique logical name/old archive и полный откат; разрешение сохраняется. Этап12.5 открыт. Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-14. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат. +## 12.5.258 — уникальное новое поколение и сохранение старого сертификата (2026-10-15) + +После256/257 issuer проверяет duplicate logical name ещё до CA order. Leaf/key получают случайное уникальное имя _new_<12hex>, signed X509 fingerprint вычисляется до native upload. rotateCertificateGeneration требует подтверждённые разные native IDs и свободное archive name; сначала проверяет новый leaf/private-key и назначения обоих SSL сервисов на него. Только после этого прежний logical объект переносится в _old_<12hex>, новый получает прежнее logical name и повторно проверяются fingerprint/private-key/services. Старый объект/ключ не удаляется, данные jobs/requestId остаются совместимыми. + +Перед архивацией re-read обнаруживает изменение old ID/fingerprint; после ACK проверяется освобождение logical name, сохранение old ID/fingerprint в archive и неизменность new native ID. Shared IDs, occupied archive, missing ID, external modification или false rename ACK не дают successful receipt. При поздней ошибке возможен частично применённый state с services на проверенном generation; auto-delete/слепого rollback/повторного PATCH нет. Old key сохраняется для reconciliation, остальные его потребители не объявляются автоматически проверенными. Нет обещания нулевого TLS reload или защиты нескольких issuers. + +Сценарии first issue/no previous и renewal/previous оставляют ровно один logical name и old key; четыре apply/rename failure checkpoints сохраняют старый материал; initial3 conflicts не вызывают apply. Дополнительно shared native ID, external fingerprint change и false archive ACK отвергаются. Все9 certificate scripts прошли, contracts/backend build и scoped ESLint passed; общая25-profile regression257 не повторялась, транспорт не менялся. Новых CA/DNS/native операций258 не было. + +Production image ux-certificates258 собран; source530/compiled1084 matched, missing/different0. Образ подготовлен, НЕ активирован: тестовый runtime остаётся backend ux-certificates257/frontend ux-certificates251 healthy. [Prepared image](ux-ui/acceptance/certificates258-preparation.json). Реальная rename/binding семантика ещё не принята по mocks. Signed certificate name изменяем по [официальной документации](https://help.mikrotik.com/docs/spaces/ROS/pages/2555969/Certificates), но фактические native/TLS postconditions проверяются отдельно. + +[TEST-ACME](../deploy/TEST-ACME.md) дополнен поколениями и cleanup правилами. Следующий259 — активировать258 только для согласованного exact mmt253-8e62a941.pngs.top/server2, свежий baseline, real first issue + one scheduler renewal. Требовать new trusted TLS fingerprint, один logical leaf, архив с исходными native ID/fingerprint/private-key и0 unfinished new names; затем убрать все только собственные поколения/root/files/jobs/DNS и восстановить settings/services/export. Старый cleanup256 нужно адаптировать к generated names, не применять вслепую. Разрешение того же сценария уже получено254, новый вопрос не нужен. Этот внешний цикл не начат на малом остатке недельного лимита, чтобы оставить запас на обязательный откат/коммит. Real 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 видит ошибку и должен учитывать фактические изменения. diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index 3bd4442a..7d4f2875 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -1,6 +1,6 @@ # UX/UI: журнал реализации -Актуальное продолжение 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 открыт. +Актуальное продолжение 2026-10-15: 258 — реализована unique generation rotation с проверкой нового leaf/SSL до архивации старого, сохранением прежнего ключа и финальным logical name. Profile9/build/lint passed, image ux-certificates258 source530/dist1084 prepared, ещё не активирован; runtime backend257/frontend251. Следующий259 — согласованный real first issue + scheduler renewal, trusted TLS, unique logical name/old archive и полный откат; разрешение сохраняется. Этап12.5 открыт. Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md). @@ -36,7 +36,19 @@ | 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–257 в записанных scopes; guard/REST pool257 проверены, unique replacement258 остаётся открытой в записанных scopes. Runtime backend ux-certificates257/frontend ux-certificates251. Общая регрессия257 и профиль сертификатов252 прошли; реальный renewal остаётся открытым. | +| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная12.3.83; внешняя12.5 открыта, блоки1–258 в записанных scopes; unique rotation258 подготовлена, native acceptance259 остаётся открытой в записанных scopes. Runtime backend ux-certificates257/frontend ux-certificates251. Общая регрессия257 и профиль сертификатов252 прошли; реальный renewal остаётся открытым. | + +## 12.5.258 — уникальное новое поколение и сохранение старого сертификата (2026-10-15) + +После256/257 issuer проверяет duplicate logical name ещё до CA order. Leaf/key получают случайное уникальное имя _new_<12hex>, signed X509 fingerprint вычисляется до native upload. rotateCertificateGeneration требует подтверждённые разные native IDs и свободное archive name; сначала проверяет новый leaf/private-key и назначения обоих SSL сервисов на него. Только после этого прежний logical объект переносится в _old_<12hex>, новый получает прежнее logical name и повторно проверяются fingerprint/private-key/services. Старый объект/ключ не удаляется, данные jobs/requestId остаются совместимыми. + +Перед архивацией re-read обнаруживает изменение old ID/fingerprint; после ACK проверяется освобождение logical name, сохранение old ID/fingerprint в archive и неизменность new native ID. Shared IDs, occupied archive, missing ID, external modification или false rename ACK не дают successful receipt. При поздней ошибке возможен частично применённый state с services на проверенном generation; auto-delete/слепого rollback/повторного PATCH нет. Old key сохраняется для reconciliation, остальные его потребители не объявляются автоматически проверенными. Нет обещания нулевого TLS reload или защиты нескольких issuers. + +Сценарии first issue/no previous и renewal/previous оставляют ровно один logical name и old key; четыре apply/rename failure checkpoints сохраняют старый материал; initial3 conflicts не вызывают apply. Дополнительно shared native ID, external fingerprint change и false archive ACK отвергаются. Все9 certificate scripts прошли, contracts/backend build и scoped ESLint passed; общая25-profile regression257 не повторялась, транспорт не менялся. Новых CA/DNS/native операций258 не было. + +Production image ux-certificates258 собран; source530/compiled1084 matched, missing/different0. Образ подготовлен, НЕ активирован: тестовый runtime остаётся backend ux-certificates257/frontend ux-certificates251 healthy. [Prepared image](ux-ui/acceptance/certificates258-preparation.json). Реальная rename/binding семантика ещё не принята по mocks. Signed certificate name изменяем по [официальной документации](https://help.mikrotik.com/docs/spaces/ROS/pages/2555969/Certificates), но фактические native/TLS postconditions проверяются отдельно. + +[TEST-ACME](../deploy/TEST-ACME.md) дополнен поколениями и cleanup правилами. Следующий259 — активировать258 только для согласованного exact mmt253-8e62a941.pngs.top/server2, свежий baseline, real first issue + one scheduler renewal. Требовать new trusted TLS fingerprint, один logical leaf, архив с исходными native ID/fingerprint/private-key и0 unfinished new names; затем убрать все только собственные поколения/root/files/jobs/DNS и восстановить settings/services/export. Старый cleanup256 нужно адаптировать к generated names, не применять вслепую. Разрешение того же сценария уже получено254, новый вопрос не нужен. Этот внешний цикл не начат на малом остатке недельного лимита, чтобы оставить запас на обязательный откат/коммит. Real renewal остаётся открытым. ## 12.5.257 — достоверный импорт сертификата и ограничение REST-сессий (2026-10-15) diff --git a/docs/ux-ui-remaining-acceptance.md b/docs/ux-ui-remaining-acceptance.md index 0801c506..be9ca911 100644 --- a/docs/ux-ui-remaining-acceptance.md +++ b/docs/ux-ui-remaining-acceptance.md @@ -1,11 +1,23 @@ # Оставшаяся внешняя приёмка — 2026-10-14 -Актуальное продолжение 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 открыт. +Актуальное продолжение 2026-10-15: 258 — реализована unique generation rotation с проверкой нового leaf/SSL до архивации старого, сохранением прежнего ключа и финальным logical name. Profile9/build/lint passed, image ux-certificates258 source530/dist1084 prepared, ещё не активирован; runtime backend257/frontend251. Следующий259 — согласованный real first issue + scheduler renewal, trusted TLS, unique logical name/old archive и полный откат; разрешение сохраняется. Этап12.5 открыт. -Это актуальная матрица после 12.5.257. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии. +Это актуальная матрица после 12.5.258. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии. Стенд: 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.258 — уникальное новое поколение и сохранение старого сертификата (2026-10-15) + +После256/257 issuer проверяет duplicate logical name ещё до CA order. Leaf/key получают случайное уникальное имя _new_<12hex>, signed X509 fingerprint вычисляется до native upload. rotateCertificateGeneration требует подтверждённые разные native IDs и свободное archive name; сначала проверяет новый leaf/private-key и назначения обоих SSL сервисов на него. Только после этого прежний logical объект переносится в _old_<12hex>, новый получает прежнее logical name и повторно проверяются fingerprint/private-key/services. Старый объект/ключ не удаляется, данные jobs/requestId остаются совместимыми. + +Перед архивацией re-read обнаруживает изменение old ID/fingerprint; после ACK проверяется освобождение logical name, сохранение old ID/fingerprint в archive и неизменность new native ID. Shared IDs, occupied archive, missing ID, external modification или false rename ACK не дают successful receipt. При поздней ошибке возможен частично применённый state с services на проверенном generation; auto-delete/слепого rollback/повторного PATCH нет. Old key сохраняется для reconciliation, остальные его потребители не объявляются автоматически проверенными. Нет обещания нулевого TLS reload или защиты нескольких issuers. + +Сценарии first issue/no previous и renewal/previous оставляют ровно один logical name и old key; четыре apply/rename failure checkpoints сохраняют старый материал; initial3 conflicts не вызывают apply. Дополнительно shared native ID, external fingerprint change и false archive ACK отвергаются. Все9 certificate scripts прошли, contracts/backend build и scoped ESLint passed; общая25-profile regression257 не повторялась, транспорт не менялся. Новых CA/DNS/native операций258 не было. + +Production image ux-certificates258 собран; source530/compiled1084 matched, missing/different0. Образ подготовлен, НЕ активирован: тестовый runtime остаётся backend ux-certificates257/frontend ux-certificates251 healthy. [Prepared image](ux-ui/acceptance/certificates258-preparation.json). Реальная rename/binding семантика ещё не принята по mocks. Signed certificate name изменяем по [официальной документации](https://help.mikrotik.com/docs/spaces/ROS/pages/2555969/Certificates), но фактические native/TLS postconditions проверяются отдельно. + +[TEST-ACME](../deploy/TEST-ACME.md) дополнен поколениями и cleanup правилами. Следующий259 — активировать258 только для согласованного exact mmt253-8e62a941.pngs.top/server2, свежий baseline, real first issue + one scheduler renewal. Требовать new trusted TLS fingerprint, один logical leaf, архив с исходными native ID/fingerprint/private-key и0 unfinished new names; затем убрать все только собственные поколения/root/files/jobs/DNS и восстановить settings/services/export. Старый cleanup256 нужно адаптировать к generated names, не применять вслепую. Разрешение того же сценария уже получено254, новый вопрос не нужен. Этот внешний цикл не начат на малом остатке недельного лимита, чтобы оставить запас на обязательный откат/коммит. Real 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 видит ошибку и должен учитывать фактические изменения. @@ -326,7 +338,7 @@ Portal offline JWT действует доexp после logout/role change; э ## Следующая последовательность — 2026-10-14 -1. **Сертификаты258:** post-import verification/REST pool257 приняты в scopes; реализовать safe unique replacement и подтвердить реальное repeat renewal/TLS без duplicate logical names. Не считать защитный failed полной приёмкой renewal. +1. **Сертификаты259:** unique generation rotation258/local checks/image готовы; activate258 + real first issue/renewal/TLS/archive/cleanup по сохранённому разрешению254. Active runtime257. 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 d2b5e6af..a3a29172 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: 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 открыт. +Актуальное продолжение 2026-10-15: 258 — реализована unique generation rotation с проверкой нового leaf/SSL до архивации старого, сохранением прежнего ключа и финальным logical name. Profile9/build/lint passed, image ux-certificates258 source530/dist1084 prepared, ещё не активирован; runtime backend257/frontend251. Следующий259 — согласованный real first issue + scheduler renewal, trusted TLS, unique logical name/old archive и полный откат; разрешение сохраняется. Этап12.5 открыт. План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push. @@ -4152,6 +4152,18 @@ 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.258 — уникальное новое поколение и сохранение старого сертификата (2026-10-15) + +После256/257 issuer проверяет duplicate logical name ещё до CA order. Leaf/key получают случайное уникальное имя _new_<12hex>, signed X509 fingerprint вычисляется до native upload. rotateCertificateGeneration требует подтверждённые разные native IDs и свободное archive name; сначала проверяет новый leaf/private-key и назначения обоих SSL сервисов на него. Только после этого прежний logical объект переносится в _old_<12hex>, новый получает прежнее logical name и повторно проверяются fingerprint/private-key/services. Старый объект/ключ не удаляется, данные jobs/requestId остаются совместимыми. + +Перед архивацией re-read обнаруживает изменение old ID/fingerprint; после ACK проверяется освобождение logical name, сохранение old ID/fingerprint в archive и неизменность new native ID. Shared IDs, occupied archive, missing ID, external modification или false rename ACK не дают successful receipt. При поздней ошибке возможен частично применённый state с services на проверенном generation; auto-delete/слепого rollback/повторного PATCH нет. Old key сохраняется для reconciliation, остальные его потребители не объявляются автоматически проверенными. Нет обещания нулевого TLS reload или защиты нескольких issuers. + +Сценарии first issue/no previous и renewal/previous оставляют ровно один logical name и old key; четыре apply/rename failure checkpoints сохраняют старый материал; initial3 conflicts не вызывают apply. Дополнительно shared native ID, external fingerprint change и false archive ACK отвергаются. Все9 certificate scripts прошли, contracts/backend build и scoped ESLint passed; общая25-profile regression257 не повторялась, транспорт не менялся. Новых CA/DNS/native операций258 не было. + +Production image ux-certificates258 собран; source530/compiled1084 matched, missing/different0. Образ подготовлен, НЕ активирован: тестовый runtime остаётся backend ux-certificates257/frontend ux-certificates251 healthy. [Prepared image](ux-ui/acceptance/certificates258-preparation.json). Реальная rename/binding семантика ещё не принята по mocks. Signed certificate name изменяем по [официальной документации](https://help.mikrotik.com/docs/spaces/ROS/pages/2555969/Certificates), но фактические native/TLS postconditions проверяются отдельно. + +[TEST-ACME](../deploy/TEST-ACME.md) дополнен поколениями и cleanup правилами. Следующий259 — активировать258 только для согласованного exact mmt253-8e62a941.pngs.top/server2, свежий baseline, real first issue + one scheduler renewal. Требовать new trusted TLS fingerprint, один logical leaf, архив с исходными native ID/fingerprint/private-key и0 unfinished new names; затем убрать все только собственные поколения/root/files/jobs/DNS и восстановить settings/services/export. Старый cleanup256 нужно адаптировать к generated names, не применять вслепую. Разрешение того же сценария уже получено254, новый вопрос не нужен. Этот внешний цикл не начат на малом остатке недельного лимита, чтобы оставить запас на обязательный откат/коммит. Real 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 видит ошибку и должен учитывать фактические изменения. diff --git a/docs/ux-ui-test-server.md b/docs/ux-ui-test-server.md index ed018bee..1c60c060 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: 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 открыт. +Актуальное продолжение 2026-10-15: 258 — реализована unique generation rotation с проверкой нового leaf/SSL до архивации старого, сохранением прежнего ключа и финальным logical name. Profile9/build/lint passed, image ux-certificates258 source530/dist1084 prepared, ещё не активирован; runtime backend257/frontend251. Следующий259 — согласованный real first issue + scheduler renewal, trusted TLS, unique logical name/old archive и полный откат; разрешение сохраняется. Этап12.5 открыт. | Параметр | Проверенное состояние | | --- | --- | @@ -34,6 +34,18 @@ 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.258 — уникальное новое поколение и сохранение старого сертификата (2026-10-15) + +После256/257 issuer проверяет duplicate logical name ещё до CA order. Leaf/key получают случайное уникальное имя _new_<12hex>, signed X509 fingerprint вычисляется до native upload. rotateCertificateGeneration требует подтверждённые разные native IDs и свободное archive name; сначала проверяет новый leaf/private-key и назначения обоих SSL сервисов на него. Только после этого прежний logical объект переносится в _old_<12hex>, новый получает прежнее logical name и повторно проверяются fingerprint/private-key/services. Старый объект/ключ не удаляется, данные jobs/requestId остаются совместимыми. + +Перед архивацией re-read обнаруживает изменение old ID/fingerprint; после ACK проверяется освобождение logical name, сохранение old ID/fingerprint в archive и неизменность new native ID. Shared IDs, occupied archive, missing ID, external modification или false rename ACK не дают successful receipt. При поздней ошибке возможен частично применённый state с services на проверенном generation; auto-delete/слепого rollback/повторного PATCH нет. Old key сохраняется для reconciliation, остальные его потребители не объявляются автоматически проверенными. Нет обещания нулевого TLS reload или защиты нескольких issuers. + +Сценарии first issue/no previous и renewal/previous оставляют ровно один logical name и old key; четыре apply/rename failure checkpoints сохраняют старый материал; initial3 conflicts не вызывают apply. Дополнительно shared native ID, external fingerprint change и false archive ACK отвергаются. Все9 certificate scripts прошли, contracts/backend build и scoped ESLint passed; общая25-profile regression257 не повторялась, транспорт не менялся. Новых CA/DNS/native операций258 не было. + +Production image ux-certificates258 собран; source530/compiled1084 matched, missing/different0. Образ подготовлен, НЕ активирован: тестовый runtime остаётся backend ux-certificates257/frontend ux-certificates251 healthy. [Prepared image](ux-ui/acceptance/certificates258-preparation.json). Реальная rename/binding семантика ещё не принята по mocks. Signed certificate name изменяем по [официальной документации](https://help.mikrotik.com/docs/spaces/ROS/pages/2555969/Certificates), но фактические native/TLS postconditions проверяются отдельно. + +[TEST-ACME](../deploy/TEST-ACME.md) дополнен поколениями и cleanup правилами. Следующий259 — активировать258 только для согласованного exact mmt253-8e62a941.pngs.top/server2, свежий baseline, real first issue + one scheduler renewal. Требовать new trusted TLS fingerprint, один logical leaf, архив с исходными native ID/fingerprint/private-key и0 unfinished new names; затем убрать все только собственные поколения/root/files/jobs/DNS и восстановить settings/services/export. Старый cleanup256 нужно адаптировать к generated names, не применять вслепую. Разрешение того же сценария уже получено254, новый вопрос не нужен. Этот внешний цикл не начат на малом остатке недельного лимита, чтобы оставить запас на обязательный откат/коммит. Real 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 видит ошибку и должен учитывать фактические изменения. diff --git a/docs/ux-ui/acceptance/certificates258-preparation.json b/docs/ux-ui/acceptance/certificates258-preparation.json new file mode 100644 index 00000000..c86c3bb4 --- /dev/null +++ b/docs/ux-ui/acceptance/certificates258-preparation.json @@ -0,0 +1,27 @@ +{ + "date": "2026-10-15", + "block": "12.5.258", + "preparedImage": "mmapp-test/backend:ux-certificates258", + "activated": false, + "activeBackendImage": "mmapp-test/backend:ux-certificates257", + "activeFrontendImage": "mmapp-test/frontend:ux-certificates251", + "sourceMatched": 530, + "compiled": { + "matched": 1084, + "missing": 0, + "different": 0 + }, + "certificateProfileScripts": 9, + "profile": "passed", + "contractsAndBackendBuild": "passed", + "scopedLint": "passed", + "rotationScenarios": { + "success": 2, + "failureCheckpoints": 4, + "initialConflicts": 3, + "revalidationFailures": 3 + }, + "newAcmeOrders": 0, + "realNativeRotationAccepted": false, + "oldGenerationAutoDeleted": false +}