diff --git a/backend/src/services/certificate-renew-collector.test.ts b/backend/src/services/certificate-renew-collector.test.ts index ea032651..3fcc9f52 100644 --- a/backend/src/services/certificate-renew-collector.test.ts +++ b/backend/src/services/certificate-renew-collector.test.ts @@ -3,6 +3,7 @@ import { createCertificateRenewCollector } from "./certificate-renew-collector.j import { getServerRowByIdString } from "./certificates-service.js" import type { CertificateIssueJobRow } from "../db/schema.js" import type { RosCertificateRow } from "./certificate-parse.js" +import { readCertificateRowsWithClock } from "./certificates-state.js" function fixture() { const settings = { enabled: true, intervalSec: 300, renewBeforeDays: 30, lastCollectedAt: null, lastDurationMs: null, lastError: null, updatedAt: new Date().toISOString() } @@ -12,14 +13,15 @@ function fixture() { let job: CertificateIssueJobRow | null = null, outcome: CertificateIssueJobRow["status"] = "done", readFailure = false, active = false let resultPatch: Partial = {}, missingResult = false, targetGate: Promise | undefined let reads = 0, creates = 0, issues = 0 + let readResult: unknown const collector = createCertificateRenewCollector({ getSettings: async () => settings, getTargets: async () => { await targetGate; return [target] }, hasActiveJob: async () => active, getServer: async () => server, - read: async () => { reads++; if (readFailure) throw new Error("RouterOS offline"); return rows }, + read: async () => { reads++; if (readFailure) throw new Error("RouterOS offline"); return readResult ?? rows }, createJob: async input => { creates++; job = { ...input, serverId: Number(input.serverId), status: "queued", step: "queued", source: "scheduler", requestedAt: new Date().toISOString(), startedAt: null, finishedAt: null, error: null } }, runJob: async () => { issues++; assert(job); job.status = outcome; job.step = outcome; job.error = outcome === "failed" ? "import refused" : null }, getJob: async () => missingResult || !job ? null : { ...job, ...resultPatch }, touchMeta: async () => {}, event: async () => {}, }) - return { collector, settings, target, counts: () => ({ reads, creates, issues }), setServer: (v: typeof server) => { server = v }, setRows: (v: typeof rows) => { rows = v }, setOutcome: (v: typeof outcome) => { outcome = v }, failRead: () => { readFailure = true }, setActive: () => { active = true }, rows: () => rows, patchResult: (v: typeof resultPatch) => { resultPatch = v }, hideResult: () => { missingResult = true }, blockTargets: (v: Promise) => { targetGate = v } } + return { collector, settings, target, counts: () => ({ reads, creates, issues }), setServer: (v: typeof server) => { server = v }, setRows: (v: typeof rows) => { rows = v }, setReadResult: (v: unknown) => { readResult = v }, setOutcome: (v: typeof outcome) => { outcome = v }, failRead: () => { readFailure = true }, setActive: () => { active = true }, rows: () => rows, patchResult: (v: typeof resultPatch) => { resultPatch = v }, hideResult: () => { missingResult = true }, blockTargets: (v: Promise) => { targetGate = v } } } for (const outcome of ["done", "failed", "running", "queued"] as const) { @@ -60,3 +62,27 @@ for (const patch of [{ serverId: 2 }, { certName: "another" }, { source: "manual assert.equal((await f.collector.run()).skipped, true); release(); assert.equal((await first).renewed, 1); assert.equal(f.counts().issues, 1) } console.log("Renewal: only matching done counts as renewed; failed/pending/disabled/unknown/ambiguous/read error never dispatch or claim success; invalid IDs do not query DB") + +for (const offsets of [["+02:00","+02:00"],["+02:00","+01:00"],[null,"+02:00"]]) { + const f=fixture(), native=f.rows().map(row=>({...row, + "invalid-before":new Date(Date.now()-86400000+7200000).toISOString().slice(0,19).replace("T"," "), + "invalid-after":new Date(Date.now()+172800000+7200000).toISOString().slice(0,19).replace("T"," ")})) + let i=0 + f.setReadResult(await readCertificateRowsWithClock(async()=>native,async()=>({"gmt-offset":offsets[i++]}))) + const result=await f.collector.run(), known=offsets[0]!==null&&offsets[0]===offsets[1] + assert.equal(result.renewed,known?1:0);assert.equal(f.counts().issues,known?1:0);assert.equal(f.counts().creates,known?1:0) + assert.equal(result.targets?.[0].daysLeft,known?2:null) +} +console.log("Native clock → canonical state → renewal: stable offset issues once; missing/changed offset never creates a job") + +{ + const f=fixture();f.setRows(f.rows().map(row=>({...row,flags:undefined}))) + assert.equal((await f.collector.run()).renewed,1);assert.equal(f.counts().issues,1) +} +for(const kind of ["missing-start","future-start","reversed-window"]) { + const f=fixture();f.setRows(f.rows().map(row=>({...row,flags:undefined, + "invalid-before":kind==="missing-start"?undefined:new Date(Date.now()+86400000).toISOString(), + "invalid-after":kind==="reversed-window"?new Date(Date.now()-86400000).toISOString():row["invalid-after"]}))) + assert.equal((await f.collector.run()).renewed,0);assert.equal(f.counts().issues,0) +} +console.log("Missing REST revocation flags do not prevent date-based renewal; unknown/future/reversed validity dates remain guarded") diff --git a/backend/src/services/certificate-renew-collector.ts b/backend/src/services/certificate-renew-collector.ts index 1de32e0f..e0797cca 100644 --- a/backend/src/services/certificate-renew-collector.ts +++ b/backend/src/services/certificate-renew-collector.ts @@ -127,7 +127,9 @@ export function createCertificateRenewCollector(overrides: Partial Date.now() || Date.parse(hit.validFrom) > Date.parse(hit.validUntil)) { item.action = "skipped" item.message = "Срок или состояние сертификата не подтверждены. Обновление не выполнялось." snapshot.skippedTargets += 1 diff --git a/docs/ux-ui-external-acceptance.md b/docs/ux-ui-external-acceptance.md index f95c3d54..77141878 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-14: 255 — canonical native даты учитывают явно наблюдаемый стабильный gmt-offset, сохраняя raw и unknown при отсутствии/сбое/смене clock. Каталог и renewal используют общий reader; профиль8 scripts/build/lint прошёл. Runtime backend ux-certificates255/frontend ux-certificates251. Следующий256 — повторная реальная issuance + scheduler renewal/fingerprint по уже сохранённому разрешению254, свежий baseline и полный откат; реальный renewal пока не принят. Этап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 открыт. 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,24 @@ 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.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). + +Native date reader255 теперь правильно даёт validFrom/validUntil в UTC и daysLeft90, совпадающие с X509. Но statusunknown: REST7.24.5 не присылает flags/revoked/expired. Первый enable-triggered run был skipped/renewed0, без нового ACME order; comparator expectation renewed1 не прошёл и сохранён отдельно [skip](ux-ui/acceptance/acme256-renewal-skipped.json). Каталог не должен выдумывать revocation state. Для renewal правило уточнено: достаточно подтверждённого срока и начавшегося непротиворечивого validity window; unknown/missing/changed clock, missing start, future start и reversed window по-прежнему не создают job. Это соответствует прежнему разрешённому renewal даже для explicit revoked/expired, не объявляет сертификат valid. + +Collector native-clock→canonical-state→issue тесты: stable offset создаёт одну задачу, changed/missing не создаёт; отсутствующие REST revocation flags с известными dates позволяют renewal, неизвестные/future/reversed dates блокируют. Все8 profile scripts, contracts/backend build, scoped lint passed. Production backend ux-certificates256/source528/compiled1080 совпал, healthy, frontend251/DB/Portal сохранены. Изменённый scope не требовал повтора полной регрессии249. + +После исправления один enable-triggered scheduler run (interval86400/threshold90), без run-now: scheduler issue →done, receipt ok/renewed1/errors0, затем enabledfalse до следующего интервала. Реально served TLS сменился с342d36bc… на70b7b9f1… и совпал с новым private-key native leaf; доверие/SNI не отключались. [After](ux-ui/acceptance/acme256-tls-after.json), [jobs](ux-ui/acceptance/acme256-jobs.json), [run](ux-ui/acceptance/acme256-renewal.json). + +Приёмка renewal всё равно открыта: /certificate содержит2 leaf с одинаковым logical name mmt253-8e62a941, разными .id/SHA-256 и private-key=true. [Duplicate leaves](ux-ui/acceptance/acme256-duplicate-leaves.json). Next collector отвергнет неоднозначность; import ACK + done не доказывают устойчивую замену. Fresh TLS действительно served new leaf, поэтому не заявляем, что TLS остался старым. До fresh sample ошибочно скопированный вручную fingerprint не прошёл SHA-256 validation; затем использован точный файл native report через SCP. Предусловие statusvalid оказалось неверным для REST flags; первый scheduler run безопасно skipped, после уточнения policy выполнен один реальный order. Не суммируем skip как успешное renewal. + +Cleanup сначала столкнулся с REST401/500; точный ответ resource500: too many concurrent logins (6). Временно остановлен только backend, затем восстановлены configured www-ssl/api-ssl, удалены оба own leaf/chain/PEM. Certificates/file stable metadata/configured services/normalized export совпали с fresh baseline; исключены dynamic connections и directory mtime. [Native](ux-ui/acceptance/acme256-native.json). Backend снова запущен и healthy, Portal послеcleanup оба CHR читает безошибочно; пароли не менялись. REST concurrency остаётся отдельным воспроизведённым ограничением. + +TXT приложение удалило; own A удалена по exact name/type/content/ID, DNS0 обоихимён [DNS](ux-ui/acceptance/acme256-cloudflare.json). ACME/renew settings восстановлены целиком, scheduler refresh применён, его empty-run metadata затем восстановлена exact SQL compare. Own issue jobs удалены, private token/baseline/helper/SQL файлы удалены с Debian и локальный secret fixture удалён. Historical events/scheduler receipts/CA records сохранены; CA revocation не заявляется. + +Следующий257 — post-import подтверждение unique logical name/expected X509 fingerprint/private key перед применением/успехом и безопасная стратегия уникальной замены без потери старого working leaf. Дополнительно проработать per-device REST concurrency/login cap. Эти дефекты не маскируются сообщением successful acceptance; новое разрешение на тот же согласованный сценарий не запрашивается без изменения его риска/объёма. + ## 12.5.255 — native даты с подтверждённым offset для каталога и renewal (2026-10-14) После измерения254 добавлен общий readCertificateRowsWithClock: читает gmt-offset перед каталогом, для native timezone-less дат повторно читает clock после; принимает только одинаковые валидные offsets. Native YYYY-MM-DD HH:mm:ss преобразуется в offset-qualified datetime с проверкой календаря, затем UTC; explicit ISO сохраняет свой offset. Не предполагается UTC/пояс backend/будущий DST. rawValidFrom/rawValidUntil остаются исходными. Отсутствующий/некорректный/сменившийся/недоступный clock сохраняет unknown/null и не обходит renewal guard252; ошибка самого каталога сохраняет error. Legacy старые форматы без надёжного контекста не угадываются. diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index 128821d1..c088545e 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -1,9 +1,27 @@ # План реализации UX/UI по пятичасовым окнам -Актуальное продолжение 2026-10-14: 255 — canonical native даты учитывают явно наблюдаемый стабильный gmt-offset, сохраняя raw и unknown при отсутствии/сбое/смене clock. Каталог и renewal используют общий reader; профиль8 scripts/build/lint прошёл. Runtime backend ux-certificates255/frontend ux-certificates251. Следующий256 — повторная реальная issuance + scheduler renewal/fingerprint по уже сохранённому разрешению254, свежий baseline и полный откат; реальный renewal пока не принят. Этап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 открыт. Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-14. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат. +## 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). + +Native date reader255 теперь правильно даёт validFrom/validUntil в UTC и daysLeft90, совпадающие с X509. Но statusunknown: REST7.24.5 не присылает flags/revoked/expired. Первый enable-triggered run был skipped/renewed0, без нового ACME order; comparator expectation renewed1 не прошёл и сохранён отдельно [skip](ux-ui/acceptance/acme256-renewal-skipped.json). Каталог не должен выдумывать revocation state. Для renewal правило уточнено: достаточно подтверждённого срока и начавшегося непротиворечивого validity window; unknown/missing/changed clock, missing start, future start и reversed window по-прежнему не создают job. Это соответствует прежнему разрешённому renewal даже для explicit revoked/expired, не объявляет сертификат valid. + +Collector native-clock→canonical-state→issue тесты: stable offset создаёт одну задачу, changed/missing не создаёт; отсутствующие REST revocation flags с известными dates позволяют renewal, неизвестные/future/reversed dates блокируют. Все8 profile scripts, contracts/backend build, scoped lint passed. Production backend ux-certificates256/source528/compiled1080 совпал, healthy, frontend251/DB/Portal сохранены. Изменённый scope не требовал повтора полной регрессии249. + +После исправления один enable-triggered scheduler run (interval86400/threshold90), без run-now: scheduler issue →done, receipt ok/renewed1/errors0, затем enabledfalse до следующего интервала. Реально served TLS сменился с342d36bc… на70b7b9f1… и совпал с новым private-key native leaf; доверие/SNI не отключались. [After](ux-ui/acceptance/acme256-tls-after.json), [jobs](ux-ui/acceptance/acme256-jobs.json), [run](ux-ui/acceptance/acme256-renewal.json). + +Приёмка renewal всё равно открыта: /certificate содержит2 leaf с одинаковым logical name mmt253-8e62a941, разными .id/SHA-256 и private-key=true. [Duplicate leaves](ux-ui/acceptance/acme256-duplicate-leaves.json). Next collector отвергнет неоднозначность; import ACK + done не доказывают устойчивую замену. Fresh TLS действительно served new leaf, поэтому не заявляем, что TLS остался старым. До fresh sample ошибочно скопированный вручную fingerprint не прошёл SHA-256 validation; затем использован точный файл native report через SCP. Предусловие statusvalid оказалось неверным для REST flags; первый scheduler run безопасно skipped, после уточнения policy выполнен один реальный order. Не суммируем skip как успешное renewal. + +Cleanup сначала столкнулся с REST401/500; точный ответ resource500: too many concurrent logins (6). Временно остановлен только backend, затем восстановлены configured www-ssl/api-ssl, удалены оба own leaf/chain/PEM. Certificates/file stable metadata/configured services/normalized export совпали с fresh baseline; исключены dynamic connections и directory mtime. [Native](ux-ui/acceptance/acme256-native.json). Backend снова запущен и healthy, Portal послеcleanup оба CHR читает безошибочно; пароли не менялись. REST concurrency остаётся отдельным воспроизведённым ограничением. + +TXT приложение удалило; own A удалена по exact name/type/content/ID, DNS0 обоихимён [DNS](ux-ui/acceptance/acme256-cloudflare.json). ACME/renew settings восстановлены целиком, scheduler refresh применён, его empty-run metadata затем восстановлена exact SQL compare. Own issue jobs удалены, private token/baseline/helper/SQL файлы удалены с Debian и локальный secret fixture удалён. Historical events/scheduler receipts/CA records сохранены; CA revocation не заявляется. + +Следующий257 — post-import подтверждение unique logical name/expected X509 fingerprint/private key перед применением/успехом и безопасная стратегия уникальной замены без потери старого working leaf. Дополнительно проработать per-device REST concurrency/login cap. Эти дефекты не маскируются сообщением successful acceptance; новое разрешение на тот же согласованный сценарий не запрашивается без изменения его риска/объёма. + ## 12.5.255 — native даты с подтверждённым offset для каталога и renewal (2026-10-14) После измерения254 добавлен общий readCertificateRowsWithClock: читает gmt-offset перед каталогом, для native timezone-less дат повторно читает clock после; принимает только одинаковые валидные offsets. Native YYYY-MM-DD HH:mm:ss преобразуется в offset-qualified datetime с проверкой календаря, затем UTC; explicit ISO сохраняет свой offset. Не предполагается UTC/пояс backend/будущий DST. rawValidFrom/rawValidUntil остаются исходными. Отсутствующий/некорректный/сменившийся/недоступный clock сохраняет unknown/null и не обходит renewal guard252; ошибка самого каталога сохраняет error. Legacy старые форматы без надёжного контекста не угадываются. diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index 0f195a8c..b3c051d5 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -1,6 +1,6 @@ # UX/UI: журнал реализации -Актуальное продолжение 2026-10-14: 255 — canonical native даты учитывают явно наблюдаемый стабильный gmt-offset, сохраняя raw и unknown при отсутствии/сбое/смене clock. Каталог и renewal используют общий reader; профиль8 scripts/build/lint прошёл. Runtime backend ux-certificates255/frontend ux-certificates251. Следующий256 — повторная реальная issuance + scheduler renewal/fingerprint по уже сохранённому разрешению254, свежий baseline и полный откат; реальный renewal пока не принят. Этап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 открыт. Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md). @@ -36,7 +36,25 @@ | 10 | Маршрутизация, диагностика, карта и терминал | Завершён в границах локальной приёмки 2026-10-01; блоки 10.1–10.71, внешние ограничения в 10.71 | | 11А | Пользователи, контейнеры, сертификаты и резервные копии | Завершён в границах локальной приёмки 2026-10-01; 11А.1–11А.33, внешние ограничения в 11А.33 | | 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 | -| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная12.3.83; внешняя12.5 открыта, блоки1–255 в записанных scopes; исправление timezone255 проверено локально, реальный renewal256 ещё открыт в записанных scopes. Runtime backend ux-certificates255/frontend ux-certificates251. Общая регрессия249 и профиль сертификатов252 прошли; реальный renewal остаётся открытым. | +| 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.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). + +Native date reader255 теперь правильно даёт validFrom/validUntil в UTC и daysLeft90, совпадающие с X509. Но statusunknown: REST7.24.5 не присылает flags/revoked/expired. Первый enable-triggered run был skipped/renewed0, без нового ACME order; comparator expectation renewed1 не прошёл и сохранён отдельно [skip](ux-ui/acceptance/acme256-renewal-skipped.json). Каталог не должен выдумывать revocation state. Для renewal правило уточнено: достаточно подтверждённого срока и начавшегося непротиворечивого validity window; unknown/missing/changed clock, missing start, future start и reversed window по-прежнему не создают job. Это соответствует прежнему разрешённому renewal даже для explicit revoked/expired, не объявляет сертификат valid. + +Collector native-clock→canonical-state→issue тесты: stable offset создаёт одну задачу, changed/missing не создаёт; отсутствующие REST revocation flags с известными dates позволяют renewal, неизвестные/future/reversed dates блокируют. Все8 profile scripts, contracts/backend build, scoped lint passed. Production backend ux-certificates256/source528/compiled1080 совпал, healthy, frontend251/DB/Portal сохранены. Изменённый scope не требовал повтора полной регрессии249. + +После исправления один enable-triggered scheduler run (interval86400/threshold90), без run-now: scheduler issue →done, receipt ok/renewed1/errors0, затем enabledfalse до следующего интервала. Реально served TLS сменился с342d36bc… на70b7b9f1… и совпал с новым private-key native leaf; доверие/SNI не отключались. [After](ux-ui/acceptance/acme256-tls-after.json), [jobs](ux-ui/acceptance/acme256-jobs.json), [run](ux-ui/acceptance/acme256-renewal.json). + +Приёмка renewal всё равно открыта: /certificate содержит2 leaf с одинаковым logical name mmt253-8e62a941, разными .id/SHA-256 и private-key=true. [Duplicate leaves](ux-ui/acceptance/acme256-duplicate-leaves.json). Next collector отвергнет неоднозначность; import ACK + done не доказывают устойчивую замену. Fresh TLS действительно served new leaf, поэтому не заявляем, что TLS остался старым. До fresh sample ошибочно скопированный вручную fingerprint не прошёл SHA-256 validation; затем использован точный файл native report через SCP. Предусловие statusvalid оказалось неверным для REST flags; первый scheduler run безопасно skipped, после уточнения policy выполнен один реальный order. Не суммируем skip как успешное renewal. + +Cleanup сначала столкнулся с REST401/500; точный ответ resource500: too many concurrent logins (6). Временно остановлен только backend, затем восстановлены configured www-ssl/api-ssl, удалены оба own leaf/chain/PEM. Certificates/file stable metadata/configured services/normalized export совпали с fresh baseline; исключены dynamic connections и directory mtime. [Native](ux-ui/acceptance/acme256-native.json). Backend снова запущен и healthy, Portal послеcleanup оба CHR читает безошибочно; пароли не менялись. REST concurrency остаётся отдельным воспроизведённым ограничением. + +TXT приложение удалило; own A удалена по exact name/type/content/ID, DNS0 обоихимён [DNS](ux-ui/acceptance/acme256-cloudflare.json). ACME/renew settings восстановлены целиком, scheduler refresh применён, его empty-run metadata затем восстановлена exact SQL compare. Own issue jobs удалены, private token/baseline/helper/SQL файлы удалены с Debian и локальный secret fixture удалён. Historical events/scheduler receipts/CA records сохранены; CA revocation не заявляется. + +Следующий257 — post-import подтверждение unique logical name/expected X509 fingerprint/private key перед применением/успехом и безопасная стратегия уникальной замены без потери старого working leaf. Дополнительно проработать per-device REST concurrency/login cap. Эти дефекты не маскируются сообщением successful acceptance; новое разрешение на тот же согласованный сценарий не запрашивается без изменения его риска/объёма. ## 12.5.255 — native даты с подтверждённым offset для каталога и renewal (2026-10-14) diff --git a/docs/ux-ui-remaining-acceptance.md b/docs/ux-ui-remaining-acceptance.md index 28962960..53049fe5 100644 --- a/docs/ux-ui-remaining-acceptance.md +++ b/docs/ux-ui-remaining-acceptance.md @@ -1,10 +1,28 @@ # Оставшаяся внешняя приёмка — 2026-10-14 -Актуальное продолжение 2026-10-14: 255 — canonical native даты учитывают явно наблюдаемый стабильный gmt-offset, сохраняя raw и unknown при отсутствии/сбое/смене clock. Каталог и renewal используют общий reader; профиль8 scripts/build/lint прошёл. Runtime backend ux-certificates255/frontend ux-certificates251. Следующий256 — повторная реальная issuance + scheduler renewal/fingerprint по уже сохранённому разрешению254, свежий baseline и полный откат; реальный renewal пока не принят. Этап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 открыт. -Это актуальная матрица после 12.5.255. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии. +Это актуальная матрица после 12.5.256. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии. -Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-certificates255`, 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-certificates256`, frontend `ux-certificates251`; production lab с195 использует AUTH_REQUIRED=true и Portal1.3.1. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись. + +## 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). + +Native date reader255 теперь правильно даёт validFrom/validUntil в UTC и daysLeft90, совпадающие с X509. Но statusunknown: REST7.24.5 не присылает flags/revoked/expired. Первый enable-triggered run был skipped/renewed0, без нового ACME order; comparator expectation renewed1 не прошёл и сохранён отдельно [skip](ux-ui/acceptance/acme256-renewal-skipped.json). Каталог не должен выдумывать revocation state. Для renewal правило уточнено: достаточно подтверждённого срока и начавшегося непротиворечивого validity window; unknown/missing/changed clock, missing start, future start и reversed window по-прежнему не создают job. Это соответствует прежнему разрешённому renewal даже для explicit revoked/expired, не объявляет сертификат valid. + +Collector native-clock→canonical-state→issue тесты: stable offset создаёт одну задачу, changed/missing не создаёт; отсутствующие REST revocation flags с известными dates позволяют renewal, неизвестные/future/reversed dates блокируют. Все8 profile scripts, contracts/backend build, scoped lint passed. Production backend ux-certificates256/source528/compiled1080 совпал, healthy, frontend251/DB/Portal сохранены. Изменённый scope не требовал повтора полной регрессии249. + +После исправления один enable-triggered scheduler run (interval86400/threshold90), без run-now: scheduler issue →done, receipt ok/renewed1/errors0, затем enabledfalse до следующего интервала. Реально served TLS сменился с342d36bc… на70b7b9f1… и совпал с новым private-key native leaf; доверие/SNI не отключались. [After](ux-ui/acceptance/acme256-tls-after.json), [jobs](ux-ui/acceptance/acme256-jobs.json), [run](ux-ui/acceptance/acme256-renewal.json). + +Приёмка renewal всё равно открыта: /certificate содержит2 leaf с одинаковым logical name mmt253-8e62a941, разными .id/SHA-256 и private-key=true. [Duplicate leaves](ux-ui/acceptance/acme256-duplicate-leaves.json). Next collector отвергнет неоднозначность; import ACK + done не доказывают устойчивую замену. Fresh TLS действительно served new leaf, поэтому не заявляем, что TLS остался старым. До fresh sample ошибочно скопированный вручную fingerprint не прошёл SHA-256 validation; затем использован точный файл native report через SCP. Предусловие statusvalid оказалось неверным для REST flags; первый scheduler run безопасно skipped, после уточнения policy выполнен один реальный order. Не суммируем skip как успешное renewal. + +Cleanup сначала столкнулся с REST401/500; точный ответ resource500: too many concurrent logins (6). Временно остановлен только backend, затем восстановлены configured www-ssl/api-ssl, удалены оба own leaf/chain/PEM. Certificates/file stable metadata/configured services/normalized export совпали с fresh baseline; исключены dynamic connections и directory mtime. [Native](ux-ui/acceptance/acme256-native.json). Backend снова запущен и healthy, Portal послеcleanup оба CHR читает безошибочно; пароли не менялись. REST concurrency остаётся отдельным воспроизведённым ограничением. + +TXT приложение удалило; own A удалена по exact name/type/content/ID, DNS0 обоихимён [DNS](ux-ui/acceptance/acme256-cloudflare.json). ACME/renew settings восстановлены целиком, scheduler refresh применён, его empty-run metadata затем восстановлена exact SQL compare. Own issue jobs удалены, private token/baseline/helper/SQL файлы удалены с Debian и локальный secret fixture удалён. Historical events/scheduler receipts/CA records сохранены; CA revocation не заявляется. + +Следующий257 — post-import подтверждение unique logical name/expected X509 fingerprint/private key перед применением/успехом и безопасная стратегия уникальной замены без потери старого working leaf. Дополнительно проработать per-device REST concurrency/login cap. Эти дефекты не маскируются сообщением successful acceptance; новое разрешение на тот же согласованный сценарий не запрашивается без изменения его риска/объёма. ## 12.5.255 — native даты с подтверждённым offset для каталога и renewal (2026-10-14) @@ -292,7 +310,7 @@ Portal offline JWT действует доexp после logout/role change; э ## Следующая последовательность — 2026-10-14 -1. **Сертификаты256:** live catalog/renewal timezone fix255 проверен локально; повторить first issuance + scheduler renewal/fingerprint по действующему разрешению254 с новым baseline и полным откатом. Реальная замена leaf ещё не принята. +1. **Сертификаты257:** real scheduler renewal256 сменил TLS, но создаёт duplicate logical names. Исправить post-import verification/unique replacement; отдельно REST max concurrent logins6. Приёмка пока открыта. 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 d43371fc..664560be 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -1,6 +1,6 @@ # Этап 12: итоговая приёмка UX/UI -Актуальное продолжение 2026-10-14: 255 — canonical native даты учитывают явно наблюдаемый стабильный gmt-offset, сохраняя raw и unknown при отсутствии/сбое/смене clock. Каталог и renewal используют общий reader; профиль8 scripts/build/lint прошёл. Runtime backend ux-certificates255/frontend ux-certificates251. Следующий256 — повторная реальная issuance + scheduler renewal/fingerprint по уже сохранённому разрешению254, свежий baseline и полный откат; реальный renewal пока не принят. Этап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 открыт. План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push. @@ -4152,6 +4152,24 @@ 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.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). + +Native date reader255 теперь правильно даёт validFrom/validUntil в UTC и daysLeft90, совпадающие с X509. Но statusunknown: REST7.24.5 не присылает flags/revoked/expired. Первый enable-triggered run был skipped/renewed0, без нового ACME order; comparator expectation renewed1 не прошёл и сохранён отдельно [skip](ux-ui/acceptance/acme256-renewal-skipped.json). Каталог не должен выдумывать revocation state. Для renewal правило уточнено: достаточно подтверждённого срока и начавшегося непротиворечивого validity window; unknown/missing/changed clock, missing start, future start и reversed window по-прежнему не создают job. Это соответствует прежнему разрешённому renewal даже для explicit revoked/expired, не объявляет сертификат valid. + +Collector native-clock→canonical-state→issue тесты: stable offset создаёт одну задачу, changed/missing не создаёт; отсутствующие REST revocation flags с известными dates позволяют renewal, неизвестные/future/reversed dates блокируют. Все8 profile scripts, contracts/backend build, scoped lint passed. Production backend ux-certificates256/source528/compiled1080 совпал, healthy, frontend251/DB/Portal сохранены. Изменённый scope не требовал повтора полной регрессии249. + +После исправления один enable-triggered scheduler run (interval86400/threshold90), без run-now: scheduler issue →done, receipt ok/renewed1/errors0, затем enabledfalse до следующего интервала. Реально served TLS сменился с342d36bc… на70b7b9f1… и совпал с новым private-key native leaf; доверие/SNI не отключались. [After](ux-ui/acceptance/acme256-tls-after.json), [jobs](ux-ui/acceptance/acme256-jobs.json), [run](ux-ui/acceptance/acme256-renewal.json). + +Приёмка renewal всё равно открыта: /certificate содержит2 leaf с одинаковым logical name mmt253-8e62a941, разными .id/SHA-256 и private-key=true. [Duplicate leaves](ux-ui/acceptance/acme256-duplicate-leaves.json). Next collector отвергнет неоднозначность; import ACK + done не доказывают устойчивую замену. Fresh TLS действительно served new leaf, поэтому не заявляем, что TLS остался старым. До fresh sample ошибочно скопированный вручную fingerprint не прошёл SHA-256 validation; затем использован точный файл native report через SCP. Предусловие statusvalid оказалось неверным для REST flags; первый scheduler run безопасно skipped, после уточнения policy выполнен один реальный order. Не суммируем skip как успешное renewal. + +Cleanup сначала столкнулся с REST401/500; точный ответ resource500: too many concurrent logins (6). Временно остановлен только backend, затем восстановлены configured www-ssl/api-ssl, удалены оба own leaf/chain/PEM. Certificates/file stable metadata/configured services/normalized export совпали с fresh baseline; исключены dynamic connections и directory mtime. [Native](ux-ui/acceptance/acme256-native.json). Backend снова запущен и healthy, Portal послеcleanup оба CHR читает безошибочно; пароли не менялись. REST concurrency остаётся отдельным воспроизведённым ограничением. + +TXT приложение удалило; own A удалена по exact name/type/content/ID, DNS0 обоихимён [DNS](ux-ui/acceptance/acme256-cloudflare.json). ACME/renew settings восстановлены целиком, scheduler refresh применён, его empty-run metadata затем восстановлена exact SQL compare. Own issue jobs удалены, private token/baseline/helper/SQL файлы удалены с Debian и локальный secret fixture удалён. Historical events/scheduler receipts/CA records сохранены; CA revocation не заявляется. + +Следующий257 — post-import подтверждение unique logical name/expected X509 fingerprint/private key перед применением/успехом и безопасная стратегия уникальной замены без потери старого working leaf. Дополнительно проработать per-device REST concurrency/login cap. Эти дефекты не маскируются сообщением successful acceptance; новое разрешение на тот же согласованный сценарий не запрашивается без изменения его риска/объёма. + ## 12.5.255 — native даты с подтверждённым offset для каталога и renewal (2026-10-14) После измерения254 добавлен общий readCertificateRowsWithClock: читает gmt-offset перед каталогом, для native timezone-less дат повторно читает clock после; принимает только одинаковые валидные offsets. Native YYYY-MM-DD HH:mm:ss преобразуется в offset-qualified datetime с проверкой календаря, затем UTC; explicit ISO сохраняет свой offset. Не предполагается UTC/пояс backend/будущий DST. rawValidFrom/rawValidUntil остаются исходными. Отсутствующий/некорректный/сменившийся/недоступный clock сохраняет unknown/null и не обходит renewal guard252; ошибка самого каталога сохраняет error. Legacy старые форматы без надёжного контекста не угадываются. diff --git a/docs/ux-ui-test-server.md b/docs/ux-ui-test-server.md index 439a3b13..e83c656d 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-14: 255 — canonical native даты учитывают явно наблюдаемый стабильный gmt-offset, сохраняя raw и unknown при отсутствии/сбое/смене clock. Каталог и renewal используют общий reader; профиль8 scripts/build/lint прошёл. Runtime backend ux-certificates255/frontend ux-certificates251. Следующий256 — повторная реальная issuance + scheduler renewal/fingerprint по уже сохранённому разрешению254, свежий baseline и полный откат; реальный renewal пока не принят. Этап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 открыт. | Параметр | Проверенное состояние | | --- | --- | @@ -34,6 +34,24 @@ 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.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). + +Native date reader255 теперь правильно даёт validFrom/validUntil в UTC и daysLeft90, совпадающие с X509. Но statusunknown: REST7.24.5 не присылает flags/revoked/expired. Первый enable-triggered run был skipped/renewed0, без нового ACME order; comparator expectation renewed1 не прошёл и сохранён отдельно [skip](ux-ui/acceptance/acme256-renewal-skipped.json). Каталог не должен выдумывать revocation state. Для renewal правило уточнено: достаточно подтверждённого срока и начавшегося непротиворечивого validity window; unknown/missing/changed clock, missing start, future start и reversed window по-прежнему не создают job. Это соответствует прежнему разрешённому renewal даже для explicit revoked/expired, не объявляет сертификат valid. + +Collector native-clock→canonical-state→issue тесты: stable offset создаёт одну задачу, changed/missing не создаёт; отсутствующие REST revocation flags с известными dates позволяют renewal, неизвестные/future/reversed dates блокируют. Все8 profile scripts, contracts/backend build, scoped lint passed. Production backend ux-certificates256/source528/compiled1080 совпал, healthy, frontend251/DB/Portal сохранены. Изменённый scope не требовал повтора полной регрессии249. + +После исправления один enable-triggered scheduler run (interval86400/threshold90), без run-now: scheduler issue →done, receipt ok/renewed1/errors0, затем enabledfalse до следующего интервала. Реально served TLS сменился с342d36bc… на70b7b9f1… и совпал с новым private-key native leaf; доверие/SNI не отключались. [After](ux-ui/acceptance/acme256-tls-after.json), [jobs](ux-ui/acceptance/acme256-jobs.json), [run](ux-ui/acceptance/acme256-renewal.json). + +Приёмка renewal всё равно открыта: /certificate содержит2 leaf с одинаковым logical name mmt253-8e62a941, разными .id/SHA-256 и private-key=true. [Duplicate leaves](ux-ui/acceptance/acme256-duplicate-leaves.json). Next collector отвергнет неоднозначность; import ACK + done не доказывают устойчивую замену. Fresh TLS действительно served new leaf, поэтому не заявляем, что TLS остался старым. До fresh sample ошибочно скопированный вручную fingerprint не прошёл SHA-256 validation; затем использован точный файл native report через SCP. Предусловие statusvalid оказалось неверным для REST flags; первый scheduler run безопасно skipped, после уточнения policy выполнен один реальный order. Не суммируем skip как успешное renewal. + +Cleanup сначала столкнулся с REST401/500; точный ответ resource500: too many concurrent logins (6). Временно остановлен только backend, затем восстановлены configured www-ssl/api-ssl, удалены оба own leaf/chain/PEM. Certificates/file stable metadata/configured services/normalized export совпали с fresh baseline; исключены dynamic connections и directory mtime. [Native](ux-ui/acceptance/acme256-native.json). Backend снова запущен и healthy, Portal послеcleanup оба CHR читает безошибочно; пароли не менялись. REST concurrency остаётся отдельным воспроизведённым ограничением. + +TXT приложение удалило; own A удалена по exact name/type/content/ID, DNS0 обоихимён [DNS](ux-ui/acceptance/acme256-cloudflare.json). ACME/renew settings восстановлены целиком, scheduler refresh применён, его empty-run metadata затем восстановлена exact SQL compare. Own issue jobs удалены, private token/baseline/helper/SQL файлы удалены с Debian и локальный secret fixture удалён. Historical events/scheduler receipts/CA records сохранены; CA revocation не заявляется. + +Следующий257 — post-import подтверждение unique logical name/expected X509 fingerprint/private key перед применением/успехом и безопасная стратегия уникальной замены без потери старого working leaf. Дополнительно проработать per-device REST concurrency/login cap. Эти дефекты не маскируются сообщением successful acceptance; новое разрешение на тот же согласованный сценарий не запрашивается без изменения его риска/объёма. + ## 12.5.255 — native даты с подтверждённым offset для каталога и renewal (2026-10-14) После измерения254 добавлен общий readCertificateRowsWithClock: читает gmt-offset перед каталогом, для native timezone-less дат повторно читает clock после; принимает только одинаковые валидные offsets. Native YYYY-MM-DD HH:mm:ss преобразуется в offset-qualified datetime с проверкой календаря, затем UTC; explicit ISO сохраняет свой offset. Не предполагается UTC/пояс backend/будущий DST. rawValidFrom/rawValidUntil остаются исходными. Отсутствующий/некорректный/сменившийся/недоступный clock сохраняет unknown/null и не обходит renewal guard252; ошибка самого каталога сохраняет error. Legacy старые форматы без надёжного контекста не угадываются. diff --git a/docs/ux-ui/acceptance/acme256-cloudflare.json b/docs/ux-ui/acceptance/acme256-cloudflare.json new file mode 100644 index 00000000..c3020fdb --- /dev/null +++ b/docs/ux-ui/acceptance/acme256-cloudflare.json @@ -0,0 +1,33 @@ +{ + "date": "2026-10-14", + "block": "12.5.256", + "domain": "pngs.top", + "checks": [ + { + "endpoint": "/user/tokens/verify", + "status": 200, + "success": true, + "errors": [] + }, + { + "endpoint": "/zones?name=pngs.top", + "status": 200, + "success": true, + "errors": [] + } + ], + "tokenActive": true, + "zoneActive": true, + "accountMatched": true, + "nameservers": [ + "cheryl.ns.cloudflare.com", + "doug.ns.cloudflare.com" + ], + "originalFqdnRecords": 0, + "originalChallengeRecords": 0, + "fqdn": "mmt253-8e62a941.pngs.top", + "applicationTxtCleanupConfirmed": true, + "ownARecordDeleted": true, + "finalFqdnRecords": 0, + "finalChallengeRecords": 0 +} diff --git a/docs/ux-ui/acceptance/acme256-duplicate-leaves.json b/docs/ux-ui/acceptance/acme256-duplicate-leaves.json new file mode 100644 index 00000000..d292401f --- /dev/null +++ b/docs/ux-ui/acceptance/acme256-duplicate-leaves.json @@ -0,0 +1,16 @@ +[ + { + ".id": "*7", + "name": "mmt253-8e62a941", + "common-name": "mmt253-8e62a941.pngs.top", + "fingerprint": "342d36bc13c4f4e807add3833f61b6b70aa4791cbf297bcbef1b0026d22b51a3", + "private-key": "true" + }, + { + ".id": "*A", + "name": "mmt253-8e62a941", + "common-name": "mmt253-8e62a941.pngs.top", + "fingerprint": "70b7b9f11a77e5e1d21b43074fd84fa334a04bb7e871315eda1739638e8164be", + "private-key": "true" + } +] \ No newline at end of file diff --git a/docs/ux-ui/acceptance/acme256-jobs.json b/docs/ux-ui/acceptance/acme256-jobs.json new file mode 100644 index 00000000..a5229d98 --- /dev/null +++ b/docs/ux-ui/acceptance/acme256-jobs.json @@ -0,0 +1 @@ +[{"id" : "5b0aa78a-375d-4b6f-841f-aa8ff189408f", "source" : "manual", "status" : "done", "step" : "done", "serverId" : 2, "certName" : "mmt253-8e62a941", "error" : null}, {"id" : "2abc385b-3dd7-4ef5-b438-679c94d46e0c", "source" : "scheduler", "status" : "done", "step" : "done", "serverId" : 2, "certName" : "mmt253-8e62a941", "error" : null}] diff --git a/docs/ux-ui/acceptance/acme256-live-ui.json b/docs/ux-ui/acceptance/acme256-live-ui.json new file mode 100644 index 00000000..f0b6f62e --- /dev/null +++ b/docs/ux-ui/acceptance/acme256-live-ui.json @@ -0,0 +1,35 @@ +{ + "date": "2026-10-14", + "block": "12.5.256", + "fqdn": "mmt253-8e62a941.pngs.top", + "certName": "mmt253-8e62a941", + "serverId": "2", + "realPortalLogin": true, + "noMocks": true, + "issuePosts": 1, + "pageErrors": [], + "settings": { + "testStatus": 200, + "testOk": true, + "saveStatus": 200, + "tokenConfigured": true, + "directoryUrl": "https://acme-v02.api.letsencrypt.org/directory" + }, + "jobId": "5b0aa78a-375d-4b6f-841f-aa8ff189408f", + "accepted": true, + "job": { + "id": "5b0aa78a-375d-4b6f-841f-aa8ff189408f", + "status": "done", + "step": "done" + }, + "uiTerminalConfirmed": true, + "postCleanup": { + "tokenConfigured": false, + "ownJobStatus": 404, + "reachableServerIds": [ + "1", + "2" + ], + "ownCertificateAbsent": true + } +} diff --git a/docs/ux-ui/acceptance/acme256-native-before.json b/docs/ux-ui/acceptance/acme256-native-before.json new file mode 100644 index 00000000..6733c6a2 --- /dev/null +++ b/docs/ux-ui/acceptance/acme256-native-before.json @@ -0,0 +1,10 @@ +{ + "fqdn": "mmt253-8e62a941.pngs.top", + "certName": "mmt253-8e62a941", + "nativePrivateKey": true, + "nativeFingerprint256": "342d36bc13c4f4e807add3833f61b6b70aa4791cbf297bcbef1b0026d22b51a3", + "rawValidFrom": "2026-10-14 17:51:49", + "rawValidUntil": "2027-01-12 17:51:48", + "clockOffset": "+02:00", + "clockTimeZone": "Europe/Warsaw" +} \ No newline at end of file diff --git a/docs/ux-ui/acceptance/acme256-native.json b/docs/ux-ui/acceptance/acme256-native.json new file mode 100644 index 00000000..38624f0a --- /dev/null +++ b/docs/ux-ui/acceptance/acme256-native.json @@ -0,0 +1,13 @@ +{ + "date": "2026-10-15", + "block": "12.5.256", + "certificatesBaselineMatched": true, + "fileStableMetadataBaselineMatched": true, + "directoryLastModifiedExcluded": true, + "configuredServicesBaselineMatched": true, + "dynamicServiceConnectionsExcluded": true, + "normalizedExportMatched": true, + "passwordChanged": false, + "temporaryBackendPauseForLoginLimit": true, + "backendRestartedAfterCleanup": true +} \ No newline at end of file diff --git a/docs/ux-ui/acceptance/acme256-renewal-skipped.json b/docs/ux-ui/acceptance/acme256-renewal-skipped.json new file mode 100644 index 00000000..856f17a2 --- /dev/null +++ b/docs/ux-ui/acceptance/acme256-renewal-skipped.json @@ -0,0 +1,57 @@ +{ + "date": "2026-10-14", + "block": "12.5.256", + "fqdn": "mmt253-8e62a941.pngs.top", + "enableRequests": 1, + "runNowRequests": 0, + "run": { + "id": "sch-1791996745022-sq19wrmj", + "jobKey": "certificates_renew", + "startedAt": "2026-10-14 16:52:24.191+00", + "finishedAt": "2026-10-14 16:52:25.02+00", + "status": "ok", + "error": null, + "durationMs": 829, + "resultJson": { + "v": 1, + "job": "certificates_renew", + "errors": [], + "checked": 1, + "renewed": 0, + "targets": [ + { + "action": "skipped", + "message": "Срок или состояние сертификата не подтверждены. Обновление не выполнялось.", + "certName": "mmt253-8e62a941", + "daysLeft": 90, + "serverId": "2" + } + ], + "sampledAt": "2026-10-14T16:52:24.191Z", + "skippedTargets": 1 + } + }, + "snapshot": { + "v": 1, + "job": "certificates_renew", + "errors": [], + "checked": 1, + "renewed": 0, + "targets": [ + { + "action": "skipped", + "message": "Срок или состояние сертификата не подтверждены. Обновление не выполнялось.", + "certName": "mmt253-8e62a941", + "daysLeft": 90, + "serverId": "2" + } + ], + "sampledAt": "2026-10-14T16:52:24.191Z", + "skippedTargets": 1 + }, + "failure": { + "name": "AssertionError", + "message": "Expected values to be strictly equal:\n\n0 !== 1\n" + }, + "disabledAfterRun": true +} diff --git a/docs/ux-ui/acceptance/acme256-renewal.json b/docs/ux-ui/acceptance/acme256-renewal.json new file mode 100644 index 00000000..39e951b0 --- /dev/null +++ b/docs/ux-ui/acceptance/acme256-renewal.json @@ -0,0 +1,73 @@ +{ + "date": "2026-10-15", + "block": "12.5.256", + "fqdn": "mmt253-8e62a941.pngs.top", + "enableRequests": 1, + "runNowRequests": 0, + "eligibility": { + "validFrom": "2026-10-14T15:51:49.000Z", + "validUntil": "2027-01-12T15:51:48.000Z", + "daysLeft": 90, + "revocationStatus": "unknown" + }, + "run": { + "id": "sch-1791997029723-ryvhn6y9", + "jobKey": "certificates_renew", + "startedAt": "2026-10-14 16:56:33.978+00", + "finishedAt": "2026-10-14 16:57:09.722+00", + "status": "ok", + "error": null, + "durationMs": 35744, + "resultJson": { + "v": 1, + "job": "certificates_renew", + "errors": [], + "checked": 1, + "renewed": 1, + "targets": [ + { + "action": "renewed", + "message": "Задача обновления завершена", + "certName": "mmt253-8e62a941", + "daysLeft": 90, + "serverId": "2" + } + ], + "sampledAt": "2026-10-14T16:56:33.978Z", + "skippedTargets": 0 + } + }, + "snapshot": { + "v": 1, + "job": "certificates_renew", + "errors": [], + "checked": 1, + "renewed": 1, + "targets": [ + { + "action": "renewed", + "message": "Задача обновления завершена", + "certName": "mmt253-8e62a941", + "daysLeft": 90, + "serverId": "2" + } + ], + "sampledAt": "2026-10-14T16:56:33.978Z", + "skippedTargets": 0 + }, + "disabledAfterRun": true, + "nativeVerification": { + "duplicateLogicalNames": true, + "servedLeafChanged": true, + "newServedLeafMatchesNative": true, + "overallRenewalAccepted": false, + "reason": "Duplicate logical names prevent unambiguous subsequent renewal" + }, + "cleanup": { + "nativeBaselineMatched": true, + "sqlSettingsRestored": true, + "ownJobsRemoved": true, + "dnsNamesEmpty": true, + "backendHealthy": true + } +} diff --git a/docs/ux-ui/acceptance/acme256-tls-after.json b/docs/ux-ui/acceptance/acme256-tls-after.json new file mode 100644 index 00000000..405054a3 --- /dev/null +++ b/docs/ux-ui/acceptance/acme256-tls-after.json @@ -0,0 +1,10 @@ +{ + "sampledAt": "2026-10-14T16:59:24.292Z", + "fqdn": "mmt253-8e62a941.pngs.top", + "remoteAddress": "104.165.244.7", + "authorized": true, + "fingerprint256": "70:B7:B9:F1:1A:77:E5:E1:D2:1B:43:07:4F:D8:4F:A3:34:A0:4B:B7:E8:71:31:5E:DA:17:39:63:8E:81:64:BE", + "validFrom": "Oct 14 15:58:34 2026 GMT", + "validUntil": "Jan 12 15:58:33 2027 GMT", + "changedFromBefore": true +} diff --git a/docs/ux-ui/acceptance/acme256-tls-before.json b/docs/ux-ui/acceptance/acme256-tls-before.json new file mode 100644 index 00000000..ec7cd3ff --- /dev/null +++ b/docs/ux-ui/acceptance/acme256-tls-before.json @@ -0,0 +1,15 @@ +{ + "sampledAt": "2026-10-14T16:52:16.930Z", + "fqdn": "mmt253-8e62a941.pngs.top", + "certName": "mmt253-8e62a941", + "remoteAddress": "104.165.244.7", + "authorized": true, + "hostnameMatched": true, + "protocol": "TLSv1.2", + "fingerprint256": "34:2D:36:BC:13:C4:F4:E8:07:AD:D3:83:3F:61:B6:B7:0A:A4:79:1C:BF:29:7B:CB:EF:1B:00:26:D2:2B:51:A3", + "validFrom": "Oct 14 15:51:49 2026 GMT", + "validUntil": "Jan 12 15:51:48 2027 GMT", + "nativeName": "mmt253-8e62a941", + "nativePrivateKey": true, + "nativeFingerprint256": "342d36bc13c4f4e807add3833f61b6b70aa4791cbf297bcbef1b0026d22b51a3" +} diff --git a/docs/ux-ui/acceptance/certificates256-deployment.json b/docs/ux-ui/acceptance/certificates256-deployment.json new file mode 100644 index 00000000..81072bbe --- /dev/null +++ b/docs/ux-ui/acceptance/certificates256-deployment.json @@ -0,0 +1,19 @@ +{ + "date": "2026-10-15", + "block": "12.5.256", + "backendImage": "mmapp-test/backend:ux-certificates256", + "frontendImage": "mmapp-test/frontend:ux-certificates251", + "health": "healthy", + "sourceMatched": 528, + "compiled": { + "matched": 1080, + "missing": 0, + "different": 0 + }, + "profileScripts": 8, + "profile": "passed", + "build": "passed", + "lint": "passed", + "realRenewalAccepted": false, + "restLoginLimitObserved": 6 +}