From f3e205950f3fe12cece5c53992f4d6d8886513a6 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Wed, 7 Oct 2026 23:28:16 +0700 Subject: [PATCH] =?UTF-8?q?fix(api):=20=D0=B2=D0=BE=D1=81=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D0=BD=D0=BE=D0=B2=D0=B8=D1=82=D1=8C=20=D1=80=D0=B0=D0=B7?= =?UTF-8?q?=D0=B4=D0=B5=D0=BB=D1=8B=20PostgreSQL=20=D0=B2=20=D0=BE=D0=B4?= =?UTF-8?q?=D0=BD=D0=BE=D0=B9=20=D1=82=D1=80=D0=B0=D0=BD=D0=B7=D0=B0=D0=BA?= =?UTF-8?q?=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/package.json | 2 +- .../src/services/system-database-backup.ts | 17 ++- .../system-database-partition-restore.test.ts | 47 +++++++ .../system-database-partition-restore.ts | 47 +++++++ docs/ux-ui-external-acceptance.md | 6 +- docs/ux-ui-plan.md | 2 +- docs/ux-ui-progress.md | 14 ++- docs/ux-ui-stage-12.md | 14 ++- docs/ux-ui-test-server.md | 4 + .../real-restore-fixed-2026-10-07.json | 118 ++++++++++++++++++ 10 files changed, 261 insertions(+), 10 deletions(-) create mode 100644 backend/src/services/system-database-partition-restore.test.ts create mode 100644 backend/src/services/system-database-partition-restore.ts create mode 100644 docs/ux-ui/acceptance/real-restore-fixed-2026-10-07.json diff --git a/backend/package.json b/backend/package.json index 27428c77..8c00e540 100644 --- a/backend/package.json +++ b/backend/package.json @@ -37,7 +37,7 @@ "test:ospf-optimizer": "tsx src/services/ospf-optimizer-identity.test.ts && tsx src/services/ospf-optimizer-metrics.test.ts && tsx src/services/ospf-optimizer-plan.test.ts && tsx src/routes/ospf-optimizer.test.ts", "test:containers": "tsx src/services/containers-state.test.ts && tsx src/routes/containers-state.test.ts", "test:certificates": "tsx src/services/certificate-issue-execution.test.ts && tsx src/services/certificate-issue-request.test.ts && tsx src/routes/certificates-issue.test.ts && tsx src/services/certificate-issue-dto.test.ts && tsx src/services/certificate-parse.test.ts && tsx src/services/certificates-state.test.ts && tsx src/routes/certificates-state.test.ts", - "test:system-database": "tsx src/services/system-database-cache.test.ts && tsx src/services/database-background-work.test.ts && tsx src/services/database-request-gate.test.ts && tsx src/services/traffic-flow-worker-shutdown.test.ts && tsx src/services/system-database-restore-execution.test.ts && tsx src/services/system-database-operation.test.ts && tsx src/routes/system-database.test.ts && tsx src/plugins/auth.system-database.test.ts" + "test:system-database": "tsx src/services/system-database-partition-restore.test.ts && tsx src/services/system-database-cache.test.ts && tsx src/services/database-background-work.test.ts && tsx src/services/database-request-gate.test.ts && tsx src/services/traffic-flow-worker-shutdown.test.ts && tsx src/services/system-database-restore-execution.test.ts && tsx src/services/system-database-operation.test.ts && tsx src/routes/system-database.test.ts && tsx src/plugins/auth.system-database.test.ts" }, "dependencies": { "@aws-sdk/client-s3": "^3.888.0", diff --git a/backend/src/services/system-database-backup.ts b/backend/src/services/system-database-backup.ts index 5ae4d02e..f89c86fa 100644 --- a/backend/src/services/system-database-backup.ts +++ b/backend/src/services/system-database-backup.ts @@ -1,6 +1,6 @@ import { randomUUID } from "node:crypto" import { spawn } from "node:child_process" -import { mkdir, rm, writeFile } from "node:fs/promises" +import { chmod, mkdir, rm, writeFile } from "node:fs/promises" import os from "node:os" import path from "node:path" import { executeSystemDatabaseRestore, validateSystemDatabaseRestoreArchive, type SystemDatabaseRestoreOutcome } from "./system-database-restore-execution.js" @@ -11,6 +11,7 @@ import { executeDatabaseOperation, type DatabaseOperationWarnings } from "./syst import { restartSchedulerAfterDatabaseOperation, stopSchedulerForDatabaseOperation } from "./scheduler.js" import { pauseRipeBackgroundWork, resumeRipeBackgroundWork } from "./traffic-flow-ripe.js" import { invalidateSystemDatabaseCaches } from "./system-database-cache.js" +import { restorePartitionedDatabase } from "./system-database-partition-restore.js" import { restartTrafficFlowListenerAfterDatabaseOperation, stopTrafficFlowListenerForDatabaseOperation, @@ -78,12 +79,18 @@ export async function restoreSystemDatabaseBackup(buffer: Buffer): Promise executeSystemDatabaseRestore(buffer, env.DATABASE_URL, { allocate: async () => { const tempDir = path.join(os.tmpdir(), "mmapp-db-restore") - await mkdir(tempDir, { recursive: true }) + await mkdir(tempDir, { recursive: true, mode: 0o700 }) + await chmod(tempDir, 0o700) return path.join(tempDir, `restore-${randomUUID()}.dump`) }, - write: (file, data) => writeFile(file, data), - run: async args => { await spawnCollected("pg_restore", args) }, - remove: file => rm(file, { force: true }), + write: (file, data) => writeFile(file, data, { mode: 0o600 }), + run: async args => { + await restorePartitionedDatabase(args[args.length - 1], env.DATABASE_URL, { + command: spawnCollected, + prepareSql: file => writeFile(file, "", { mode: 0o600 }), + }) + }, + remove: async file => { await Promise.all([rm(file, { force: true }), rm(`${file}.sql`, { force: true })]) }, invalidate: invalidateSystemDatabaseCaches, })) } diff --git a/backend/src/services/system-database-partition-restore.test.ts b/backend/src/services/system-database-partition-restore.test.ts new file mode 100644 index 00000000..975ee329 --- /dev/null +++ b/backend/src/services/system-database-partition-restore.test.ts @@ -0,0 +1,47 @@ +import assert from "node:assert/strict" +import { test } from "node:test" +import { partitionRestorePrelude, restorePartitionedDatabase } from "./system-database-partition-restore.js" +const toc = "1; 1259 1 TABLE public servers mmapp\n2; 1259 2 TABLE public schema_migrations mmapp\n3; 1259 3 TABLE public server_snapshots mmapp\n" + +test("preparation covers only known application parents present in the archive", () => { + const sql = partitionRestorePrelude(toc + "4; 1259 4 TABLE other flow_buckets owner\n5; 1259 5 TABLE public custom_table owner\n") + assert.ok(sql.includes("IN ('server_snapshots')")) + assert.ok(sql.includes("c.conparentid = 0")) + assert.ok(sql.includes("format('ALTER TABLE %I.%I DROP CONSTRAINT %I CASCADE'")) + assert.equal(sql.includes("flow_buckets"), false) + assert.equal(sql.includes("custom_table"), false) + assert.throws(() => partitionRestorePrelude("1; 1259 1 TABLE public unrelated owner\n"), /обязательные таблицы/) +}) + +test("archive decoding precedes one transactional psql execution with fail-fast SQL", async () => { + const calls: string[] = [] + await restorePartitionedDatabase("fixture.dump", "fixture-db", { + prepareSql: async file => { assert.equal(file, "fixture.dump.sql"); calls.push("prepare") }, + command: async (command, args) => { + calls.push(command) + if (command === "pg_restore" && args[0] === "--list") return Buffer.from(toc) + if (command === "psql") { + assert.deepEqual(args.slice(0, 6), ["-X", "--single-transaction", "--set", "ON_ERROR_STOP=on", "-d", "fixture-db"]) + assert.equal(args[6], "-c"); assert.ok(args[7].includes("DROP CONSTRAINT")) + assert.deepEqual(args.slice(8), ["-f", "fixture.dump.sql"]) + } + return Buffer.alloc(0) + }, + }) + assert.deepEqual(calls, ["pg_restore", "prepare", "pg_restore", "psql"]) +}) + +test("invalid or undecodable archive never starts target SQL", async () => { + for (const phase of ["list", "decode"]) { + let executions = 0 + await assert.rejects(restorePartitionedDatabase("fixture.dump", "fixture-db", { + prepareSql: async () => {}, + command: async (command, args) => { + if (command === "psql") executions++ + if ((phase === "list" && args[0] === "--list") || (phase === "decode" && args[0] === "--clean")) throw Error("archive failed") + return Buffer.from(toc) + }, + }), /archive failed/) + assert.equal(executions, 0) + } +}) diff --git a/backend/src/services/system-database-partition-restore.ts b/backend/src/services/system-database-partition-restore.ts new file mode 100644 index 00000000..ac04b475 --- /dev/null +++ b/backend/src/services/system-database-partition-restore.ts @@ -0,0 +1,47 @@ +import { PARTITION_SPECS } from "../db/partitions.js" + +/** Only application parents actually present in the archive may be prepared. */ +export function partitionRestorePrelude(toc: string): string { + const tables = new Set() + for (const line of toc.split(/\r?\n/)) { + const match = /^\d+;\s+\d+\s+\d+\s+TABLE public ([a-z_][a-z_0-9]*)\s/.exec(line) + if (match) tables.add(match[1]) + } + if (!tables.has("servers") || !tables.has("schema_migrations")) { + throw new Error("Архив не содержит обязательные таблицы базы приложения") + } + const parents = PARTITION_SPECS.map(s => s.parent).filter(name => tables.has(name)) + if (!parents.length) return "SELECT 1;" + return `DO $mm_restore$ +DECLARE r record; +BEGIN + FOR r IN + SELECT n.nspname, t.relname, c.conname + FROM pg_constraint c + JOIN pg_class t ON t.oid = c.conrelid + JOIN pg_namespace n ON n.oid = t.relnamespace + WHERE n.nspname = 'public' AND t.relkind = 'p' + AND c.conparentid = 0 AND c.contype IN ('p', 'u') + AND t.relname IN (${parents.map(name => `'${name}'`).join(", ")}) + LOOP + EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I CASCADE', r.nspname, r.relname, r.conname); + END LOOP; +END $mm_restore$;` +} + +interface Dependencies { + command: (command: string, args: string[]) => Promise + prepareSql: (file: string) => Promise +} + +export async function restorePartitionedDatabase(file: string, databaseUrl: string, deps: Dependencies): Promise { + const toc = await deps.command("pg_restore", ["--list", file]) + const prelude = partitionRestorePrelude(toc.toString("utf8")) + const sqlFile = `${file}.sql` + await deps.prepareSql(sqlFile) + // Fully decode before connecting to the target database; never rewrite archive SQL/data. + await deps.command("pg_restore", ["--clean", "--if-exists", "--file", sqlFile, file]) + // psql wraps both -c and -f in ONE transaction and rolls back on a SQL error. + await deps.command("psql", ["-X", "--single-transaction", "--set", "ON_ERROR_STOP=on", + "-d", databaseUrl, "-c", prelude, "-f", sqlFile]) +} diff --git a/docs/ux-ui-external-acceptance.md b/docs/ux-ui-external-acceptance.md index 17b57521..d2c396d7 100644 --- a/docs/ux-ui-external-acceptance.md +++ b/docs/ux-ui-external-acceptance.md @@ -23,7 +23,7 @@ Реализация: [bootstrap](../backend/src/db/bootstrap.ts), [migrations](../backend/src/db/migrate.ts), [0008](../backend/drizzle/0008_alert_outbox_outcomes.sql), [0009](../backend/drizzle/0009_alert_outbox_resolution.sql), [database operation](../backend/src/services/system-database-operation.ts), [restore execution](../backend/src/services/system-database-restore-execution.ts). -Приложение использует pg_dump -Fc и pg_restore --single-transaction --clean --if-exists. Транзакционный режим описан в [документации PostgreSQL](https://www.postgresql.org/docs/current/app-pgrestore.html). Проверка пяти байтов PGDMP проверяет заголовок, но не полноту и не доверие к архиву. Для rollback нужен сценарий с ошибкой исполнения restore, а не только отказ HTTP до его начала. +После12.5.15 приложение используетpg_dump -Fc, проверкуpg_restore --list, генерациюpg_restore --clean --if-exists --file иодинpsql -X --single-transaction --set ON_ERROR_STOP=on -c partition parent preparation -f SQL. Предварительнаяподготовкаиrestore находятсявобщейтранзакции. Транзакционный режим описан в [документации PostgreSQL](https://www.postgresql.org/docs/current/app-pgrestore.html). Проверка пяти байтов PGDMP проверяет заголовок, но не полноту и не доверие к архиву. Для rollback нужен сценарий с ошибкой исполнения restore, а не только отказ HTTP до его начала. В локальном блоке12.5.8 добавлено ожидание текущих scheduler jobs с закрытием admission, но этот код ещё не развёрнут на стенде673e5ba. Блок12.5.9 добавляет отдельный graceful shutdown worker для database operation, serial flush, pool.end и обязательные acknowledgement/exit0. Локальные protocol tests и настоящий isolated collector/UDP/blocked SQL drain12.5.10 прошли: [evidence](ux-ui/acceptance/netflow-worker-2026-10-07.json). Основной код на стенде ещё не обновлён; app-wide quiescence этим не подтверждена. Блок12.5.11 локально добавляет admission/drain всех API handlers иtraffic live streams; новые tests/build passed, на стенде patch не развёрнут. В12.5.12 локально добавлен общий background registry/drain дляbackup/audit/certificate/partition maintenance/cache work, атакжеpause RIPE enqueue. Tests/build passed; В12.5.13 добавленcache invalidation послеуспешногоrestore идоconsumer restart, локальныесовместныеhelpers tests passed. Всяцепочка требуетreal-PG integrated quiescence/restore12.5.14. Основнойстенд покаsource673e5ba. Другие экземпляры/хосты требуют отдельного контроля. PG-6 остаётся открытым до наблюдаемого подтверждения; восстановление стенда выполняется после его получения. Реальные тесты backend, способные очищать таблицы, запускаются только против выделенной тестовой БД. @@ -93,3 +93,7 @@ BGP-prefix evaluator, отдельное live GRE расписание и recove ## Обнаруженный блокер restore — 2026-10-07 12.5.14 realAPI/sourcef1aac49 навыделеннойPG18.6 подтвердилadmission/drain,ноrestore400 из-заinheritedpartitionPRIMARY KEY при--clean. [Evidence/rollback controls](ux-ui/acceptance/real-restore-2026-10-07.json). PG-4 подтверждёнвграницахнастоящейSQLошибкииконтрольныхстрок/sequence/constraint; PG-3успех/последующийID иполныйPG-6 непроверены. Следующий12.5.15 исправляеттранзакционнуюочистку. Основнойruntime673e5ba сохранён. + +## Restore исправлен — 2026-10-07 + +12.5.15 realAPI200/контрольархива/новыйID3 иinjectedCREATE INDEX400/rollback данных,sequence и1587constraints прошли: [evidence](ux-ui/acceptance/real-restore-fixed-2026-10-07.json). PG-3/4 принятывэтихграницах, Portal/UI/fullchecksum не проверены. Основнойruntime673e5ba сохранён; integratedPG-6/deployment ещёпредстоят. Приrestart ресурсныйcollector попыталсяопрашиватьdisabledserver3; исправление12.5.16. diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index bb0492d0..666d3566 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -1,6 +1,6 @@ # План реализации UX/UI по пятичасовым окнам -Актуальное продолжение 2026-10-07:12.5.14 выполненкакреальнаяприёмка isolated API/sourcef1aac49. ОбнаружендефектPG-3:pg_restore --clean падаетнаinherited partition PRIMARY KEY. HTTP admission409/ожиданиеwrite иrollback контрольныхданныхподтверждены. Основнойruntime673e5ba сохранён. Следующий12.5.15 — исправитьтранзакционнуюочисткуразделов иповторитьrestore; PG-3/полныйPG-6 открыты. +Актуальное продолжение 2026-10-07:12.5.15 завершён — транзакционный partition-safe restore исправлен и прошёл realAPI200/контрольархива/новыйID3; injected CREATE INDEX failure400 сохранилданные/sequence/1587constraints.25tests,HTTP/JWT иbuild passed. Основнойruntime673e5ba не обновлён. Следующий12.5.16 — disabled-server collector defect, integrated PG-6 иdeployment; весьэтап12.5 остаётсяоткрытым. Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-07. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат. diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index d5d31f06..bec9b764 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -1,6 +1,6 @@ # UX/UI: журнал реализации -Актуальное продолжение 2026-10-07:12.5.14 выполненкакреальнаяприёмка isolated API/sourcef1aac49. ОбнаружендефектPG-3:pg_restore --clean падаетнаinherited partition PRIMARY KEY. HTTP admission409/ожиданиеwrite иrollback контрольныхданныхподтверждены. Основнойruntime673e5ba сохранён. Следующий12.5.15 — исправитьтранзакционнуюочисткуразделов иповторитьrestore; PG-3/полныйPG-6 открыты. +Актуальное продолжение 2026-10-07:12.5.15 завершён — транзакционный partition-safe restore исправлен и прошёл realAPI200/контрольархива/новыйID3; injected CREATE INDEX failure400 сохранилданные/sequence/1587constraints.25tests,HTTP/JWT иbuild passed. Основнойruntime673e5ba не обновлён. Следующий12.5.16 — disabled-server collector defect, integrated PG-6 иdeployment; весьэтап12.5 остаётсяоткрытым. Общий план: [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.15 — транзакционное восстановление разделов PostgreSQL (2026-10-07) + +Исправленреальныйдефект12.5.14. pg_restore --list проверяетобязательныеTABLE public servers/schema_migrations иопределяетприсутствующиевархивеизвестныеpartition parents приложения. ВременныйSQL полностьюформируется pg_restore --clean --if-exists --file безподключениякцелевойБД. Затемодинpsql -X --single-transaction --set ON_ERROR_STOP=on выполняет -c parent constraint preparation и -f SQLархива. РодительскиеPK/UNIQUE снимаютсячерезpg_catalog/format(%I) внутриэтойжетрaнзакции; inheritedchildconstraints удаляютсякаскадно, поэтомупоследующиеIF EXISTS DROP больше непадают. Подготовкаограниченаpublic/известнымиparents, присутствующимивархиве; нетотдельногоcommittedDROP/SQLпереписыванияданных/автоповтора. [Транзакционныйрежимpsql](https://www.postgresql.org/docs/18/app-psql.html) охватываетобеопции-c/-f. + +Customdump иSQL содержатприватныеданные: tempdirectory0700 (chmod такжеисправляетранее созданныйкаталог), файлы0600, обафайлаудаляютсяприуспехе/ошибке. Ошибкаdecode/неподходящийTOC непускаютtargetSQL. Архивдолженбытьдовереннымполнымbackup приложения; проверкадвухTABLE неявляетсяпроверкойвсейсхемыилидовериякSQLархива. CASCADEdependencies восстанавливаютсяизполногоархива; частичные/custom archives этимнепринимались. + +Локальныепроверки:3новыхpartition-restoretests включенывtest:system-database; полныйнабор25pure/protocol tests иHTTP/JWT scripts passed, contracts/backend build passed. Провереныscope parent/table/schema, quotedcatalogidentifiers, полностьюdecode-before-targetSQL, одинpsqltransaction, отказdecodeбезtargetSQL. Существующийrestore lifecycle сохраняетcache invalidation толькопослеуспеха. + +[RealAPI evidence](ux-ui/acceptance/real-restore-fixed-2026-10-07.json):161.104.46.222/PG18.6, isolatedcompiledf1aac49+partition-restoreworktreepatch (SHA256 patch совпал), mmapp_acceptance_20261007. Backup200/475305bytes; concurrentactiveHTTPwrite ждалSQLlock, новыйGET409. Restore200/oktrueбезwarnings, контрольныеservers/retention/speed/migrations/sequence совпалисархивом. СледующийINSERT disabledtestserver192.0.2.250 получилID3. Послеretention19 ивведенияeventtrigger, которыйпадаетнаCREATE INDEX, второйrestore400/необъявленуспешным. НезависимыеSQLпослеrollback подтвердиливсеперечисленныеконтрольныеданные/sequence3 и1587constraint descriptors. Eventtrigger/function удалены, temporarycontainer--rm/consumers/pool штатноостановлены. + +ПроверочнаяБД теперьсодержитdisabledserver3/retention19; основнаясохранена:server1–2,retention14,healthok/source673e5ba. Во времяштатногоrestart ресурсныйcollector неожиданнопыталсяпрочитатьdisabledserver3 иполучилtimeout10s — отдельныйдефектдляследующегоблока. RouterOS apply не выполнялся. PG-3/4 принятывграницахrealAPI/контрольныхданных/новогоID/SQLrollback/constraints, безполногоchecksum/Portal/browser UI. Integrated UDP+detachedjobs втомжекритическоминтервалеимультиэкземплярыещёнеприняты; полныйPG-6 открыт. Новыйкоднеразвёрнутнаосновномстенде. Следующий12.5.16 — исправитьdisabled-server polling, integratedPG-6/deployment. Локальныйкоммитбезpush. + ## 12.5.14 — реальный API restore: дефект очистки разделов и контроль rollback (2026-10-07) Временный docker compose run --rm --no-deps backend использовалcompiled dist sourcef1aac49, mount/app/backend/acceptance, DATABASE_URL толькоmmapp_acceptance_20261007, HTTP127.0.0.1:8014 внутриконтейнера. Основнойbackend/frontend не обновлялись. [Очищенный отчёт](ux-ui/acceptance/real-restore-2026-10-07.json); SHA256 переданногоcompiled archive совпалслокальным. РеальныйbuildApp иAPI routes работалибезHTTP mocks; startScheduler=false приinitial build, стандартныеdatabase operation restarts выполнялиреальныйrefreshScheduler/consumer lifecycle. diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index 2f620c6e..29445be5 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -1,6 +1,6 @@ # Этап 12: итоговая приёмка UX/UI -Актуальное продолжение 2026-10-07:12.5.14 выполненкакреальнаяприёмка isolated API/sourcef1aac49. ОбнаружендефектPG-3:pg_restore --clean падаетнаinherited partition PRIMARY KEY. HTTP admission409/ожиданиеwrite иrollback контрольныхданныхподтверждены. Основнойruntime673e5ba сохранён. Следующий12.5.15 — исправитьтранзакционнуюочисткуразделов иповторитьrestore; PG-3/полныйPG-6 открыты. +Актуальное продолжение 2026-10-07:12.5.15 завершён — транзакционный partition-safe restore исправлен и прошёл realAPI200/контрольархива/новыйID3; injected CREATE INDEX failure400 сохранилданные/sequence/1587constraints.25tests,HTTP/JWT иbuild passed. Основнойruntime673e5ba не обновлён. Следующий12.5.16 — disabled-server collector defect, integrated PG-6 иdeployment; весьэтап12.5 остаётсяоткрытым. План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push. @@ -1088,3 +1088,15 @@ GETbackup200/478810bytes/PGDMP полученвпамять безкопиров Изолированныйконтейнерудалён--rm,егоscheduler/background/worker остановлены,соединениязакрыты. ПроверочнаяБД теперьretention16; основнойстендпо-прежнемуretention14,обаCHRзаписи сохранены,healthok/version1.0.0-test.673e5ba. Nativegre/BGP collector можетвыполнятьчтениеприштатномrestart,ноRouterOS apply/конфигурациянеизменялись. РеальныйPortal/browser restore UI иintegrated UDP+detached jobs втомжекритическоминтервалеэтимнепроверены. Обнаруженныйдефектблокируетуспешнуюприёмкуrestore; коднеразвёрнутвосновномприложении. Следующий12.5.15 — исправитьочисткуpartition constraints внутриоднойтранзакции, безпредварительногонеобратимогоDROP ибезавтоматическогоповторапосленеизвестногоcommit, затемповторитьPG-3/4/полныйPG-6. Локальныйкоммитбезpush. + +## 12.5.15 — транзакционное восстановление разделов PostgreSQL (2026-10-07) + +Исправленреальныйдефект12.5.14. pg_restore --list проверяетобязательныеTABLE public servers/schema_migrations иопределяетприсутствующиевархивеизвестныеpartition parents приложения. ВременныйSQL полностьюформируется pg_restore --clean --if-exists --file безподключениякцелевойБД. Затемодинpsql -X --single-transaction --set ON_ERROR_STOP=on выполняет -c parent constraint preparation и -f SQLархива. РодительскиеPK/UNIQUE снимаютсячерезpg_catalog/format(%I) внутриэтойжетрaнзакции; inheritedchildconstraints удаляютсякаскадно, поэтомупоследующиеIF EXISTS DROP больше непадают. Подготовкаограниченаpublic/известнымиparents, присутствующимивархиве; нетотдельногоcommittedDROP/SQLпереписыванияданных/автоповтора. [Транзакционныйрежимpsql](https://www.postgresql.org/docs/18/app-psql.html) охватываетобеопции-c/-f. + +Customdump иSQL содержатприватныеданные: tempdirectory0700 (chmod такжеисправляетранее созданныйкаталог), файлы0600, обафайлаудаляютсяприуспехе/ошибке. Ошибкаdecode/неподходящийTOC непускаютtargetSQL. Архивдолженбытьдовереннымполнымbackup приложения; проверкадвухTABLE неявляетсяпроверкойвсейсхемыилидовериякSQLархива. CASCADEdependencies восстанавливаютсяизполногоархива; частичные/custom archives этимнепринимались. + +Локальныепроверки:3новыхpartition-restoretests включенывtest:system-database; полныйнабор25pure/protocol tests иHTTP/JWT scripts passed, contracts/backend build passed. Провереныscope parent/table/schema, quotedcatalogidentifiers, полностьюdecode-before-targetSQL, одинpsqltransaction, отказdecodeбезtargetSQL. Существующийrestore lifecycle сохраняетcache invalidation толькопослеуспеха. + +[RealAPI evidence](ux-ui/acceptance/real-restore-fixed-2026-10-07.json):161.104.46.222/PG18.6, isolatedcompiledf1aac49+partition-restoreworktreepatch (SHA256 patch совпал), mmapp_acceptance_20261007. Backup200/475305bytes; concurrentactiveHTTPwrite ждалSQLlock, новыйGET409. Restore200/oktrueбезwarnings, контрольныеservers/retention/speed/migrations/sequence совпалисархивом. СледующийINSERT disabledtestserver192.0.2.250 получилID3. Послеretention19 ивведенияeventtrigger, которыйпадаетнаCREATE INDEX, второйrestore400/необъявленуспешным. НезависимыеSQLпослеrollback подтвердиливсеперечисленныеконтрольныеданные/sequence3 и1587constraint descriptors. Eventtrigger/function удалены, temporarycontainer--rm/consumers/pool штатноостановлены. + +ПроверочнаяБД теперьсодержитdisabledserver3/retention19; основнаясохранена:server1–2,retention14,healthok/source673e5ba. Во времяштатногоrestart ресурсныйcollector неожиданнопыталсяпрочитатьdisabledserver3 иполучилtimeout10s — отдельныйдефектдляследующегоблока. RouterOS apply не выполнялся. PG-3/4 принятывграницахrealAPI/контрольныхданных/новогоID/SQLrollback/constraints, безполногоchecksum/Portal/browser UI. Integrated UDP+detachedjobs втомжекритическоминтервалеимультиэкземплярыещёнеприняты; полныйPG-6 открыт. Новыйкоднеразвёрнутнаосновномстенде. Следующий12.5.16 — исправитьdisabled-server polling, integratedPG-6/deployment. Локальныйкоммитбезpush. diff --git a/docs/ux-ui-test-server.md b/docs/ux-ui-test-server.md index 6991109d..346ff3e5 100644 --- a/docs/ux-ui-test-server.md +++ b/docs/ux-ui-test-server.md @@ -101,3 +101,7 @@ ssh -N -L 3000:127.0.0.1:3000 -L 8000:127.0.0.1:8000 root@161.104.46.222 ## Реальный API restore failure — 2026-10-07 [12.5.14 evidence](ux-ui/acceptance/real-restore-2026-10-07.json): isolatedsourcef1aac49/API againstmmapp_acceptance_20261007, backup200, activewrite drain/GET409. Restore400:inheritedpartitionPK нельзяудалитьсуществующим--clean. Контрольrollback прошёл (servers/speed/migrations/sequence/retention16/constraint). ПроверочнаяБДretention16,основнаяretention14/source673e5ba/healthok сохранены. Runtimeнеобновлён,PG-3 иполныйPG-6 открыты. Следующий12.5.15 — транзакционноеисправлениеclean иповторнаяприёмка. + +## Partition-safe API restore — 2026-10-07 + +12.5.15:isolatedf1aac49+compiledpatch, [realAPIrestore200/новыйID3/injectedSQL400/rollback1587constraints](ux-ui/acceptance/real-restore-fixed-2026-10-07.json). ПроверочнаяБДmmapp_acceptance_20261007 содержитserver3 disabled192.0.2.250/retention19. ОсновнаяБДserver1–2/retention14/source673e5ba/healthok сохранена.25localtests,HTTP/JWT иbuild passed. Patchещёнеразвёрнут; следующий12.5.16 — disabled-server collector defect, integratedPG-6/deployment. diff --git a/docs/ux-ui/acceptance/real-restore-fixed-2026-10-07.json b/docs/ux-ui/acceptance/real-restore-fixed-2026-10-07.json new file mode 100644 index 00000000..bc523047 --- /dev/null +++ b/docs/ux-ui/acceptance/real-restore-fixed-2026-10-07.json @@ -0,0 +1,118 @@ +{ + "date": "2026-10-07", + "source": "f1aac49+partition-restore-worktree", + "database": "mmapp_acceptance_20261007", + "mode": "real HTTP API against isolated PostgreSQL", + "mainDatabaseTouched": false, + "backup": { + "status": 200, + "bytes": 475305 + }, + "admission": { + "blockedGet": 409, + "waitedForActiveWrite": true + }, + "restore": { + "status": 200, + "outcome": { + "ok": true + } + }, + "controlsMatch": true, + "nextServerId": 3, + "failedRestore": { + "status": 400, + "expectedSqlFailure": true, + "reportedSuccess": false + }, + "rollbackControlsMatch": true, + "constraintsMatch": true, + "constraintCount": 1587, + "controlsAfterFailure": { + "servers": [ + { + "id": 1, + "host": "194.226.121.150", + "enabled": true + }, + { + "id": 2, + "host": "104.165.244.7", + "enabled": true + }, + { + "id": 3, + "host": "192.0.2.250", + "enabled": false + } + ], + "uptime": [ + { + "id": 1, + "retention_days": 19 + } + ], + "speed": [ + { + "count": 5 + } + ], + "sequence": [ + { + "last_value": 3, + "is_called": true + } + ], + "migrations": [ + { + "id": "0000_postgresql" + }, + { + "id": "0002_compact_schema" + }, + { + "id": "0003_backup_s3" + }, + { + "id": "0004_geoip_settings" + }, + { + "id": "0005_flow_facts" + }, + { + "id": "0006_config_revisions" + }, + { + "id": "0007_flow_buckets_nat" + }, + { + "id": "0008_alert_outbox_outcomes" + }, + { + "id": "0009_alert_outbox_resolution" + } + ] + }, + "limits": [ + "One isolated backend instance; no cross-host quiescence proof", + "Integrated UDP and detached jobs in same critical interval not injected", + "No Portal SSO or browser restore UI tested", + "Main runtime remains673e5ba; isolated f1aac49 plus compiled patch tested", + "Restart resource collector attempted disabled server3 at192.0.2.250 and timed out; separate defect to investigate" + ], + "patchSha256": "19fca8bb99b1b7051224078a554562abacf35feebe0ff918756e082adc9dcaee", + "restoreMethod": "pg_restore --list, pg_restore --clean --if-exists --file, psql -X --single-transaction --set ON_ERROR_STOP=on -c parent preparation -f generated SQL", + "healthMainAfter": { + "status": "ok", + "timestamp": "2026-10-07T16:26:19.859Z", + "version": "1.0.0-test.673e5ba", + "eventLoopDelayMs": 20, + "flowWorker": { + "alive": false, + "bound": false, + "pendingSize": 0, + "dropped": 0, + "packetsReceived": 0 + } + } +} \ No newline at end of file