diff --git a/backend/scripts/acceptance/s3-roundtrip.mjs b/backend/scripts/acceptance/s3-roundtrip.mjs new file mode 100644 index 00000000..97eeb6ab --- /dev/null +++ b/backend/scripts/acceptance/s3-roundtrip.mjs @@ -0,0 +1,76 @@ +import assert from 'node:assert/strict' +import { createHash, randomUUID } from 'node:crypto' +import fs from 'node:fs' + +// Run in the compiled test backend image. Never upload a device configuration here. +async function main() { +const endpoint = new URL(process.env.MMAPP_TEST_S3_ENDPOINT ?? '') +assert.equal(endpoint.protocol, 'https:', 'Verified HTTPS endpoint required') +assert(!endpoint.username && !endpoint.password && !endpoint.search && !endpoint.hash, 'Endpoint must not contain credentials or query parameters') +assert.equal(process.env.MMAPP_TEST_S3_OWNED_SCOPE, 'yes', 'Explicit bucket/prefix authorization required') +const bucket = process.env.MMAPP_TEST_S3_BUCKET +assert(bucket && bucket.trim() === bucket, 'Explicit bucket required') +const accessKeyId = process.env.MMAPP_TEST_S3_ACCESS_KEY, secretAccessKey = process.env.MMAPP_TEST_S3_SECRET_KEY +assert(accessKeyId && secretAccessKey, 'Explicit test credentials required') +const region = process.env.MMAPP_TEST_S3_REGION +assert(region, 'Explicit region required') +const pathStyle = process.env.MMAPP_TEST_S3_PATH_STYLE +assert(['true', 'false'].includes(pathStyle), 'Explicit path-style choice required') +const prefix = 'mmapp-acceptance/' + randomUUID() +const keys = [prefix + '/probe-1.rsc', prefix + '/probe-2.rsc'] +if (process.env.MMAPP_TEST_S3_DRY_RUN === 'true') { + console.log(JSON.stringify({ dryRun: true, networkRequests: 0, plannedKeys: keys, deviceConfigurationUploads: 0 })) +} else { + assert.equal(process.env.MMAPP_TEST_S3_RUN, 'confirmed', 'Explicit live execution required') + const output = process.env.MMAPP_TEST_REPORT + assert(output, 'Explicit report path required') + const { createS3ClientFromConfig, s3PutObject, s3GetObject, s3ListObjects, s3DeleteObject } = await import('/app/backend/dist/services/s3-backup-client.js') + const client = createS3ClientFromConfig({ endpoint: endpoint.toString(), region, bucket, prefix, accessKeyId, secretAccessKey, forcePathStyle: pathStyle === 'true' }) + const report = { startedAt: new Date().toISOString(), scope: 'Compiled application S3 client; harmless synthetic bodies; dedicated UUID prefix', prefix, passed: false, checks: [], cleanup: [], 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'] } + const attempted = [] + report.plannedKeys = keys + report.attemptedKeys = attempted + const save = () => fs.writeFileSync(output, JSON.stringify(report, null, 2) + '\n') + const failure = error => ({ name: /^[\w:.-]+$/.test(error.name) ? error.name : 'Error', httpStatus: error.$metadata?.httpStatusCode ?? null }) + const sha = bytes => createHash('sha256').update(bytes).digest('hex') + save() + try { + // An empty namespace is checked before recording ownership or attempting a write. + assert.equal((await s3ListObjects(client, bucket, prefix)).length, 0, 'Namespace already contains objects; no writes allowed') + report.checks.push({ operation: 'empty-prefix-preflight', passed: true }); save() + for (const key of keys) { + const body = Buffer.from('# MikroTik Manager owned S3 roundtrip probe\n# ' + key + '\n') + attempted.push(key); save() + await s3PutObject(client, bucket, key, body) + const received = await s3GetObject(client, bucket, key) + assert.equal(sha(received), sha(body), 'Object content mismatch') + report.checks.push({ operation: 'put-get-content', key, bytes: received.length, passed: true }); save() + } + const listed = (await s3ListObjects(client, bucket, prefix)).map(row => row.key).sort() + assert.deepEqual(listed, [...keys].sort(), 'Unexpected listing in owned namespace') + report.checks.push({ operation: 'list-exact-owned-keys', passed: true }) + report.passed = true + } catch (error) { report.failure = failure(error) } + finally { + for (const key of attempted) { + try { await s3DeleteObject(client, bucket, key); report.cleanup.push({ key, acknowledged: true }) } + catch (error) { report.cleanup.push({ key, acknowledged: false, failure: failure(error) }); report.passed = false } + } + if (attempted.length) { + try { + const remaining = (await s3ListObjects(client, bucket, prefix)).filter(row => attempted.includes(row.key)).length + report.currentOwnedObjectsRemaining = remaining + if (remaining) report.passed = false + } catch (error) { report.cleanupReadbackFailure = failure(error); report.passed = false } + } + report.completedAt = new Date().toISOString(); save(); client.destroy() + } + console.log(JSON.stringify({ passed: report.passed, checks: report.checks.length, attemptedObjects: attempted.length, currentOwnedObjectsRemaining: report.currentOwnedObjectsRemaining ?? null })) + if (!report.passed) process.exitCode = 1 +} +} +await main().catch(error => { + const name = /^[\w:.-]+$/.test(error.name) ? error.name : 'Error' + console.error('S3_ACCEPTANCE_FAILED: ' + name) + process.exitCode = 1 +}) diff --git a/docs/ux-ui-external-acceptance.md b/docs/ux-ui-external-acceptance.md index dbe36902..391e69ca 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: UI226 —1366 unique accepted (основной1364 passed/2 failed; scoped5/5 recovered2), screenshots364 сохранены. Android227 human full-tunnel/reconnect accepted,16 own objects удалены, поля13 коллекций сохранены, оба CHR доступны/пароли прежние; синхронных native packet counters нет. Backend ux-regression222/frontend ux-routing218. Далее228 — S3 после получения endpoint/bucket/region/keys; этап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 открыт. 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). @@ -2639,3 +2639,7 @@ Runtime backend ux-regression222/frontend ux-routing218. Latest backend25/UI52 ## 12.5.226 — общий UI-набор и scoped recovery (2026-10-14) 1366 unique accepted:1364 primary passed/2 failed,5/5 rerun recovered2. Whole fresh zero-failure run не заявляется. Сохранены364 исходных screenshots, изменён только artifact path GRE/VXLAN теста; application/runtime unchanged. [Итог](ux-ui/acceptance/general226-ui-regression-2026-10-14.json). Далее228 — S3 prerequisites. + +## 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). diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index 79643591..8d762a34 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -1,6 +1,6 @@ # План реализации UX/UI по пятичасовым окнам -Актуальное продолжение 2026-10-14: UI226 —1366 unique accepted (основной1364 passed/2 failed; scoped5/5 recovered2), screenshots364 сохранены. Android227 human full-tunnel/reconnect accepted,16 own objects удалены, поля13 коллекций сохранены, оба CHR доступны/пароли прежние; синхронных native packet counters нет. Backend ux-regression222/frontend ux-routing218. Далее228 — S3 после получения endpoint/bucket/region/keys; этап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 открыт. Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-14. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат. diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index 63cc0985..bc9b1853 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -1,6 +1,6 @@ # UX/UI: журнал реализации -Актуальное продолжение 2026-10-14: UI226 —1366 unique accepted (основной1364 passed/2 failed; scoped5/5 recovered2), screenshots364 сохранены. Android227 human full-tunnel/reconnect accepted,16 own objects удалены, поля13 коллекций сохранены, оба CHR доступны/пароли прежние; синхронных native packet counters нет. Backend ux-regression222/frontend ux-routing218. Далее228 — S3 после получения endpoint/bucket/region/keys; этап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 открыт. Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md). @@ -38,6 +38,14 @@ | 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.5.228 — подготовка безопасной реальной 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//, проверяет пустоту prefix до записи, сохраняет planned/attempted keys до каждого PUT и удаляет только собственные attempted keys, включая неопределённый результат PUT. Тела — безвредные synthetic .rsc comments; native config/backup/credential exports не загружаются. + +Запуск требует 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 не объявлять. + +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) Основной configured default E2E набор229 файлов/1366 unique:1364 passed,2 failed, skipped0/flaky0, exit1, фактически40.6m. Context suites, исключённые playwright.config.ts, не включены; тесты работают по ordinary live HTTP path с подставными ответами. Это не native/production data-plane или полноценная screen-reader приёмка. diff --git a/docs/ux-ui-remaining-acceptance.md b/docs/ux-ui-remaining-acceptance.md index 6daac0e5..b2b3efa3 100644 --- a/docs/ux-ui-remaining-acceptance.md +++ b/docs/ux-ui-remaining-acceptance.md @@ -1,6 +1,6 @@ # Оставшаяся внешняя приёмка — 2026-10-14 -Актуальное продолжение 2026-10-14: UI226 —1366 unique accepted (основной1364 passed/2 failed; scoped5/5 recovered2), screenshots364 сохранены. Android227 human full-tunnel/reconnect accepted,16 own objects удалены, поля13 коллекций сохранены, оба CHR доступны/пароли прежние; синхронных native packet counters нет. Backend ux-regression222/frontend ux-routing218. Далее228 — S3 после получения endpoint/bucket/region/keys; этап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 открыт. Это актуальная матрица после 12.5.227. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии. @@ -1569,3 +1569,7 @@ Backend25/UI52, disabled33/months5 и оба IPv6 UDP/SQL worker режима п ## 12.5.226 — общий UI-набор и scoped recovery (2026-10-14) 1366 unique accepted:1364 primary passed/2 failed,5/5 rerun recovered2. Whole fresh zero-failure run не заявляется. Сохранены364 исходных screenshots, изменён только artifact path GRE/VXLAN теста; application/runtime unchanged. [Итог](ux-ui/acceptance/general226-ui-regression-2026-10-14.json). Далее228 — S3 prerequisites. + +## 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). diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index 54a82add..ef57af26 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: UI226 —1366 unique accepted (основной1364 passed/2 failed; scoped5/5 recovered2), screenshots364 сохранены. Android227 human full-tunnel/reconnect accepted,16 own objects удалены, поля13 коллекций сохранены, оба CHR доступны/пароли прежние; синхронных native packet counters нет. Backend ux-regression222/frontend ux-routing218. Далее228 — S3 после получения endpoint/bucket/region/keys; этап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 открыт. План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push. @@ -4064,3 +4064,12 @@ 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) + +Пользователь предложил тестовый S3; endpoint/region/bucket/access/secret и path-style ещё не получены. Подготовлен backend/scripts/acceptance/s3-roundtrip.mjs для запуска в compiled test backend image. Он вызывает существующий production S3 client, использует отдельный mmapp-acceptance//, проверяет пустоту prefix до записи, сохраняет planned/attempted keys до каждого PUT и удаляет только собственные attempted keys, включая неопределённый результат PUT. Тела — безвредные synthetic .rsc comments; native config/backup/credential exports не загружаются. + +Запуск требует 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 не объявлять. + +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 открыт. + diff --git a/docs/ux-ui-test-server.md b/docs/ux-ui-test-server.md index b1963041..a8c2d92e 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: UI226 —1366 unique accepted (основной1364 passed/2 failed; scoped5/5 recovered2), screenshots364 сохранены. Android227 human full-tunnel/reconnect accepted,16 own objects удалены, поля13 коллекций сохранены, оба CHR доступны/пароли прежние; синхронных native packet counters нет. Backend ux-regression222/frontend ux-routing218. Далее228 — S3 после получения endpoint/bucket/region/keys; этап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 открыт. | Параметр | Проверенное состояние | | --- | --- | @@ -2370,3 +2370,7 @@ Runtime backend ux-regression222/frontend ux-routing218. Latest backend25/UI52 ## 12.5.226 — общий UI-набор и scoped recovery (2026-10-14) 1366 unique accepted:1364 primary passed/2 failed,5/5 rerun recovered2. Whole fresh zero-failure run не заявляется. Сохранены364 исходных screenshots, изменён только artifact path GRE/VXLAN теста; application/runtime unchanged. [Итог](ux-ui/acceptance/general226-ui-regression-2026-10-14.json). Далее228 — S3 prerequisites. + +## 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). diff --git a/docs/ux-ui/acceptance/s3-guard-preflight-2026-10-14.json b/docs/ux-ui/acceptance/s3-guard-preflight-2026-10-14.json new file mode 100644 index 00000000..e928b3a2 --- /dev/null +++ b/docs/ux-ui/acceptance/s3-guard-preflight-2026-10-14.json @@ -0,0 +1,64 @@ +{ + "date": "2026-10-14", + "block": "12.5.228", + "scope": "Local safety guards and dry-run only; no credentials/endpoints supplied by user yet", + "checks": [ + { + "case": "dry-run", + "expectedOutcomeVerified": true, + "credentialMarkersAbsent": true + }, + { + "case": "http", + "expectedOutcomeVerified": true, + "credentialMarkersAbsent": true + }, + { + "case": "credentials-in-url", + "expectedOutcomeVerified": true, + "credentialMarkersAbsent": true + }, + { + "case": "query-in-url", + "expectedOutcomeVerified": true, + "credentialMarkersAbsent": true + }, + { + "case": "invalid-url", + "expectedOutcomeVerified": true, + "credentialMarkersAbsent": true + }, + { + "case": "missing-scope", + "expectedOutcomeVerified": true, + "credentialMarkersAbsent": true + }, + { + "case": "missing-bucket", + "expectedOutcomeVerified": true, + "credentialMarkersAbsent": true + }, + { + "case": "missing-credentials", + "expectedOutcomeVerified": true, + "credentialMarkersAbsent": true + }, + { + "case": "missing-region", + "expectedOutcomeVerified": true, + "credentialMarkersAbsent": true + }, + { + "case": "invalid-path-style", + "expectedOutcomeVerified": true, + "credentialMarkersAbsent": true + }, + { + "case": "missing-live-confirmation", + "expectedOutcomeVerified": true, + "credentialMarkersAbsent": true + } + ], + "networkRequests": 0, + "realS3Accepted": false +}