chore(test): проверить REST за десятиминутным окном сессий

This commit is contained in:
Denozordec committed 2026-10-14 14:31:21 +07:00
1 parent 6617b776bb
commit 0216415953
8 files changed
+7296 -12

No files matched your search

@@ -1,9 +1,9 @@
import assert from 'node:assert/strict';import fs from 'node:fs';import path from 'node:path';import{pathToFileURL}from'node:url';import{createHash}from'node:crypto';
const vantage=process.env.MMAPP_TEST_VANTAGE;assert(['workstation','debian'].includes(vantage));const output=process.env.MMAPP_TEST_REPORT;assert(output);const hosts=['194.226.121.150','104.165.244.7'],root=path.resolve('backend/dist'),{MikrotikClient}=await import(pathToFileURL(root+'/services/mikrotik.js'));let rows;
if(vantage==='debian'){const{pool}=await import(pathToFileURL(root+'/db/index.js'));try{rows=(await pool.query("SELECT host,port,use_ssl,enabled,username,password FROM servers WHERE host IN ('194.226.121.150','104.165.244.7') ORDER BY host")).rows}finally{await pool.end()}}else{assert(process.env.MMAPP_TEST_CHR_USER&&process.env.MMAPP_TEST_CHR_PASSWORD);rows=hosts.map(host=>({host,port:80,use_ssl:false,enabled:true,username:process.env.MMAPP_TEST_CHR_USER,password:process.env.MMAPP_TEST_CHR_PASSWORD}))}assert.equal(rows.length,2);assert.deepEqual(rows.map(r=>r.host).sort(),[...hosts].sort());assert(rows.every(r=>r.enabled&&r.port===80&&!r.use_ssl));
const report={date:'2026-10-14',block:'12.5.236',vantage,credentialSource:vantage==='debian'?'stored-enabled-server-rows':'explicit-owned-test-env',startedAt:new Date().toISOString(),requests:[],diagnostics:[],configurationWrites:0,passwordChanges:0,rootCauseKnown:false};const sleep=ms=>new Promise(r=>setTimeout(r,ms));const clients=new Map(rows.map(r=>[r.host,new MikrotikClient({host:r.host,port:80,useSsl:false,verifySsl:false,username:r.username,password:r.password})]));
const cycles=Number(process.env.MMAPP_REST_CYCLES??8),periodMs=Number(process.env.MMAPP_REST_PERIOD_MS??12000),maxMs=Number(process.env.MMAPP_REST_MAX_MS??960000);assert(Number.isInteger(cycles)&&cycles>=1&&cycles<=32);assert(Number.isInteger(periodMs)&&periodMs>=1000&&periodMs<=60000);assert(Number.isInteger(maxMs)&&maxMs>=10000&&maxMs<=960000);const deadline=Date.now()+maxMs;const report={date:'2026-10-14',block:process.env.MMAPP_REST_BLOCK??'12.5.236',vantage,cycles,periodMs,maxMs,credentialSource:vantage==='debian'?'stored-enabled-server-rows':'explicit-owned-test-env',startedAt:new Date().toISOString(),requests:[],diagnostics:[],configurationWrites:0,passwordChanges:0,rootCauseKnown:false};const sleep=ms=>new Promise(r=>setTimeout(r,ms));const clients=new Map(rows.map(r=>[r.host,new MikrotikClient({host:r.host,port:80,useSsl:false,verifySsl:false,username:r.username,password:r.password})]));
function errorMetadata(e,body){let obj;try{obj=JSON.parse(body)}catch{}const detail=String(obj?.detail??'');return{errorName:e?.name??null,errorBodySha256:body?createHash('sha256').update(body).digest('hex'):null,errorKeys:obj&&typeof obj==='object'?Object.keys(obj).sort():[],detailCategory:/not enough permissions/i.test(detail)?'insufficient-policy':/not allowed/i.test(detail)?'not-allowed':/session closed/i.test(detail)?'session-closed':detail?'unclassified':'absent'}}
async function read(row,endpoint,mode,cycle,kind='sample'){const begin=Date.now(),record={host:row.host,path:endpoint,mode,cycle,kind,startedAt:new Date().toISOString()};let data;try{if(mode==='compiled-client'){data=await clients.get(row.host).get(endpoint,6000);record.status=200}else{const authorization='Basic '+Buffer.from(row.username+':'+row.password).toString('base64');const r=await fetch('http://'+row.host+'/rest'+endpoint,{headers:{authorization,Connection:'close'},signal:AbortSignal.timeout(6000)}),body=await r.text();record.status=r.status;record.basicChallenge=/Basic/i.test(r.headers.get('www-authenticate')??'');if(r.ok)data=JSON.parse(body);else Object.assign(record,errorMetadata(null,body))}}catch(e){record.status=e.statusCode??null;Object.assign(record,errorMetadata(e,e.body??''))}record.durationMs=Date.now()-begin;report.requests.push(record);await sleep(125);return data}
async function diagnostic(row,phase){const userRows=await read(row,'/user','compiled-client',0,'diagnostic'),users=Array.isArray(userRows)?userRows.filter(u=>u.name===row.username):[];const groups=await read(row,'/user/group','compiled-client',0,'diagnostic'),group=Array.isArray(groups)?groups.find(g=>g.name===users[0]?.group):null;const services=await read(row,'/ip/service','compiled-client',0,'diagnostic'),resource=await read(row,'/system/resource','compiled-client',0,'diagnostic'),logs=await read(row,'/log','compiled-client',0,'diagnostic');const auth=Array.isArray(logs)?logs.filter(l=>/login failure/i.test(String(l.message))):[];const events=auth.map(l=>{const message=String(l.message);return{time:l.time??null,via:message.match(/via (\S+)/)?.[1]??null,fromTestDebian:/from 161\.104\.46\.222\b/.test(message),forConfiguredUser:message.includes('for user '+row.username+' ')}});const www=Array.isArray(services)?services.filter(s=>s.name==='www'):[];report.diagnostics.push({host:row.host,phase,userPresent:users.length===1,userDisabled:users[0]?.disabled??null,userAddressRestriction:users[0]?.address??null,hasApiPolicy:group?String(group.policy).split(',').includes('api'):null,hasRestApiPolicy:group?String(group.policy).split(',').includes('rest-api'):null,wwwEnabled:www.some(s=>s.disabled==='false'),wwwMaxSessions:www.find(s=>s['max-sessions'])?.['max-sessions']??null,wwwDynamicRows:www.filter(s=>s.dynamic==='true').length,resource:resource?{version:resource.version,uptime:resource.uptime,cpuLoad:resource['cpu-load'],freeMemory:resource['free-memory']}:null,retainedAuthFailureCount:auth.length,recentFailures:events.slice(-15),retainedLogIsNotCurrentRate:true})}
async function diagnostic(row,phase){const userRows=await read(row,'/user','compiled-client',0,'diagnostic'),users=Array.isArray(userRows)?userRows.filter(u=>u.name===row.username):[];const groups=await read(row,'/user/group','compiled-client',0,'diagnostic'),group=Array.isArray(groups)?groups.find(g=>g.name===users[0]?.group):null;const active=await read(row,'/user/active','compiled-client',0,'diagnostic');const apiSessions=Array.isArray(active)?active.filter(s=>s.name===row.username&&s.via==='api').map(s=>createHash('sha256').update(String(s['.id'])+'|'+String(s.when)).digest('hex')).sort():null;const services=await read(row,'/ip/service','compiled-client',0,'diagnostic'),resource=await read(row,'/system/resource','compiled-client',0,'diagnostic'),logs=await read(row,'/log','compiled-client',0,'diagnostic');const auth=Array.isArray(logs)?logs.filter(l=>/login failure/i.test(String(l.message))):[];const events=auth.map(l=>{const message=String(l.message);return{time:l.time??null,via:message.match(/via (\S+)/)?.[1]??null,fromTestDebian:/from 161\.104\.46\.222\b/.test(message),forConfiguredUser:message.includes('for user '+row.username+' ')}});const www=Array.isArray(services)?services.filter(s=>s.name==='www'):[];report.diagnostics.push({host:row.host,phase,apiSessionFingerprints:apiSessions,retainedRestSessions:Array.isArray(active)?active.filter(s=>s.via==='rest-api').length:null,userPresent:users.length===1,userDisabled:users[0]?.disabled??null,userAddressRestriction:users[0]?.address??null,hasApiPolicy:group?String(group.policy).split(',').includes('api'):null,hasRestApiPolicy:group?String(group.policy).split(',').includes('rest-api'):null,wwwEnabled:www.some(s=>s.disabled==='false'),wwwMaxSessions:www.find(s=>s['max-sessions'])?.['max-sessions']??null,wwwDynamicRows:www.filter(s=>s.dynamic==='true').length,resource:resource?{version:resource.version,uptime:resource.uptime,cpuLoad:resource['cpu-load'],freeMemory:resource['free-memory']}:null,retainedAuthFailureCount:auth.length,recentFailures:events.slice(-15),retainedLogIsNotCurrentRate:true})}
function save(){const samples=report.requests.filter(r=>r.kind==='sample');report.completedAt=new Date().toISOString();report.summary={sampleRequests:samples.length,failedSamples:samples.filter(r=>r.status!==200).length,allRequests:report.requests.length,failures:report.requests.filter(r=>r.status!==200).map(r=>({host:r.host,path:r.path,mode:r.mode,status:r.status,kind:r.kind,startedAt:r.startedAt}))};fs.writeFileSync(output,JSON.stringify(report,null,2)+'\n')}
try{for(const row of rows)await diagnostic(row,'before');const started=Date.now(),paths=['/system/resource','/interface/gre','/routing/bgp/session','/routing/ospf/neighbor','/interface/wireguard/peers','/interface/vxlan'];for(let cycle=1;cycle<=8;cycle++){for(const row of rows)for(const endpoint of paths){const modes=cycle%2?['compiled-client','fetch-close']:['fetch-close','compiled-client'];for(const mode of modes){const previous=report.requests.length;await read(row,endpoint,mode,cycle);if(report.requests[previous].status!==200){await read(row,'/system/resource','fetch-close',cycle,'failure-control');await diagnostic(row,'failure-'+cycle)}}}save();console.log(JSON.stringify({vantage,cycle,samples:report.summary.sampleRequests,failed:report.summary.failedSamples}));if(cycle<8)await sleep(Math.max(0,started+cycle*12000-Date.now()))}for(const row of rows)await diagnostic(row,'after')}finally{save();console.log(JSON.stringify({vantage,summary:report.summary,rootCauseKnown:false}))}
try{for(const row of rows)await diagnostic(row,'before');const started=Date.now(),paths=['/system/resource','/interface/gre','/routing/bgp/session','/routing/ospf/neighbor','/interface/wireguard/peers','/interface/vxlan'];for(let cycle=1;cycle<=cycles&&Date.now()<deadline;cycle++){for(const row of rows)for(const endpoint of paths){const modes=cycle%2?['compiled-client','fetch-close']:['fetch-close','compiled-client'];for(const mode of modes){const previous=report.requests.length;await read(row,endpoint,mode,cycle);if(report.requests[previous].status!==200){await read(row,'/system/resource','fetch-close',cycle,'failure-control');await diagnostic(row,'failure-'+cycle)}}}save();console.log(JSON.stringify({vantage,cycle,samples:report.summary.sampleRequests,failed:report.summary.failedSamples}));if(cycle<cycles)await sleep(Math.max(0,Math.min(deadline,started+cycle*periodMs)-Date.now()))}for(const row of rows)await diagnostic(row,'after')}finally{save();console.log(JSON.stringify({vantage,summary:report.summary,rootCauseKnown:false}))}
+11 -1
View File
@@ -1,6 +1,6 @@
# UX/UI: протокол внешней приёмки 12.5
Актуальное продолжение 2026-10-14:237 — история бэкапов показывает подтверждённое отсутствие локального файла; скачивание/restore заблокированы, удаление только записи требует отдельного режима и receipt. Production SQL/API, 39 уникальных E2E и live UI ПК/телефон приняты в записанных границах. Backend/frontend ux-backups237; исходные10 строк каталога сохранены без изменений. Следующий238 — длительное REST-наблюдение; затем S3 restore через UI и retention. Этап12.5 открыт.
Актуальное продолжение 2026-10-14:238 — REST наблюдался763.844s:624 paired samples +24 diagnostics, все200. На обоих CHR изменились API session records; retained REST rows844→846/320→322. Это не доказывает причину прежних401/500 или точный механизм cached-login renewal. Runtime backend/frontend ux-backups237; missing-file UX237 принят. Следующий239 — S3 restore через live UI в пределах собственного bridge и retention. Этап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,16 @@ 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.238 — REST за пределами десятиминутного окна (2026-10-14)
Read-only workstation-наблюдение длилось763.844s (07:01:21.663Z–07:14:05.507Z):26 циклов с целевым периодом30s, шесть endpoints × два CHR × compiled MikrotikClient/fetch Connection:close. 624 samples и24 diagnostics, все HTTP200; повторные запросы при отказе не выполнялись, потому что отказов не было. Учётные данные передавались из приватного process env, временный env-файл удалён; Basic/JWT/пароли в публичный отчёт не включены. RouterOS configurationWrites0/passwordChanges0.
На обоих CHR7.24.5 до/после было по2 записи via=api для configured user; SHA256 от id/when изменились. Retained via=rest-api rows844→846 и320→322. Наблюдалась смена записей сессий в окне более10 минут без отказов. Это не доказательство точного cached-login механизма7.24.5 и не контроль firmware: vendor docs уже описывают7.25. Найденный236 vendor fix удаления inactive REST sessions остаётся гипотезой; причины прежних401/500 не установлены. Большое число сохранённых rows нельзя приравнивать к concurrent www sessions или доказанному исчерпанию ресурса.
Acceptance helper chr-rest-correlated.mjs теперь принимает ограниченные cycles1–32, period1–60s и maxMs10–960s, сохраняя прежние defaults8/12s. Deadline проверяется между циклами, запросы имеют timeout6s; failure diagnostics/финальное чтение могут выйти за целевую длительность. Это не жёсткий end-to-end deadline. Сохранён именно протестированный helper; node --check passed. Production-код и Docker образы после237 не менялись, полная регрессия не повторялась без основания.
Протокол: [REST763.844s](ux-ui/acceptance/chr238-rest-renewal.json). Следующий239 — реальный S3 restore через UI только собственного bridge без портов/адресов, затем retention в отдельном own scope. REST остаётся открытым для корреляции воспроизведённого сбоя/согласованного сравнения firmware; обновление RouterOS не выполнялось.
## 12.5.237 — отсутствующие файлы бэкапов и безопасное удаление записи (2026-10-14)
API добавляет необязательный localFileStatus: available/missing/unknown/not-stored. Локальные проверки ограничены восемью workers и не опрашивают S3. Ошибка доступа/каталог/некорректный путь не превращаются в «файл отсутствует». Для local/missing существующая таблица ReUI показывает «Нет файла», описание и блокирует скачивание/restore. Для both/missing остаётся запрос S3; состояние unknown и старый DTO не блокируются по догадке. Проверка наличия файла перед импортом остаётся обязательной на backend.
+11 -1
View File
@@ -1,9 +1,19 @@
# План реализации UX/UI по пятичасовым окнам
Актуальное продолжение 2026-10-14:237 — история бэкапов показывает подтверждённое отсутствие локального файла; скачивание/restore заблокированы, удаление только записи требует отдельного режима и receipt. Production SQL/API, 39 уникальных E2E и live UI ПК/телефон приняты в записанных границах. Backend/frontend ux-backups237; исходные10 строк каталога сохранены без изменений. Следующий238 — длительное REST-наблюдение; затем S3 restore через UI и retention. Этап12.5 открыт.
Актуальное продолжение 2026-10-14:238 — REST наблюдался763.844s:624 paired samples +24 diagnostics, все200. На обоих CHR изменились API session records; retained REST rows844→846/320→322. Это не доказывает причину прежних401/500 или точный механизм cached-login renewal. Runtime backend/frontend ux-backups237; missing-file UX237 принят. Следующий239 — S3 restore через live UI в пределах собственного bridge и retention. Этап12.5 открыт.
Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-14. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат.
## 12.5.238 — REST за пределами десятиминутного окна (2026-10-14)
Read-only workstation-наблюдение длилось763.844s (07:01:21.663Z–07:14:05.507Z):26 циклов с целевым периодом30s, шесть endpoints × два CHR × compiled MikrotikClient/fetch Connection:close. 624 samples и24 diagnostics, все HTTP200; повторные запросы при отказе не выполнялись, потому что отказов не было. Учётные данные передавались из приватного process env, временный env-файл удалён; Basic/JWT/пароли в публичный отчёт не включены. RouterOS configurationWrites0/passwordChanges0.
На обоих CHR7.24.5 до/после было по2 записи via=api для configured user; SHA256 от id/when изменились. Retained via=rest-api rows844→846 и320→322. Наблюдалась смена записей сессий в окне более10 минут без отказов. Это не доказательство точного cached-login механизма7.24.5 и не контроль firmware: vendor docs уже описывают7.25. Найденный236 vendor fix удаления inactive REST sessions остаётся гипотезой; причины прежних401/500 не установлены. Большое число сохранённых rows нельзя приравнивать к concurrent www sessions или доказанному исчерпанию ресурса.
Acceptance helper chr-rest-correlated.mjs теперь принимает ограниченные cycles1–32, period1–60s и maxMs10–960s, сохраняя прежние defaults8/12s. Deadline проверяется между циклами, запросы имеют timeout6s; failure diagnostics/финальное чтение могут выйти за целевую длительность. Это не жёсткий end-to-end deadline. Сохранён именно протестированный helper; node --check passed. Production-код и Docker образы после237 не менялись, полная регрессия не повторялась без основания.
Протокол: [REST763.844s](ux-ui/acceptance/chr238-rest-renewal.json). Следующий239 — реальный S3 restore через UI только собственного bridge без портов/адресов, затем retention в отдельном own scope. REST остаётся открытым для корреляции воспроизведённого сбоя/согласованного сравнения firmware; обновление RouterOS не выполнялось.
## 12.5.237 — отсутствующие файлы бэкапов и безопасное удаление записи (2026-10-14)
API добавляет необязательный localFileStatus: available/missing/unknown/not-stored. Локальные проверки ограничены восемью workers и не опрашивают S3. Ошибка доступа/каталог/некорректный путь не превращаются в «файл отсутствует». Для local/missing существующая таблица ReUI показывает «Нет файла», описание и блокирует скачивание/restore. Для both/missing остаётся запрос S3; состояние unknown и старый DTO не блокируются по догадке. Проверка наличия файла перед импортом остаётся обязательной на backend.
+11 -1
View File
@@ -1,6 +1,6 @@
# UX/UI: журнал реализации
Актуальное продолжение 2026-10-14:237 — история бэкапов показывает подтверждённое отсутствие локального файла; скачивание/restore заблокированы, удаление только записи требует отдельного режима и receipt. Production SQL/API, 39 уникальных E2E и live UI ПК/телефон приняты в записанных границах. Backend/frontend ux-backups237; исходные10 строк каталога сохранены без изменений. Следующий238 — длительное REST-наблюдение; затем S3 restore через UI и retention. Этап12.5 открыт.
Актуальное продолжение 2026-10-14:238 — REST наблюдался763.844s:624 paired samples +24 diagnostics, все200. На обоих CHR изменились API session records; retained REST rows844→846/320→322. Это не доказывает причину прежних401/500 или точный механизм cached-login renewal. Runtime backend/frontend ux-backups237; missing-file UX237 принят. Следующий239 — S3 restore через live UI в пределах собственного bridge и retention. Этап12.5 открыт.
Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md).
@@ -38,6 +38,16 @@
| 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.238 — REST за пределами десятиминутного окна (2026-10-14)
Read-only workstation-наблюдение длилось763.844s (07:01:21.663Z–07:14:05.507Z):26 циклов с целевым периодом30s, шесть endpoints × два CHR × compiled MikrotikClient/fetch Connection:close. 624 samples и24 diagnostics, все HTTP200; повторные запросы при отказе не выполнялись, потому что отказов не было. Учётные данные передавались из приватного process env, временный env-файл удалён; Basic/JWT/пароли в публичный отчёт не включены. RouterOS configurationWrites0/passwordChanges0.
На обоих CHR7.24.5 до/после было по2 записи via=api для configured user; SHA256 от id/when изменились. Retained via=rest-api rows844→846 и320→322. Наблюдалась смена записей сессий в окне более10 минут без отказов. Это не доказательство точного cached-login механизма7.24.5 и не контроль firmware: vendor docs уже описывают7.25. Найденный236 vendor fix удаления inactive REST sessions остаётся гипотезой; причины прежних401/500 не установлены. Большое число сохранённых rows нельзя приравнивать к concurrent www sessions или доказанному исчерпанию ресурса.
Acceptance helper chr-rest-correlated.mjs теперь принимает ограниченные cycles1–32, period1–60s и maxMs10–960s, сохраняя прежние defaults8/12s. Deadline проверяется между циклами, запросы имеют timeout6s; failure diagnostics/финальное чтение могут выйти за целевую длительность. Это не жёсткий end-to-end deadline. Сохранён именно протестированный helper; node --check passed. Production-код и Docker образы после237 не менялись, полная регрессия не повторялась без основания.
Протокол: [REST763.844s](ux-ui/acceptance/chr238-rest-renewal.json). Следующий239 — реальный S3 restore через UI только собственного bridge без портов/адресов, затем retention в отдельном own scope. REST остаётся открытым для корреляции воспроизведённого сбоя/согласованного сравнения firmware; обновление RouterOS не выполнялось.
## 12.5.237 — отсутствующие файлы бэкапов и безопасное удаление записи (2026-10-14)
API добавляет необязательный localFileStatus: available/missing/unknown/not-stored. Локальные проверки ограничены восемью workers и не опрашивают S3. Ошибка доступа/каталог/некорректный путь не превращаются в «файл отсутствует». Для local/missing существующая таблица ReUI показывает «Нет файла», описание и блокирует скачивание/restore. Для both/missing остаётся запрос S3; состояние unknown и старый DTO не блокируются по догадке. Проверка наличия файла перед импортом остаётся обязательной на backend.
+14 -4
View File
@@ -1,11 +1,21 @@
# Оставшаяся внешняя приёмка — 2026-10-14
Актуальное продолжение 2026-10-14:237 — история бэкапов показывает подтверждённое отсутствие локального файла; скачивание/restore заблокированы, удаление только записи требует отдельного режима и receipt. Production SQL/API, 39 уникальных E2E и live UI ПК/телефон приняты в записанных границах. Backend/frontend ux-backups237; исходные10 строк каталога сохранены без изменений. Следующий238 — длительное REST-наблюдение; затем S3 restore через UI и retention. Этап12.5 открыт.
Актуальное продолжение 2026-10-14:238 — REST наблюдался763.844s:624 paired samples +24 diagnostics, все200. На обоих CHR изменились API session records; retained REST rows844→846/320→322. Это не доказывает причину прежних401/500 или точный механизм cached-login renewal. Runtime backend/frontend ux-backups237; missing-file UX237 принят. Следующий239 — S3 restore через live UI в пределах собственного bridge и retention. Этап12.5 открыт.
Это актуальная матрица после 12.5.237. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
Это актуальная матрица после 12.5.238. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 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.238 — REST за пределами десятиминутного окна (2026-10-14)
Read-only workstation-наблюдение длилось763.844s (07:01:21.663Z–07:14:05.507Z):26 циклов с целевым периодом30s, шесть endpoints × два CHR × compiled MikrotikClient/fetch Connection:close. 624 samples и24 diagnostics, все HTTP200; повторные запросы при отказе не выполнялись, потому что отказов не было. Учётные данные передавались из приватного process env, временный env-файл удалён; Basic/JWT/пароли в публичный отчёт не включены. RouterOS configurationWrites0/passwordChanges0.
На обоих CHR7.24.5 до/после было по2 записи via=api для configured user; SHA256 от id/when изменились. Retained via=rest-api rows844→846 и320→322. Наблюдалась смена записей сессий в окне более10 минут без отказов. Это не доказательство точного cached-login механизма7.24.5 и не контроль firmware: vendor docs уже описывают7.25. Найденный236 vendor fix удаления inactive REST sessions остаётся гипотезой; причины прежних401/500 не установлены. Большое число сохранённых rows нельзя приравнивать к concurrent www sessions или доказанному исчерпанию ресурса.
Acceptance helper chr-rest-correlated.mjs теперь принимает ограниченные cycles1–32, period1–60s и maxMs10–960s, сохраняя прежние defaults8/12s. Deadline проверяется между циклами, запросы имеют timeout6s; failure diagnostics/финальное чтение могут выйти за целевую длительность. Это не жёсткий end-to-end deadline. Сохранён именно протестированный helper; node --check passed. Production-код и Docker образы после237 не менялись, полная регрессия не повторялась без основания.
Протокол: [REST763.844s](ux-ui/acceptance/chr238-rest-renewal.json). Следующий239 — реальный S3 restore через UI только собственного bridge без портов/адресов, затем retention в отдельном own scope. REST остаётся открытым для корреляции воспроизведённого сбоя/согласованного сравнения firmware; обновление RouterOS не выполнялось.
## 12.5.237 — отсутствующие файлы бэкапов и безопасное удаление записи (2026-10-14)
API добавляет необязательный localFileStatus: available/missing/unknown/not-stored. Локальные проверки ограничены восемью workers и не опрашивают S3. Ошибка доступа/каталог/некорректный путь не превращаются в «файл отсутствует». Для local/missing существующая таблица ReUI показывает «Нет файла», описание и блокирует скачивание/restore. Для both/missing остаётся запрос S3; состояние unknown и старый DTO не блокируются по догадке. Проверка наличия файла перед импортом остаётся обязательной на backend.
@@ -30,7 +40,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 — гипотеза; причинность401/500,10min renewal/version control остаются открытыми |
| 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 принят; S3 restore через UI, миграция между buckets/multi-instance и retention; provider/container/Gitea/MaxMind jobs с соответствующими endpoints/аккаунтами; private/paid credentials не выдумывать |
@@ -60,7 +70,7 @@ Portal offline JWT действует доexp после logout/role change; э
## Следующая последовательность — 2026-10-14
1. **238 — REST длительное окно:** сохранить протокол наблюдения более10 минут и изменения API session records без недоказанного объяснения прежних401/500. Затем — S3 restore через UI и retention только собственных объектов.
1. **239 — S3 restore через UI:** только собственный bridge без портов/адресов; отдельный prefix/catalog row, сверка результата и очистки. Затем — retention только собственных объектов.
2. **Профильный контроль:** для новых изменений выполнять затронутые SQL/API/UI проверки. Общая регрессия192 выполнена; не повторять её без изменений общей основы или нового основания.
3. **Внешние зависимости:** Portal issuer/login/revoke, Telegram bot/chat/разрешённое сообщение, клиентские ОС, независимые WAN/public IPv6 и scale/multi-instance — отдельные группы по таблице выше. Ограничения текущего стенда не превращать в обещание приёмки всех конфигураций.
+11 -1
View File
@@ -1,6 +1,6 @@
# Этап 12: итоговая приёмка UX/UI
Актуальное продолжение 2026-10-14:237 — история бэкапов показывает подтверждённое отсутствие локального файла; скачивание/restore заблокированы, удаление только записи требует отдельного режима и receipt. Production SQL/API, 39 уникальных E2E и live UI ПК/телефон приняты в записанных границах. Backend/frontend ux-backups237; исходные10 строк каталога сохранены без изменений. Следующий238 — длительное REST-наблюдение; затем S3 restore через UI и retention. Этап12.5 открыт.
Актуальное продолжение 2026-10-14:238 — REST наблюдался763.844s:624 paired samples +24 diagnostics, все200. На обоих CHR изменились API session records; retained REST rows844→846/320→322. Это не доказывает причину прежних401/500 или точный механизм cached-login renewal. Runtime backend/frontend ux-backups237; missing-file UX237 принят. Следующий239 — S3 restore через live UI в пределах собственного bridge и retention. Этап12.5 открыт.
План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push.
@@ -4152,6 +4152,16 @@ 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.238 — REST за пределами десятиминутного окна (2026-10-14)
Read-only workstation-наблюдение длилось763.844s (07:01:21.663Z–07:14:05.507Z):26 циклов с целевым периодом30s, шесть endpoints × два CHR × compiled MikrotikClient/fetch Connection:close. 624 samples и24 diagnostics, все HTTP200; повторные запросы при отказе не выполнялись, потому что отказов не было. Учётные данные передавались из приватного process env, временный env-файл удалён; Basic/JWT/пароли в публичный отчёт не включены. RouterOS configurationWrites0/passwordChanges0.
На обоих CHR7.24.5 до/после было по2 записи via=api для configured user; SHA256 от id/when изменились. Retained via=rest-api rows844→846 и320→322. Наблюдалась смена записей сессий в окне более10 минут без отказов. Это не доказательство точного cached-login механизма7.24.5 и не контроль firmware: vendor docs уже описывают7.25. Найденный236 vendor fix удаления inactive REST sessions остаётся гипотезой; причины прежних401/500 не установлены. Большое число сохранённых rows нельзя приравнивать к concurrent www sessions или доказанному исчерпанию ресурса.
Acceptance helper chr-rest-correlated.mjs теперь принимает ограниченные cycles1–32, period1–60s и maxMs10–960s, сохраняя прежние defaults8/12s. Deadline проверяется между циклами, запросы имеют timeout6s; failure diagnostics/финальное чтение могут выйти за целевую длительность. Это не жёсткий end-to-end deadline. Сохранён именно протестированный helper; node --check passed. Production-код и Docker образы после237 не менялись, полная регрессия не повторялась без основания.
Протокол: [REST763.844s](ux-ui/acceptance/chr238-rest-renewal.json). Следующий239 — реальный S3 restore через UI только собственного bridge без портов/адресов, затем retention в отдельном own scope. REST остаётся открытым для корреляции воспроизведённого сбоя/согласованного сравнения firmware; обновление RouterOS не выполнялось.
## 12.5.237 — отсутствующие файлы бэкапов и безопасное удаление записи (2026-10-14)
API добавляет необязательный localFileStatus: available/missing/unknown/not-stored. Локальные проверки ограничены восемью workers и не опрашивают S3. Ошибка доступа/каталог/некорректный путь не превращаются в «файл отсутствует». Для local/missing существующая таблица ReUI показывает «Нет файла», описание и блокирует скачивание/restore. Для both/missing остаётся запрос S3; состояние unknown и старый DTO не блокируются по догадке. Проверка наличия файла перед импортом остаётся обязательной на backend.
+11 -1
View File
@@ -1,6 +1,6 @@
# Тестовый сервер UX/UI — 2026-10-10
Актуальное продолжение 2026-10-14:237 — история бэкапов показывает подтверждённое отсутствие локального файла; скачивание/restore заблокированы, удаление только записи требует отдельного режима и receipt. Production SQL/API, 39 уникальных E2E и live UI ПК/телефон приняты в записанных границах. Backend/frontend ux-backups237; исходные10 строк каталога сохранены без изменений. Следующий238 — длительное REST-наблюдение; затем S3 restore через UI и retention. Этап12.5 открыт.
Актуальное продолжение 2026-10-14:238 — REST наблюдался763.844s:624 paired samples +24 diagnostics, все200. На обоих CHR изменились API session records; retained REST rows844→846/320→322. Это не доказывает причину прежних401/500 или точный механизм cached-login renewal. Runtime backend/frontend ux-backups237; missing-file UX237 принят. Следующий239 — S3 restore через live UI в пределах собственного bridge и retention. Этап12.5 открыт.
| Параметр | Проверенное состояние |
| --- | --- |
@@ -34,6 +34,16 @@ 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.238 — REST за пределами десятиминутного окна (2026-10-14)
Read-only workstation-наблюдение длилось763.844s (07:01:21.663Z–07:14:05.507Z):26 циклов с целевым периодом30s, шесть endpoints × два CHR × compiled MikrotikClient/fetch Connection:close. 624 samples и24 diagnostics, все HTTP200; повторные запросы при отказе не выполнялись, потому что отказов не было. Учётные данные передавались из приватного process env, временный env-файл удалён; Basic/JWT/пароли в публичный отчёт не включены. RouterOS configurationWrites0/passwordChanges0.
На обоих CHR7.24.5 до/после было по2 записи via=api для configured user; SHA256 от id/when изменились. Retained via=rest-api rows844→846 и320→322. Наблюдалась смена записей сессий в окне более10 минут без отказов. Это не доказательство точного cached-login механизма7.24.5 и не контроль firmware: vendor docs уже описывают7.25. Найденный236 vendor fix удаления inactive REST sessions остаётся гипотезой; причины прежних401/500 не установлены. Большое число сохранённых rows нельзя приравнивать к concurrent www sessions или доказанному исчерпанию ресурса.
Acceptance helper chr-rest-correlated.mjs теперь принимает ограниченные cycles1–32, period1–60s и maxMs10–960s, сохраняя прежние defaults8/12s. Deadline проверяется между циклами, запросы имеют timeout6s; failure diagnostics/финальное чтение могут выйти за целевую длительность. Это не жёсткий end-to-end deadline. Сохранён именно протестированный helper; node --check passed. Production-код и Docker образы после237 не менялись, полная регрессия не повторялась без основания.
Протокол: [REST763.844s](ux-ui/acceptance/chr238-rest-renewal.json). Следующий239 — реальный S3 restore через UI только собственного bridge без портов/адресов, затем retention в отдельном own scope. REST остаётся открытым для корреляции воспроизведённого сбоя/согласованного сравнения firmware; обновление RouterOS не выполнялось.
## 12.5.237 — отсутствующие файлы бэкапов и безопасное удаление записи (2026-10-14)
API добавляет необязательный localFileStatus: available/missing/unknown/not-stored. Локальные проверки ограничены восемью workers и не опрашивают S3. Ошибка доступа/каталог/некорректный путь не превращаются в «файл отсутствует». Для local/missing существующая таблица ReUI показывает «Нет файла», описание и блокирует скачивание/restore. Для both/missing остаётся запрос S3; состояние unknown и старый DTO не блокируются по догадке. Проверка наличия файла перед импортом остаётся обязательной на backend.
File diff suppressed because it is too large. Load diff