diff --git a/backend/scripts/acceptance/wan-runtime-sql.mjs b/backend/scripts/acceptance/wan-runtime-sql.mjs new file mode 100644 index 00000000..14f68b51 --- /dev/null +++ b/backend/scripts/acceptance/wan-runtime-sql.mjs @@ -0,0 +1,27 @@ +import assert from 'node:assert/strict' +import fs from 'node:fs' +import {createServer} from 'node:http' +import {randomBytes} from 'node:crypto' +const database=process.env.MMAPP_OWN_SQL_DB,url=new URL(process.env.DATABASE_URL) +assert(/^mm_accept_166_\d+$/.test(database??''));assert.equal(url.pathname,'/'+database);assert.equal(url.username,database) +process.env.AUTH_REQUIRED='true';process.env.AUTH_JWT_SECRET=randomBytes(32).toString('hex');process.env.AUTH_ISSUER='https://own217.invalid';process.env.AUTH_PORTAL_URL='https://own217.invalid' +const {pool,closePool}=await import('/app/backend/dist/db/index.js'),{initDatabase}=await import('/app/backend/dist/db/bootstrap.js'),{buildApp}=await import('/app/backend/dist/index.js') +assert.equal((await pool.query('SELECT current_database() AS name')).rows[0].name,database);assert(Object.values((await pool.query('SELECT rolsuper,rolcreatedb,rolcreaterole,rolbypassrls FROM pg_roles WHERE rolname=current_user')).rows[0]).every(v=>v===false)) +const baseline=process.env.MMAPP_BASELINE==='true',cases=[];let phase='populated',nativeCalls=0,nativeWrites=0,app +const collections={dhcp:[{interface:'ether1',address:'192.0.2.10/24',gateway:'192.0.2.254',status:'bound'}],addresses:[{address:'192.0.2.10/24',interface:'ether1'}],routes:[{'dst-address':'0.0.0.0/0',gateway:'192.0.2.254','immediate-gw':'192.0.2.254%ether1',active:'true',distance:'1'}]} +const native=createServer((req,res)=>{nativeCalls++;if(req.method!=='GET')nativeWrites++;res.setHeader('content-type','application/json');const section=req.url==='/rest/ip/dhcp-client'?'dhcp':req.url==='/rest/ip/address'?'addresses':'routes';if(phase==='fail-'+section){res.statusCode=401;res.end(JSON.stringify({error:401,message:'own217 denied'}));return}res.end(JSON.stringify(phase==='malformed-'+section?[{unknown:'fixture'}]:phase==='empty'?[]:collections[section]))});await new Promise(r=>native.listen(0,'127.0.0.1',r)) +const report={date:'2026-10-14',block:'12.5.217',baseline,database,cases,scope:'Compiled WAN runtime route/auth, owned SQL/native HTTP fixture; real CHR untouched',passed:false} +try{ + await initDatabase();const uplinks=[{id:'wan1',iface:'ether1',name:'WAN-test',isp:'Fixture',ip:'198.51.100.10',maxDl:100,maxUl:50}],id=Number((await pool.query("INSERT INTO servers(name,host,port,use_ssl,enabled,username,password,type,wan_uplinks) VALUES('own217-native','127.0.0.1',$1,false,true,'fixture','fixture','home-router',$2::jsonb) RETURNING id",[native.address().port,JSON.stringify(uplinks)])).rows[0].id) + app=await buildApp({logger:false,startScheduler:false});await app.ready();const token=permissions=>app.jwt.sign({sub:'own217-reader',apps:['mm'],permissions,iss:process.env.AUTH_ISSUER},{expiresIn:'5m'}),headers={authorization:'Bearer '+token(['mm:servers:read'])},endpoint='/api/servers/'+id+'/wan-runtime' + for(const [name,h,status] of [['anonymous',{},401],['wrong permission',{authorization:'Bearer '+token(['mm:network:read'])},403]]){const before=nativeCalls,r=await app.inject({method:'GET',url:endpoint,headers:h});assert.equal(r.statusCode,status);assert.equal(nativeCalls,before);cases.push({phase:name,status,nativeCalls:0})} + for(phase of ['populated','empty','fail-dhcp','fail-addresses','fail-routes','malformed-dhcp','malformed-addresses','malformed-routes','recovery']){ + const before=nativeCalls,r=await app.inject({method:'GET',url:endpoint,headers}),body=r.json(),failed=phase.startsWith('fail-')||phase.startsWith('malformed-');assert.equal(r.statusCode,failed&&!baseline?502:200) + if(failed&&!baseline)assert.match(body.error,/WAN:/) + else{assert.deepEqual(Object.keys(body).sort(),['defaultGateway','defaultInterface','immediateGateway','uplinks']);assert.equal(body.uplinks.length,1);if(['populated','recovery'].includes(phase)){assert.equal(body.defaultGateway,'192.0.2.254');assert.equal(body.defaultInterface,'ether1');assert.equal(body.uplinks[0].leasedIp,'192.0.2.10');assert.equal(body.uplinks[0].isDefault,true)}if(phase==='empty'){assert.equal(body.defaultGateway,null);assert.equal(body.uplinks[0].leasedIp,null);assert.equal(body.uplinks[0].isDefault,false)}} + cases.push({phase,status:r.statusCode,nativeCalls:nativeCalls-before}) + } + phase='populated';for(const type of ['home-router','exit-node']){await pool.query('UPDATE servers SET enabled=false,type=$1 WHERE id=$2',[type,id]);const before=nativeCalls,r=await app.inject({method:'GET',url:endpoint,headers});assert.equal(r.statusCode,baseline?200:409);if(!baseline){assert.equal(nativeCalls,before);assert.match(r.json().error,/выключен/)}else assert(nativeCalls>before);cases.push({phase:'disabled',type,status:r.statusCode,nativeCalls:nativeCalls-before})} + for(const [path,status] of [['/api/servers/9999999/wan-runtime',404],['/api/servers/1oops/wan-runtime',400]]){const before=nativeCalls,r=await app.inject({method:'GET',url:path,headers});assert.equal(r.statusCode,status);assert.equal(nativeCalls,before);cases.push({phase:'missing-invalid',status,nativeCalls:0})} + assert.equal(nativeWrites,0);report.passed=true +}catch(e){report.errorName=e.name;fs.writeFileSync('/app/backend/acceptance/wan217-private.log',String(e.stack??e),{mode:0o600})}finally{if(app)await app.close();native.closeAllConnections();await new Promise(r=>native.close(r));await closePool();report.nativeWrites=nativeWrites;fs.writeFileSync('/app/backend/acceptance/wan217-'+(baseline?'baseline':'fixed')+'.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report))}if(!report.passed)process.exitCode=1 diff --git a/backend/src/routes/servers.ts b/backend/src/routes/servers.ts index 47c2a0ba..3a0fc5c1 100644 --- a/backend/src/routes/servers.ts +++ b/backend/src/routes/servers.ts @@ -24,6 +24,7 @@ import { } from "../modules/servers/service/servers-service.js" import { getServerRowById } from "../modules/servers/repository/servers-repository.js" import {serverReadForUser} from "../modules/servers/mapper/servers-mapper.js" +import { readInternetPathRows } from "../services/internet-path-snapshot.js" // ── plugin ───────────────────────────────────────────────────────────────────── @@ -96,6 +97,7 @@ const serversRoutes: FastifyPluginAsyncZod = async (app) => { const params = req.params as ServerIdParams const server = await getServerReadById(params.id) if (!server) return reply.status(404).send({ error: "Server not found" }) + if (server.enabled === false) return reply.status(409).send({ error: "Сервер выключен. Опрос WAN не выполнялся" }) const toIp = (raw: string | null | undefined): string | null => { const v = String(raw ?? "").trim() @@ -144,15 +146,16 @@ const serversRoutes: FastifyPluginAsyncZod = async (app) => { try { const row = await getServerRowById(params.id) if (!row) return reply.status(404).send({ error: "Server not found" }) + if (row.enabled === false) return reply.status(409).send({ error: "Сервер выключен. Опрос WAN не выполнялся" }) const client = MikrotikClient.fromServer(row) const isTrue = (v: unknown) => { const s = String(v ?? "").trim().toLowerCase() return s === "true" || s === "yes" } const [dhcpRaw, ipAddrs, routes] = await Promise.all([ - client.get>>("/ip/dhcp-client").catch(() => []), - client.getIpAddresses().catch(() => []), - client.getIpRoutes().catch(() => []), + client.get("/ip/dhcp-client").then(raw => readInternetPathRows(raw, "WAN DHCP", ["interface"])), + client.get("/ip/address").then(raw => readInternetPathRows(raw, "WAN addresses", ["address", "interface"])), + client.get("/ip/route").then(raw => readInternetPathRows(raw, "WAN routes", ["dst-address"])), ]) const defaultRoute = routes @@ -225,7 +228,7 @@ const serversRoutes: FastifyPluginAsyncZod = async (app) => { uplinks, }) } catch (err) { - return reply.status(502).send({ error: err instanceof Error ? err.message : "Failed to read WAN runtime" }) + return reply.status(502).send({ error: err instanceof Error ? `WAN: ${err.message}` : "Не удалось прочитать состояние WAN" }) } }) diff --git a/docs/ux-ui-external-acceptance.md b/docs/ux-ui-external-acceptance.md index 4e9ab420..e1fa7e12 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: блок216 завершён. Legacy BGP GET и реальный источник BGP snapshots больше не скрывают неполный опрос. Ошибка наблюдения записывается в snapshot; GRE сохраняется. Пять профильных скриптов, SQL/native17 и live API2/anonymous2/native2/UI1440/320 прошли. Backend ux-bgp216/compiled1052/source483 matched, frontend ux-gre214; собственные DB/roles удалены, пароли CHR не менялись. Следующий217 — WAN runtime: скрытые read errors и disabled no-poll. Этап12.5 открыт. +Актуальное продолжение 2026-10-14: блок217 завершён. Legacy WAN runtime больше не скрывает ошибки DHCP/addresses/routes и не опрашивает выключенные Home Router/Exit Node. Empty success сохранён. Production15, профиль snapshot/build, live API2/native6/anonymous2/dashboard smoke1440/320 прошли. Backend ux-wan217/compiled1052/source484 matched, frontend ux-gre214; own DB/roles удалены, пароли не менялись. Следующий218 — доступность заполненных таблиц маршрутизации. Этап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). @@ -2591,3 +2591,7 @@ Production compiled SQL/API29 на собственной ограниченно ## 12.5.216 — BGP snapshots (2026-10-14) Неполный BGP poll больше не считается успешным пустым ответом/снимком. GRE при ошибке BGP сохраняется. Production17 и реальный API/UI приняты. Подробности и границы — [журнал](ux-ui-progress.md). Backend ux-bgp216, frontend ux-gre214; следующий217 — WAN runtime. + +## 12.5.217 — WAN runtime (2026-10-14) + +Read failures/malformed collections дают502, выключенные устройства409 без native poll, empty success сохранён. Production15 и реальная сверка API/native прошли. Подробности и границы — [журнал](ux-ui-progress.md). Backend ux-wan217, frontend ux-gre214; следующий218 — routing table accessibility. diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index 24c27c04..992b7d76 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -1,6 +1,6 @@ # План реализации UX/UI по пятичасовым окнам -Актуальное продолжение 2026-10-14: блок216 завершён. Legacy BGP GET и реальный источник BGP snapshots больше не скрывают неполный опрос. Ошибка наблюдения записывается в snapshot; GRE сохраняется. Пять профильных скриптов, SQL/native17 и live API2/anonymous2/native2/UI1440/320 прошли. Backend ux-bgp216/compiled1052/source483 matched, frontend ux-gre214; собственные DB/roles удалены, пароли CHR не менялись. Следующий217 — WAN runtime: скрытые read errors и disabled no-poll. Этап12.5 открыт. +Актуальное продолжение 2026-10-14: блок217 завершён. Legacy WAN runtime больше не скрывает ошибки DHCP/addresses/routes и не опрашивает выключенные Home Router/Exit Node. Empty success сохранён. Production15, профиль snapshot/build, live API2/native6/anonymous2/dashboard smoke1440/320 прошли. Backend ux-wan217/compiled1052/source484 matched, frontend ux-gre214; own DB/roles удалены, пароли не менялись. Следующий218 — доступность заполненных таблиц маршрутизации. Этап12.5 открыт. Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-13. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат. diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index b66f2328..f72ac053 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -1,6 +1,6 @@ # UX/UI: журнал реализации -Актуальное продолжение 2026-10-14: блок216 завершён. Legacy BGP GET и реальный источник BGP snapshots больше не скрывают неполный опрос. Ошибка наблюдения записывается в snapshot; GRE сохраняется. Пять профильных скриптов, SQL/native17 и live API2/anonymous2/native2/UI1440/320 прошли. Backend ux-bgp216/compiled1052/source483 matched, frontend ux-gre214; собственные DB/roles удалены, пароли CHR не менялись. Следующий217 — WAN runtime: скрытые read errors и disabled no-poll. Этап12.5 открыт. +Актуальное продолжение 2026-10-14: блок217 завершён. Legacy WAN runtime больше не скрывает ошибки DHCP/addresses/routes и не опрашивает выключенные Home Router/Exit Node. Empty success сохранён. Production15, профиль snapshot/build, live API2/native6/anonymous2/dashboard smoke1440/320 прошли. Backend ux-wan217/compiled1052/source484 matched, frontend ux-gre214; own DB/roles удалены, пароли не менялись. Следующий218 — доступность заполненных таблиц маршрутизации. Этап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–196 и ограничения ниже. Runtime backend/frontend ux-peak196. | +## 12.5.217 — достоверный WAN runtime и disabled no-poll (2026-10-14) + +/api/servers/:id/wan-runtime подавлял ошибки DHCP, addresses и routes через [] и опрашивал выключенные устройства. Добавлены409 перед native client (с повторной проверкой актуальной DB row) и строгая валидация трёх коллекций тем же readInternetPathRows, что используется Internet path collector. Read failure/malformed row возвращает502 с WAN error; успешный envelope defaultGateway/immediateGateway/defaultInterface/uplinks и корректный empty200 сохранены. Методы/права/алгоритм выбора default route не менялись. Активный dashboard читает сохранённый Internet path, поэтому legacy WAN endpoint не объявляется его единственным источником. + +Baseline ux-bgp216 и fixed production ux-wan217 прошли по15 own SQL/native HTTP случаев: anonymous/wrong permission без native reads; populated/empty; отдельный401 и malformed row каждой коллекции; recovery; disabled Home Router/Exit Node; missing/invalid ID. Baseline подтверждает200 при ошибках и native reads выключенных устройств; fixed502/409. Native writes0, own DB/roles удалены. Профиль internet-path-snapshot и backend build прошли. + +Compiled1052/source484 matched; backend healthy, frontend ux-gre214 сохранён. Реальный Portal JWT: WAN GET200 обоих CHR сверены с шестью native DHCP/address/route GET200; gateway/immediateGateway/имеющиеся leases совпали, anonymous401. Dashboard1440/320 smoke без page errors/mutations не является тестом прямого legacy WAN consumer или независимого multi-WAN маршрута. Пароли/сетевые настройки не менялись; physical WAN loss и полный route provenance остаются отдельной приёмкой. + +Протоколы: [baseline](ux-ui/acceptance/wan217-baseline.json), [fixed](ux-ui/acceptance/wan217-fixed.json), [build](ux-ui/acceptance/wan217-build-proof-2026-10-14.json), [deployment](ux-ui/acceptance/wan217-deployment-2026-10-14.json), [live](ux-ui/acceptance/wan217-live-2026-10-14.json). Следующий218 — accessibility заполненных таблиц маршрутизации: контраст статусов и метрик в light/dark на ПК/телефоне. + ## 12.5.216 — неполный BGP опрос в legacy API и snapshots (2026-10-14) Legacy /api/bgp/sessions возвращал sessions, отбрасывая failures. fetchBgpSessionsForAlerts также подавлял native errors через пустые массивы, и collector отмечал такой снимок без ошибки источника. Теперь endpoint возвращает503+failures, а источник snapshots отвергает неполный опрос. Collector записывает BGP error, сохраняет независимо прочитанный GRE; права, rules/evaluation и формат успешного массива не менялись. /bgp/catalog сохраняет отдельный контракт частичных результатов. diff --git a/docs/ux-ui-remaining-acceptance.md b/docs/ux-ui-remaining-acceptance.md index a9b837ca..26029bf5 100644 --- a/docs/ux-ui-remaining-acceptance.md +++ b/docs/ux-ui-remaining-acceptance.md @@ -1,10 +1,10 @@ # Оставшаяся внешняя приёмка — 2026-10-13 -Актуальное продолжение 2026-10-14: блок216 завершён. Legacy BGP GET и реальный источник BGP snapshots больше не скрывают неполный опрос. Ошибка наблюдения записывается в snapshot; GRE сохраняется. Пять профильных скриптов, SQL/native17 и live API2/anonymous2/native2/UI1440/320 прошли. Backend ux-bgp216/compiled1052/source483 matched, frontend ux-gre214; собственные DB/roles удалены, пароли CHR не менялись. Следующий217 — WAN runtime: скрытые read errors и disabled no-poll. Этап12.5 открыт. +Актуальное продолжение 2026-10-14: блок217 завершён. Legacy WAN runtime больше не скрывает ошибки DHCP/addresses/routes и не опрашивает выключенные Home Router/Exit Node. Empty success сохранён. Production15, профиль snapshot/build, live API2/native6/anonymous2/dashboard smoke1440/320 прошли. Backend ux-wan217/compiled1052/source484 matched, frontend ux-gre214; own DB/roles удалены, пароли не менялись. Следующий218 — доступность заполненных таблиц маршрутизации. Этап12.5 открыт. -Это актуальная матрица после 12.5.216. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии. +Это актуальная матрица после 12.5.217. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии. -Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-bgp216`, frontend `ux-gre214`; production lab с195 использует AUTH_REQUIRED=true и Portal1.3.1. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись. +Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-wan217`, frontend `ux-gre214`; production lab с195 использует AUTH_REQUIRED=true и Portal1.3.1. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись. | Раздел | Подтверждено | Осталось / условие завершения | | --- | --- | --- | @@ -1509,3 +1509,7 @@ Production compiled SQL/API29 на собственной ограниченно ## 12.5.216 — BGP snapshots (2026-10-14) Неполный BGP poll больше не считается успешным пустым ответом/снимком. GRE при ошибке BGP сохраняется. Production17 и реальный API/UI приняты. Подробности и границы — [журнал](ux-ui-progress.md). Backend ux-bgp216, frontend ux-gre214; следующий217 — WAN runtime. + +## 12.5.217 — WAN runtime (2026-10-14) + +Read failures/malformed collections дают502, выключенные устройства409 без native poll, empty success сохранён. Production15 и реальная сверка API/native прошли. Подробности и границы — [журнал](ux-ui-progress.md). Backend ux-wan217, frontend ux-gre214; следующий218 — routing table accessibility. diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index f3596e35..dfb14228 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: блок216 завершён. Legacy BGP GET и реальный источник BGP snapshots больше не скрывают неполный опрос. Ошибка наблюдения записывается в snapshot; GRE сохраняется. Пять профильных скриптов, SQL/native17 и live API2/anonymous2/native2/UI1440/320 прошли. Backend ux-bgp216/compiled1052/source483 matched, frontend ux-gre214; собственные DB/roles удалены, пароли CHR не менялись. Следующий217 — WAN runtime: скрытые read errors и disabled no-poll. Этап12.5 открыт. +Актуальное продолжение 2026-10-14: блок217 завершён. Legacy WAN runtime больше не скрывает ошибки DHCP/addresses/routes и не опрашивает выключенные Home Router/Exit Node. Empty success сохранён. Production15, профиль snapshot/build, live API2/native6/anonymous2/dashboard smoke1440/320 прошли. Backend ux-wan217/compiled1052/source484 matched, frontend ux-gre214; own DB/roles удалены, пароли не менялись. Следующий218 — доступность заполненных таблиц маршрутизации. Этап12.5 открыт. План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push. @@ -3943,3 +3943,14 @@ Backend ux-bgp216 healthy: compiled1052/source483 matched; frontend ux-gre214 с Протоколы: [baseline](ux-ui/acceptance/bgp216-baseline.json), [fixed](ux-ui/acceptance/bgp216-fixed.json), [initial fixture failure](ux-ui/acceptance/bgp216-initial-fixture-failure.json), [build](ux-ui/acceptance/bgp216-build-proof-2026-10-14.json), [deployment](ux-ui/acceptance/bgp216-deployment-2026-10-14.json), [live](ux-ui/acceptance/bgp216-live-2026-10-14.json). Следующий217 — legacy WAN runtime: подавление ошибок чтения и опрос выключенного устройства. + +## 12.5.217 — достоверный WAN runtime и disabled no-poll (2026-10-14) + +/api/servers/:id/wan-runtime подавлял ошибки DHCP, addresses и routes через [] и опрашивал выключенные устройства. Добавлены409 перед native client (с повторной проверкой актуальной DB row) и строгая валидация трёх коллекций тем же readInternetPathRows, что используется Internet path collector. Read failure/malformed row возвращает502 с WAN error; успешный envelope defaultGateway/immediateGateway/defaultInterface/uplinks и корректный empty200 сохранены. Методы/права/алгоритм выбора default route не менялись. Активный dashboard читает сохранённый Internet path, поэтому legacy WAN endpoint не объявляется его единственным источником. + +Baseline ux-bgp216 и fixed production ux-wan217 прошли по15 own SQL/native HTTP случаев: anonymous/wrong permission без native reads; populated/empty; отдельный401 и malformed row каждой коллекции; recovery; disabled Home Router/Exit Node; missing/invalid ID. Baseline подтверждает200 при ошибках и native reads выключенных устройств; fixed502/409. Native writes0, own DB/roles удалены. Профиль internet-path-snapshot и backend build прошли. + +Compiled1052/source484 matched; backend healthy, frontend ux-gre214 сохранён. Реальный Portal JWT: WAN GET200 обоих CHR сверены с шестью native DHCP/address/route GET200; gateway/immediateGateway/имеющиеся leases совпали, anonymous401. Dashboard1440/320 smoke без page errors/mutations не является тестом прямого legacy WAN consumer или независимого multi-WAN маршрута. Пароли/сетевые настройки не менялись; physical WAN loss и полный route provenance остаются отдельной приёмкой. + +Протоколы: [baseline](ux-ui/acceptance/wan217-baseline.json), [fixed](ux-ui/acceptance/wan217-fixed.json), [build](ux-ui/acceptance/wan217-build-proof-2026-10-14.json), [deployment](ux-ui/acceptance/wan217-deployment-2026-10-14.json), [live](ux-ui/acceptance/wan217-live-2026-10-14.json). Следующий218 — accessibility заполненных таблиц маршрутизации: контраст статусов и метрик в light/dark на ПК/телефоне. + diff --git a/docs/ux-ui-test-server.md b/docs/ux-ui-test-server.md index 4c14710e..068ea261 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: блок216 завершён. Legacy BGP GET и реальный источник BGP snapshots больше не скрывают неполный опрос. Ошибка наблюдения записывается в snapshot; GRE сохраняется. Пять профильных скриптов, SQL/native17 и live API2/anonymous2/native2/UI1440/320 прошли. Backend ux-bgp216/compiled1052/source483 matched, frontend ux-gre214; собственные DB/roles удалены, пароли CHR не менялись. Следующий217 — WAN runtime: скрытые read errors и disabled no-poll. Этап12.5 открыт. +Актуальное продолжение 2026-10-14: блок217 завершён. Legacy WAN runtime больше не скрывает ошибки DHCP/addresses/routes и не опрашивает выключенные Home Router/Exit Node. Empty success сохранён. Production15, профиль snapshot/build, live API2/native6/anonymous2/dashboard smoke1440/320 прошли. Backend ux-wan217/compiled1052/source484 matched, frontend ux-gre214; own DB/roles удалены, пароли не менялись. Следующий218 — доступность заполненных таблиц маршрутизации. Этап12.5 открыт. | Параметр | Проверенное состояние | | --- | --- | @@ -2322,3 +2322,7 @@ Production compiled SQL/API29 на собственной ограниченно ## 12.5.216 — BGP snapshots (2026-10-14) Неполный BGP poll больше не считается успешным пустым ответом/снимком. GRE при ошибке BGP сохраняется. Production17 и реальный API/UI приняты. Подробности и границы — [журнал](ux-ui-progress.md). Backend ux-bgp216, frontend ux-gre214; следующий217 — WAN runtime. + +## 12.5.217 — WAN runtime (2026-10-14) + +Read failures/malformed collections дают502, выключенные устройства409 без native poll, empty success сохранён. Production15 и реальная сверка API/native прошли. Подробности и границы — [журнал](ux-ui-progress.md). Backend ux-wan217, frontend ux-gre214; следующий218 — routing table accessibility. diff --git a/docs/ux-ui/acceptance/wan217-baseline.json b/docs/ux-ui/acceptance/wan217-baseline.json new file mode 100644 index 00000000..d208de9a --- /dev/null +++ b/docs/ux-ui/acceptance/wan217-baseline.json @@ -0,0 +1,89 @@ +{ + "date": "2026-10-14", + "block": "12.5.217", + "baseline": true, + "database": "mm_accept_166_1791913409174", + "cases": [ + { + "phase": "anonymous", + "status": 401, + "nativeCalls": 0 + }, + { + "phase": "wrong permission", + "status": 403, + "nativeCalls": 0 + }, + { + "phase": "populated", + "status": 200, + "nativeCalls": 3 + }, + { + "phase": "empty", + "status": 200, + "nativeCalls": 3 + }, + { + "phase": "fail-dhcp", + "status": 200, + "nativeCalls": 3 + }, + { + "phase": "fail-addresses", + "status": 200, + "nativeCalls": 3 + }, + { + "phase": "fail-routes", + "status": 200, + "nativeCalls": 3 + }, + { + "phase": "malformed-dhcp", + "status": 200, + "nativeCalls": 3 + }, + { + "phase": "malformed-addresses", + "status": 200, + "nativeCalls": 3 + }, + { + "phase": "malformed-routes", + "status": 200, + "nativeCalls": 3 + }, + { + "phase": "recovery", + "status": 200, + "nativeCalls": 3 + }, + { + "phase": "disabled", + "type": "home-router", + "status": 200, + "nativeCalls": 3 + }, + { + "phase": "disabled", + "type": "exit-node", + "status": 200, + "nativeCalls": 3 + }, + { + "phase": "missing-invalid", + "status": 404, + "nativeCalls": 0 + }, + { + "phase": "missing-invalid", + "status": 400, + "nativeCalls": 0 + } + ], + "scope": "Compiled WAN runtime route/auth, owned SQL/native HTTP fixture; real CHR untouched", + "passed": true, + "nativeWrites": 0, + "ownedDatabaseAndRoleRemoved": true +} diff --git a/docs/ux-ui/acceptance/wan217-build-proof-2026-10-14.json b/docs/ux-ui/acceptance/wan217-build-proof-2026-10-14.json new file mode 100644 index 00000000..5ee6bddb --- /dev/null +++ b/docs/ux-ui/acceptance/wan217-build-proof-2026-10-14.json @@ -0,0 +1,19 @@ +{ + "date": "2026-10-14", + "block": "12.5.217", + "compiled": { + "matched": 1052, + "missing": 0, + "different": 0 + }, + "source": [ + { + "kind": "backend", + "matched": 484 + } + ], + "profileScripts": [ + "internet-path-snapshot" + ], + "productionCases": 15 +} diff --git a/docs/ux-ui/acceptance/wan217-deployment-2026-10-14.json b/docs/ux-ui/acceptance/wan217-deployment-2026-10-14.json new file mode 100644 index 00000000..9ed888b6 --- /dev/null +++ b/docs/ux-ui/acceptance/wan217-deployment-2026-10-14.json @@ -0,0 +1,21 @@ +{ + "date": "2026-10-14", + "block": "12.5.217", + "backendSourceMatched": 484, + "compiledMatched": 1052, + "frontendChanged": false, + "services": { + "backend": { + "image": "mmapp-test/backend:ux-wan217", + "imageId": "sha256:f5fac6167453e180e7aacb3b88de14e49860479dfcf46bc7a9ffd34db682e42b", + "running": true, + "health": "healthy" + }, + "frontend": { + "image": "mmapp-test/frontend:ux-gre214", + "imageId": "sha256:7035e5ba628276a9927cce4814739dadd920dba0606e3b9fc316cc0262f77d5f", + "running": true, + "health": null + } + } +} diff --git a/docs/ux-ui/acceptance/wan217-fixed.json b/docs/ux-ui/acceptance/wan217-fixed.json new file mode 100644 index 00000000..e2132a09 --- /dev/null +++ b/docs/ux-ui/acceptance/wan217-fixed.json @@ -0,0 +1,89 @@ +{ + "date": "2026-10-14", + "block": "12.5.217", + "baseline": false, + "database": "mm_accept_166_1791913523373", + "cases": [ + { + "phase": "anonymous", + "status": 401, + "nativeCalls": 0 + }, + { + "phase": "wrong permission", + "status": 403, + "nativeCalls": 0 + }, + { + "phase": "populated", + "status": 200, + "nativeCalls": 3 + }, + { + "phase": "empty", + "status": 200, + "nativeCalls": 3 + }, + { + "phase": "fail-dhcp", + "status": 502, + "nativeCalls": 3 + }, + { + "phase": "fail-addresses", + "status": 502, + "nativeCalls": 3 + }, + { + "phase": "fail-routes", + "status": 502, + "nativeCalls": 3 + }, + { + "phase": "malformed-dhcp", + "status": 502, + "nativeCalls": 3 + }, + { + "phase": "malformed-addresses", + "status": 502, + "nativeCalls": 3 + }, + { + "phase": "malformed-routes", + "status": 502, + "nativeCalls": 3 + }, + { + "phase": "recovery", + "status": 200, + "nativeCalls": 3 + }, + { + "phase": "disabled", + "type": "home-router", + "status": 409, + "nativeCalls": 0 + }, + { + "phase": "disabled", + "type": "exit-node", + "status": 409, + "nativeCalls": 0 + }, + { + "phase": "missing-invalid", + "status": 404, + "nativeCalls": 0 + }, + { + "phase": "missing-invalid", + "status": 400, + "nativeCalls": 0 + } + ], + "scope": "Compiled WAN runtime route/auth, owned SQL/native HTTP fixture; real CHR untouched", + "passed": true, + "nativeWrites": 0, + "ownedDatabaseAndRoleRemoved": true +} diff --git a/docs/ux-ui/acceptance/wan217-live-2026-10-14.json b/docs/ux-ui/acceptance/wan217-live-2026-10-14.json new file mode 100644 index 00000000..56b2a496 --- /dev/null +++ b/docs/ux-ui/acceptance/wan217-live-2026-10-14.json @@ -0,0 +1,60 @@ +{ + "date": "2026-10-14", + "block": "12.5.217", + "backend": "ux-wan217", + "frontend": "ux-gre214", + "cases": [ + { + "host": "194.226.121.150", + "serverId": 1, + "nativeCollections": 3, + "nativeStatuses": [ + 200, + 200, + 200 + ], + "applicationStatus": 200, + "anonymousStatus": 401, + "defaultGatewayMatchesNative": true, + "immediateGatewayMatchesNative": true, + "configuredUplinks": 0, + "leaseProjectionMatchesNative": true, + "passwordUnchanged": true + }, + { + "host": "104.165.244.7", + "serverId": 2, + "nativeCollections": 3, + "nativeStatuses": [ + 200, + 200, + 200 + ], + "applicationStatus": 200, + "anonymousStatus": 401, + "defaultGatewayMatchesNative": true, + "immediateGatewayMatchesNative": true, + "configuredUplinks": 0, + "leaseProjectionMatchesNative": true, + "passwordUnchanged": true + } + ], + "ui": [ + { + "width": 1440, + "pageErrors": 0, + "mutationRequests": 0, + "scope": "Dashboard smoke; does not invoke legacy WAN endpoint directly" + }, + { + "width": 320, + "pageErrors": 0, + "mutationRequests": 0, + "scope": "Dashboard smoke; does not invoke legacy WAN endpoint directly" + } + ], + "realPortalJWT": true, + "apiMocks": false, + "nativeWrites": 0, + "scope": "Healthy WAN direct API/native projection and dashboard smoke; failures/disabled states tested only in owned SQL fixture" +}