chore(test): проверить Selectel S3 и операции резервных копий

This commit is contained in:
Denozordec committed 2026-10-14 10:01:35 +07:00
1 parent 2277a76196
commit 03b0896369
14 files changed
+377 -19

No files matched your search

@@ -0,0 +1,32 @@
#!/bin/bash
set -euo pipefail
umask 077
cd /opt/mmapp-test
mm229_db="mm_accept_166_$(date +%s%3N)"
mm229_pass="$(openssl rand -hex 24)"
mm229_role_created=false
mm229_db_created=false
cleanup229() {
docker rm mmapp-s3app229-owned >/dev/null 2>&1 || true
if $mm229_db_created; then docker exec mmapp-test-postgres psql -U mmapp -d mmapp -v ON_ERROR_STOP=1 -c "DROP DATABASE \"$mm229_db\" WITH (FORCE)"; fi
if $mm229_role_created; then docker exec mmapp-test-postgres psql -U mmapp -d mmapp -v ON_ERROR_STOP=1 -c "DROP ROLE \"$mm229_db\""; fi
remaining=$(docker exec mmapp-test-postgres psql -U mmapp -d mmapp -Atc "SELECT (SELECT count(*) FROM pg_database WHERE datname='$mm229_db')+(SELECT count(*) FROM pg_roles WHERE rolname='$mm229_db')")
[ "$remaining" = 0 ]
python3 - <<'PY'
import json,pathlib
p=pathlib.Path('/opt/mmapp-test/artifacts/s3-229/s3-application229.json')
if p.exists():
r=json.loads(p.read_text());r['ownedDatabaseAndRoleRemoved']=True;p.write_text(json.dumps(r,indent=2)+'\n')
PY
printf 'OWNED_DATABASE_AND_ROLE_REMOVED\n'
unset mm229_pass
}
trap cleanup229 EXIT
[[ "$mm229_db" =~ ^mm_accept_166_[0-9]+$ ]]
existing=$(docker exec mmapp-test-postgres psql -U mmapp -d mmapp -Atc "SELECT (SELECT count(*) FROM pg_database WHERE datname='$mm229_db')+(SELECT count(*) FROM pg_roles WHERE rolname='$mm229_db')")
[ "$existing" = 0 ]
printf "CREATE ROLE \"%s\" LOGIN PASSWORD '%s' NOSUPERUSER NOCREATEDB NOCREATEROLE NOBYPASSRLS;\n" "$mm229_db" "$mm229_pass" | docker exec -i mmapp-test-postgres psql -U mmapp -d mmapp -v ON_ERROR_STOP=1
mm229_role_created=true
docker exec mmapp-test-postgres psql -U mmapp -d mmapp -v ON_ERROR_STOP=1 -c "CREATE DATABASE \"$mm229_db\" OWNER \"$mm229_db\""
mm229_db_created=true
docker run --name mmapp-s3app229-owned --network mmapp-test_default --cap-drop ALL --security-opt no-new-privileges --env-file /opt/mmapp-test/artifacts/s3-228-private.env -e NODE_ENV=test -e "DATABASE_URL=postgres://$mm229_db:$mm229_pass@postgres:5432/$mm229_db" -e "MMAPP_OWN_SQL_DB=$mm229_db" -e "DATABASE_PATH=/tmp/$mm229_db/legacy.db" -e "GEOIP_DIR=/tmp/$mm229_db/geoip" -v /opt/mmapp-test/artifacts/s3-229:/checks --entrypoint node mmapp-test/backend:ux-regression222 /checks/s3-backups-api.mjs
@@ -0,0 +1,28 @@
import assert from 'node:assert/strict';import fs from 'node:fs';import{randomUUID,createHash}from'node:crypto';import{createServer}from'node:http';import{createRequire}from'node:module';
assert.equal(process.env.MMAPP_TEST_S3_OWNED_SCOPE,'yes');assert.equal(process.env.MMAPP_TEST_S3_RUN,'confirmed');assert.equal(process.env.MMAPP_TEST_S3_PATH_STYLE,'false');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{db,pool,closePool}=await import('/app/backend/dist/db/index.js');const{initDatabase}=await import('/app/backend/dist/db/bootstrap.js');const{servers}=await import('/app/backend/dist/db/schema.js');const{eq}=await import('/app/node_modules/drizzle-orm/index.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{s3ListObjects,s3PutObject,s3DeleteObject,createS3ClientFromConfig}=await import('/app/backend/dist/services/s3-backup-client.js');
const prefix='mmapp-acceptance/'+randomUUID(),secret=process.env.MMAPP_TEST_S3_SECRET_KEY,config={provider:'s3',s3Endpoint:process.env.MMAPP_TEST_S3_ENDPOINT,s3Region:process.env.MMAPP_TEST_S3_REGION,s3Bucket:process.env.MMAPP_TEST_S3_BUCKET,s3Prefix:prefix,s3AccessKeyId:process.env.MMAPP_TEST_S3_ACCESS_KEY,s3SecretAccessKey:secret,s3ForcePathStyle:false,keepLocalCopy:true};assert(secret&&config.s3Endpoint==='https://s3.ru-1.storage.selcloud.ru'&&config.s3Bucket==='mikrotik-test-s3');const client=createS3ClientFromConfig({endpoint:config.s3Endpoint,region:config.s3Region,bucket:config.s3Bucket,prefix,accessKeyId:config.s3AccessKeyId,secretAccessKey:secret,forcePathStyle:false});
const report={date:'2026-10-14',block:'12.5.229',scope:'Compiled backup API + owned PostgreSQL + real Selectel S3; synthetic RouterOS HTTP export fixture, no CHR contacts',prefix,checks:[],passed:false,cleanup:{},nativeChrContacts:0,nativeRestoreCalls:0};const file='/checks/s3-application229.json';const save=()=>fs.writeFileSync(file,JSON.stringify(report,null,2)+'\n');const check=(name,extra={})=>{report.checks.push({name,passed:true,...extra});save()};const sha=b=>createHash('sha256').update(b).digest('hex');let exportCalls=0;const body='# Owned MikroTik Manager S3 application acceptance\n';
const fixture=createServer((req,res)=>{if(req.method==='POST'&&req.url==='/rest/console/export'){exportCalls++;res.setHeader('content-type','application/json');res.end(JSON.stringify({ret:body}));}else{res.statusCode=404;res.end('{}')}});await new Promise(r=>fixture.listen(0,'127.0.0.1',r));const names=['s3-positive-'+randomUUID(),'s3-negative-'+randomUUID(),'s3-only-'+randomUUID()];const [server]=await db.insert(servers).values({name:names[0],host:'127.0.0.1',port:fixture.address().port,useSsl:false,username:'fixture',password:'fixture',enabled:true,type:'exit-node'}).returning();const app=Fastify({logger:false});app.setValidatorCompiler(validatorCompiler);app.setSerializerCompiler(serializerCompiler);await app.register(createBackupsRoutes(),{prefix:'/api'});await app.ready();
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+' HTTP '+r.statusCode);return r}
async function job(){const requestId=randomUUID(),before=exportCalls;await api('POST','/backups/create',{serverIds:[server.id],requestId},202);for(let n=0;n<100;n++){const r=(await api('GET','/backups/jobs/'+requestId)).json();if(r.status==='done'){assert.equal(r.completed,1);assert.equal(r.failures.length,0);assert.equal(r.created.length,1);assert.equal(exportCalls,before+1);await api('POST','/backups/create',{serverIds:[server.id],requestId},202);assert.equal(exportCalls,before+1);return r.created[0]}await new Promise(r=>setTimeout(r,100))}throw Error('Job did not complete')}
try{
assert.equal((await s3ListObjects(client,config.s3Bucket,prefix)).length,0);check('empty-prefix-preflight');
let settings=(await api('PUT','/backups/storage',config)).json();assert(settings.secretConfigured&&settings.lastTestAt===null);assert(!JSON.stringify(settings).includes(secret));check('settings-save-without-secret-disclosure');
settings=(await api('POST','/backups/storage/test')).json();assert(settings.lastTestAt&&!settings.lastTestError);check('real-headbucket-test-receipt');
const both=await job();assert.equal(both.storage,'both');assert(!both.uploadError&&both.s3Key.startsWith(prefix+'/'));check('async-create-both-and-request-id-no-repeat');
const downloaded=await api('GET','/backups/'+both.id+'/download');assert.equal(sha(downloaded.rawPayload),sha(Buffer.from(body)));check('download-exact-bytes');
await api('PUT','/backups/storage',{s3SecretAccessKey:secret+'-invalid'});const denied=(await api('POST','/backups/storage/test',undefined,400)).json();assert(denied.settings.lastTestError&&denied.settings.secretConfigured);check('denied-credentials-test-preserves-settings');
const fallback=await api('GET','/backups/'+both.id+'/download');assert.equal(sha(fallback.rawPayload),sha(Buffer.from(body)));check('download-local-fallback-during-s3-denial');
await db.update(servers).set({name:names[1]}).where(eq(servers.id,server.id));const partial=await job();assert.equal(partial.storage,'local');assert(partial.uploadError&&!partial.s3Key);check('upload-denial-keeps-local-backup-and-error');
await api('PUT','/backups/storage',{s3SecretAccessKey:secret,keepLocalCopy:false});await db.update(servers).set({name:names[2]}).where(eq(servers.id,server.id));const remote=await job();assert.equal(remote.storage,'s3');assert(!remote.uploadError);assert(!fs.existsSync('storage/backups/'+remote.filename));const remoteBytes=await api('GET','/backups/'+remote.id+'/download');assert.equal(sha(remoteBytes.rawPayload),sha(Buffer.from(body)));check('s3-only-removes-local-copy-and-downloads-remote');
const key=prefix+'/seed/probe-sync.rsc';await s3PutObject(client,config.s3Bucket,key,body);const sync=(await api('POST','/backups/storage/sync')).json();assert.equal(sync.imported,1);const again=(await api('POST','/backups/storage/sync')).json();assert.equal(again.imported,0);check('sync-import-and-idempotent-repeat');
const catalog=(await api('GET','/backups')).json();assert.equal(catalog.length,4);const imported=catalog.find(r=>r.s3Key===key);assert(imported&&imported.serverId===null);assert.equal((await api('GET','/backups/'+imported.id+'/download')).payload,body);check('catalog-and-imported-object-download');
for(const row of catalog)await api('DELETE','/backups/'+row.id,undefined,204);assert.equal((await api('GET','/backups')).json().length,0);assert.equal((await s3ListObjects(client,config.s3Bucket,prefix)).length,0);await api('DELETE','/backups/'+both.id,undefined,404);check('delete-catalog-and-current-remote-objects');report.passed=true;
}catch(e){report.failure={name:e.name};}
finally{
try{await api('PUT','/backups/storage',{s3SecretAccessKey:secret});const rows=(await api('GET','/backups')).json();for(const row of rows){assert(!row.s3Key||row.s3Key.startsWith(prefix+'/'));await api('DELETE','/backups/'+row.id,undefined,204)}const leftovers=await s3ListObjects(client,config.s3Bucket,prefix);for(const row of leftovers){assert(row.key===prefix+'/seed/probe-sync.rsc'||names.some(n=>row.key.startsWith(prefix+'/'+n+'/'+n+'_')),'Unexpected key; do not delete');await s3DeleteObject(client,config.s3Bucket,row.key)}report.cleanup.currentObjectsRemaining=(await s3ListObjects(client,config.s3Bucket,prefix)).length;report.cleanup.catalogRowsRemaining=(await api('GET','/backups')).json().length;report.cleanup.localFilesRemaining=fs.existsSync('storage/backups')?fs.readdirSync('storage/backups').length:0;assert.equal(report.cleanup.currentObjectsRemaining,0);assert.equal(report.cleanup.localFilesRemaining,0)}catch(e){report.cleanup.failure={name:e.name};report.passed=false}
await databaseBackgroundWork.drain();await app.close();await new Promise(r=>fixture.close(r));await closePool();client.destroy();report.fixtureExportCalls=exportCalls;report.completedAt=new Date().toISOString();report.bounds=['Native export is HTTP fixture; no real device backup/restore or UI repeated','RBAC not repeated in this standalone injected API','S3 current-object deletion only; retained versions/retention not inspected'];save();console.log(JSON.stringify({passed:report.passed,checks:report.checks.length,cleanup:report.cleanup,fixtureExportCalls:exportCalls,nativeChrContacts:0}));if(!report.passed)process.exitCode=1;
}
@@ -0,0 +1,4 @@
import assert from 'node:assert/strict';import fs from 'node:fs';import{createRequire}from'node:module';import{createS3ClientFromConfig}from'/app/backend/dist/services/s3-backup-client.js';const require=createRequire('/app/backend/package.json'),{ListObjectVersionsCommand,DeleteObjectCommand}=require('@aws-sdk/client-s3');const bucket=process.env.MMAPP_TEST_S3_BUCKET;assert.equal(bucket,'mikrotik-test-s3');const client=createS3ClientFromConfig({endpoint:process.env.MMAPP_TEST_S3_ENDPOINT,region:process.env.MMAPP_TEST_S3_REGION,bucket,prefix:'mmapp-acceptance',accessKeyId:process.env.MMAPP_TEST_S3_ACCESS_KEY,secretAccessKey:process.env.MMAPP_TEST_S3_SECRET_KEY,forcePathStyle:false});const reports=JSON.parse(fs.readFileSync('/checks/owned-prefixes-public.json'));const plans=[],out={at:new Date().toISOString(),bucketConfigurationWrites:0,prefixes:[],passed:false};
async function list(prefix){let keyMarker,versionMarker,rows=[];for(let page=0;page<50;page++){const r=await client.send(new ListObjectVersionsCommand({Bucket:bucket,Prefix:prefix+'/',KeyMarker:keyMarker,VersionIdMarker:versionMarker}));rows.push(...(r.Versions??[]).map(v=>({...v,kind:'version'})),...(r.DeleteMarkers??[]).map(v=>({...v,kind:'delete-marker'})));if(!r.IsTruncated)return rows;assert(r.NextKeyMarker||r.NextVersionIdMarker);keyMarker=r.NextKeyMarker;versionMarker=r.NextVersionIdMarker;}throw Error('Version pagination limit')}
function isOwn(r,key){if(r.plannedKeys)return r.plannedKeys.includes(key);const relative=key.slice(r.prefix.length+1);return relative==='seed/probe-sync.rsc'||/^(s3-(?:positive|only)-[a-f0-9-]{36})\/\1_\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}\.rsc$/.test(relative)}
try{for(const r of reports){assert(/^mmapp-acceptance\/[a-f0-9-]{36}$/.test(r.prefix));const rows=await list(r.prefix);for(const row of rows)assert(row.Key?.startsWith(r.prefix+'/')&&isOwn(r,row.Key)&&typeof row.VersionId==='string'&&row.VersionId.length>0,'Unexpected version: do not delete');plans.push({r,rows});}for(const{r,rows}of plans){let removed=0;for(const row of rows){await client.send(new DeleteObjectCommand({Bucket:bucket,Key:row.Key,VersionId:row.VersionId}));removed++;}const after=await list(r.prefix);assert.equal(after.length,0);out.prefixes.push({prefix:r.prefix,versionsBefore:rows.filter(r=>r.kind==='version').length,deleteMarkersBefore:rows.filter(r=>r.kind==='delete-marker').length,removed,versionsAndMarkersRemaining:0});}out.passed=true}catch(e){out.failure={name:e.name,httpStatus:e.$metadata?.httpStatusCode??null}}finally{client.destroy();fs.writeFileSync('/checks/owned-versions-cleanup-public.json',JSON.stringify(out,null,2)+'\n');console.log(JSON.stringify(out));if(!out.passed)process.exitCode=1}
+5 -1
View File
@@ -1,6 +1,6 @@
# UX/UI: протокол внешней приёмки 12.5
Актуальное продолжение 2026-10-14: подготовлен S3 runner228,11 safety/dry-run checks passed, network requests0; реальная S3 приёмка ожидает endpoint/region/bucket/keys/path-style. UI226 —1366 unique accepted со scoped recovery2;364 исходных screenshots сохранены. Android227 human full-tunnel/reconnect accepted,16 own objects удалены, исходные поля13 коллекций сохранены, оба CHR доступны/пароли прежние. Backend ux-regression222/frontend ux-routing218; этап12.5 открыт.
Актуальное продолжение 2026-10-14: Selectel S3 принят228–229:2 real roundtrip objects и12 unique compiled backup API/owned SQL сценариев, включая upload denial/local fallback/sync/delete. Все собственные DB/roles/files/current objects очищены; enabled versioning обработан отдельно —8versions+8delete markers удалены из3 own prefixes, remaining0. Keys не в Git/reports, private env удалены. Native export — HTTP fixture, CHR contacts0; real native backup/restore/live UI остаются230. Backend ux-regression222/frontend ux-routing218; этап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).
@@ -2643,3 +2643,7 @@ Runtime backend ux-regression222/frontend ux-routing218. Latest backend25/UI52
## 12.5.228 — S3 runner подготовлен, реальная проверка ожидается (2026-10-14)
11 safety/dry-run checks passed, network0. Helper использует compiled S3 client, harmless own UUID prefix2 objects, HTTPS/explicit live guards и cleanup только attempted keys. Application jobs/native restore/versioned retention не приняты. Требуются endpoint/region/bucket/keys/path-style. [Guard протокол](ux-ui/acceptance/s3-guard-preflight-2026-10-14.json).
## 12.5.228–229 — Selectel S3/client/API/owned SQL приняты (2026-10-14)
2 roundtrip objects,12 unique API scenarios, synthetic native export/CHR contacts0. Own DB/roles/catalog/localfiles/currentobjects cleaned. Versioning Enabled:8versions+8markers removed,3 own prefixes remaining0; bucket configuration unchanged. Native backup/restore/live UI/смена storage origin/retention semantics остаются230. Private env удалены, secrets не в published files. [API](ux-ui/acceptance/s3-application229.json), [version cleanup](ux-ui/acceptance/s3-version-cleanup229.json).
+2 -2
View File
@@ -1,6 +1,6 @@
# План реализации UX/UI по пятичасовым окнам
Актуальное продолжение 2026-10-14: подготовлен S3 runner228,11 safety/dry-run checks passed, network requests0; реальная S3 приёмка ожидает endpoint/region/bucket/keys/path-style. UI226 —1366 unique accepted со scoped recovery2;364 исходных screenshots сохранены. Android227 human full-tunnel/reconnect accepted,16 own objects удалены, исходные поля13 коллекций сохранены, оба CHR доступны/пароли прежние. Backend ux-regression222/frontend ux-routing218; этап12.5 открыт.
Актуальное продолжение 2026-10-14: Selectel S3 принят228–229:2 real roundtrip objects и12 unique compiled backup API/owned SQL сценариев, включая upload denial/local fallback/sync/delete. Все собственные DB/roles/files/current objects очищены; enabled versioning обработан отдельно —8versions+8delete markers удалены из3 own prefixes, remaining0. Keys не в Git/reports, private env удалены. Native export — HTTP fixture, CHR contacts0; real native backup/restore/live UI остаются230. Backend ux-regression222/frontend ux-routing218; этап12.5 открыт.
Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-14. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат.
@@ -19,7 +19,7 @@
| REST и достоверность данных | Ошибки OSPF/BFD/BGP/GRE/WAN215–221 явные; latest API smoke222 прошёл | Причина единичных native401/500 неизвестна. Короткое независимое наблюдение возможно сейчас; оно не даст SLA |
| Клиентские ОС и внешняя сеть | CHR↔CHR/Linux, private IPv4/ULA и synthetic IPv6/IPFIX; Android full-tunnel/reconnect227 подтверждены пользователем | Windows/macOS/iOS VPN, расширенная Android приёмка, независимые WAN/multi-WAN и public IPv6; public IPv6 на текущем стенде отсутствует |
| Масштаб и длительная устойчивость | Контролируемые lock/dedupe/loss/recovery и короткие native проверки | Несколько backend/worker/writers, длительный rekey/REST/traffic, physical disconnect и maximum throughput |
| Внешние хранилища и jobs | Локальные API/errors/RBAC/owned SQL; реальные Portal1.3.1 и Telegram доставлены195–201 | Реальные S3/provider/container/Gitea/MaxMind jobs с соответствующими endpoints/аккаунтами; private/paid credentials не выдумывать |
| Внешние хранилища и jobs | Локальные API/errors/RBAC/owned SQL; реальные Portal1.3.1/Telegram195–201 и Selectel S3/client/API/ownSQL228–229 | Native S3 backup/restore/live UI/retention; provider/container/Gitea/MaxMind jobs с соответствующими endpoints/аккаунтами; private/paid credentials не выдумывать |
| Расширенные сетевые конфигурации | GRE/WG/VXLAN/IPsec/OSPF/BFD/BGP сценарии и точные границы записаны | from-pool/EUI-64/multiple-address import/export, outer IPv6, произвольная PKI/OID/policy/path-length и длительные переходы; BFD-only gate гарантией не стал |
| Общий UX, доступность и производительность | Полная UI192 исторически; latest backend25/UI52, scoped contrast/live218 и keyboard206 | Финальная свежая UI-регрессия перед выпуском, профильный performance и ручная accessibility/screen-reader приёмка; scoped axe не полный WCAG |
| Границы выпуска | План/доказательства/датированные блоки сохранены; коммиты локальные | Определить требуемые конфигурации и принять результат в этих границах; невыполненные варианты остаются явными ограничениями |
+19 -6
View File
@@ -1,6 +1,6 @@
# UX/UI: журнал реализации
Актуальное продолжение 2026-10-14: подготовлен S3 runner228,11 safety/dry-run checks passed, network requests0; реальная S3 приёмка ожидает endpoint/region/bucket/keys/path-style. UI226 —1366 unique accepted со scoped recovery2;364 исходных screenshots сохранены. Android227 human full-tunnel/reconnect accepted,16 own objects удалены, исходные поля13 коллекций сохранены, оба CHR доступны/пароли прежние. Backend ux-regression222/frontend ux-routing218; этап12.5 открыт.
Актуальное продолжение 2026-10-14: Selectel S3 принят228–229:2 real roundtrip objects и12 unique compiled backup API/owned SQL сценариев, включая upload denial/local fallback/sync/delete. Все собственные DB/roles/files/current objects очищены; enabled versioning обработан отдельно —8versions+8delete markers удалены из3 own prefixes, remaining0. Keys не в Git/reports, private env удалены. Native export — HTTP fixture, CHR contacts0; real native backup/restore/live UI остаются230. Backend ux-regression222/frontend ux-routing218; этап12.5 открыт.
Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md).
@@ -36,15 +36,28 @@
| 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–227 завершены в записанных scopes, этап открыт. Runtime backend ux-regression222/frontend ux-routing218; backend25/UI52 и дополнительные SQL/UDP222 прошли. |
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка12.3.83 в границах журнала. Внешняя12.5: блоки1–229 завершены в записанных scopes, этап открыт. Runtime backend ux-regression222/frontend ux-routing218; backend25/UI52 и дополнительные SQL/UDP222 прошли. |
## 12.5.228 — подготовка безопасной реальной S3-проверки (2026-10-14)
## 12.5.229 — API резервных копий с real S3, owned SQL и очистка версий (2026-10-14)
Пользователь предложил тестовый S3; endpoint/region/bucket/access/secret и path-style ещё не получены. Подготовлен backend/scripts/acceptance/s3-roundtrip.mjs для запуска в compiled test backend image. Он вызывает существующий production S3 client, использует отдельный mmapp-acceptance/<UUID>/, проверяет пустоту prefix до записи, сохраняет planned/attempted keys до каждого PUT и удаляет только собственные attempted keys, включая неопределённый результат PUT. Тела — безвредные synthetic .rsc comments; native config/backup/credential exports не загружаются.
12 unique сценариев compiled createBackupsRoutes/backup-service + Selectel S3 + отдельная PostgreSQL mm_accept_166_<timestamp>. Role NOSUPERUSER/NOCREATEDB/NOCREATEROLE/NOBYPASSRLS подтверждена до bootstrap; main DSN/данные не копировались. RouterOS export заменён только HTTP fixture на container loopback, возвращающим harmless .rsc comment: actual fixture exports3/run, CHR contacts0, native restore0. Приложение/production images не менялись.
Запуск требует HTTPS без userinfo/query/hash, явного owned-scope=yes, region/path-style и live RUN=confirmed. Нет TLS bypass или передачи credentials в вывод. Предусмотрены PUT→GET+SHA256, exact listing2 keys, Delete и readback текущих объектов; report содержит acknowledgement/cleanup failures отдельно. Version history/delete markers/retention, application backup jobs/SQL/UI/native restore в этот helper НЕ входят. Реальную S3-приёмку по его dry-run не объявлять.
Приняты: storage PUT без secret disclosure; real HeadBucket test receipt; async create both; повтор requestId без второй выгрузки; download точных bytes; invalid credential test400 с сохранением settings; download fallback local при отказе S3; upload failure сохраняет local backup/uploadError; s3-only удаляет local copy и скачивается remote; sync/import/idempotent repeat; catalog/imported download; DELETE204/current-object cleanup/missing404. Сохраняемый в репо helper дополнен explicit owned/live/vHosted guards; свежий повтор тех же12 passed, не24 unique. UI/RBAC/native export+restore этим не объявляются повторёнными.
Обе own SQL DB/roles/containers удалены и отсутствие подтверждено trap. Catalog rows0/local files0/current own S3 objects0. GetBucketVersioning200 выявил Enabled. Для трёх наших UUID prefixes выполнена строгая проверка allowed keys и DeleteObject с точными VersionId:8versions+8delete markers удалены, version readback0 всех трёх. Чужие prefixes/versions не затрагивались, bucket settings не менялись. Credentials отсутствуют в published scripts/reports, private env удалены после cleanup.
[API12](ux-ui/acceptance/s3-application229.json), [initial12](ux-ui/acceptance/s3-application229-first.json), [versioning](ux-ui/acceptance/s3-versioning229.json), [cleanup16versions/markers](ux-ui/acceptance/s3-version-cleanup229.json). Reproducible helpers: backend/scripts/acceptance/s3-backups-api.mjs, run-s3-backups-api.sh, s3-owned-versions-cleanup.mjs. Wrapper на Debian ожидает private env /opt/mmapp-test/artifacts/s3-228-private.env и mount s3-229; version helper получает curated owned-prefixes-public.json из наших reports, не произвольный namespace.
Следующий230 — real S3 с native backup/ограниченным restore и live UI в согласованном scope; отдельно проверить смену bucket/endpoint при имеющемся каталоге и retention/version semantics. Остальные группы матрицы остаются открытыми. Runtime backend ux-regression222/frontend ux-routing218. Этап12.5 не завершён.
## 12.5.228 — реальные операции Selectel S3 (2026-10-14)
После11 safety/dry-run guards пользователь предоставил тестовый Selectel S3: HTTPS s3.ru-1.storage.selcloud.ru:443, region ru-1, bucket mikrotik-test-s3, virtual-hosted (forcePathStyle=false). Завершающий слеш в Secret key подтверждён как форматирование и не использован. Credentials сохранялись только в ignored/private env, не в коде/Git/публичных отчётах; временные env после всей проверки удалены локально и на Debian.
Runner вызвал существующий compiled production S3 client в отдельном read-only/cap-drop container. В собственном mmapp-acceptance/<UUID>/ выполнены empty-prefix preflight,2 PUT→GET+SHA256 equality и exact listing2. Два Delete подтверждены; текущих объектов0. Тела — harmless synthetic .rsc comments, device configuration exports0. TLS verification сохранена. [Реальный roundtrip](ux-ui/acceptance/s3-real228.json), [guards11](ux-ui/acceptance/s3-guard-preflight-2026-10-14.json).
В229 обнаружено enabled versioning: ordinary Delete создаёт delete marker. После API проверок удалены версии/markers только трёх собственных UUID prefixes; initial228 prefix2versions+2markers очищен отдельно. Bucket configuration writes0. Version history/retention поведения самого приложения этим не принимаются: очистка acceptance fixture не меняет production deletion semantics. Далее229 — compiled backup API/SQL с real S3 и synthetic native export.
11 локальных guard/dry-run cases passed: HTTP, URL credentials/query/invalid URL, отсутствующие scope/bucket/credentials/region, invalid path-style и отсутствующее live confirmation отклонены; fake secret/access markers отсутствуют в stdout/stderr. Dry-run0 network requests. Syntax/diff check passed. [Guard протокол](ux-ui/acceptance/s3-guard-preflight-2026-10-14.json). Подготовка runner закончена; реальное выполнение228 и расширенная application S3 приёмка ожидают параметров пользователя. Backend ux-regression222/frontend ux-routing218 сохранены. Этап12.5 открыт.
## 12.5.226 — свежая общая UI-регрессия и защита пользовательских снимков (2026-10-14)
+8 -4
View File
@@ -1,8 +1,8 @@
# Оставшаяся внешняя приёмка — 2026-10-14
Актуальное продолжение 2026-10-14: подготовлен S3 runner228,11 safety/dry-run checks passed, network requests0; реальная S3 приёмка ожидает endpoint/region/bucket/keys/path-style. UI226 —1366 unique accepted со scoped recovery2;364 исходных screenshots сохранены. Android227 human full-tunnel/reconnect accepted,16 own objects удалены, исходные поля13 коллекций сохранены, оба CHR доступны/пароли прежние. Backend ux-regression222/frontend ux-routing218; этап12.5 открыт.
Актуальное продолжение 2026-10-14: Selectel S3 принят228–229:2 real roundtrip objects и12 unique compiled backup API/owned SQL сценариев, включая upload denial/local fallback/sync/delete. Все собственные DB/roles/files/current objects очищены; enabled versioning обработан отдельно —8versions+8delete markers удалены из3 own prefixes, remaining0. Keys не в Git/reports, private env удалены. Native export — HTTP fixture, CHR contacts0; real native backup/restore/live UI остаются230. Backend ux-regression222/frontend ux-routing218; этап12.5 открыт.
Это актуальная матрица после 12.5.227. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
Это актуальная матрица после 12.5.229. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-regression222`, frontend `ux-routing218`; production lab с195 использует AUTH_REQUIRED=true и Portal1.3.1. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись.
@@ -15,7 +15,7 @@
| REST и достоверность данных | Ошибки OSPF/BFD/BGP/GRE/WAN215–221 явные; latest API smoke222 прошёл | Причина единичных native401/500 неизвестна. Короткое независимое наблюдение возможно сейчас; оно не даст SLA |
| Клиентские ОС и внешняя сеть | CHR↔CHR/Linux, private IPv4/ULA и synthetic IPv6/IPFIX; Android full-tunnel/reconnect227 подтверждены пользователем | Windows/macOS/iOS VPN, расширенная Android приёмка, независимые WAN/multi-WAN и public IPv6; public IPv6 на текущем стенде отсутствует |
| Масштаб и длительная устойчивость | Контролируемые lock/dedupe/loss/recovery и короткие native проверки | Несколько backend/worker/writers, длительный rekey/REST/traffic, physical disconnect и maximum throughput |
| Внешние хранилища и jobs | Локальные API/errors/RBAC/owned SQL; реальные Portal1.3.1 и Telegram доставлены195–201 | Реальные S3/provider/container/Gitea/MaxMind jobs с соответствующими endpoints/аккаунтами; private/paid credentials не выдумывать |
| Внешние хранилища и jobs | Локальные API/errors/RBAC/owned SQL; реальные Portal1.3.1/Telegram195–201 и Selectel S3/client/API/ownSQL228–229 | Native S3 backup/restore/live UI/retention; provider/container/Gitea/MaxMind jobs с соответствующими endpoints/аккаунтами; private/paid credentials не выдумывать |
| Расширенные сетевые конфигурации | GRE/WG/VXLAN/IPsec/OSPF/BFD/BGP сценарии и точные границы записаны | from-pool/EUI-64/multiple-address import/export, outer IPv6, произвольная PKI/OID/policy/path-length и длительные переходы; BFD-only gate гарантией не стал |
| Общий UX, доступность и производительность | Полная UI192 исторически; latest backend25/UI52, scoped contrast/live218 и keyboard206 | Свежая UI226 принята в записанных границах; профильный performance и ручная accessibility/screen-reader приёмка; scoped axe не полный WCAG |
| Границы выпуска | План/доказательства/датированные блоки сохранены; коммиты локальные | Определить требуемые конфигурации и принять результат в этих границах; невыполненные варианты остаются явными ограничениями |
@@ -35,7 +35,7 @@ Portal offline JWT действует доexp после logout/role change; э
| BGP | Established83, exact /32 exchange/API/UI/administrative withdrawal92, BFD primary GRE loss/alternate/recovery93; basic native IPv6 eBGP/AFI/NLRI/Ping/withdrawal/API/UI155; native IPv6 BGP/BFD scoped withdrawal/recovery160; private4-byte ASN/as4/API/UI162 | Hold timer/TCP loss безBFD принят104; packet loss в переходе, разные внешние peers/WAN policy, basic BFDv6 принят160; IPv6 outer/public WAN/scale. Два логических пути используют тот же public WAN; physical redundancy не принята |
| Recursive routes | Production PUT/change/own history restore88, disabled guards, partial/unknown/applied outcomes, live формы/контекст | Автоматический rollback/retry, durable receipts и несколько экземпляров отсутствуют; PUT не является idempotent noop. Полная произвольная legacy catalog миграция не принята |
| Diagnostics/terminal | Allowlist/native reads, Ping/trace/route/MTU/DNS, actual avg-rtt, disabled guards, JWT/live PC/mobile89; real default memory tail/log limits/API/UI133 | DNS использует backend resolver; MTU — ограниченная оценка, не гарантированный максимум. Stop ожидания не доказывает остановку RouterOS команды; arbitrary CLI и physical disconnect не приняты |
| Portal/интеграции | Реальный Portal1.3.1/issuer/login/reader privacy195–196, expiry/reissue/role change/logout boundaries198, context/return_to199; actual Telegram delivery201 | Offline access JWT не отзывается logout/ролью мгновенно доexp; public IPv6 WAN, licensed MaxMind/S3/container/certificate/Gitea jobs требуют отдельных endpoints/аккаунтов. Клиентские ОС/scale отдельно |
| Portal/интеграции | Реальный Portal1.3.1/issuer/login/reader privacy195–196, expiry/reissue/role change/logout boundaries198, context/return_to199; actual Telegram delivery201 | Offline access JWT не отзывается logout/ролью мгновенно доexp; public IPv6 WAN, Selectel S3 client/API228–229 принят; native S3 backup/restore/live UI/retention открыты. Licensed MaxMind/container/certificate/Gitea jobs требуют отдельных endpoints/аккаунтов. Клиентские ОС/scale отдельно |
| Общая приёмка | Локальная12.3.83; UI1244/context221/compiled984 parity139; профильные build/E2E/live проверки последующих блоков | Traffic initial live mock-id gate passed141; IPv6 special ranges/cache passed142; unknown RIPE coordinates passed143; IPv6 synthetic UDP/worker/SQL passed144; native CHR ULA/IPFIX/API/UI passed145; ICMPv6/IPv6 endpoint labels passed146; HTTP DTO validation passed147; scoped flow catalog/serverId guards passed148; strict monthly calendar passed149; Sessions labels passed150; application GRE static IPv6 lifecycle151 passed; WireGuard static IPv6 lifecycle152 и disabled SQL/API gates passed; OSPF/BFD boundary/warning153 passed; OSPFv3 routes154 accepted in recorded bounds; BGP IPv6 basic155 passed; bigint ID/SQL guards156 passed; общая UI1260/context221/backend24 regression157 passed; IPv6 BFD correlation158 profile/deployed passed; native OSPFv3/BFDv6 boundary/recovery159 passed; BGP IPv6/BFD withdrawal/recovery160 passed; selected BGP/alert unknown guards161 replay/profiles/deployed passed (SQL stability skipped); private4-byte ASN162 native/API/UI passed; duplicate BGP alert keys163 replay/profiles/deployed passed; nullable selected BGP164 replay/profiles/API/UI passed; collector165 parallel/current guards/read-only PG passed; positive partial/error SQL166/full24 with PG passed (optional network SQL not repeated); GRE/BGP freshness167/SQL8 passed; other source audit168 выявил identity/disabled/freshness gaps; локальный server/traffic identity guard169 passed без SQL/deploy, source/per-probe/freshness170/ownSQL16/compiled1008/deployed passed; source diagnostics171/42E2E/ownSQL metadata/live2 passed; monitoring history172/ownSQL26/compiled1020/deployed passed; Ping known/error173/ownHTTP-SQL8/85uniqueE2E/native4/4/deployed passed; traffic confidence174 baseline reproduced/local guard passed; owned SQL9/9358ms/deploy175/1024compiled/live smoke passed; legacy history/provenance open; compound RTT176 local/native replay2 passed, SQL10/deploy177/1024compiled/live smoke passed; history178/legacy provenance/current TTL/manual truth/SQL9+14/35unique E2E/native GET/deployed passed; live179 complete/disable-config/bounded fallback/ownSQL10/nativeSSE2/liveUI2 passed; volume180 exact/unit marker/ownSQL6/18E2E/API/liveUI/deployed passed; binding status181 native/ownSQL9/8E2E/deployed passed; peer atomicity182 ownSQL3/native/API/UI passed; peer failure warning183 ownSQL2/4E2E/deployed passed; inactive history184 ownSQL2/deployed passed; gap185/identity186/restore187/wave188/sourceguard189/confirmed peak190 passed; aggregate peaks191, request dedupe193, cross-process lock194/200, full regression192 (1286 unique E2E и25-й профиль privacy196 scoped), Portal195–199/Telegram201 passed; последующие изменения проверять отдельно; scoped axe не означает полную WCAG сертификацию |
Доказательства и точные ограничения: [полная история этапа12](ux-ui-stage-12.md), [внешний протокол](ux-ui-external-acceptance.md), [BGP93](ux-ui/acceptance/bgp-failover-2026-10-10.json), [паспорт стенда](ux-ui-test-server.md). Невыполненные пункты не являются все блокерами данного лабораторного сценария: для выпуска необходимо явно определить требуемые конфигурации и принять их либо согласовать ограничение.
@@ -1573,3 +1573,7 @@ Backend25/UI52, disabled33/months5 и оба IPv6 UDP/SQL worker режима п
## 12.5.228 — S3 runner подготовлен, реальная проверка ожидается (2026-10-14)
11 safety/dry-run checks passed, network0. Helper использует compiled S3 client, harmless own UUID prefix2 objects, HTTPS/explicit live guards и cleanup только attempted keys. Application jobs/native restore/versioned retention не приняты. Требуются endpoint/region/bucket/keys/path-style. [Guard протокол](ux-ui/acceptance/s3-guard-preflight-2026-10-14.json).
## 12.5.228–229 — Selectel S3/client/API/owned SQL приняты (2026-10-14)
2 roundtrip objects,12 unique API scenarios, synthetic native export/CHR contacts0. Own DB/roles/catalog/localfiles/currentobjects cleaned. Versioning Enabled:8versions+8markers removed,3 own prefixes remaining0; bucket configuration unchanged. Native backup/restore/live UI/смена storage origin/retention semantics остаются230. Private env удалены, secrets не в published files. [API](ux-ui/acceptance/s3-application229.json), [version cleanup](ux-ui/acceptance/s3-version-cleanup229.json).
+17 -5
View File
@@ -1,6 +1,6 @@
# Этап 12: итоговая приёмка UX/UI
Актуальное продолжение 2026-10-14: подготовлен S3 runner228,11 safety/dry-run checks passed, network requests0; реальная S3 приёмка ожидает endpoint/region/bucket/keys/path-style. UI226 —1366 unique accepted со scoped recovery2;364 исходных screenshots сохранены. Android227 human full-tunnel/reconnect accepted,16 own objects удалены, исходные поля13 коллекций сохранены, оба CHR доступны/пароли прежние. Backend ux-regression222/frontend ux-routing218; этап12.5 открыт.
Актуальное продолжение 2026-10-14: Selectel S3 принят228–229:2 real roundtrip objects и12 unique compiled backup API/owned SQL сценариев, включая upload denial/local fallback/sync/delete. Все собственные DB/roles/files/current objects очищены; enabled versioning обработан отдельно —8versions+8delete markers удалены из3 own prefixes, remaining0. Keys не в Git/reports, private env удалены. Native export — HTTP fixture, CHR contacts0; real native backup/restore/live UI остаются230. Backend ux-regression222/frontend ux-routing218; этап12.5 открыт.
План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push.
@@ -4065,11 +4065,23 @@ VXLAN mobile layout упал UNKNOWN при записи tracked docs/ux-ui/scre
Runtime unchanged: backend ux-regression222/frontend ux-routing218, новые production build/deploy не требовались для test artifact path. Android227 принят по human confirmation/cleanup16 в соседней записи. Далее228 — тестовый S3 с выделенным bucket/prefix после получения endpoint/region/keys. Остальные группы матрицы и причина native intermittent401/500 остаются открытыми; этап12.5 не завершён.
## 12.5.228 — подготовка безопасной реальной S3-проверки (2026-10-14)
## 12.5.228 — реальные операции Selectel S3 (2026-10-14)
Пользователь предложил тестовый S3; endpoint/region/bucket/access/secret и path-style ещё не получены. Подготовлен backend/scripts/acceptance/s3-roundtrip.mjs для запуска в compiled test backend image. Он вызывает существующий production S3 client, использует отдельный mmapp-acceptance/<UUID>/, проверяет пустоту prefix до записи, сохраняет planned/attempted keys до каждого PUT и удаляет только собственные attempted keys, включая неопределённый результат PUT. Тела — безвредные synthetic .rsc comments; native config/backup/credential exports не загружаются.
После11 safety/dry-run guards пользователь предоставил тестовый Selectel S3: HTTPS s3.ru-1.storage.selcloud.ru:443, region ru-1, bucket mikrotik-test-s3, virtual-hosted (forcePathStyle=false). Завершающий слеш в Secret key подтверждён как форматирование и не использован. Credentials сохранялись только в ignored/private env, не в коде/Git/публичных отчётах; временные env после всей проверки удалены локально и на Debian.
Запуск требует HTTPS без userinfo/query/hash, явного owned-scope=yes, region/path-style и live RUN=confirmed. Нет TLS bypass или передачи credentials в вывод. Предусмотрены PUT→GET+SHA256, exact listing2 keys, Delete и readback текущих объектов; report содержит acknowledgement/cleanup failures отдельно. Version history/delete markers/retention, application backup jobs/SQL/UI/native restore в этот helper НЕ входят. Реальную S3-приёмку по его dry-run не объявлять.
Runner вызвал существующий compiled production S3 client в отдельном read-only/cap-drop container. В собственном mmapp-acceptance/<UUID>/ выполнены empty-prefix preflight,2 PUT→GET+SHA256 equality и exact listing2. Два Delete подтверждены; текущих объектов0. Тела — harmless synthetic .rsc comments, device configuration exports0. TLS verification сохранена. [Реальный roundtrip](ux-ui/acceptance/s3-real228.json), [guards11](ux-ui/acceptance/s3-guard-preflight-2026-10-14.json).
11 локальных guard/dry-run cases passed: HTTP, URL credentials/query/invalid URL, отсутствующие scope/bucket/credentials/region, invalid path-style и отсутствующее live confirmation отклонены; fake secret/access markers отсутствуют в stdout/stderr. Dry-run0 network requests. Syntax/diff check passed. [Guard протокол](ux-ui/acceptance/s3-guard-preflight-2026-10-14.json). Подготовка runner закончена; реальное выполнение228 и расширенная application S3 приёмка ожидают параметров пользователя. Backend ux-regression222/frontend ux-routing218 сохранены. Этап12.5 открыт.
В229 обнаружено enabled versioning: ordinary Delete создаёт delete marker. После API проверок удалены версии/markers только трёх собственных UUID prefixes; initial228 prefix2versions+2markers очищен отдельно. Bucket configuration writes0. Version history/retention поведения самого приложения этим не принимаются: очистка acceptance fixture не меняет production deletion semantics. Далее229 — compiled backup API/SQL с real S3 и synthetic native export.
## 12.5.229 — API резервных копий с real S3, owned SQL и очистка версий (2026-10-14)
12 unique сценариев compiled createBackupsRoutes/backup-service + Selectel S3 + отдельная PostgreSQL mm_accept_166_<timestamp>. Role NOSUPERUSER/NOCREATEDB/NOCREATEROLE/NOBYPASSRLS подтверждена до bootstrap; main DSN/данные не копировались. RouterOS export заменён только HTTP fixture на container loopback, возвращающим harmless .rsc comment: actual fixture exports3/run, CHR contacts0, native restore0. Приложение/production images не менялись.
Приняты: storage PUT без secret disclosure; real HeadBucket test receipt; async create both; повтор requestId без второй выгрузки; download точных bytes; invalid credential test400 с сохранением settings; download fallback local при отказе S3; upload failure сохраняет local backup/uploadError; s3-only удаляет local copy и скачивается remote; sync/import/idempotent repeat; catalog/imported download; DELETE204/current-object cleanup/missing404. Сохраняемый в репо helper дополнен explicit owned/live/vHosted guards; свежий повтор тех же12 passed, не24 unique. UI/RBAC/native export+restore этим не объявляются повторёнными.
Обе own SQL DB/roles/containers удалены и отсутствие подтверждено trap. Catalog rows0/local files0/current own S3 objects0. GetBucketVersioning200 выявил Enabled. Для трёх наших UUID prefixes выполнена строгая проверка allowed keys и DeleteObject с точными VersionId:8versions+8delete markers удалены, version readback0 всех трёх. Чужие prefixes/versions не затрагивались, bucket settings не менялись. Credentials отсутствуют в published scripts/reports, private env удалены после cleanup.
[API12](ux-ui/acceptance/s3-application229.json), [initial12](ux-ui/acceptance/s3-application229-first.json), [versioning](ux-ui/acceptance/s3-versioning229.json), [cleanup16versions/markers](ux-ui/acceptance/s3-version-cleanup229.json). Reproducible helpers: backend/scripts/acceptance/s3-backups-api.mjs, run-s3-backups-api.sh, s3-owned-versions-cleanup.mjs. Wrapper на Debian ожидает private env /opt/mmapp-test/artifacts/s3-228-private.env и mount s3-229; version helper получает curated owned-prefixes-public.json из наших reports, не произвольный namespace.
Следующий230 — real S3 с native backup/ограниченным restore и live UI в согласованном scope; отдельно проверить смену bucket/endpoint при имеющемся каталоге и retention/version semantics. Остальные группы матрицы остаются открытыми. Runtime backend ux-regression222/frontend ux-routing218. Этап12.5 не завершён.
+5 -1
View File
@@ -1,6 +1,6 @@
# Тестовый сервер UX/UI — 2026-10-10
Актуальное продолжение 2026-10-14: подготовлен S3 runner228,11 safety/dry-run checks passed, network requests0; реальная S3 приёмка ожидает endpoint/region/bucket/keys/path-style. UI226 —1366 unique accepted со scoped recovery2;364 исходных screenshots сохранены. Android227 human full-tunnel/reconnect accepted,16 own objects удалены, исходные поля13 коллекций сохранены, оба CHR доступны/пароли прежние. Backend ux-regression222/frontend ux-routing218; этап12.5 открыт.
Актуальное продолжение 2026-10-14: Selectel S3 принят228–229:2 real roundtrip objects и12 unique compiled backup API/owned SQL сценариев, включая upload denial/local fallback/sync/delete. Все собственные DB/roles/files/current objects очищены; enabled versioning обработан отдельно —8versions+8delete markers удалены из3 own prefixes, remaining0. Keys не в Git/reports, private env удалены. Native export — HTTP fixture, CHR contacts0; real native backup/restore/live UI остаются230. Backend ux-regression222/frontend ux-routing218; этап12.5 открыт.
| Параметр | Проверенное состояние |
| --- | --- |
@@ -2374,3 +2374,7 @@ Runtime backend ux-regression222/frontend ux-routing218. Latest backend25/UI52
## 12.5.228 — S3 runner подготовлен, реальная проверка ожидается (2026-10-14)
11 safety/dry-run checks passed, network0. Helper использует compiled S3 client, harmless own UUID prefix2 objects, HTTPS/explicit live guards и cleanup только attempted keys. Application jobs/native restore/versioned retention не приняты. Требуются endpoint/region/bucket/keys/path-style. [Guard протокол](ux-ui/acceptance/s3-guard-preflight-2026-10-14.json).
## 12.5.228–229 — Selectel S3/client/API/owned SQL приняты (2026-10-14)
2 roundtrip objects,12 unique API scenarios, synthetic native export/CHR contacts0. Own DB/roles/catalog/localfiles/currentobjects cleaned. Versioning Enabled:8versions+8markers removed,3 own prefixes remaining0; bucket configuration unchanged. Native backup/restore/live UI/смена storage origin/retention semantics остаются230. Private env удалены, secrets не в published files. [API](ux-ui/acceptance/s3-application229.json), [version cleanup](ux-ui/acceptance/s3-version-cleanup229.json).
@@ -0,0 +1,72 @@
{
"date": "2026-10-14",
"block": "12.5.229",
"scope": "Compiled backup API + owned PostgreSQL + real Selectel S3; synthetic RouterOS HTTP export fixture, no CHR contacts",
"prefix": "mmapp-acceptance/33808c8a-93ae-45a8-ac1a-eb7e9430e4d9",
"checks": [
{
"name": "empty-prefix-preflight",
"passed": true
},
{
"name": "settings-save-without-secret-disclosure",
"passed": true
},
{
"name": "real-headbucket-test-receipt",
"passed": true
},
{
"name": "async-create-both-and-request-id-no-repeat",
"passed": true
},
{
"name": "download-exact-bytes",
"passed": true
},
{
"name": "denied-credentials-test-preserves-settings",
"passed": true
},
{
"name": "download-local-fallback-during-s3-denial",
"passed": true
},
{
"name": "upload-denial-keeps-local-backup-and-error",
"passed": true
},
{
"name": "s3-only-removes-local-copy-and-downloads-remote",
"passed": true
},
{
"name": "sync-import-and-idempotent-repeat",
"passed": true
},
{
"name": "catalog-and-imported-object-download",
"passed": true
},
{
"name": "delete-catalog-and-current-remote-objects",
"passed": true
}
],
"passed": true,
"cleanup": {
"currentObjectsRemaining": 0,
"catalogRowsRemaining": 0,
"localFilesRemaining": 0
},
"nativeChrContacts": 0,
"nativeRestoreCalls": 0,
"fixtureExportCalls": 3,
"completedAt": "2026-10-14T02:46:27.754Z",
"bounds": [
"Native export is HTTP fixture; no real device backup/restore or UI repeated",
"RBAC not repeated in this standalone injected API",
"S3 current-object deletion only; retained versions/retention not inspected"
],
"ownedDatabaseAndRoleRemoved": true
}
@@ -0,0 +1,89 @@
{
"date": "2026-10-14",
"block": "12.5.229",
"scope": "Compiled backup API + owned PostgreSQL + real Selectel S3; synthetic RouterOS HTTP export fixture, no CHR contacts",
"prefix": "mmapp-acceptance/1c4ed929-296f-458b-ba57-9ffeae1f44e4",
"checks": [
{
"name": "empty-prefix-preflight",
"passed": true
},
{
"name": "settings-save-without-secret-disclosure",
"passed": true
},
{
"name": "real-headbucket-test-receipt",
"passed": true
},
{
"name": "async-create-both-and-request-id-no-repeat",
"passed": true
},
{
"name": "download-exact-bytes",
"passed": true
},
{
"name": "denied-credentials-test-preserves-settings",
"passed": true
},
{
"name": "download-local-fallback-during-s3-denial",
"passed": true
},
{
"name": "upload-denial-keeps-local-backup-and-error",
"passed": true
},
{
"name": "s3-only-removes-local-copy-and-downloads-remote",
"passed": true
},
{
"name": "sync-import-and-idempotent-repeat",
"passed": true
},
{
"name": "catalog-and-imported-object-download",
"passed": true
},
{
"name": "delete-catalog-and-current-remote-objects",
"passed": true
}
],
"passed": true,
"cleanup": {
"currentObjectsRemaining": 0,
"catalogRowsRemaining": 0,
"localFilesRemaining": 0
},
"nativeChrContacts": 0,
"nativeRestoreCalls": 0,
"fixtureExportCalls": 3,
"completedAt": "2026-10-14T02:51:42.670Z",
"bounds": [
"Native export is HTTP fixture; no real device backup/restore or UI repeated",
"RBAC not repeated in this standalone injected API",
"S3 current-object deletion only; retained versions/retention not inspected"
],
"ownedDatabaseAndRoleRemoved": true,
"provider": {
"endpoint": "https://s3.ru-1.storage.selcloud.ru",
"region": "ru-1",
"bucket": "mikrotik-test-s3",
"addressing": "virtual-hosted",
"tlsVerification": true
},
"uniqueApiScenarios": 12,
"firstCoreAttemptPassed": true,
"repeatReason": "Final published helper adds explicit ownership/live/path-style guards; same12 cases repeated, not24 unique",
"versionedCleanup": {
"versioning": "Enabled",
"versionsRemoved": 8,
"deleteMarkersRemoved": 8,
"remaining": 0,
"bucketConfigurationWrites": 0
}
}
+62
View File
@@ -0,0 +1,62 @@
{
"startedAt": "2026-10-14T02:41:23.800Z",
"scope": "Compiled application S3 client; harmless synthetic bodies; dedicated UUID prefix",
"prefix": "mmapp-acceptance/238b046e-dd1a-4a7d-8bec-62b67e7b411c",
"passed": true,
"checks": [
{
"operation": "empty-prefix-preflight",
"passed": true
},
{
"operation": "put-get-content",
"key": "mmapp-acceptance/238b046e-dd1a-4a7d-8bec-62b67e7b411c/probe-1.rsc",
"bytes": 112,
"passed": true
},
{
"operation": "put-get-content",
"key": "mmapp-acceptance/238b046e-dd1a-4a7d-8bec-62b67e7b411c/probe-2.rsc",
"bytes": 112,
"passed": true
},
{
"operation": "list-exact-owned-keys",
"passed": true
}
],
"cleanup": [
{
"key": "mmapp-acceptance/238b046e-dd1a-4a7d-8bec-62b67e7b411c/probe-1.rsc",
"acknowledged": true
},
{
"key": "mmapp-acceptance/238b046e-dd1a-4a7d-8bec-62b67e7b411c/probe-2.rsc",
"acknowledged": true
}
],
"deviceConfigurationUploads": 0,
"bounds": [
"No application backup jobs, SQL catalog, native restore or UI acceptance",
"Cleanup covers current objects; retained versions/delete markers/retention policies are not inspected"
],
"plannedKeys": [
"mmapp-acceptance/238b046e-dd1a-4a7d-8bec-62b67e7b411c/probe-1.rsc",
"mmapp-acceptance/238b046e-dd1a-4a7d-8bec-62b67e7b411c/probe-2.rsc"
],
"attemptedKeys": [
"mmapp-acceptance/238b046e-dd1a-4a7d-8bec-62b67e7b411c/probe-1.rsc",
"mmapp-acceptance/238b046e-dd1a-4a7d-8bec-62b67e7b411c/probe-2.rsc"
],
"currentOwnedObjectsRemaining": 0,
"completedAt": "2026-10-14T02:41:24.497Z",
"date": "2026-10-14",
"block": "12.5.228",
"provider": {
"endpoint": "https://s3.ru-1.storage.selcloud.ru",
"region": "ru-1",
"bucket": "mikrotik-test-s3",
"addressing": "virtual-hosted",
"tlsVerification": true
}
}
@@ -0,0 +1,28 @@
{
"at": "2026-10-14T02:54:41.959Z",
"bucketConfigurationWrites": 0,
"prefixes": [
{
"prefix": "mmapp-acceptance/238b046e-dd1a-4a7d-8bec-62b67e7b411c",
"versionsBefore": 2,
"deleteMarkersBefore": 2,
"removed": 4,
"versionsAndMarkersRemaining": 0
},
{
"prefix": "mmapp-acceptance/33808c8a-93ae-45a8-ac1a-eb7e9430e4d9",
"versionsBefore": 3,
"deleteMarkersBefore": 3,
"removed": 6,
"versionsAndMarkersRemaining": 0
},
{
"prefix": "mmapp-acceptance/1c4ed929-296f-458b-ba57-9ffeae1f44e4",
"versionsBefore": 3,
"deleteMarkersBefore": 3,
"removed": 6,
"versionsAndMarkersRemaining": 0
}
],
"passed": true
}
@@ -0,0 +1,6 @@
{
"at": "2026-10-14T02:53:27.486Z",
"httpStatus": 200,
"status": "Enabled",
"bucketConfigurationWrites": 0
}