fix(api): сохранить неизвестный исход записи speed без ложной ошибки
This commit is contained in:
1 parent
c337fddac9
commit
f408afba20
12 files changed
+220
-51
No files matched your search
@@ -3,7 +3,7 @@ import Fastify from "fastify"
|
||||
import { SignJWT } from "jose"
|
||||
import { speedTestCommandSchema, type SpeedTestCommand } from "@mmapp/contracts"
|
||||
import { createSpeedTestRoute } from "./speed-test.js"
|
||||
import { SpeedProbeEndpointsChanged, SpeedProbeContextChanged } from "../services/speed-run-persistence.js"
|
||||
import { SpeedProbeEndpointsChanged, SpeedProbeContextChanged, SpeedRunPersistenceUnknown } from "../services/speed-run-persistence.js"
|
||||
const commands: SpeedTestCommand[] = [], failures: Array<SpeedTestCommand & { message: string }> = []
|
||||
let lookups = 0, missing = false, problem: Error | null = null
|
||||
const result = (c: SpeedTestCommand) => ({ createdAt: "2026-10-07T00:00:00Z", runId: c.runId ?? "ad-hoc", srcInterface: c.srcInterface || null, dstInterface: c.dstInterface || null, address: "192.0.2.2", srcAddress: "192.0.2.1", dstAddress: "192.0.2.2", srcInterfaceAddress: null, dstInterfaceAddress: null, protocol: c.protocol, direction: c.direction, durationSec: c.durationSec, txAvgMbps: null, rxAvgMbps: 0, txSeries: [], rxSeries: [0], raw: [{ status: "done testing" }], afterBtPing: { rttMs: null, lossPct: null, error: null } })
|
||||
@@ -28,7 +28,13 @@ missing = true; assert.equal((await send(base)).statusCode, 404); assert.equal(c
|
||||
for (const [error, code] of [[new SpeedProbeEndpointsChanged(), 409], [new SpeedProbeContextChanged(), 409], [Error("На интерфейсе назначения нет активного IP"), 400], [Error("Server not found"), 400], [Error("authentication failed"), 502], [Error("socket hang up"), 502]] as const) {
|
||||
problem = error; const response = await send({ ...base, runId: "failed", direction: "transmit", durationSec: 20 }); assert.equal(response.statusCode, code); assert.equal(failures.at(-1)?.runId, "failed"); assert.equal(failures.at(-1)?.durationSec, 20); assert.equal(failures.at(-1)?.direction, "transmit")
|
||||
}
|
||||
await app.close(); console.log("Speed-test HTTP:26 invalid inputs before lookups/native/history; defaults/ad-hoc/trim/nullable/zero/404/409/400/502")
|
||||
const beforeUnknown = { runs: commands.length, failures: failures.length }
|
||||
problem = new SpeedRunPersistenceUnknown("actual-run-id")
|
||||
const unknown = await send(base)
|
||||
assert.equal(unknown.statusCode, 502); assert.equal(unknown.json().outcome, "unknown")
|
||||
assert.equal(unknown.json().runId, "actual-run-id"); assert.match(unknown.json().error, /сохранение результата не подтверждено/)
|
||||
assert.equal(failures.length, beforeUnknown.failures); assert.equal(commands.length, beforeUnknown.runs + 1)
|
||||
await app.close(); console.log("Speed-test HTTP:26 invalid inputs; explicit persistence unknown/runId skips failure write and command retry")
|
||||
process.env.AUTH_REQUIRED = "true"; process.env.AUTH_JWT_SECRET = "speed-command-test-secret"; process.env.AUTH_ISSUER = "https://auth.test.local"; process.env.NODE_ENV = "test"
|
||||
const { default: authPlugin, requireAuth } = await import("../plugins/auth.js")
|
||||
const secured = Fastify(); await secured.register(authPlugin); secured.addHook("preHandler", requireAuth)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { FastifyPluginAsyncZod } from "@fastify/type-provider-zod"
|
||||
import { speedTestCommandSchema, type SpeedTestCommand } from "@mmapp/contracts"
|
||||
import type { SpeedTestRunResult } from "../services/uptime-speed-service.js"
|
||||
import { SpeedProbeContextChanged } from "../services/speed-run-persistence.js"
|
||||
import { SpeedProbeContextChanged, SpeedRunPersistenceUnknown } from "../services/speed-run-persistence.js"
|
||||
|
||||
export function createSpeedTestRoute(deps: {
|
||||
serverExists: (id: number) => Promise<boolean>
|
||||
@@ -18,6 +18,9 @@ export function createSpeedTestRoute(deps: {
|
||||
try {
|
||||
return reply.send({ ok: true, result: await deps.run(command) })
|
||||
} catch (error) {
|
||||
if (error instanceof SpeedRunPersistenceUnknown) {
|
||||
return reply.code(502).send({ error: error.message, outcome: "unknown", runId: error.runId })
|
||||
}
|
||||
const raw = error instanceof Error ? error.message : "Bandwidth test failed"
|
||||
await deps.failure({ ...command, message: raw })
|
||||
const message = /timed out|socket hang up|ECONNRESET/i.test(raw)
|
||||
|
||||
@@ -69,3 +69,16 @@ test("post-collection hook failure is reported separately from the recorded outc
|
||||
const receipt = await runRecordedSchedulerJob(h.deps)
|
||||
assert.equal(receipt.status, "ok"); assert.equal(h.records.length, 1); assert.equal(h.reported.length, 1)
|
||||
})
|
||||
|
||||
test("unconfirmed speed persistence is an execution limitation, not a successful job", async () => {
|
||||
const snapshot: SchedulerRunSnapshot = { v: 1, job: "uptime_speed", sampledAt: "now", runs: [{
|
||||
probeId: "probe", srcServerId: 1, srcServerName: "source", dstServerId: 2, dstServerName: "destination",
|
||||
srcInterface: "", dstInterface: "", protocol: "tcp", direction: "transmit", durationSec: 3,
|
||||
ok: false, error: "Сохранение результата не подтверждено; runId actual-run",
|
||||
}] }
|
||||
const h = harness(snapshot), receipt = await runRecordedSchedulerJob(h.deps)
|
||||
assert.equal(receipt.status, "error"); assert.match(receipt.error!, /runId actual-run/)
|
||||
assert.equal(h.events.some(event => event.eventType === "scheduler.job.ok"), false)
|
||||
assert.equal(h.records.length, 1)
|
||||
assert.equal(h.counts().collections, 1)
|
||||
})
|
||||
@@ -20,6 +20,9 @@ export function schedulerSnapshotError(snapshot: SchedulerRunSnapshot | null): s
|
||||
if (!snapshot) return null
|
||||
if ("fatalError" in snapshot && snapshot.fatalError) return snapshot.fatalError
|
||||
const errors = "errors" in snapshot ? [...(snapshot.errors ?? [])] : []
|
||||
if (snapshot.job === "uptime_speed") {
|
||||
for (const run of snapshot.runs) if (!run.ok && run.error) errors.push(run.error)
|
||||
}
|
||||
if (snapshot.job === "backups") {
|
||||
errors.push(...(snapshot.eventErrors ?? []))
|
||||
if (snapshot.metadataError && !errors.some(error => error.includes(snapshot.metadataError!))) errors.push("Метаданные: " + snapshot.metadataError)
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
export interface SpeedProbeEndpoints { id: string; srcServerId: number; dstServerId: number }
|
||||
export class SpeedRunPersistenceUnknown extends Error {
|
||||
constructor(public readonly runId: string, cause?: unknown) {
|
||||
super(`Сетевой тест завершён, сохранение результата не подтверждено. Проверьте историю по runId ${runId} перед повтором`, { cause })
|
||||
}
|
||||
}
|
||||
|
||||
export interface SpeedProbeContext extends SpeedProbeEndpoints {
|
||||
srcInterface: string; dstInterface: string; protocol: "tcp" | "udp"; direction: "both" | "transmit" | "receive"; durationSec: number
|
||||
|
||||
@@ -8,7 +8,7 @@ import { withBtestNodeLocks } from "./btest-node-locks.js"
|
||||
import { MikrotikClient } from "./mikrotik.js"
|
||||
import { parsePing } from "./uptime-collector.js"
|
||||
import { assertBandwidthTestCompleted } from "./bandwidth-test-status.js"
|
||||
import { assertSpeedProbeContext, persistSpeedRun, type SpeedProbeContext } from "./speed-run-persistence.js"
|
||||
import { assertSpeedProbeContext, persistSpeedRun, SpeedRunPersistenceUnknown, type SpeedProbeContext } from "./speed-run-persistence.js"
|
||||
import { readBandwidthTestRates } from "./bandwidth-test-rates.js"
|
||||
|
||||
type SpeedWriteTransaction = Parameters<Parameters<typeof db.transaction>[0]>[0]
|
||||
@@ -143,49 +143,52 @@ export async function executeBandwidthSpeedTestInner(params: SpeedTestRunParams)
|
||||
}
|
||||
|
||||
const runId = String(params.runId ?? "").trim() || `sr-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
||||
await persistSpeedRun<SpeedWriteTransaction, SpeedProbeContext>(probeId ? target : null, {
|
||||
transaction: work => db.transaction(work),
|
||||
updateProbe: async (tx, target) => {
|
||||
const rows = await tx.update(uptimeSpeedProbes).set({
|
||||
lastRunAt: pingNow,
|
||||
lastTxAvgMbps: txFinal,
|
||||
lastRxAvgMbps: rxFinal,
|
||||
lastStatus: "done",
|
||||
lastError: "",
|
||||
lastPingRttMs: afterBtPing.rttMs,
|
||||
lastPingLossPct: afterBtPing.lossPct,
|
||||
lastPingAt: pingNow,
|
||||
lastPingError: afterBtPing.error ?? "",
|
||||
updatedAt: pingNow,
|
||||
}).where(speedProbeContextWhere(target)).returning({ id: uptimeSpeedProbes.id })
|
||||
return rows.length === 1
|
||||
},
|
||||
insertRun: async (tx, associatedProbeId) => {
|
||||
await tx.insert(uptimeSpeedTestRuns).values({
|
||||
id: runId,
|
||||
probeId: associatedProbeId,
|
||||
srcServerId: srcId,
|
||||
dstServerId: dstId,
|
||||
srcInterface: srcInterface || "",
|
||||
dstInterface: dstInterface || "",
|
||||
srcAddress: srcAddress || src.host,
|
||||
dstAddress: initialAddress,
|
||||
srcInterfaceAddress,
|
||||
dstInterfaceAddress,
|
||||
protocol,
|
||||
direction,
|
||||
durationSec,
|
||||
txAvgMbps: txFinal,
|
||||
rxAvgMbps: rxFinal,
|
||||
pingRttMs: afterBtPing.rttMs,
|
||||
pingLossPct: afterBtPing.lossPct,
|
||||
pingError: afterBtPing.error ?? null,
|
||||
status: "done",
|
||||
error: null,
|
||||
createdAt: pingNow,
|
||||
})
|
||||
},
|
||||
})
|
||||
try {
|
||||
await persistSpeedRun<SpeedWriteTransaction, SpeedProbeContext>(probeId ? target : null, {
|
||||
transaction: work => db.transaction(work),
|
||||
updateProbe: async (tx, target) => {
|
||||
const rows = await tx.update(uptimeSpeedProbes).set({
|
||||
lastRunAt: pingNow,
|
||||
lastTxAvgMbps: txFinal,
|
||||
lastRxAvgMbps: rxFinal,
|
||||
lastStatus: "done",
|
||||
lastError: "",
|
||||
lastPingRttMs: afterBtPing.rttMs,
|
||||
lastPingLossPct: afterBtPing.lossPct,
|
||||
lastPingAt: pingNow,
|
||||
lastPingError: afterBtPing.error ?? "",
|
||||
updatedAt: pingNow,
|
||||
}).where(speedProbeContextWhere(target)).returning({ id: uptimeSpeedProbes.id })
|
||||
return rows.length === 1
|
||||
},
|
||||
insertRun: async (tx, associatedProbeId) => {
|
||||
await tx.insert(uptimeSpeedTestRuns).values({
|
||||
id: runId,
|
||||
probeId: associatedProbeId,
|
||||
srcServerId: srcId,
|
||||
dstServerId: dstId,
|
||||
srcInterface: srcInterface || "",
|
||||
dstInterface: dstInterface || "",
|
||||
srcAddress: srcAddress || src.host,
|
||||
dstAddress: initialAddress,
|
||||
srcInterfaceAddress,
|
||||
dstInterfaceAddress,
|
||||
protocol,
|
||||
direction,
|
||||
durationSec,
|
||||
txAvgMbps: txFinal,
|
||||
rxAvgMbps: rxFinal,
|
||||
pingRttMs: afterBtPing.rttMs,
|
||||
pingLossPct: afterBtPing.lossPct,
|
||||
pingError: afterBtPing.error ?? null,
|
||||
status: "done",
|
||||
error: null,
|
||||
createdAt: pingNow,
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
} catch (error) { throw new SpeedRunPersistenceUnknown(runId, error) }
|
||||
|
||||
return {
|
||||
createdAt: pingNow,
|
||||
@@ -325,7 +328,7 @@ export async function runScheduledSpeedProbesOnce(): Promise<SpeedScheduledRunSn
|
||||
})
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : String(e)
|
||||
await recordSpeedTestFailure({
|
||||
if (!(e instanceof SpeedRunPersistenceUnknown)) await recordSpeedTestFailure({
|
||||
probeId: p.id,
|
||||
srcServerId: p.srcServerId,
|
||||
dstServerId: p.dstServerId,
|
||||
|
||||
@@ -109,3 +109,7 @@ BGP-prefix evaluator, отдельное live GRE расписание и recove
|
||||
## RBAC иbrowser restore — 2026-10-08
|
||||
|
||||
12.5.18 [realJWT/processPG-7](ux-ui/acceptance/rbac-4c12c55-2026-10-08.json) и[browserrestore200/400/guard/reload](ux-ui/acceptance/browser-restore-2026-10-08.json) прошли. ИзоляцияfrontendпроверяласьмаркеромБД:runtimeURLoverride не менялbakedNextrewrites,поэтомуиспользованавременнаяотдельнаяDocker network/backendalias. Основнойстенднеизменён,temporarycontainers/network/tunnelудалены. PortalSSO/commit-loss/PG-8/остальныеинтеграции ещёнеприняты.
|
||||
|
||||
## 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 сохраняется.
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# План реализации UX/UI по пятичасовым окнам
|
||||
|
||||
Актуальное продолжение 2026-10-08:12.5.18 завершён — PG-7 realJWT401/403/нулевыеchildprocesses,adminbackup200; realbrowserrestore200/архивныйконтроль и400/файлсохранён/unknownguard/reload/явныйreset passed наотдельнойБД. Backend4c12c55/frontend673e5ba сохраняются,основнаяБДнеизменена. Следующий12.5.19 — PG-8 commit acknowledgement/speed persistence иоставшиесяnative/integration сценарии;12.5 открыта.
|
||||
Актуальное продолжение 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/интеграции.
|
||||
|
||||
Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-07. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат.
|
||||
|
||||
|
||||
+13
-1
@@ -1,6 +1,6 @@
|
||||
# UX/UI: журнал реализации
|
||||
|
||||
Актуальное продолжение 2026-10-08:12.5.18 завершён — PG-7 realJWT401/403/нулевыеchildprocesses,adminbackup200; realbrowserrestore200/архивныйконтроль и400/файлсохранён/unknownguard/reload/явныйreset passed наотдельнойБД. Backend4c12c55/frontend673e5ba сохраняются,основнаяБДнеизменена. Следующий12.5.19 — PG-8 commit acknowledgement/speed persistence иоставшиесяnative/integration сценарии;12.5 открыта.
|
||||
Актуальное продолжение 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/интеграции.
|
||||
|
||||
Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md).
|
||||
|
||||
@@ -34,6 +34,18 @@
|
||||
| 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.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.
|
||||
|
||||
SchedulerSnapshotError раньшеигнорировалruns[].okfalse уuptime_speed, поэтому реальныйплановыйackloss помечалсяjobok. Теперьошибкиисполненияspeedrun попадаютвreceipt.statuserror/ограничение,безпереименованиядействительногосохранённогорезультатасетевоготеставerror. Корректные observedoffline/down ресурсы/ping по-прежнемунесчитаютсяисключениемоперации.
|
||||
|
||||
[ОчищеннаяrealPG-8 матрица](ux-ui/acceptance/pg8-speed-2026-10-08.json): image4c12c55 плюсcompiledworktreepatch, mmapp_acceptance_20261007, настоящиеCHR1→CHR2 TCPtransmit3s/afterping,RouterOSответынеподменены. SQLBEFOREINSERTtrigger отказалhistory:HTTP502unknown/actualrunId,baseline snapshot42 сохранился,history0,ровно1nativecall. Дляackloss node-pg boundary действительноисполнялCOMMIT,затемотклонялclientpromise:HTTP502unknown,независимыйPGread показал1historydone иsnapshotdone ссовпадающимTX1929.132904Mbps/RXnull. Плановыйackloss: HTTP200/okfalse receipt,1nativecall,историяdone/TX1729.056592/RXnull,snapshotнепереписанвerror. Этоинъекцияclientackfault послереальногосervercommit,нефизическийобрывTCP; универсальныйrollback длялюбогосетевогоотказанеутверждается.
|
||||
|
||||
Матрицапотребоваладиагностики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.18 — реальные права backup/restore и сценарии восстановления в браузере (2026-10-08)
|
||||
|
||||
Блокначат7октября,завершён8октября поAsia/Novosibirsk; отчётыдатированыподатеокончания. [PG-7 evidence](ux-ui/acceptance/rbac-4c12c55-2026-10-08.json): temporarycontainerсреальнымimageux-4c12c55, AUTH_REQUIRED=true,mmapp_acceptance_20261007. JWTтестовогоissuerсозданытольковпамяти, безPortalSSO. 16GETbackup/POSTrestore:anonymous/wrongissuer/expired/invalidsignature401;settingsreader/writer/trafficadmin/missingapp403. Instrumentation productionchild_process.spawn (безподменывыполнениякоманд) подтвердилаpg_dump0/pg_restore0/psql0длявсехотказов; контрольныеservers/retention/sequence/speed послеотказовсовпали. Adminmm:settings:adminbackup200/478183bytes/PGDMP запустилровноодиннастоящийpg_dump. ЭтоусиливаетпрежниеHTTP-onlyPG-7 проверкинаблюдениемпроцессов.
|
||||
|
||||
+13
-1
@@ -1,6 +1,6 @@
|
||||
# Этап 12: итоговая приёмка UX/UI
|
||||
|
||||
Актуальное продолжение 2026-10-08:12.5.18 завершён — PG-7 realJWT401/403/нулевыеchildprocesses,adminbackup200; realbrowserrestore200/архивныйконтроль и400/файлсохранён/unknownguard/reload/явныйreset passed наотдельнойБД. Backend4c12c55/frontend673e5ba сохраняются,основнаяБДнеизменена. Следующий12.5.19 — PG-8 commit acknowledgement/speed persistence иоставшиесяnative/integration сценарии;12.5 открыта.
|
||||
Актуальное продолжение 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/интеграции.
|
||||
|
||||
План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push.
|
||||
|
||||
@@ -1134,3 +1134,15 @@ ComposeдобавленoptionalBACKEND_IMAGE_TAG, которыйпереопре
|
||||
Backup478920bytesиспользовантольковпамятиNode/browser input,локальный.dumpнесохранён. Контрольretention14→19 сделанAPIввыделеннойБД. ЧерезнастоящийsettingsUI:выборфайла→Продолжить→подтверждение→restore200→подпись «База приложения восстановлена»; GETuptimesettings независимоподтвердилretention14. ЗатемPGDMP-invalid-fixture получил400:controlretention14сохранён,filenameвдиалогесохранён,unknowncaptionвиден,restoreкнопказаблокирована. Guardпережилreload, «Выбрать файл»disabled; послефактическойпроверкиcontrolнажато «Я проверил состояние — разрешить новый запрос»,кнопкавновьenabled. POSTstatusesровно[200,400],автоповторанет;0JSerrors,320px dialogfits. HTTP-моковнет. ЭтоbrowserPG-3/errorUX,неPortalSSO иневсеfailure/commit-lossварианты.
|
||||
|
||||
Temporaryfrontend/backend/самостоятельныйRBACcontainerудалены--rm, privateDocker networkудалена, временныйтуннель3018закрыт. Основнойтуннель3000/8000/стендсохранены;backend4c12c55healthy,основнаяБДserver1–2/retention14безизменений,AUTHfalseкакдоэтогоблока. Продуктовыйкодне менялся. PG-7 иbrowserrestoreпринятывуказанныхграницах; полный12.5 ещёоткрыт. Следующий12.5.19 — PG-8 speedpersistence/потеряcommitacknowledgement иостальныеROS/Q/integrationсценарии. Локальныйкоммитбез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.
|
||||
|
||||
SchedulerSnapshotError раньшеигнорировалruns[].okfalse уuptime_speed, поэтому реальныйплановыйackloss помечалсяjobok. Теперьошибкиисполненияspeedrun попадаютвreceipt.statuserror/ограничение,безпереименованиядействительногосохранённогорезультатасетевоготеставerror. Корректные observedoffline/down ресурсы/ping по-прежнемунесчитаютсяисключениемоперации.
|
||||
|
||||
[ОчищеннаяrealPG-8 матрица](ux-ui/acceptance/pg8-speed-2026-10-08.json): image4c12c55 плюсcompiledworktreepatch, mmapp_acceptance_20261007, настоящиеCHR1→CHR2 TCPtransmit3s/afterping,RouterOSответынеподменены. SQLBEFOREINSERTtrigger отказалhistory:HTTP502unknown/actualrunId,baseline snapshot42 сохранился,history0,ровно1nativecall. Дляackloss node-pg boundary действительноисполнялCOMMIT,затемотклонялclientpromise:HTTP502unknown,независимыйPGread показал1historydone иsnapshotdone ссовпадающимTX1929.132904Mbps/RXnull. Плановыйackloss: HTTP200/okfalse receipt,1nativecall,историяdone/TX1729.056592/RXnull,snapshotнепереписанвerror. Этоинъекцияclientackfault послереальногосervercommit,нефизическийобрывTCP; универсальныйrollback длялюбогосетевогоотказанеутверждается.
|
||||
|
||||
Матрицапотребоваладиагностики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.
|
||||
@@ -117,3 +117,7 @@ ssh -N -L 3000:127.0.0.1:3000 -L 8000:127.0.0.1:8000 [email protected]
|
||||
## RealRBAC иbrowserrestore — 2026-10-08
|
||||
|
||||
12.5.18 [RBAC16denials/childprocess0/adminbackup200](ux-ui/acceptance/rbac-4c12c55-2026-10-08.json),[browserrestore200/архивныйcontrol/400guardreload](ux-ui/acceptance/browser-restore-2026-10-08.json) passedнавыделеннойБД. Временныеfrontend/backend использовалиизолированнуюDocker network,посколькуNextrewritesфиксированыприbuild; guardпредотвратилwritesприошибкемаршрутизации. Temporarycontainers/network/3018tunnelудалены,основнойbackend4c12c55/frontend673e5ba/server1–2/retention14/AUTHfalse сохранены. PortalSSO иполный12.5 ещёоткрыты.
|
||||
|
||||
## 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.
|
||||
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"date": "2026-10-08",
|
||||
"source": "4c12c55+pg8-worktree",
|
||||
"database": "mmapp_acceptance_20261007",
|
||||
"native": "real CHR1→CHR2 TCP transmit3s, no RouterOS response mocks",
|
||||
"cases": [
|
||||
{
|
||||
"case": "history-insert-failure",
|
||||
"receipt": {
|
||||
"status": 502,
|
||||
"body": {
|
||||
"error": "Сетевой тест завершён, сохранение результата не подтверждено. Проверьте историю по runId acceptance-pg8-write-fail перед повтором",
|
||||
"outcome": "unknown",
|
||||
"runId": "acceptance-pg8-write-fail"
|
||||
}
|
||||
},
|
||||
"nativeCalls": 1,
|
||||
"snapshotUnchanged": true,
|
||||
"historyRows": 0
|
||||
},
|
||||
{
|
||||
"case": "commit-acknowledgement-loss",
|
||||
"receipt": {
|
||||
"status": 502,
|
||||
"body": {
|
||||
"error": "Сетевой тест завершён, сохранение результата не подтверждено. Проверьте историю по runId acceptance-pg8-ack-loss-final перед повтором",
|
||||
"outcome": "unknown",
|
||||
"runId": "acceptance-pg8-ack-loss-final"
|
||||
}
|
||||
},
|
||||
"nativeCalls": 1,
|
||||
"ackInjectedAfterRealCommit": true,
|
||||
"snapshot": {
|
||||
"id": "acceptance-pg8",
|
||||
"last_status": "done",
|
||||
"last_tx_avg_mbps": 1929.1329039999998,
|
||||
"last_rx_avg_mbps": null,
|
||||
"last_run_at": "2026-10-07T17:18:19.184Z",
|
||||
"last_error": "",
|
||||
"last_ping_rtt_ms": 53,
|
||||
"last_ping_loss_pct": 0
|
||||
},
|
||||
"history": [
|
||||
{
|
||||
"id": "acceptance-pg8-ack-loss-final",
|
||||
"probe_id": "acceptance-pg8",
|
||||
"status": "done",
|
||||
"tx_avg_mbps": 1929.1329039999998,
|
||||
"rx_avg_mbps": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case": "scheduled-commit-acknowledgement-loss",
|
||||
"status": 200,
|
||||
"ok": false,
|
||||
"nativeCalls": 1,
|
||||
"runId": "sr-1791393512355-j4ser0",
|
||||
"history": [
|
||||
{
|
||||
"id": "sr-1791393512355-j4ser0",
|
||||
"probe_id": "acceptance-pg8",
|
||||
"status": "done",
|
||||
"tx_avg_mbps": 1729.056592,
|
||||
"rx_avg_mbps": null
|
||||
}
|
||||
],
|
||||
"snapshotNotRelabelled": true
|
||||
}
|
||||
],
|
||||
"mainDatabaseTouched": false,
|
||||
"limits": [
|
||||
"Commit acknowledgement fault injected at node-pg boundary AFTER real COMMIT, not a physical network disconnect",
|
||||
"Initial INSERT failure real PostgreSQL trigger; response remains conservatively unknown until independent read",
|
||||
"Three short functional TCP tests; not sustained throughput benchmark",
|
||||
"No browser unknown-commit UX or main deployment in this block"
|
||||
],
|
||||
"compiledArchiveSha256": "d753bb025e42973942ee5969971107da7343cb9247d630219a8009d840380222",
|
||||
"finalPatchSha256": "ba1186c4cb441bc80e247c71f093c03304cab96662f2f5104aa952494848653a",
|
||||
"compiledFiles": {
|
||||
"speed-run-persistence": "07294f5d587d3b7db98aa02e7dda276ca9a254247fb49114f98ab9eb197d6ef0",
|
||||
"uptime-speed-service": "00b02b173c3a074343875dd73cc553e0a811b9aa887bd2fb85ffeac56097e614",
|
||||
"scheduler-recorded-run": "86e8ba60e2cc2242ae64dcce7719a44f9ecb80d4e5f757e847b7cb5facd8640e"
|
||||
},
|
||||
"attempts": [
|
||||
"Initial harness missed Pool.connect callback overload; failed before native testing",
|
||||
"Initial complete native matrix exposed scheduler job incorrectly marked ok; product classifier corrected",
|
||||
"A diagnostic rerun encountered500 with callback instrumentation; callback query bypass and BEGIN reset added; final matrix passed",
|
||||
"Diagnostic error handler override remained in harness; successful checked branches handled by production route, no unexpected errors in final run"
|
||||
],
|
||||
"healthMainAfter": {
|
||||
"status": "ok",
|
||||
"timestamp": "2026-10-07T17:19:21.227Z",
|
||||
"version": "1.0.0-test.4c12c55",
|
||||
"eventLoopDelayMs": 20,
|
||||
"flowWorker": {
|
||||
"alive": false,
|
||||
"bound": false,
|
||||
"pendingSize": 0,
|
||||
"dropped": 0,
|
||||
"packetsReceived": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user