From 026a358ca698017e79c2264914afc53ff65ed634 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Wed, 14 Oct 2026 14:46:25 +0700 Subject: [PATCH] =?UTF-8?q?chore(test):=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D0=B8=D1=82=D1=8C=20=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BF=D0=BE=D1=81=D0=BB=D0=B5=D0=B4=D0=BD=D0=B8?= =?UTF-8?q?=D1=85=20=D0=BA=D0=BE=D0=BF=D0=B8=D0=B9=20=D0=B8=20=D1=87=D0=B0?= =?UTF-8?q?=D1=81=D1=82=D0=B8=D1=87=D0=BD=D0=BE=D0=B5=20=D1=83=D0=B4=D0=B0?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acceptance/run-s3-backup-retention.sh | 32 +++++++ .../acceptance/s3-backup-retention.mjs | 84 +++++++++++++++++++ docs/ux-ui-external-acceptance.md | 14 +++- docs/ux-ui-plan.md | 14 +++- docs/ux-ui-progress.md | 14 +++- docs/ux-ui-remaining-acceptance.md | 20 ++++- docs/ux-ui-stage-12.md | 14 +++- docs/ux-ui-test-server.md | 14 +++- docs/ux-ui/acceptance/s3-retention240.json | 79 +++++++++++++++++ 9 files changed, 276 insertions(+), 9 deletions(-) create mode 100644 backend/scripts/acceptance/run-s3-backup-retention.sh create mode 100644 backend/scripts/acceptance/s3-backup-retention.mjs create mode 100644 docs/ux-ui/acceptance/s3-retention240.json diff --git a/backend/scripts/acceptance/run-s3-backup-retention.sh b/backend/scripts/acceptance/run-s3-backup-retention.sh new file mode 100644 index 00000000..354610b4 --- /dev/null +++ b/backend/scripts/acceptance/run-s3-backup-retention.sh @@ -0,0 +1,32 @@ +#!/bin/bash +set -euo pipefail +umask 077 +cd /opt/mmapp-test +mm240_db="mm_accept_166_$(date +%s%3N)" +mm240_pass="$(openssl rand -hex 24)" +mm240_role_created=false +mm240_db_created=false +cleanup240() { + docker rm mmapp-s3app240-owned >/dev/null 2>&1 || true + if $mm240_db_created; then docker exec mmapp-test-postgres psql -U mmapp -d mmapp -v ON_ERROR_STOP=1 -c "DROP DATABASE \"$mm240_db\" WITH (FORCE)"; fi + if $mm240_role_created; then docker exec mmapp-test-postgres psql -U mmapp -d mmapp -v ON_ERROR_STOP=1 -c "DROP ROLE \"$mm240_db\""; fi + remaining=$(docker exec mmapp-test-postgres psql -U mmapp -d mmapp -Atc "SELECT (SELECT count(*) FROM pg_database WHERE datname='$mm240_db')+(SELECT count(*) FROM pg_roles WHERE rolname='$mm240_db')") + [ "$remaining" = 0 ] + python3 - <<'PY' +import json,pathlib +p=pathlib.Path('/opt/mmapp-test/artifacts/s3-retention240/s3-retention240.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 mm240_pass +} +trap cleanup240 EXIT +[[ "$mm240_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='$mm240_db')+(SELECT count(*) FROM pg_roles WHERE rolname='$mm240_db')") +[ "$existing" = 0 ] +printf "CREATE ROLE \"%s\" LOGIN PASSWORD '%s' NOSUPERUSER NOCREATEDB NOCREATEROLE NOBYPASSRLS;\n" "$mm240_db" "$mm240_pass" | docker exec -i mmapp-test-postgres psql -U mmapp -d mmapp -v ON_ERROR_STOP=1 +mm240_role_created=true +docker exec mmapp-test-postgres psql -U mmapp -d mmapp -v ON_ERROR_STOP=1 -c "CREATE DATABASE \"$mm240_db\" OWNER \"$mm240_db\"" +mm240_db_created=true +docker run --name mmapp-s3app240-owned --network mmapp-test_default --cap-drop ALL --security-opt no-new-privileges -e "MMAPP_ORIGIN_BASELINE=${2:-false}" --env-file /opt/mmapp-test/artifacts/s3-retention240/private.env -e NODE_ENV=test -e "DATABASE_URL=postgres://$mm240_db:$mm240_pass@postgres:5432/$mm240_db" -e "MMAPP_OWN_SQL_DB=$mm240_db" -e "DATABASE_PATH=/tmp/$mm240_db/legacy.db" -e "GEOIP_DIR=/tmp/$mm240_db/geoip" -v /opt/mmapp-test/artifacts/s3-retention240:/checks --entrypoint node "${1:-mmapp-test/backend:ux-backups237}" /checks/s3-backup-retention.mjs diff --git a/backend/scripts/acceptance/s3-backup-retention.mjs b/backend/scripts/acceptance/s3-backup-retention.mjs new file mode 100644 index 00000000..a7224fb3 --- /dev/null +++ b/backend/scripts/acceptance/s3-backup-retention.mjs @@ -0,0 +1,84 @@ +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import { randomUUID, createHash } from 'node:crypto'; +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'), { ListObjectVersionsCommand, DeleteObjectCommand } = require('@aws-sdk/client-s3'); +const { pool, db, 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 { databaseBackgroundWork } = await import('/app/backend/dist/services/database-background-work.js'); +const { createS3ClientFromConfig, s3PutObject, s3GetObject } = await import('/app/backend/dist/services/s3-backup-client.js'); +const { updateBackupStorageSettings, pruneBackupsForServer, readBackupContent, listBackups } = await import('/app/backend/dist/services/backup-service.js'); +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 prefix = 'mmapp-acceptance/' + randomUUID(), secret = process.env.MMAPP_TEST_S3_SECRET_KEY; +const cfg = { endpoint: process.env.MMAPP_TEST_S3_ENDPOINT, region: process.env.MMAPP_TEST_S3_REGION, bucket: process.env.MMAPP_TEST_S3_BUCKET, accessKeyId: process.env.MMAPP_TEST_S3_ACCESS_KEY, secretAccessKey: secret, forcePathStyle: false }; +assert.equal(cfg.endpoint, 'https://s3.ru-1.storage.selcloud.ru'); assert.equal(cfg.bucket, 'mikrotik-test-s3'); assert(secret); +const s3 = createS3ClientFromConfig(cfg), plannedKeys = [], seededIds = [], payloads = new Map(); +const report = { date: '2026-10-14', block: '12.5.240', scope: 'Owned PostgreSQL + compiled retention service + real Selectel S3; no CHR contacts', prefix, passed: false, checks: [], cleanup: {}, chrContacts: 0, bucketSettingsWrites: 0 }; +const save = () => fs.writeFileSync('/checks/s3-retention240.json', JSON.stringify(report, null, 2) + '\n'); +const check = (name, details = {}) => { report.checks.push({ name, ...details }); save(); }; +const catalog = async () => (await pool.query('SELECT id,storage,s3_key FROM backup_entries ORDER BY id')).rows; +async function versions() { + let keyMarker, versionMarker; const all = []; + for (let n = 0; n < 10; n++) { + const r = await s3.send(new ListObjectVersionsCommand({ Bucket: cfg.bucket, Prefix: prefix + '/', KeyMarker: keyMarker, VersionIdMarker: versionMarker })); + all.push(...(r.Versions ?? []).map(v => ({ ...v, kind: 'version' })), ...(r.DeleteMarkers ?? []).map(v => ({ ...v, kind: 'delete-marker' }))); + if (!r.IsTruncated) return all; + assert(r.NextKeyMarker || r.NextVersionIdMarker); keyMarker = r.NextKeyMarker; versionMarker = r.NextVersionIdMarker; + } throw Error('Own pagination limit'); +} +try { + assert.equal((await versions()).length, 0); + const hosts = await db.insert(servers).values(['a', 'b', 'c'].map(s => ({ name: 'owned-retention240-' + s, host: '127.0.0.1', port: 9, useSsl: false, username: 'fixture', password: 'fixture', enabled: true, type: 'exit-node' }))).returning(); + await updateBackupStorageSettings({ provider: 's3', s3Endpoint: cfg.endpoint, s3Region: cfg.region, s3Bucket: cfg.bucket, s3Prefix: prefix, s3AccessKeyId: cfg.accessKeyId, s3SecretAccessKey: secret, s3ForcePathStyle: false, keepLocalCopy: true }); + fs.mkdirSync('storage/backups', { recursive: true }); + const fixtures = [['a4',0,4,'both'],['a3',0,3,'s3'],['a2',0,2,'local'],['a1',0,1,'s3'],['b2',1,2,'both'],['b1',1,1,'local'],['b0',1,0,'s3'],['c0',2,0,'local']]; + for (const [id, hostIndex, seconds, storage] of fixtures) { + const filename = id + '-retention240.rsc', bytes = Buffer.from('# Own retention240 ' + id + '\n'), key = storage === 'local' ? null : prefix + '/' + filename; + seededIds.push(id); payloads.set(id, bytes); + if (key) { plannedKeys.push(key); await s3PutObject(s3, cfg.bucket, key, bytes); assert.deepEqual(await s3GetObject(s3, cfg.bucket, key), bytes); } + if (storage !== 's3') fs.writeFileSync('storage/backups/' + filename, bytes); + await pool.query('INSERT INTO backup_entries(id,server_id,server_name,filename,size_bytes,kind,storage,s3_key,created_at) VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9)', [id, hosts[hostIndex].id, hosts[hostIndex].name, filename, bytes.length, id.endsWith('0') ? 'manual' : 'auto', storage, key, new Date(Date.UTC(2026,9,14,0,0,seconds)).toISOString()]); + } + check('eight-fixtures-three-servers', { remoteObjects: plannedKeys.length, localFiles: fixtures.filter(f => f[3] !== 's3').length }); + const untouched = (await catalog()).filter(r => !r.id.startsWith('a')); + await updateBackupStorageSettings({ s3SecretAccessKey: secret + '-invalid' }); + let failure; + try { await pruneBackupsForServer(String(hosts[0].id), 2); } catch (e) { failure = e; } + assert.equal(failure?.name, 'BackupPruneError'); assert.equal(failure.pruned, 1); + let remaining = await catalog(); assert.deepEqual(remaining.filter(r => !r.id.startsWith('a')), untouched); + assert.deepEqual(remaining.filter(r => r.id.startsWith('a')).map(r => r.id), ['a1','a3','a4']); + assert(!fs.existsSync('storage/backups/a2-retention240.rsc')); + assert.deepEqual(await s3GetObject(s3, cfg.bucket, prefix + '/a1-retention240.rsc'), payloads.get('a1')); + check('partial-failure-acknowledges-one-local-deletion', { pruned: failure.pruned, failedRowRetained: true, remoteObjectRetained: true, otherServersUnchanged: true }); + await updateBackupStorageSettings({ s3SecretAccessKey: secret }); + assert.equal(await pruneBackupsForServer(String(hosts[0].id), 2), 1); + assert.equal(await pruneBackupsForServer(String(hosts[0].id), 2), 0); + assert.deepEqual((await catalog()).filter(r => r.id.startsWith('a')).map(r => r.id), ['a3','a4']); + check('retry-retains-latest-two-and-repeat-noop', { retryPruned: 1, repeatPruned: 0 }); + assert.equal(await pruneBackupsForServer(String(hosts[1].id), 1), 2); + assert.deepEqual((await catalog()).map(r => r.id), ['a3','a4','b2','c0']); + check('second-server-retains-latest-and-unselected-manual-copy', { pruned: 2, retained: ['a3','a4','b2','c0'] }); + const copies = await listBackups(); + for (const copy of copies) assert.deepEqual(await readBackupContent(copy), payloads.get(copy.id)); + assert.equal(copies.find(r => r.id === 'a3').localFileStatus, 'not-stored'); + assert(copies.filter(r => r.id !== 'a3').every(r => r.localFileStatus === 'available')); + check('retained-content-hashes-and-local-states', { copies: copies.map(r => ({ id: r.id, localFileStatus: r.localFileStatus, sha256: createHash('sha256').update(payloads.get(r.id)).digest('hex') })) }); + report.passed = true; +} catch (e) { report.failure = { name: e.name }; process.exitCode = 1; } +finally { + try { + const existing = await versions(); assert(existing.every(v => plannedKeys.includes(v.Key) && v.VersionId)); + report.cleanup = { plannedKeys, versionsBefore: existing.filter(v => v.kind === 'version').length, deleteMarkersBefore: existing.filter(v => v.kind === 'delete-marker').length, removed: 0 }; save(); + for (const v of existing) { await s3.send(new DeleteObjectCommand({ Bucket: cfg.bucket, Key: v.Key, VersionId: v.VersionId })); report.cleanup.removed++; save(); } + assert.equal((await versions()).length, 0); report.cleanup.versionsAndMarkersRemaining = 0; + if (seededIds.length) await pool.query('DELETE FROM backup_entries WHERE id=ANY($1::text[])', [seededIds]); + report.cleanup.ownCatalogRemaining = Number((await pool.query('SELECT count(*) AS n FROM backup_entries')).rows[0].n); assert.equal(report.cleanup.ownCatalogRemaining, 0); + } catch (e) { report.cleanup.failure = { name: e.name }; report.passed = false; process.exitCode = 1; } + await databaseBackgroundWork.drain(); await closePool(); s3.destroy(); save(); console.log(JSON.stringify(report)); +} diff --git a/docs/ux-ui-external-acceptance.md b/docs/ux-ui-external-acceptance.md index 6bb3ad98..99a989b4 100644 --- a/docs/ux-ui-external-acceptance.md +++ b/docs/ux-ui-external-acceptance.md @@ -1,6 +1,6 @@ # UX/UI: протокол внешней приёмки 12.5 -Актуальное продолжение 2026-10-14:239 — Selectel S3 restore через реальный Portal/UI принят для собственного95-byte скрипта bridge-only:download SHA256 совпал,restore200/receipt и native bridge без ports/IP подтверждены,delete204. Bridge/file/catalog/S3versions очищены; исходные10 строк, storage/schedule сверены целиком и восстановлены; приватные ключи/SQL-снимок удалены. Runtime backend/frontend ux-backups237. Следующий240 — retention в изолированном собственном S3 scope. Этап12.5 открыт. +Актуальное продолжение 2026-10-14:240 — retention принят в own PostgreSQL + real Selectel S3:8 copies/3 fixture servers, partial deletion1 с сохранением failed row/object, retry1/noop0, второй server prune2, latest contents/states подтверждены. Удалены5 versions+2markers, own catalog/DB/role/container и приватные ключи; CHRcontacts0/main catalog не использовался. Runtime backend/frontend ux-backups237. Следующий241 — большой список со статусами файлов после237, затем уточнение оставшихся внешних jobs/границ выпуска. Этап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). @@ -18,6 +18,18 @@ OSPF84: real GRE Full adjacency/private routes/Ping3/3 и4 live PC/320px URL/bro BFD85: Up/Full/own route/Ping,4 live UI и loss/withdrawal/recovery приняты; exact own cleanup8 коллекций×2 passed. Withdrawal5,245ms не подтверждает sub-second BFD timing; этот вопрос открыт. Runtime routing83 без новой сборки. [Отчёт](ux-ui/acceptance/ospf-bfd-loss-2026-10-09.json). +## 12.5.240 — retention на реальном S3 и частичный отказ удаления (2026-10-14) + +В production image ux-backups237 запущен compiled pruneBackupsForServer/delete/read service в отдельной PostgreSQL базе с собственным NOSUPERUSER/NOCREATEDB/NOCREATEROLE/NOBYPASSRLS пользователем. Основной каталог/настройки не использовались. Seed8 harmless copies на3 собственных fixture servers:5 точных объектов в новом UUID S3 prefix и5 local files (both учитываются в обеих группах). RouterOS не опрашивался и не применялся; cfg bucket virtual-hosted Selectel, настройки бакета не менялись. + +При keepCount2 первого сервера и намеренно неправильном S3 secret local old copy удалена первой, затем remote delete отказал:BackupPruneError.pruned=1. Failed row/object сохранены, две новые копии и остальные servers неизменны. После возвращения верного secret retry удалил1, следующий prune вернул0. Второй сервер keepCount1 удалил2 старых copies, сохранилlatest both; третий unselected manual local copy не затронут. Все4 retained files прочитаны compiled service с точными исходными байтами/hash; remote-only localFileStatus=not-stored, остальныеavailable. + +Пять укрупнённых SQL/S3 проверок прошли. Cleanup удалил только planned keys и их5 versions/2delete markers (итого7), final listing0, own catalog0. Wrapper удалил temporary DB/role/container и подтвердил отсутствие DB/role. Временные ключевые файлы workstation/Debian удалены; секреты в публичном JSON отсутствуют. node --check passed; runtime источники/образы не менялись, повторной полной регрессии нет. + +Протокол: [retention240](ux-ui/acceptance/s3-retention240.json). Helpers:s3-backup-retention.mjs/run-s3-backup-retention.sh. Это реальное хранилище с контролируемыми SQL/file fixtures, не native scheduler production collection и не физический lost ACK; продолжительная работа, version lifecycle policy, миграция buckets и multiple backend/writers остаются отдельно. Ошибка S3 signature намеренно введена, пароли CHR не менялись. + +Следующий241 проверяет1000 строк нового missing/unknown/both UX237:поиск, pagination, last-good и mobile controls. Затем уточнить доступные external jobs и границы выпуска; семь общих групп не превращать в обещание фиксированного числа проверок. + ## 12.5.239 — восстановление из Selectel S3 через реальный интерфейс (2026-10-14) Пользователь явно разрешил передачу тестовых S3 access/secret keys на Debian после отказа automatic approval review. Ключи находились только в приватных ignored/0600 файлах и временных настройках приложения. Перед изменением сохранены raw SQL storage/schedule и10 исходных backup rows приватно. Расписание временно остановлено, virtual-hosted S3 сохранён и проверен через live UI с реальным Portal1.3.1/JWT. Использован новый UUID prefix и ровно один собственный95-byte .rsc: добавление bridge mm-ui239-* с comment=mm-ui239 без портов/адресов. Это специально ограниченный скрипт, не native full export и не полное восстановление CHR. diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index ca7b27e8..bccc09c4 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -1,9 +1,21 @@ # План реализации UX/UI по пятичасовым окнам -Актуальное продолжение 2026-10-14:239 — Selectel S3 restore через реальный Portal/UI принят для собственного95-byte скрипта bridge-only:download SHA256 совпал,restore200/receipt и native bridge без ports/IP подтверждены,delete204. Bridge/file/catalog/S3versions очищены; исходные10 строк, storage/schedule сверены целиком и восстановлены; приватные ключи/SQL-снимок удалены. Runtime backend/frontend ux-backups237. Следующий240 — retention в изолированном собственном S3 scope. Этап12.5 открыт. +Актуальное продолжение 2026-10-14:240 — retention принят в own PostgreSQL + real Selectel S3:8 copies/3 fixture servers, partial deletion1 с сохранением failed row/object, retry1/noop0, второй server prune2, latest contents/states подтверждены. Удалены5 versions+2markers, own catalog/DB/role/container и приватные ключи; CHRcontacts0/main catalog не использовался. Runtime backend/frontend ux-backups237. Следующий241 — большой список со статусами файлов после237, затем уточнение оставшихся внешних jobs/границ выпуска. Этап12.5 открыт. Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-14. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат. +## 12.5.240 — retention на реальном S3 и частичный отказ удаления (2026-10-14) + +В production image ux-backups237 запущен compiled pruneBackupsForServer/delete/read service в отдельной PostgreSQL базе с собственным NOSUPERUSER/NOCREATEDB/NOCREATEROLE/NOBYPASSRLS пользователем. Основной каталог/настройки не использовались. Seed8 harmless copies на3 собственных fixture servers:5 точных объектов в новом UUID S3 prefix и5 local files (both учитываются в обеих группах). RouterOS не опрашивался и не применялся; cfg bucket virtual-hosted Selectel, настройки бакета не менялись. + +При keepCount2 первого сервера и намеренно неправильном S3 secret local old copy удалена первой, затем remote delete отказал:BackupPruneError.pruned=1. Failed row/object сохранены, две новые копии и остальные servers неизменны. После возвращения верного secret retry удалил1, следующий prune вернул0. Второй сервер keepCount1 удалил2 старых copies, сохранилlatest both; третий unselected manual local copy не затронут. Все4 retained files прочитаны compiled service с точными исходными байтами/hash; remote-only localFileStatus=not-stored, остальныеavailable. + +Пять укрупнённых SQL/S3 проверок прошли. Cleanup удалил только planned keys и их5 versions/2delete markers (итого7), final listing0, own catalog0. Wrapper удалил temporary DB/role/container и подтвердил отсутствие DB/role. Временные ключевые файлы workstation/Debian удалены; секреты в публичном JSON отсутствуют. node --check passed; runtime источники/образы не менялись, повторной полной регрессии нет. + +Протокол: [retention240](ux-ui/acceptance/s3-retention240.json). Helpers:s3-backup-retention.mjs/run-s3-backup-retention.sh. Это реальное хранилище с контролируемыми SQL/file fixtures, не native scheduler production collection и не физический lost ACK; продолжительная работа, version lifecycle policy, миграция buckets и multiple backend/writers остаются отдельно. Ошибка S3 signature намеренно введена, пароли CHR не менялись. + +Следующий241 проверяет1000 строк нового missing/unknown/both UX237:поиск, pagination, last-good и mobile controls. Затем уточнить доступные external jobs и границы выпуска; семь общих групп не превращать в обещание фиксированного числа проверок. + ## 12.5.239 — восстановление из Selectel S3 через реальный интерфейс (2026-10-14) Пользователь явно разрешил передачу тестовых S3 access/secret keys на Debian после отказа automatic approval review. Ключи находились только в приватных ignored/0600 файлах и временных настройках приложения. Перед изменением сохранены raw SQL storage/schedule и10 исходных backup rows приватно. Расписание временно остановлено, virtual-hosted S3 сохранён и проверен через live UI с реальным Portal1.3.1/JWT. Использован новый UUID prefix и ровно один собственный95-byte .rsc: добавление bridge mm-ui239-* с comment=mm-ui239 без портов/адресов. Это специально ограниченный скрипт, не native full export и не полное восстановление CHR. diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index 2fb6d60b..c718402a 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -1,6 +1,6 @@ # UX/UI: журнал реализации -Актуальное продолжение 2026-10-14:239 — Selectel S3 restore через реальный Portal/UI принят для собственного95-byte скрипта bridge-only:download SHA256 совпал,restore200/receipt и native bridge без ports/IP подтверждены,delete204. Bridge/file/catalog/S3versions очищены; исходные10 строк, storage/schedule сверены целиком и восстановлены; приватные ключи/SQL-снимок удалены. Runtime backend/frontend ux-backups237. Следующий240 — retention в изолированном собственном S3 scope. Этап12.5 открыт. +Актуальное продолжение 2026-10-14:240 — retention принят в own PostgreSQL + real Selectel S3:8 copies/3 fixture servers, partial deletion1 с сохранением failed row/object, retry1/noop0, второй server prune2, latest contents/states подтверждены. Удалены5 versions+2markers, own catalog/DB/role/container и приватные ключи; CHRcontacts0/main catalog не использовался. Runtime backend/frontend ux-backups237. Следующий241 — большой список со статусами файлов после237, затем уточнение оставшихся внешних jobs/границ выпуска. Этап12.5 открыт. Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md). @@ -38,6 +38,18 @@ | 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 | | 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка12.3.83 в границах журнала. Внешняя12.5: блоки1–236 завершены в записанных scopes, этап открыт. Runtime backend ux-backup234/frontend ux-routing218; backend25/UI52 и дополнительные SQL/UDP222 прошли. | +## 12.5.240 — retention на реальном S3 и частичный отказ удаления (2026-10-14) + +В production image ux-backups237 запущен compiled pruneBackupsForServer/delete/read service в отдельной PostgreSQL базе с собственным NOSUPERUSER/NOCREATEDB/NOCREATEROLE/NOBYPASSRLS пользователем. Основной каталог/настройки не использовались. Seed8 harmless copies на3 собственных fixture servers:5 точных объектов в новом UUID S3 prefix и5 local files (both учитываются в обеих группах). RouterOS не опрашивался и не применялся; cfg bucket virtual-hosted Selectel, настройки бакета не менялись. + +При keepCount2 первого сервера и намеренно неправильном S3 secret local old copy удалена первой, затем remote delete отказал:BackupPruneError.pruned=1. Failed row/object сохранены, две новые копии и остальные servers неизменны. После возвращения верного secret retry удалил1, следующий prune вернул0. Второй сервер keepCount1 удалил2 старых copies, сохранилlatest both; третий unselected manual local copy не затронут. Все4 retained files прочитаны compiled service с точными исходными байтами/hash; remote-only localFileStatus=not-stored, остальныеavailable. + +Пять укрупнённых SQL/S3 проверок прошли. Cleanup удалил только planned keys и их5 versions/2delete markers (итого7), final listing0, own catalog0. Wrapper удалил temporary DB/role/container и подтвердил отсутствие DB/role. Временные ключевые файлы workstation/Debian удалены; секреты в публичном JSON отсутствуют. node --check passed; runtime источники/образы не менялись, повторной полной регрессии нет. + +Протокол: [retention240](ux-ui/acceptance/s3-retention240.json). Helpers:s3-backup-retention.mjs/run-s3-backup-retention.sh. Это реальное хранилище с контролируемыми SQL/file fixtures, не native scheduler production collection и не физический lost ACK; продолжительная работа, version lifecycle policy, миграция buckets и multiple backend/writers остаются отдельно. Ошибка S3 signature намеренно введена, пароли CHR не менялись. + +Следующий241 проверяет1000 строк нового missing/unknown/both UX237:поиск, pagination, last-good и mobile controls. Затем уточнить доступные external jobs и границы выпуска; семь общих групп не превращать в обещание фиксированного числа проверок. + ## 12.5.239 — восстановление из Selectel S3 через реальный интерфейс (2026-10-14) Пользователь явно разрешил передачу тестовых S3 access/secret keys на Debian после отказа automatic approval review. Ключи находились только в приватных ignored/0600 файлах и временных настройках приложения. Перед изменением сохранены raw SQL storage/schedule и10 исходных backup rows приватно. Расписание временно остановлено, virtual-hosted S3 сохранён и проверен через live UI с реальным Portal1.3.1/JWT. Использован новый UUID prefix и ровно один собственный95-byte .rsc: добавление bridge mm-ui239-* с comment=mm-ui239 без портов/адресов. Это специально ограниченный скрипт, не native full export и не полное восстановление CHR. diff --git a/docs/ux-ui-remaining-acceptance.md b/docs/ux-ui-remaining-acceptance.md index 359c185c..908923bf 100644 --- a/docs/ux-ui-remaining-acceptance.md +++ b/docs/ux-ui-remaining-acceptance.md @@ -1,11 +1,23 @@ # Оставшаяся внешняя приёмка — 2026-10-14 -Актуальное продолжение 2026-10-14:239 — Selectel S3 restore через реальный Portal/UI принят для собственного95-byte скрипта bridge-only:download SHA256 совпал,restore200/receipt и native bridge без ports/IP подтверждены,delete204. Bridge/file/catalog/S3versions очищены; исходные10 строк, storage/schedule сверены целиком и восстановлены; приватные ключи/SQL-снимок удалены. Runtime backend/frontend ux-backups237. Следующий240 — retention в изолированном собственном S3 scope. Этап12.5 открыт. +Актуальное продолжение 2026-10-14:240 — retention принят в own PostgreSQL + real Selectel S3:8 copies/3 fixture servers, partial deletion1 с сохранением failed row/object, retry1/noop0, второй server prune2, latest contents/states подтверждены. Удалены5 versions+2markers, own catalog/DB/role/container и приватные ключи; CHRcontacts0/main catalog не использовался. Runtime backend/frontend ux-backups237. Следующий241 — большой список со статусами файлов после237, затем уточнение оставшихся внешних jobs/границ выпуска. Этап12.5 открыт. -Это актуальная матрица после 12.5.239. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии. +Это актуальная матрица после 12.5.240. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии. Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-backups237`, frontend `ux-backups237`; production lab с195 использует AUTH_REQUIRED=true и Portal1.3.1. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись. +## 12.5.240 — retention на реальном S3 и частичный отказ удаления (2026-10-14) + +В production image ux-backups237 запущен compiled pruneBackupsForServer/delete/read service в отдельной PostgreSQL базе с собственным NOSUPERUSER/NOCREATEDB/NOCREATEROLE/NOBYPASSRLS пользователем. Основной каталог/настройки не использовались. Seed8 harmless copies на3 собственных fixture servers:5 точных объектов в новом UUID S3 prefix и5 local files (both учитываются в обеих группах). RouterOS не опрашивался и не применялся; cfg bucket virtual-hosted Selectel, настройки бакета не менялись. + +При keepCount2 первого сервера и намеренно неправильном S3 secret local old copy удалена первой, затем remote delete отказал:BackupPruneError.pruned=1. Failed row/object сохранены, две новые копии и остальные servers неизменны. После возвращения верного secret retry удалил1, следующий prune вернул0. Второй сервер keepCount1 удалил2 старых copies, сохранилlatest both; третий unselected manual local copy не затронут. Все4 retained files прочитаны compiled service с точными исходными байтами/hash; remote-only localFileStatus=not-stored, остальныеavailable. + +Пять укрупнённых SQL/S3 проверок прошли. Cleanup удалил только planned keys и их5 versions/2delete markers (итого7), final listing0, own catalog0. Wrapper удалил temporary DB/role/container и подтвердил отсутствие DB/role. Временные ключевые файлы workstation/Debian удалены; секреты в публичном JSON отсутствуют. node --check passed; runtime источники/образы не менялись, повторной полной регрессии нет. + +Протокол: [retention240](ux-ui/acceptance/s3-retention240.json). Helpers:s3-backup-retention.mjs/run-s3-backup-retention.sh. Это реальное хранилище с контролируемыми SQL/file fixtures, не native scheduler production collection и не физический lost ACK; продолжительная работа, version lifecycle policy, миграция buckets и multiple backend/writers остаются отдельно. Ошибка S3 signature намеренно введена, пароли CHR не менялись. + +Следующий241 проверяет1000 строк нового missing/unknown/both UX237:поиск, pagination, last-good и mobile controls. Затем уточнить доступные external jobs и границы выпуска; семь общих групп не превращать в обещание фиксированного числа проверок. + ## 12.5.239 — восстановление из Selectel S3 через реальный интерфейс (2026-10-14) Пользователь явно разрешил передачу тестовых S3 access/secret keys на Debian после отказа automatic approval review. Ключи находились только в приватных ignored/0600 файлах и временных настройках приложения. Перед изменением сохранены raw SQL storage/schedule и10 исходных backup rows приватно. Расписание временно остановлено, virtual-hosted S3 сохранён и проверен через live UI с реальным Portal1.3.1/JWT. Использован новый UUID prefix и ровно один собственный95-byte .rsc: добавление bridge mm-ui239-* с comment=mm-ui239 без портов/адресов. Это специально ограниченный скрипт, не native full export и не полное восстановление CHR. @@ -59,7 +71,7 @@ Production backend/frontend ux-backups237 собраны и развёрнуты | REST и достоверность данных | Ошибки OSPF/BFD/BGP/GRE/WAN215–221 явные; latest API smoke222 прошёл | Correlated236:384 samples/40 diagnostics без отказов; current policies/address/load исправны.828/304 retained REST sessions и vendor inactive-session fix — гипотеза; 238:763.844s/624samples/24diagnostics все200, API records изменились без отказа; причинность401/500 и version control остаются открытыми | | Клиентские ОС и внешняя сеть | 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/Telegram195–201 и Selectel S3/client/API/ownSQL228–229 | Large native exports проверены HTTP fixture; Missing local files UX237 принят; Bridge-only S3 restore через UI239 принят; миграция между buckets/multi-instance и retention; 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 | Large native exports проверены HTTP fixture; Missing local files UX237 принят; Bridge-only S3 restore через UI239 принят; Retention240 в own scope принят; миграция между buckets/multi-instance; 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 | | Границы выпуска | План/доказательства/датированные блоки сохранены; коммиты локальные | Определить требуемые конфигурации и принять результат в этих границах; невыполненные варианты остаются явными ограничениями | @@ -86,7 +98,7 @@ Portal offline JWT действует доexp после logout/role change; э ## Следующая последовательность — 2026-10-14 -1. **240 — retention:** изолированные own SQL/S3 scope, сохранение latest copies по каждому серверу, partial cleanup и точная очистка версий. Исходный каталог приложения не использовать для pruning. +1. **241 — большой список:** после изменения237 проверить1000 missing/unknown/both строк, global search/pagination, сохранение last-good и mobile controls. Затем — доступные external jobs и явные границы выпуска. 2. **Профильный контроль:** для новых изменений выполнять затронутые SQL/API/UI проверки. Общая регрессия192 выполнена; не повторять её без изменений общей основы или нового основания. 3. **Внешние зависимости:** Portal issuer/login/revoke, Telegram bot/chat/разрешённое сообщение, клиентские ОС, независимые WAN/public IPv6 и scale/multi-instance — отдельные группы по таблице выше. Ограничения текущего стенда не превращать в обещание приёмки всех конфигураций. diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index 5e4a6ee6..9700bea3 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -1,6 +1,6 @@ # Этап 12: итоговая приёмка UX/UI -Актуальное продолжение 2026-10-14:239 — Selectel S3 restore через реальный Portal/UI принят для собственного95-byte скрипта bridge-only:download SHA256 совпал,restore200/receipt и native bridge без ports/IP подтверждены,delete204. Bridge/file/catalog/S3versions очищены; исходные10 строк, storage/schedule сверены целиком и восстановлены; приватные ключи/SQL-снимок удалены. Runtime backend/frontend ux-backups237. Следующий240 — retention в изолированном собственном S3 scope. Этап12.5 открыт. +Актуальное продолжение 2026-10-14:240 — retention принят в own PostgreSQL + real Selectel S3:8 copies/3 fixture servers, partial deletion1 с сохранением failed row/object, retry1/noop0, второй server prune2, latest contents/states подтверждены. Удалены5 versions+2markers, own catalog/DB/role/container и приватные ключи; CHRcontacts0/main catalog не использовался. Runtime backend/frontend ux-backups237. Следующий241 — большой список со статусами файлов после237, затем уточнение оставшихся внешних jobs/границ выпуска. Этап12.5 открыт. План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push. @@ -4152,6 +4152,18 @@ Real Portal/browser API, provider local verified: создан один job ро Runtime backend ux-backup234/frontend ux-routing218 healthy; новое application image для volume-only fix не требовалось. Пароли CHR не менялись, S3 mutations0 в235, configs body/JWT не опубликованы, private local manifest удалён. Исходные10 missing local files не восстановлены и records не удалены. Force-recreate — не host reboot/power loss/fsync/DR приёмка. Далее236 — UX недоступных local files, затем version retention границы и оставшиеся7 группы матрицы; multi-instance guard не принимается. Этап12.5 открыт. +## 12.5.240 — retention на реальном S3 и частичный отказ удаления (2026-10-14) + +В production image ux-backups237 запущен compiled pruneBackupsForServer/delete/read service в отдельной PostgreSQL базе с собственным NOSUPERUSER/NOCREATEDB/NOCREATEROLE/NOBYPASSRLS пользователем. Основной каталог/настройки не использовались. Seed8 harmless copies на3 собственных fixture servers:5 точных объектов в новом UUID S3 prefix и5 local files (both учитываются в обеих группах). RouterOS не опрашивался и не применялся; cfg bucket virtual-hosted Selectel, настройки бакета не менялись. + +При keepCount2 первого сервера и намеренно неправильном S3 secret local old copy удалена первой, затем remote delete отказал:BackupPruneError.pruned=1. Failed row/object сохранены, две новые копии и остальные servers неизменны. После возвращения верного secret retry удалил1, следующий prune вернул0. Второй сервер keepCount1 удалил2 старых copies, сохранилlatest both; третий unselected manual local copy не затронут. Все4 retained files прочитаны compiled service с точными исходными байтами/hash; remote-only localFileStatus=not-stored, остальныеavailable. + +Пять укрупнённых SQL/S3 проверок прошли. Cleanup удалил только planned keys и их5 versions/2delete markers (итого7), final listing0, own catalog0. Wrapper удалил temporary DB/role/container и подтвердил отсутствие DB/role. Временные ключевые файлы workstation/Debian удалены; секреты в публичном JSON отсутствуют. node --check passed; runtime источники/образы не менялись, повторной полной регрессии нет. + +Протокол: [retention240](ux-ui/acceptance/s3-retention240.json). Helpers:s3-backup-retention.mjs/run-s3-backup-retention.sh. Это реальное хранилище с контролируемыми SQL/file fixtures, не native scheduler production collection и не физический lost ACK; продолжительная работа, version lifecycle policy, миграция buckets и multiple backend/writers остаются отдельно. Ошибка S3 signature намеренно введена, пароли CHR не менялись. + +Следующий241 проверяет1000 строк нового missing/unknown/both UX237:поиск, pagination, last-good и mobile controls. Затем уточнить доступные external jobs и границы выпуска; семь общих групп не превращать в обещание фиксированного числа проверок. + ## 12.5.239 — восстановление из Selectel S3 через реальный интерфейс (2026-10-14) Пользователь явно разрешил передачу тестовых S3 access/secret keys на Debian после отказа automatic approval review. Ключи находились только в приватных ignored/0600 файлах и временных настройках приложения. Перед изменением сохранены raw SQL storage/schedule и10 исходных backup rows приватно. Расписание временно остановлено, virtual-hosted S3 сохранён и проверен через live UI с реальным Portal1.3.1/JWT. Использован новый UUID prefix и ровно один собственный95-byte .rsc: добавление bridge mm-ui239-* с comment=mm-ui239 без портов/адресов. Это специально ограниченный скрипт, не native full export и не полное восстановление CHR. diff --git a/docs/ux-ui-test-server.md b/docs/ux-ui-test-server.md index ac071220..22f7f1c6 100644 --- a/docs/ux-ui-test-server.md +++ b/docs/ux-ui-test-server.md @@ -1,6 +1,6 @@ # Тестовый сервер UX/UI — 2026-10-10 -Актуальное продолжение 2026-10-14:239 — Selectel S3 restore через реальный Portal/UI принят для собственного95-byte скрипта bridge-only:download SHA256 совпал,restore200/receipt и native bridge без ports/IP подтверждены,delete204. Bridge/file/catalog/S3versions очищены; исходные10 строк, storage/schedule сверены целиком и восстановлены; приватные ключи/SQL-снимок удалены. Runtime backend/frontend ux-backups237. Следующий240 — retention в изолированном собственном S3 scope. Этап12.5 открыт. +Актуальное продолжение 2026-10-14:240 — retention принят в own PostgreSQL + real Selectel S3:8 copies/3 fixture servers, partial deletion1 с сохранением failed row/object, retry1/noop0, второй server prune2, latest contents/states подтверждены. Удалены5 versions+2markers, own catalog/DB/role/container и приватные ключи; CHRcontacts0/main catalog не использовался. Runtime backend/frontend ux-backups237. Следующий241 — большой список со статусами файлов после237, затем уточнение оставшихся внешних jobs/границ выпуска. Этап12.5 открыт. | Параметр | Проверенное состояние | | --- | --- | @@ -34,6 +34,18 @@ OSPF84: real GRE Full adjacency/private routes/Ping3/3 и4 live PC/320px URL/bro BFD85: Up/Full/own route/Ping,4 live UI и loss/withdrawal/recovery приняты; exact own cleanup8 коллекций×2 passed. Withdrawal5,245ms не подтверждает sub-second BFD timing; этот вопрос открыт. Runtime routing83 без новой сборки. [Отчёт](ux-ui/acceptance/ospf-bfd-loss-2026-10-09.json). +## 12.5.240 — retention на реальном S3 и частичный отказ удаления (2026-10-14) + +В production image ux-backups237 запущен compiled pruneBackupsForServer/delete/read service в отдельной PostgreSQL базе с собственным NOSUPERUSER/NOCREATEDB/NOCREATEROLE/NOBYPASSRLS пользователем. Основной каталог/настройки не использовались. Seed8 harmless copies на3 собственных fixture servers:5 точных объектов в новом UUID S3 prefix и5 local files (both учитываются в обеих группах). RouterOS не опрашивался и не применялся; cfg bucket virtual-hosted Selectel, настройки бакета не менялись. + +При keepCount2 первого сервера и намеренно неправильном S3 secret local old copy удалена первой, затем remote delete отказал:BackupPruneError.pruned=1. Failed row/object сохранены, две новые копии и остальные servers неизменны. После возвращения верного secret retry удалил1, следующий prune вернул0. Второй сервер keepCount1 удалил2 старых copies, сохранилlatest both; третий unselected manual local copy не затронут. Все4 retained files прочитаны compiled service с точными исходными байтами/hash; remote-only localFileStatus=not-stored, остальныеavailable. + +Пять укрупнённых SQL/S3 проверок прошли. Cleanup удалил только planned keys и их5 versions/2delete markers (итого7), final listing0, own catalog0. Wrapper удалил temporary DB/role/container и подтвердил отсутствие DB/role. Временные ключевые файлы workstation/Debian удалены; секреты в публичном JSON отсутствуют. node --check passed; runtime источники/образы не менялись, повторной полной регрессии нет. + +Протокол: [retention240](ux-ui/acceptance/s3-retention240.json). Helpers:s3-backup-retention.mjs/run-s3-backup-retention.sh. Это реальное хранилище с контролируемыми SQL/file fixtures, не native scheduler production collection и не физический lost ACK; продолжительная работа, version lifecycle policy, миграция buckets и multiple backend/writers остаются отдельно. Ошибка S3 signature намеренно введена, пароли CHR не менялись. + +Следующий241 проверяет1000 строк нового missing/unknown/both UX237:поиск, pagination, last-good и mobile controls. Затем уточнить доступные external jobs и границы выпуска; семь общих групп не превращать в обещание фиксированного числа проверок. + ## 12.5.239 — восстановление из Selectel S3 через реальный интерфейс (2026-10-14) Пользователь явно разрешил передачу тестовых S3 access/secret keys на Debian после отказа automatic approval review. Ключи находились только в приватных ignored/0600 файлах и временных настройках приложения. Перед изменением сохранены raw SQL storage/schedule и10 исходных backup rows приватно. Расписание временно остановлено, virtual-hosted S3 сохранён и проверен через live UI с реальным Portal1.3.1/JWT. Использован новый UUID prefix и ровно один собственный95-byte .rsc: добавление bridge mm-ui239-* с comment=mm-ui239 без портов/адресов. Это специально ограниченный скрипт, не native full export и не полное восстановление CHR. diff --git a/docs/ux-ui/acceptance/s3-retention240.json b/docs/ux-ui/acceptance/s3-retention240.json new file mode 100644 index 00000000..0a60e104 --- /dev/null +++ b/docs/ux-ui/acceptance/s3-retention240.json @@ -0,0 +1,79 @@ +{ + "date": "2026-10-14", + "block": "12.5.240", + "scope": "Owned PostgreSQL + compiled retention service + real Selectel S3; no CHR contacts", + "prefix": "mmapp-acceptance/c05507bd-c1df-4350-af8b-cb8c8c07ee9d", + "passed": true, + "checks": [ + { + "name": "eight-fixtures-three-servers", + "remoteObjects": 5, + "localFiles": 5 + }, + { + "name": "partial-failure-acknowledges-one-local-deletion", + "pruned": 1, + "failedRowRetained": true, + "remoteObjectRetained": true, + "otherServersUnchanged": true + }, + { + "name": "retry-retains-latest-two-and-repeat-noop", + "retryPruned": 1, + "repeatPruned": 0 + }, + { + "name": "second-server-retains-latest-and-unselected-manual-copy", + "pruned": 2, + "retained": [ + "a3", + "a4", + "b2", + "c0" + ] + }, + { + "name": "retained-content-hashes-and-local-states", + "copies": [ + { + "id": "a4", + "localFileStatus": "available", + "sha256": "9704a7906274a6e758f5f03254b38f00bb4287240ec6fa8b6e8847f6415d36bf" + }, + { + "id": "a3", + "localFileStatus": "not-stored", + "sha256": "6b0cf64533cbd3efd0fd040c32f357d10f93ee66b7b2fcc51bc52b01cd4fc2ec" + }, + { + "id": "b2", + "localFileStatus": "available", + "sha256": "a698acf949d2a6598f00e4f9cb132b43b9864b2a5f7938fefb651c9075746c06" + }, + { + "id": "c0", + "localFileStatus": "available", + "sha256": "6c11004b65701dc3228c164f1a050b6e5e52f601088e8fb125d0171797c7d565" + } + ] + } + ], + "cleanup": { + "plannedKeys": [ + "mmapp-acceptance/c05507bd-c1df-4350-af8b-cb8c8c07ee9d/a4-retention240.rsc", + "mmapp-acceptance/c05507bd-c1df-4350-af8b-cb8c8c07ee9d/a3-retention240.rsc", + "mmapp-acceptance/c05507bd-c1df-4350-af8b-cb8c8c07ee9d/a1-retention240.rsc", + "mmapp-acceptance/c05507bd-c1df-4350-af8b-cb8c8c07ee9d/b2-retention240.rsc", + "mmapp-acceptance/c05507bd-c1df-4350-af8b-cb8c8c07ee9d/b0-retention240.rsc" + ], + "versionsBefore": 5, + "deleteMarkersBefore": 2, + "removed": 7, + "versionsAndMarkersRemaining": 0, + "ownCatalogRemaining": 0 + }, + "chrContacts": 0, + "bucketSettingsWrites": 0, + "ownedDatabaseAndRoleRemoved": true, + "privateKeyFileRemoved": true +}