diff --git a/app/(main)/uptime/page.tsx b/app/(main)/uptime/page.tsx index 5ee4357e..b29c55e4 100644 --- a/app/(main)/uptime/page.tsx +++ b/app/(main)/uptime/page.tsx @@ -2,6 +2,7 @@ import { publishSpeedProbeSnapshots } from "@/lib/speed-probe-snapshot" import { parseSpeedRunResponse } from "@/lib/speed-run-response" +import { isSpeedPersistenceUnknown } from "@/lib/speed-persistence-outcome" import { dashboardProbeOverviewSchema } from "@/lib/dashboard-probes" @@ -1811,7 +1812,7 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId lines: [`status: error`, message], }) : r)) setSpeedError(message) - setSpeedProbes((prev) => prev.map((p) => p.id === probe.id ? ({ + if (!isSpeedPersistenceUnknown(e, runId)) setSpeedProbes((prev) => prev.map((p) => p.id === probe.id ? ({ ...p, lastRunAt: new Date().toISOString(), lastStatus: "error", diff --git a/docs/ux-ui-external-acceptance.md b/docs/ux-ui-external-acceptance.md index b4fbfcf1..1885d5b6 100644 --- a/docs/ux-ui-external-acceptance.md +++ b/docs/ux-ui-external-acceptance.md @@ -113,3 +113,7 @@ BGP-prefix evaluator, отдельное live GRE расписание и recove ## PG-8 — 2026-10-08 12.5.19 [realPG/nativeTCP матрица](ux-ui/acceptance/pg8-speed-2026-10-08.json) подтвердилаINSERTrollback иclient-sideackfault послереальногоCOMMIT,безложногоfailurewrite/повтора. API502unknown/runId;scheduledreceiptokfalse,история/snapshotdoneсохранены.18scheduler/16persistence/HTTP-RBAC/build passed. Это не физическийTCPdisconnect;browserunknown-speed/deploymentpatch ещё12.5.20. Основнойbackend4c12c55 сохраняется. + +## Локальный snapshot guard — 2026-10-08 + +12.5.20 сохраняетlastconfirmedspeed snapshot дляunknown502/совпадающегоrunId.2unit tests/frontendtypecheck passed;browserunknownUX ещёнепроверен,patchнеразвёрнут. Backend4c12c55/frontend673e5ba прежние. Следующий12.5.21 — build/browserunknown/deploymentобоихобразов. diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index 9eee9576..8bc064df 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -1,6 +1,6 @@ # План реализации UX/UI по пятичасовым окнам -Актуальное продолжение 2026-10-08:12.5.19 завершён — realPG/nativeTCP подтвердилиINSERTrollback иclient-sideackloss послеCOMMIT; APIunknown/runId безложногofailurewrite/повтора. Schedulerreceipt исправлен: unknownpersistence неложныйok.18scheduler/16persistence/HTTP-RBAC/build passed. Основнойbackend4c12c55 сохраняется,patchнеразвёрнут. Следующий12.5.20 — deploymentpatch/browserunknown-speed иоставшиесяROS/Q/интеграции. +Актуальное продолжение 2026-10-08:12.5.20 завершёнлокально — unknown502/currentrunId не переписываетподтверждённыйsnapshot пробы.2unit tests/frontendtypecheck passed;временнаястроказапросаещёerror,полныйbrowserunknownUX открыт. Runtimebackend4c12c55/frontend673e5ba прежние. Следующий12.5.21 — build/browserunknownspeed/deploymentобоихобразов. Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-07. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат. diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index 9a25d7db..fbd33fc2 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -1,6 +1,6 @@ # UX/UI: журнал реализации -Актуальное продолжение 2026-10-08:12.5.19 завершён — realPG/nativeTCP подтвердилиINSERTrollback иclient-sideackloss послеCOMMIT; APIunknown/runId безложногofailurewrite/повтора. Schedulerreceipt исправлен: unknownpersistence неложныйok.18scheduler/16persistence/HTTP-RBAC/build passed. Основнойbackend4c12c55 сохраняется,patchнеразвёрнут. Следующий12.5.20 — deploymentpatch/browserunknown-speed иоставшиесяROS/Q/интеграции. +Актуальное продолжение 2026-10-08:12.5.20 завершёнлокально — unknown502/currentrunId не переписываетподтверждённыйsnapshot пробы.2unit tests/frontendtypecheck passed;временнаястроказапросаещёerror,полныйbrowserunknownUX открыт. Runtimebackend4c12c55/frontend673e5ba прежние. Следующий12.5.21 — build/browserunknownspeed/deploymentобоихобразов. Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md). @@ -34,6 +34,14 @@ | 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 | | 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в описанных границах:12.3.83,main1186/1186,context221/221. Этап остаётся в работе: стенд12.5.1–3 развёрнут,bootstrap/smoke проверены;остальные сценарии12.5 не выполнены. | +## 12.5.20 — сохранение подтверждённого speed snapshot при unknown receipt (2026-10-08) + +Приостатке5%окнавыполненнебольшойлокальныйблокпередdeployment. Frontendcatch раньше ставилlastStatuserror/новыйlastRunAt любойошибкеPOSTspeed-test,включаяunknowncommit. Теперьподтверждённыйsnapshot пробысохраняется,еслиApiClientError502 содержитoutcomeunknown иrunId текущегозапроса. ЧужойrunId/неправильныйpayload/403/обычнаяошибка нераспознаютсякакэтотисход. Существующееservererrorсообщениепоказывается;новыекомпоненты/композицияReUIне менялись. Временнаястрокасамогозапросапо-прежнемуимеетstatuserror; полныйunknown-state/browserUX ещёнужнопроверить. + +Добавленыlib/speed-persistence-outcome.ts и2meaningfultests;подключенguardвuptime/page.tsx. Обаunit tests passed. ПервыйroottscподхватилкопиистарогоCI contextвignoredtmp ивернулисходныеошибкиэтихкопий. Повторныйполныйfrontendtypecheckсвременнымconfig,которыйсохраняетrootoptions/исключения иисключаетtmp,passed; основнойtsconfigне менялся. Diff-check passed. ReUI skillпрочитан: адаптациясуществующегоэкрана,безновойregistryinstallation/редизайна. Browserunknownspeed/productionbuild/deploymentвэтомблокенезапускались. + +Основнойruntimebackend4c12c55/frontend673e5ba сохраняется;backendpatchf408afb иfrontendguardещёнеразвёрнуты. Следующий12.5.21 — собратьобаобновлённыхобраза,проверитьbrowserunknownspeed/историю иразвернутьпослепроверок. Журналобновлён;локальныйкоммитбезpush. + ## 12.5.19 — speed persistence, ошибка INSERT и потерянное подтверждение commit (2026-10-08) Выявленриск: общийcatch native run послеошибкиpersist запускалrecordSpeedTestFailure, хотяbandwidth-test ужезавершён. Послеcommitackloss это моглоподменитьsnapshot/log ложнымerror (особенноеслиrunId не былпередан). ДобавленSpeedRunPersistenceUnknown(actualrunId,cause) тольконаграничепубликацииуспешногоnativeрезультата. Routeвозвращает502/outcomeunknown/runId/просьбупроверитьисторию; failurewriterне вызывается. Плановыйrun тоже не записываетложныйfailedresult. Исходныйdrivercause сохранёнвError,публичныйreceipt не раскрываетSQL/credentials. diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index fd1ba5b4..700a88a5 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -1,6 +1,6 @@ # Этап 12: итоговая приёмка UX/UI -Актуальное продолжение 2026-10-08:12.5.19 завершён — realPG/nativeTCP подтвердилиINSERTrollback иclient-sideackloss послеCOMMIT; APIunknown/runId безложногofailurewrite/повтора. Schedulerreceipt исправлен: unknownpersistence неложныйok.18scheduler/16persistence/HTTP-RBAC/build passed. Основнойbackend4c12c55 сохраняется,patchнеразвёрнут. Следующий12.5.20 — deploymentpatch/browserunknown-speed иоставшиесяROS/Q/интеграции. +Актуальное продолжение 2026-10-08:12.5.20 завершёнлокально — unknown502/currentrunId не переписываетподтверждённыйsnapshot пробы.2unit tests/frontendtypecheck passed;временнаястроказапросаещёerror,полныйbrowserunknownUX открыт. Runtimebackend4c12c55/frontend673e5ba прежние. Следующий12.5.21 — build/browserunknownspeed/deploymentобоихобразов. План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push. @@ -1146,3 +1146,11 @@ SchedulerSnapshotError раньшеигнорировалruns[].okfalse уuptime Матрицапотребоваладиагностикиharness: первыйwrapperнеподдерживалPool.connectcallback иупалдонативныхтестов; перваяполнаяnativeматрицавыявилаschedulerok дефект; rerunполучил500 приcallback instrumentation. Добавленыcallbackbypass/BEGINreset,разовыеmanualrunIDs иdiagnosticerrorhandler; окончательнаяматрицапрошла,проверяемые502обработаныproductionroute,неdiagnostic handler. Выполнялисьручныеповторыпослеисправленияharness/classifier; это неавтоповторвнутриоперации. ФинальныеcompiledJSиpatchSHA совпалислокальными. Temporaryprobe/trigger/function удалены,count0; historicaltest runs сохраненывпроверочнойБД,probeassociationпослеcleanupFK=null. Temporarycontainer--rm/consumers/pool остановлены,mainhealth4c12c55ok/БДнеизменена,RouterOSconfigurationне менялась. Локально18scheduler tests (включаяновыйunknownspeedreceipt),16speedpersistence tests,HTTPcommand/RBAC/unknownrunId-no-failure-write test иcontracts/backend build прошли; diff-check passed. Старыйосновнойruntime4c12c55 ещёбезэтогопatch. BrowserunknowncommitUX/физическийnetworkloss/deploymentpatch иостальной12.5 открыты. Остатокпятичасовоголимитапоследнейпроверки9%;новыйкрупныйблокненачат. Следующий12.5.20 — собрать/deploypatch иbrowserunknown-speed,затемROS/Q/integrationпротокол. Локальныйкоммитбезpush. + +## 12.5.20 — сохранение подтверждённого speed snapshot при unknown receipt (2026-10-08) + +Приостатке5%окнавыполненнебольшойлокальныйблокпередdeployment. Frontendcatch раньше ставилlastStatuserror/новыйlastRunAt любойошибкеPOSTspeed-test,включаяunknowncommit. Теперьподтверждённыйsnapshot пробысохраняется,еслиApiClientError502 содержитoutcomeunknown иrunId текущегозапроса. ЧужойrunId/неправильныйpayload/403/обычнаяошибка нераспознаютсякакэтотисход. Существующееservererrorсообщениепоказывается;новыекомпоненты/композицияReUIне менялись. Временнаястрокасамогозапросапо-прежнемуимеетstatuserror; полныйunknown-state/browserUX ещёнужнопроверить. + +Добавленыlib/speed-persistence-outcome.ts и2meaningfultests;подключенguardвuptime/page.tsx. Обаunit tests passed. ПервыйroottscподхватилкопиистарогоCI contextвignoredtmp ивернулисходныеошибкиэтихкопий. Повторныйполныйfrontendtypecheckсвременнымconfig,которыйсохраняетrootoptions/исключения иисключаетtmp,passed; основнойtsconfigне менялся. Diff-check passed. ReUI skillпрочитан: адаптациясуществующегоэкрана,безновойregistryinstallation/редизайна. Browserunknownspeed/productionbuild/deploymentвэтомблокенезапускались. + +Основнойruntimebackend4c12c55/frontend673e5ba сохраняется;backendpatchf408afb иfrontendguardещёнеразвёрнуты. Следующий12.5.21 — собратьобаобновлённыхобраза,проверитьbrowserunknownspeed/историю иразвернутьпослепроверок. Журналобновлён;локальныйкоммитбезpush. diff --git a/docs/ux-ui-test-server.md b/docs/ux-ui-test-server.md index bfe44208..b64d9630 100644 --- a/docs/ux-ui-test-server.md +++ b/docs/ux-ui-test-server.md @@ -121,3 +121,7 @@ ssh -N -L 3000:127.0.0.1:3000 -L 8000:127.0.0.1:8000 root@161.104.46.222 ## Isolated PG-8 speed — 2026-10-08 12.5.19 [INSERTfailure/realCOMMITackfault/scheduledreceipt](ux-ui/acceptance/pg8-speed-2026-10-08.json) passedнаmmapp_acceptance_20261007/source4c12c55+worktreepatch среальнымиCHRtransmit3s. Temporaryprobe/trigger/function удалены,историческиетестовыерезультатыосталисьвпроверочнойБД. Основнойruntime4c12c55healthy/БДнеизменена;patchещёнеразвёрнут. Следующий12.5.20 — deployment/browserunknown-speed. + +## Локальный snapshot guard — 2026-10-08 + +12.5.20 сохраняетlastconfirmedspeed snapshot дляunknown502/совпадающегоrunId.2unit tests/frontendtypecheck passed;browserunknownUX ещёнепроверен,patchнеразвёрнут. Backend4c12c55/frontend673e5ba прежние. Следующий12.5.21 — build/browserunknown/deploymentобоихобразов. diff --git a/lib/speed-persistence-outcome.test.ts b/lib/speed-persistence-outcome.test.ts new file mode 100644 index 00000000..603e4cb6 --- /dev/null +++ b/lib/speed-persistence-outcome.test.ts @@ -0,0 +1,19 @@ +import assert from "node:assert/strict" +import { test } from "node:test" +import { ApiClientError } from "../shared/api/http-client" +import { isSpeedPersistenceUnknown } from "./speed-persistence-outcome" + +test("matching unknown persistence receipt preserves the confirmed snapshot", () => { + assert.equal(isSpeedPersistenceUnknown(new ApiClientError("unknown", 502, { outcome: "unknown", runId: "current" }), "current"), true) +}) + +test("other operations, malformed responses and ordinary failures cannot masquerade as unknown persistence", () => { + for (const error of [ + Error("unknown"), + new ApiClientError("unknown", 502, { outcome: "unknown", runId: "other" }), + new ApiClientError("unknown", 502, { outcome: "unknown" }), + new ApiClientError("unknown", 502, { outcome: "failed", runId: "current" }), + new ApiClientError("forbidden", 403, { outcome: "unknown", runId: "current" }), + new ApiClientError("bad body", 502, null), + ]) assert.equal(isSpeedPersistenceUnknown(error, "current"), false) +}) diff --git a/lib/speed-persistence-outcome.ts b/lib/speed-persistence-outcome.ts new file mode 100644 index 00000000..d58b9065 --- /dev/null +++ b/lib/speed-persistence-outcome.ts @@ -0,0 +1,9 @@ +import { ApiClientError } from "@/shared/api/http-client" + +/** Preserve the last confirmed snapshot only for this operation's explicit unknown receipt. */ +export function isSpeedPersistenceUnknown(error: unknown, runId: string): boolean { + if (!(error instanceof ApiClientError) || error.status !== 502) return false + if (!error.payload || typeof error.payload !== "object") return false + const payload = error.payload as { outcome?: unknown; runId?: unknown } + return payload.outcome === "unknown" && payload.runId === runId +}