fix(backups): отклонять восстановление старых частичных экспортов
This commit is contained in:
1 parent
faf452d149
commit
659e658f18
16 files changed
+1175
-12
No files matched your search
@@ -0,0 +1,3 @@
|
||||
import assert from 'node:assert/strict';import fs from 'node:fs';import{randomUUID}from'node:crypto';import{createServer}from'node:http';import{createRequire}from'node:module';
|
||||
const database=process.env.MMAPP_OWN_SQL_DB;assert(/^mm_accept_166_\d+$/.test(database));const url=new URL(process.env.DATABASE_URL);assert.equal(url.pathname,'/'+database);assert.equal(url.username,database);fs.mkdirSync('/tmp/'+database,{recursive:true});process.chdir('/tmp/'+database);
|
||||
const require=createRequire('/app/backend/package.json'),Fastify=require('fastify'),{validatorCompiler,serializerCompiler}=require('@fastify/type-provider-zod');const{pool,closePool,db}=await import('/app/backend/dist/db/index.js');const{initDatabase}=await import('/app/backend/dist/db/bootstrap.js');assert.equal((await pool.query('SELECT current_database() AS name')).rows[0].name,database);const role=(await pool.query('SELECT rolsuper,rolcreatedb,rolcreaterole,rolbypassrls FROM pg_roles WHERE rolname=current_user')).rows[0];assert(Object.values(role).every(v=>v===false));await initDatabase();const{createBackupsRoutes}=await import('/app/backend/dist/routes/backups.js');const{servers}=await import('/app/backend/dist/db/schema.js');const{databaseBackgroundWork}=await import('/app/backend/dist/services/database-background-work.js');let nativeRequests=0;const native=createServer((_req,res)=>{nativeRequests++;res.statusCode=500;res.end('{}')});await new Promise(r=>native.listen(0,'127.0.0.1',r));const[server]=await db.insert(servers).values({name:'owned-legacy234',host:'127.0.0.1',port:native.address().port,useSsl:false,username:'fixture',password:'fixture',enabled:true,type:'exit-node'}).returning();const filename='legacy234-'+randomUUID()+'.rsc',file='storage/backups/'+filename;fs.mkdirSync('storage/backups',{recursive:true});await pool.query("INSERT INTO backup_entries(id,server_id,server_name,filename,size_bytes,kind,storage,created_at) VALUES('owned-legacy234',$1,'fixture',$2,1,'manual','local',now())",[server.id,filename]);const app=Fastify({logger:false});app.setValidatorCompiler(validatorCompiler);app.setSerializerCompiler(serializerCompiler);await app.register(createBackupsRoutes(),{prefix:'/api'});await app.ready();const report={date:'2026-10-14',block:'12.5.234',checks:[],passed:false,chrContacts:0,externalS3Contacts:0};try{for(const prefix of['','\uFEFF\r\n ']){const content=prefix+'# synthetic export generated by MikrotikManager\n/system identity\nset name=legacy-fixture\n';fs.writeFileSync(file,content);const download=await app.inject({method:'GET',url:'/api/backups/owned-legacy234/download'});assert.equal(download.statusCode,200);assert.equal(download.body,content);const restore=await app.inject({method:'POST',url:'/api/backups/owned-legacy234/restore'});assert.equal(restore.statusCode,400);assert.equal(restore.json().outcome,'not-applied');assert.match(restore.json().error,/частичным экспортом/);assert.equal(fs.readFileSync(file,'utf8'),content);assert.equal(nativeRequests,0);assert.equal((await pool.query("SELECT count(*)::int AS n FROM backup_entries WHERE id='owned-legacy234'")).rows[0].n,1);report.checks.push({bomAndWhitespace:Boolean(prefix),downloadStatus:200,restoreStatus:400,outcome:'not-applied',fileAndCatalogPreserved:true,nativeRequests:0})}report.passed=true}catch(e){report.failure={name:e.name}}finally{await databaseBackgroundWork.drain();const deleted=await app.inject({method:'DELETE',url:'/api/backups/owned-legacy234'});assert.equal(deleted.statusCode,204);assert(!fs.existsSync(file));assert.equal((await pool.query('SELECT count(*)::int AS n FROM backup_entries')).rows[0].n,0);report.cleanup={catalogRows:0,localFiles:0};await app.close();await new Promise(r=>native.close(r));await closePool();fs.writeFileSync('/checks/legacy-restore234.json',JSON.stringify(report,null,2)+'\n');console.log(JSON.stringify(report));if(!report.passed)process.exitCode=1}
|
||||
@@ -3,4 +3,3 @@ const database=process.env.MMAPP_OWN_SQL_DB;assert(/^mm_accept_166_\d+$/.test(da
|
||||
const require=createRequire('/app/backend/package.json'),Fastify=require('fastify'),{validatorCompiler,serializerCompiler}=require('@fastify/type-provider-zod');const{pool,closePool}=await import('/app/backend/dist/db/index.js');const{initDatabase}=await import('/app/backend/dist/db/bootstrap.js');assert.equal((await pool.query('SELECT current_database() AS name')).rows[0].name,database);const role=(await pool.query('SELECT rolsuper,rolcreatedb,rolcreaterole,rolbypassrls FROM pg_roles WHERE rolname=current_user')).rows[0];assert(Object.values(role).every(v=>v===false));await initDatabase();const{createBackupsRoutes}=await import('/app/backend/dist/routes/backups.js');const{databaseBackgroundWork}=await import('/app/backend/dist/services/database-background-work.js');const baseline=process.env.MMAPP_ORIGIN_BASELINE==='true',report={date:'2026-10-14',block:'12.5.233',baseline,checks:[],externalS3Contacts:0,chrContacts:0,passed:false};const app=Fastify({logger:false});app.setValidatorCompiler(validatorCompiler);app.setSerializerCompiler(serializerCompiler);await app.register(createBackupsRoutes(),{prefix:'/api'});await app.ready();const config={provider:'s3',s3Endpoint:'https://s3.invalid',s3Region:'test-region',s3Bucket:'origin-bucket',s3Prefix:'owned-test',s3AccessKeyId:'fixture',s3SecretAccessKey:'fixture',s3ForcePathStyle:false,keepLocalCopy:true};async function api(method,path,payload,status=200){const r=await app.inject({method,url:'/api'+path,...(payload?{payload}:{})});assert.equal(r.statusCode,status,method+' '+path);return r}
|
||||
async function insert(){await pool.query("INSERT INTO backup_entries(id,server_id,server_name,filename,size_bytes,kind,storage,s3_key,created_at) VALUES('owned-origin',NULL,'fixture','fixture.rsc',1,'manual','s3','owned-test/fixture.rsc',now())")}
|
||||
try{await api('PUT','/backups/storage',config);await insert();for(const patch of[{s3Bucket:'other-bucket'},{s3Endpoint:'https://other.invalid'},{s3Region:'other'},{s3ForcePathStyle:true},{provider:'local'}]){const before=(await api('GET','/backups/storage')).json();await api('PUT','/backups/storage',patch,baseline?200:409);const after=(await api('GET','/backups/storage')).json();if(!baseline)assert.deepEqual(after,before);else await api('PUT','/backups/storage',config);report.checks.push({change:Object.keys(patch)[0],status:baseline?200:409,settingsUnchanged:!baseline})}for(const patch of[{s3Prefix:'new-prefix'},{s3SecretAccessKey:'rotated-fixture'},{keepLocalCopy:false}]){await api('PUT','/backups/storage',patch);report.checks.push({allowed:Object.keys(patch)[0],status:200})}await pool.query("DELETE FROM backup_entries WHERE id='owned-origin'");await api('PUT','/backups/storage',{s3Bucket:'empty-catalog-bucket'});report.checks.push({emptyCatalogChangeAllowed:true});if(!baseline){const{withBackupStorageOperation}=await import('/app/backend/dist/services/backup-storage-operation.js');let release,started;const active=new Promise(r=>started=r),wait=new Promise(r=>release=r);const creator=withBackupStorageOperation(async()=>{started();await wait;await insert()});await active;let settled=false;const update=api('PUT','/backups/storage',{s3Bucket:'racing-bucket'},409).then(()=>settled=true);await new Promise(r=>setTimeout(r,20));await update;assert.equal(settled,true);release();await creator;assert.equal((await api('GET','/backups/storage')).json().s3Bucket,'empty-catalog-bucket');report.checks.push({inFlightUpdateRejectedBeforeCatalogPublication:true});const{readBackupContent,getBackupById}=await import('/app/backend/dist/services/backup-service.js');const stale=await getBackupById('owned-origin');let releaseDelete;const deletion=withBackupStorageOperation(async()=>{await new Promise(r=>releaseDelete=r);await pool.query("DELETE FROM backup_entries WHERE id='owned-origin'")});const download=readBackupContent(stale);const rejected=assert.rejects(download,/Бэкап не найден/);await Promise.resolve();releaseDelete();await deletion;await rejected;report.checks.push({staleDownloadRejectsBeforeS3AfterDeletion:true})}report.passed=true}catch(e){report.failure={name:e.name}}finally{await pool.query("DELETE FROM backup_entries WHERE id='owned-origin'");await databaseBackgroundWork.drain();await app.close();await closePool();fs.writeFileSync('/checks/storage-origin233.json',JSON.stringify(report,null,2)+'\n');console.log(JSON.stringify(report));if(!report.passed)process.exitCode=1}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import assert from "node:assert/strict"
|
||||
import { test } from "node:test"
|
||||
import { MikrotikClient, MikrotikError } from "./mikrotik.js"
|
||||
import { BackupRestoreUnknownError, importBackupOnce } from "./backup-restore.js"
|
||||
import { assertRestorableBackupScript, BackupRestoreUnknownError, importBackupOnce } from "./backup-restore.js"
|
||||
const makeClient = () => new MikrotikClient({ host: "router.invalid", port: 80, useSsl: false, verifySsl: false, username: "test", password: "test" })
|
||||
test("legacy partial exports are rejected while native and limited scripts remain usable", () => {
|
||||
for (const prefix of ["", "\uFEFF\r\n "]) assert.throws(() => assertRestorableBackupScript(prefix + "# synthetic export generated by MikrotikManager\n/system identity\nset name=legacy"), /частичным экспортом/)
|
||||
for (const script of ["# oct/14/2026 by RouterOS 7.24.5\n/system identity\nset name=native", "/interface bridge\nadd name=owned-probe", "# harmless fixture\n"]) assert.doesNotThrow(() => assertRestorableBackupScript(script))
|
||||
})
|
||||
test("import does not fallback after ambiguous failure or command rejection", async () => {
|
||||
for (const error of [new Error("connection lost after apply"), new Error("Invalid JSON"), new MikrotikError(500, "/import", "error"), new MikrotikError(400, "/import", "script failed"), new MikrotikError(401, "/import", "unauthorized")]) {
|
||||
const client = makeClient(), calls: string[] = []
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
export class BackupRestoreUnknownError extends Error {
|
||||
constructor(options?: ErrorOptions) { super("Результат импорта неизвестен. Проверьте конфигурацию роутера перед повтором.", options); this.name = "BackupRestoreUnknownError" }
|
||||
}
|
||||
export function assertRestorableBackupScript(script: string): void {
|
||||
if (/^#\s*synthetic export generated by MikrotikManager\b/i.test(script.trimStart())) {
|
||||
throw new Error("Этот файл создан старым частичным экспортом MikroTik Manager. Его нельзя использовать для восстановления конфигурации; создайте новую нативную резервную копию.")
|
||||
}
|
||||
}
|
||||
export async function importBackupOnce(file: string, apply: (file: string) => Promise<unknown>): Promise<void> {
|
||||
try { await apply(file) } catch (cause) { throw new BackupRestoreUnknownError({ cause }) }
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import type {
|
||||
} from "@mmapp/contracts/backups"
|
||||
import { mapBackupEntry } from "./backup-dto.js"
|
||||
import { pruneBackupRecords } from "./backup-prune.js"
|
||||
import { importBackupOnce } from "./backup-restore.js"
|
||||
import { assertRestorableBackupScript, importBackupOnce } from "./backup-restore.js"
|
||||
import { deleteStoredBackup } from "./backup-deletion.js"
|
||||
import { assertBackupStorageConnectionChange, planBackupStorageUpdate } from "./backup-storage-update.js"
|
||||
import { withBackupStorageOperation, withBackupStorageSettingsUpdate } from "./backup-storage-operation.js"
|
||||
@@ -502,6 +502,7 @@ export async function restoreBackupToDevice(id: string): Promise<{ backupId: str
|
||||
const row = await getServerRowById(meta.serverId)
|
||||
if (!row) throw new Error("Сервер не найден")
|
||||
const content = await readBackupContent(meta)
|
||||
assertRestorableBackupScript(content.toString("utf8"))
|
||||
const client = MikrotikClient.fromServer(row)
|
||||
const uploaded = await client.uploadTextFile(meta.filename, content.toString("utf8"), 60_000)
|
||||
await importBackupOnce(uploaded, file => client.importUploadedFile(file))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# UX/UI: протокол внешней приёмки 12.5
|
||||
|
||||
Актуальное продолжение 2026-10-14:230–232 — native S3 backup/ограниченный restore/порционное чтение и real UI save/test приняты, fixtures очищены.233 —5 небезопасных изменений S3 подключения отклоняются409 без записи; гонки create/sync/read/delete/settings согласованы в одном backend.11 owned SQL/API scenarios passed, старая231 принимала5 опасных changes200. Backend ux-backup233/frontend ux-routing218 healthy. Далее234 — запрет restore старых synthetic partial backups; version retention/multi-instance/расширенная приёмка открыты. Этап12.5 открыт.
|
||||
Актуальное продолжение 2026-10-14:230–233 — native S3 backup/ограниченный restore/UI и защита подключения/гонок приняты.234 — известный legacy synthetic export отклоняется400/not-applied до RouterOS; download/file/catalog сохранены,2 owned SQL/API cases passed. Backend ux-backup234 healthy/frontend ux-routing218;504 source/1064 outputs matched. Обнаружены10 исходных catalog rows с отсутствующими local files и непостоянный backup path; следующий235 — постоянный Compose volume и native backup recreation proof. Этап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).
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# План реализации UX/UI по пятичасовым окнам
|
||||
|
||||
Актуальное продолжение 2026-10-14:230–232 — native S3 backup/ограниченный restore/порционное чтение и real UI save/test приняты, fixtures очищены.233 —5 небезопасных изменений S3 подключения отклоняются409 без записи; гонки create/sync/read/delete/settings согласованы в одном backend.11 owned SQL/API scenarios passed, старая231 принимала5 опасных changes200. Backend ux-backup233/frontend ux-routing218 healthy. Далее234 — запрет restore старых synthetic partial backups; version retention/multi-instance/расширенная приёмка открыты. Этап12.5 открыт.
|
||||
Актуальное продолжение 2026-10-14:230–233 — native S3 backup/ограниченный restore/UI и защита подключения/гонок приняты.234 — известный legacy synthetic export отклоняется400/not-applied до RouterOS; download/file/catalog сохранены,2 owned SQL/API cases passed. Backend ux-backup234 healthy/frontend ux-routing218;504 source/1064 outputs matched. Обнаружены10 исходных catalog rows с отсутствующими local files и непостоянный backup path; следующий235 — постоянный Compose volume и native backup recreation proof. Этап12.5 открыт.
|
||||
|
||||
Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-14. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат.
|
||||
|
||||
|
||||
+10
-2
@@ -1,6 +1,6 @@
|
||||
# UX/UI: журнал реализации
|
||||
|
||||
Актуальное продолжение 2026-10-14:230–232 — native S3 backup/ограниченный restore/порционное чтение и real UI save/test приняты, fixtures очищены.233 —5 небезопасных изменений S3 подключения отклоняются409 без записи; гонки create/sync/read/delete/settings согласованы в одном backend.11 owned SQL/API scenarios passed, старая231 принимала5 опасных changes200. Backend ux-backup233/frontend ux-routing218 healthy. Далее234 — запрет restore старых synthetic partial backups; version retention/multi-instance/расширенная приёмка открыты. Этап12.5 открыт.
|
||||
Актуальное продолжение 2026-10-14:230–233 — native S3 backup/ограниченный restore/UI и защита подключения/гонок приняты.234 — известный legacy synthetic export отклоняется400/not-applied до RouterOS; download/file/catalog сохранены,2 owned SQL/API cases passed. Backend ux-backup234 healthy/frontend ux-routing218;504 source/1064 outputs matched. Обнаружены10 исходных catalog rows с отсутствующими local files и непостоянный backup path; следующий235 — постоянный Compose volume и native backup recreation proof. Этап12.5 открыт.
|
||||
|
||||
Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md).
|
||||
|
||||
@@ -36,7 +36,15 @@
|
||||
| 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–233 завершены в записанных scopes, этап открыт. Runtime backend ux-backup233/frontend ux-routing218; backend25/UI52 и дополнительные SQL/UDP222 прошли. |
|
||||
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка12.3.83 в границах журнала. Внешняя12.5: блоки1–234 завершены в записанных scopes, этап открыт. Runtime backend ux-backup234/frontend ux-routing218; backend25/UI52 и дополнительные SQL/UDP222 прошли. |
|
||||
|
||||
## 12.5.234 — отказ восстановления старого частичного экспорта (2026-10-14)
|
||||
|
||||
Известный старый header «# synthetic export generated by MikrotikManager» теперь проверяется после чтения файла, до upload/import. Такой файл возвращает400/outcome=not-applied с указанием создать новую нативную копию; скачивание и исходный файл/catalog остаются доступны. Это точечная защита известного legacy format, не общая валидация произвольных .rsc scripts.
|
||||
|
||||
Unit проверяет обычный и BOM/whitespace header, сохранены native/limited scripts; test:backups (13 exporter/4 restore/5 storage cases и остальные профильные suites), contracts/backend build и scoped ESLint/diff check passed. Compiled routes+owned SQL+HTTP RouterOS fixture:2 legacy variants download200→restore400, file/catalog unchanged, native requests0/S3 contacts0; DELETE204 очистил own record/file, DB/role/container removed. [SQL/API](ux-ui/acceptance/legacy-restore234.json).
|
||||
|
||||
Image ux-backup234:504 source/1064 compiled matched; main healthy/image ID verified, frontend ux-routing218 сохранён. [Deploy](ux-ui/acceptance/backup234-deployment.json). Существующие10 main catalog rows проверены read-only: files missing10, legacy/native classification не получена. Node main cwd=/app, mount был только /app/data; BACKUPS_DIR=/app/storage/backups находится вне bind. Это не доказательство содержимого или прежнего существования10 missing files. [Диагностика](ux-ui/acceptance/legacy234-catalog.json). Следующий235 — persist mount/recreate proof; original missing rows не удалять и не объявлять восстановленными. Этап12.5 открыт.
|
||||
|
||||
## 12.5.233 — защита адресации S3 и гонок с каталогом (2026-10-14)
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Оставшаяся внешняя приёмка — 2026-10-14
|
||||
|
||||
Актуальное продолжение 2026-10-14:230–232 — native S3 backup/ограниченный restore/порционное чтение и real UI save/test приняты, fixtures очищены.233 —5 небезопасных изменений S3 подключения отклоняются409 без записи; гонки create/sync/read/delete/settings согласованы в одном backend.11 owned SQL/API scenarios passed, старая231 принимала5 опасных changes200. Backend ux-backup233/frontend ux-routing218 healthy. Далее234 — запрет restore старых synthetic partial backups; version retention/multi-instance/расширенная приёмка открыты. Этап12.5 открыт.
|
||||
Актуальное продолжение 2026-10-14:230–233 — native S3 backup/ограниченный restore/UI и защита подключения/гонок приняты.234 — известный legacy synthetic export отклоняется400/not-applied до RouterOS; download/file/catalog сохранены,2 owned SQL/API cases passed. Backend ux-backup234 healthy/frontend ux-routing218;504 source/1064 outputs matched. Обнаружены10 исходных catalog rows с отсутствующими local files и непостоянный backup path; следующий235 — постоянный Compose volume и native backup recreation proof. Этап12.5 открыт.
|
||||
|
||||
Это актуальная матрица после 12.5.233. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
|
||||
Это актуальная матрица после 12.5.234. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
|
||||
|
||||
Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-backup233`, frontend `ux-routing218`; 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-backup234`, frontend `ux-routing218`; production lab с195 использует AUTH_REQUIRED=true и Portal1.3.1. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись.
|
||||
|
||||
## Остаток приёмки по группам — 2026-10-14
|
||||
|
||||
|
||||
+10
-1
@@ -1,6 +1,6 @@
|
||||
# Этап 12: итоговая приёмка UX/UI
|
||||
|
||||
Актуальное продолжение 2026-10-14:230–232 — native S3 backup/ограниченный restore/порционное чтение и real UI save/test приняты, fixtures очищены.233 —5 небезопасных изменений S3 подключения отклоняются409 без записи; гонки create/sync/read/delete/settings согласованы в одном backend.11 owned SQL/API scenarios passed, старая231 принимала5 опасных changes200. Backend ux-backup233/frontend ux-routing218 healthy. Далее234 — запрет restore старых synthetic partial backups; version retention/multi-instance/расширенная приёмка открыты. Этап12.5 открыт.
|
||||
Актуальное продолжение 2026-10-14:230–233 — native S3 backup/ограниченный restore/UI и защита подключения/гонок приняты.234 — известный legacy synthetic export отклоняется400/not-applied до RouterOS; download/file/catalog сохранены,2 owned SQL/API cases passed. Backend ux-backup234 healthy/frontend ux-routing218;504 source/1064 outputs matched. Обнаружены10 исходных catalog rows с отсутствующими local files и непостоянный backup path; следующий235 — постоянный Compose volume и native backup recreation proof. Этап12.5 открыт.
|
||||
|
||||
План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push.
|
||||
|
||||
@@ -4131,3 +4131,12 @@ Backup row хранит s3Key, а bucket/endpoint берёт из global setting
|
||||
|
||||
Image ux-backup233:503 source files/1064 compiled outputs совпали с frozen snapshot; main backend healthy/image matched, frontend ux-routing218 сохранён. [Deploy](ux-ui/acceptance/backup233-deployment.json). Реальные S3 upload/restore и live UI232 повторно не выполнялись; production data/settings не менялись кроме image tag. Межпроцессная/межэкземплярная синхронизация и миграция существующих S3 файлов между buckets этим не реализованы. Privileged SQL writers обходят application guard; несколько backend остаются открытой группой. Далее234 — отклонить restore известного старого partial synthetic export до native upload/import. Этап12.5 открыт.
|
||||
|
||||
|
||||
## 12.5.234 — отказ восстановления старого частичного экспорта (2026-10-14)
|
||||
|
||||
Известный старый header «# synthetic export generated by MikrotikManager» теперь проверяется после чтения файла, до upload/import. Такой файл возвращает400/outcome=not-applied с указанием создать новую нативную копию; скачивание и исходный файл/catalog остаются доступны. Это точечная защита известного legacy format, не общая валидация произвольных .rsc scripts.
|
||||
|
||||
Unit проверяет обычный и BOM/whitespace header, сохранены native/limited scripts; test:backups (13 exporter/4 restore/5 storage cases и остальные профильные suites), contracts/backend build и scoped ESLint/diff check passed. Compiled routes+owned SQL+HTTP RouterOS fixture:2 legacy variants download200→restore400, file/catalog unchanged, native requests0/S3 contacts0; DELETE204 очистил own record/file, DB/role/container removed. [SQL/API](ux-ui/acceptance/legacy-restore234.json).
|
||||
|
||||
Image ux-backup234:504 source/1064 compiled matched; main healthy/image ID verified, frontend ux-routing218 сохранён. [Deploy](ux-ui/acceptance/backup234-deployment.json). Существующие10 main catalog rows проверены read-only: files missing10, legacy/native classification не получена. Node main cwd=/app, mount был только /app/data; BACKUPS_DIR=/app/storage/backups находится вне bind. Это не доказательство содержимого или прежнего существования10 missing files. [Диагностика](ux-ui/acceptance/legacy234-catalog.json). Следующий235 — persist mount/recreate proof; original missing rows не удалять и не объявлять восстановленными. Этап12.5 открыт.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Тестовый сервер UX/UI — 2026-10-10
|
||||
|
||||
Актуальное продолжение 2026-10-14:230–232 — native S3 backup/ограниченный restore/порционное чтение и real UI save/test приняты, fixtures очищены.233 —5 небезопасных изменений S3 подключения отклоняются409 без записи; гонки create/sync/read/delete/settings согласованы в одном backend.11 owned SQL/API scenarios passed, старая231 принимала5 опасных changes200. Backend ux-backup233/frontend ux-routing218 healthy. Далее234 — запрет restore старых synthetic partial backups; version retention/multi-instance/расширенная приёмка открыты. Этап12.5 открыт.
|
||||
Актуальное продолжение 2026-10-14:230–233 — native S3 backup/ограниченный restore/UI и защита подключения/гонок приняты.234 — известный legacy synthetic export отклоняется400/not-applied до RouterOS; download/file/catalog сохранены,2 owned SQL/API cases passed. Backend ux-backup234 healthy/frontend ux-routing218;504 source/1064 outputs matched. Обнаружены10 исходных catalog rows с отсутствующими local files и непостоянный backup path; следующий235 — постоянный Compose volume и native backup recreation proof. Этап12.5 открыт.
|
||||
|
||||
| Параметр | Проверенное состояние |
|
||||
| --- | --- |
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"date": "2026-10-14",
|
||||
"block": "12.5.234",
|
||||
"backendImage": "ux-backup234",
|
||||
"imageId": "sha256:0bbf998de821f9a45c58f9155506e2e2d52ca8ffcf4453351f4dde094a306b2b",
|
||||
"healthy": true,
|
||||
"sourceMatched": 504,
|
||||
"compiledMatched": 1064,
|
||||
"frontendChanged": false
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"date": "2026-10-14",
|
||||
"block": "12.5.234",
|
||||
"kinds": [
|
||||
{
|
||||
"kind": "backend",
|
||||
"matched": 504
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"date": "2026-10-14",
|
||||
"block": "12.5.234",
|
||||
"checks": [
|
||||
{
|
||||
"bomAndWhitespace": false,
|
||||
"downloadStatus": 200,
|
||||
"restoreStatus": 400,
|
||||
"outcome": "not-applied",
|
||||
"fileAndCatalogPreserved": true,
|
||||
"nativeRequests": 0
|
||||
},
|
||||
{
|
||||
"bomAndWhitespace": true,
|
||||
"downloadStatus": 200,
|
||||
"restoreStatus": 400,
|
||||
"outcome": "not-applied",
|
||||
"fileAndCatalogPreserved": true,
|
||||
"nativeRequests": 0
|
||||
}
|
||||
],
|
||||
"passed": true,
|
||||
"chrContacts": 0,
|
||||
"externalS3Contacts": 0,
|
||||
"cleanup": {
|
||||
"catalogRows": 0,
|
||||
"localFiles": 0
|
||||
},
|
||||
"ownedDatabaseAndRoleRemoved": true
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"date": "2026-10-14",
|
||||
"block": "12.5.234",
|
||||
"total": 10,
|
||||
"legacySynthetic": 0,
|
||||
"nativeHeader": 0,
|
||||
"other": 0,
|
||||
"missing": 10,
|
||||
"configurationWrites": 0
|
||||
}
|
||||
Reference in new issue
Block a user