fix(gre): отклонять повреждённые и повторяющиеся native идентификаторы

This commit is contained in:
Denozordec committed 2026-10-14 02:09:13 +07:00
1 parent 51a579d370
commit 7a38bb36aa
19 files changed
+678 -19

No files matched your search

+1 -1
View File
@@ -24,7 +24,7 @@
"test:users": "tsx src/modules/users/iface-type.test.ts && tsx src/modules/users/bindings.test.ts && tsx src/modules/users/interface-inventory.test.ts && tsx src/routes/users-catalog.test.ts",
"test:pg": "tsx src/db/pg-pool.test.ts && tsx src/db/sql-bind.test.ts && tsx src/db/sqlite-json.test.ts && tsx src/db/traffic-flags.test.ts && tsx src/db/pg-schema.test.ts && tsx src/services/config-revisions.test.ts",
"test:recursive-routes": "tsx src/routes/recursive-routes.test.ts && tsx src/services/recursive-route-catalog.test.ts && tsx src/services/recursive-apply.test.ts",
"test:config-sync": "tsx src/utils/server-id.test.ts && tsx src/services/config-apply-plan.test.ts && tsx src/services/entity-snapshots.test.ts && tsx src/services/gre-ipv6.test.ts && tsx src/routes/gre-ipv6-validation.test.ts && tsx src/services/network-disabled-server.test.ts",
"test:config-sync": "tsx src/utils/server-id.test.ts && tsx src/services/config-apply-plan.test.ts && tsx src/services/entity-snapshots.test.ts && tsx src/services/gre-native-identity.test.ts && tsx src/services/gre-ipv6.test.ts && tsx src/routes/gre-ipv6-validation.test.ts && tsx src/services/network-disabled-server.test.ts",
"test:ros-file": "tsx src/services/ros-file-contents.test.ts && tsx src/services/mikrotik-response.test.ts",
"test:scheduler": "tsx src/services/scheduler-running.test.ts && tsx src/services/scheduler-recorded-run.test.ts && tsx src/routes/scheduler.test.ts",
"test:backups": "tsx src/services/backup-scheduler-run.test.ts && tsx src/services/backup-restore.test.ts && tsx src/services/backup-deletion.test.ts && tsx src/services/backup-dto.test.ts && tsx src/services/backup-storage-update.test.ts && tsx src/services/backup-job-execution.test.ts && tsx src/routes/backups-operations.test.ts && tsx src/services/s3-backup-client.test.ts",
@@ -0,0 +1,26 @@
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://own221.invalid';process.env.AUTH_PORTAL_URL='https://own221.invalid'
const {pool,closePool}=await import('/app/backend/dist/db/index.js'),{initDatabase}=await import('/app/backend/dist/db/bootstrap.js'),{collectGreBgpSnapshotOnce}=await import('/app/backend/dist/services/gre-bgp-snapshot-collector.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 app,nativeCalls=0,nativeWrites=0,disabledCalls=0,variant='valid'
const fixture=label=>createServer((req,res)=>{nativeCalls++;if(label==='disabled')disabledCalls++;if(req.method!=='GET')nativeWrites++;res.setHeader('content-type','application/json');const gre=req.url==='/rest/interface/gre',bgp=req.url==='/rest/routing/bgp/session',normal={'.id':'*A',name:'own221-GRE «тест» / peer',disabled:'false',running:'true'},kinds={valid:[normal],empty:[],'missing-id':[{name:normal.name,disabled:'false',running:'true'}],'missing-name':[{'.id':'*A',disabled:'false',running:'true'}],'blank-id':[{...normal,'.id':''}],'blank-name':[{...normal,name:' '}],'whitespace-id':[{...normal,'.id':' *A'}],'number-field':[{...normal,mtu:1400}],'duplicate-id':[normal,{...normal,name:'other-GRE'}],'duplicate-name':[normal,{...normal,'.id':'*B'}],'trimmed-name-collision':[normal,{...normal,'.id':'*B',name:' '+normal.name+' '}]},rows=gre?(label==='second'?kinds[variant]:[normal]):bgp?[{'.id':'*1',name:'own221-peer',established:'true','remote.as':'65221','local.as':'65222'}]:[];res.end(JSON.stringify(rows))})
const natives=['first','second','disabled'].map(fixture);for(const n of natives)await new Promise(r=>n.listen(0,'127.0.0.1',r))
const report={date:'2026-10-14',block:'12.5.221',baseline,database,cases,scope:'Actual compiled Native GRE identity and actual API/config/snapshot paths, owned SQL/native HTTP fixture; signed reader/authenticated GRE API',passed:false}
try{
await initDatabase();const ids=[];for(let i=0;i<natives.length;i++)ids.push(Number((await pool.query("INSERT INTO servers(name,host,port,use_ssl,enabled,username,password,type) VALUES($1,'127.0.0.1',$2,false,$3,'fixture','fixture',$4) RETURNING id",['own221-'+i,natives[i].address().port,i<2,i===2?'home-router':'exit-node'])).rows[0].id))
app=await buildApp({logger:false,startScheduler:false});await app.ready();const token=app.jwt.sign({sub:'own221-reader',apps:['mm'],permissions:['mm:network:read'],iss:process.env.AUTH_ISSUER},{expiresIn:'5m'}),headers={authorization:'Bearer '+token};
for(variant of ['valid','empty','missing-id','missing-name','blank-id','blank-name','whitespace-id','number-field','duplicate-id','duplicate-name','trimmed-name-collision']){
const bad=!['valid','empty'].includes(variant),oldSourceAccepts=['valid','empty','whitespace-id','duplicate-name','trimmed-name-collision'].includes(variant),sourceUnknown=baseline?!oldSourceAccepts:bad,oldCount=['duplicate-id','duplicate-name','trimmed-name-collision'].includes(variant)?3:variant==='empty'?1:2,snapshot=await collectGreBgpSnapshotOnce()
assert.equal(snapshot.greWritten,sourceUnknown?0:oldCount);assert.equal(snapshot.bgpWritten,2);assert.equal(snapshot.errors.length,sourceUnknown?1:0);assert.equal(snapshot.fatalError,undefined)
cases.push({phase:variant,endpoint:'compiled collector',greWritten:snapshot.greWritten,bgpWritten:2,sourceErrors:snapshot.errors.length})
const before=Number((await pool.query("SELECT count(*) AS count FROM config_revisions WHERE server_id=$1 AND section='gre'",[ids[1]])).rows[0].count),api=await app.inject({method:'GET',url:'/api/gre/tunnels',headers}),body=api.json();assert.equal(api.statusCode,200);assert.equal(body.failures.length,bad&&!baseline?1:0);assert.equal(body.tunnels.length,bad&&!baseline?1:oldCount);const after=Number((await pool.query("SELECT count(*) AS count FROM config_revisions WHERE server_id=$1 AND section='gre'",[ids[1]])).rows[0].count);if(bad&&!baseline)assert.equal(after,before)
cases.push({phase:variant,endpoint:'signed GRE catalog',status:200,tunnels:body.tunnels.length,failures:body.failures.length,observedRevisionsAdded:after-before})
}
assert.equal(nativeWrites,0);assert.equal(disabledCalls,0);report.passed=true
}catch(e){report.errorName=e.name;fs.writeFileSync('/app/backend/acceptance/gre221-private.log',String(e.stack??e),{mode:0o600})}finally{if(app)await app.close();for(const n of natives){n.closeAllConnections();await new Promise(r=>n.close(r))}await closePool();report.nativeWrites=nativeWrites;report.disabledHomeRouterCalls=disabledCalls;fs.writeFileSync('/app/backend/acceptance/gre221-'+(baseline?'baseline':'fixed')+'.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report))}if(!report.passed)process.exitCode=1
+6 -5
View File
@@ -12,7 +12,7 @@ import {
type GreSnapshot,
} from "./entity-snapshots.js"
import { captureAndAppendRevision } from "./config-revisions.js"
import { readGreObservedState } from "./gre-observed-state.js"
import { readGreObservedState, readGreNativeRows } from "./gre-observed-state.js"
type ServerRow = typeof servers.$inferSelect
@@ -114,8 +114,9 @@ export function mapGreLive(
gre: GreLiveIface[]
addrs: GreLiveAddr[]
} {
const observedGre = readGreNativeRows(greRaw)
const addrsByIface = new Map<string, GreLiveAddr[]>()
const greNames = new Set(greRaw.map(g => (g.name ?? "").trim()))
const greNames = new Set(observedGre.map(g => g.name.trim()))
for (const a of addrsRaw) {
if (rosDisabled(a.dynamic)) continue
const iface = (a.interface ?? "").trim()
@@ -134,9 +135,9 @@ export function mapGreLive(
const addrs: GreLiveAddr[] = []
const tunnels: LiveGreTunnel[] = []
greRaw.forEach((g, idx) => {
const rosId = String(g[".id"] ?? g.name ?? `gre-${idx}`)
const name = (g.name ?? "").trim() || `gre-${idx + 1}`
observedGre.forEach((g) => {
const rosId = g[".id"]
const name = g.name.trim()
const keepalive = parseKeepalive(g.keepalive)
const fromComment = parseInnerFromComment(g.comment)
const ifaceAddrs = addrsByIface.get(name) ?? []
@@ -0,0 +1,9 @@
import assert from 'node:assert/strict'
import {readGreNativeRows} from './gre-observed-state.js'
import {mapGreLive} from './gre-live.js'
import {servers} from '../db/schema.js'
const row={'.id':'*A',name:'GRE «тест» / peer',disabled:'false',running:'true'},server={id:1} as typeof servers.$inferSelect
assert.deepEqual(readGreNativeRows([]),[]);assert.equal(mapGreLive(server,[row],[]).tunnels[0].id,'*A');assert.equal(mapGreLive(server,[row],[]).tunnels[0].name,row.name)
const broken=[{},null,[null],[[]],[{...row,'.id':''}],[{...row,name:' '}],[{...row,'.id':undefined}],[{...row,name:undefined}],[{...row,mtu:1400}],[{...row,'.id':' *A'}],[row,row],[row,{...row,'.id':'*B'}],[row,{...row,'.id':'*B',name:' '+row.name+' '}]]
for(const raw of broken){assert.throws(()=>readGreNativeRows(raw),/каталог GRE|native ID/);assert.throws(()=>mapGreLive(server,raw as never,[]),/каталог GRE|native ID/)}
console.log('GRE identity: '+broken.length+' malformed/duplicate cases rejected by raw reader and catalog mapper; empty/native ID/special name preserved')
@@ -1,5 +1,20 @@
import { observedBoolean } from "./ospf-observed-values.js"
/** Native identity must exist before commands or configuration snapshots refer to it. */
export function readGreNativeRows(raw: unknown): Array<Record<string, string>> {
if (!Array.isArray(raw) || raw.some(row => !row || typeof row !== "object" || Array.isArray(row)
|| Object.values(row).some(value => typeof value !== "string")
|| [".id", "name"].some(key => typeof row[key] !== "string" || !row[key].trim())
|| row[".id"] !== row[".id"].trim())) {
throw new Error("Некорректный каталог GRE: отсутствует native ID/имя или повреждены поля")
}
if (new Set(raw.map(row => row[".id"])).size !== raw.length
|| new Set(raw.map(row => row.name.trim())).size !== raw.length) {
throw new Error("Повторяющиеся native ID или имена GRE")
}
return raw as Array<Record<string, string>>
}
/** Disabled is authoritative; an enabled interface also needs a known running flag. */
export function readGreObservedState(flags: { running?: string; disabled?: string }, name: string) {
const disabled = observedBoolean(flags.disabled)
@@ -16,7 +16,7 @@ import {mapGreLive} from './gre-live.js'
import {mapGreAlertRow} from './gre-tunnels-live.js'
let combinations=0
for(const disabled of ['true','yes','false','no',undefined,'','junk'])for(const running of ['true','yes','false','no',undefined,'','junk']){
const row={'.id':'*1',name:'own-flags',disabled,running},off=['true','yes'].includes(disabled??''),on=['false','no'].includes(disabled??''),runningKnown=['true','yes','false','no'].includes(running??'')
const row={'.id':'*1',name:'own-flags',...(disabled===undefined?{}:{disabled}),...(running===undefined?{}:{running})},off=['true','yes'].includes(disabled??''),on=['false','no'].includes(disabled??''),runningKnown=['true','yes','false','no'].includes(running??'')
if(off||on&&runningKnown){const expected=off?'down':['true','yes'].includes(running??'')?'up':'degraded';assert.equal(mapGreAlertRow(devices[0],row).status,expected);assert.equal(mapGreLive(devices[0],[row],[]).tunnels[0].status,expected)}
else {assert.throws(()=>mapGreAlertRow(devices[0],row),/Не подтверждено состояние GRE/);assert.throws(()=>mapGreLive(devices[0],[row],[]),/Не подтверждено состояние GRE/)}
combinations++
+2 -4
View File
@@ -2,8 +2,7 @@ import { eq } from "drizzle-orm"
import { db } from "../db/index.js"
import { servers } from "../db/schema.js"
import { MikrotikClient } from "./mikrotik.js"
import { readInternetPathRows } from "./internet-path-snapshot.js"
import { readGreObservedState } from "./gre-observed-state.js"
import { readGreObservedState, readGreNativeRows } from "./gre-observed-state.js"
type ServerRow = typeof servers.$inferSelect
@@ -40,8 +39,7 @@ export function mapGreAlertRow(server: ServerRow, g: RosGre, idx = 0): GreTunnel
async function fetchGreForServer(server: ServerRow): Promise<GreTunnelLiveRow[]> {
const client = MikrotikClient.fromServer(server)
const greRaw = readInternetPathRows(await client.get<unknown>("/interface/gre"), "GRE alert source", [".id", "name"])
if (new Set(greRaw.map(row => row[".id"])).size !== greRaw.length) throw new Error("Повторяющиеся ID GRE")
const greRaw = readGreNativeRows(await client.get<unknown>("/interface/gre"))
return greRaw.map((g, idx) => mapGreAlertRow(server, g, idx))
}
+5 -1
View File
@@ -1,6 +1,6 @@
# UX/UI: протокол внешней приёмки 12.5
Актуальное продолжение 2026-10-14: блок220 завершён. GRE catalog/config snapshots/alert source используют одну модель подтверждённых operational flags. Unknown flags не превращаются в degraded/up. Local49, profiles14+6, production30, real up→down/degraded→up/Ping3/3×2 и source/UI прошли; own objects удалены. Первичный native401104.165.244.7 после перепроверки не повторился, причина неизвестна. Backend ux-gre220/compiled1056/source488 matched, frontend ux-routing218. Следующий221 — native GRE identity/duplicate guards для каталога и snapshots. Этап12.5 открыт.
Актуальное продолжение 2026-10-14: блок221 завершён. GRE API/config/snapshot source отклоняют missing/duplicate native ID/name и повреждённые records, fallback identity удалены. Local13/flags49, configSync7 и production22 прошли; false observed revisions не добавляются. Whole actual source2own GRE/UI passed, own tunnels/DB/roles удалены. Первичный native500104.165.244.7 сохранён; причина неизвестна. Backend ux-gre221/compiled1056/source490 matched, frontend ux-routing218. Следующий222 — общая backend регрессия в собственных PostgreSQL DB и профильный GRE UI. Этап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).
@@ -2607,3 +2607,7 @@ BGP/OSPF contrast: baseline22/fixed104 cells0 violations; E2E22 и real UI8 pass
## 12.5.220 — GRE operational flags (2026-10-14)
Общий reader catalog/config/alerts не подменяет неизвестные flags. Production30/local49 и цельный native lifecycle/Ping/source/UI прошли, own objects удалены. Первичный401 сохранён, причина неизвестна. Подробности и границы — [журнал](ux-ui-progress.md). Backend ux-gre220, frontend ux-routing218; следующий221 — GRE identity guards.
## 12.5.221 — GRE native identity (2026-10-14)
Общий identity boundary предотвращает fallback IDs/names и false config revisions. Local13/flags49, production22 и real source/API/UI passed; own objects удалены. Первичный500 сохранён, причина неизвестна. Подробности и границы — [журнал](ux-ui-progress.md). Backend ux-gre221, frontend ux-routing218; следующий222 — общая backend/профильная UI регрессия.
+1 -1
View File
@@ -1,6 +1,6 @@
# План реализации UX/UI по пятичасовым окнам
Актуальное продолжение 2026-10-14: блок220 завершён. GRE catalog/config snapshots/alert source используют одну модель подтверждённых operational flags. Unknown flags не превращаются в degraded/up. Local49, profiles14+6, production30, real up→down/degraded→up/Ping3/3×2 и source/UI прошли; own objects удалены. Первичный native401104.165.244.7 после перепроверки не повторился, причина неизвестна. Backend ux-gre220/compiled1056/source488 matched, frontend ux-routing218. Следующий221 — native GRE identity/duplicate guards для каталога и snapshots. Этап12.5 открыт.
Актуальное продолжение 2026-10-14: блок221 завершён. GRE API/config/snapshot source отклоняют missing/duplicate native ID/name и повреждённые records, fallback identity удалены. Local13/flags49, configSync7 и production22 прошли; false observed revisions не добавляются. Whole actual source2own GRE/UI passed, own tunnels/DB/roles удалены. Первичный native500104.165.244.7 сохранён; причина неизвестна. Backend ux-gre221/compiled1056/source490 matched, frontend ux-routing218. Следующий222 — общая backend регрессия в собственных PostgreSQL DB и профильный GRE UI. Этап12.5 открыт.
Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-13. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат.
+11 -1
View File
@@ -1,6 +1,6 @@
# UX/UI: журнал реализации
Актуальное продолжение 2026-10-14: блок220 завершён. GRE catalog/config snapshots/alert source используют одну модель подтверждённых operational flags. Unknown flags не превращаются в degraded/up. Local49, profiles14+6, production30, real up→down/degraded→up/Ping3/3×2 и source/UI прошли; own objects удалены. Первичный native401104.165.244.7 после перепроверки не повторился, причина неизвестна. Backend ux-gre220/compiled1056/source488 matched, frontend ux-routing218. Следующий221 — native GRE identity/duplicate guards для каталога и snapshots. Этап12.5 открыт.
Актуальное продолжение 2026-10-14: блок221 завершён. GRE API/config/snapshot source отклоняют missing/duplicate native ID/name и повреждённые records, fallback identity удалены. Local13/flags49, configSync7 и production22 прошли; false observed revisions не добавляются. Whole actual source2own GRE/UI passed, own tunnels/DB/roles удалены. Первичный native500104.165.244.7 сохранён; причина неизвестна. Backend ux-gre221/compiled1056/source490 matched, frontend ux-routing218. Следующий222 — общая backend регрессия в собственных PostgreSQL DB и профильный GRE UI. Этап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.221 — native identity GRE без выдуманных fallback (2026-10-14)
Общий readGreNativeRows валидирует коллекцию, строковые поля, native .id/name, blank/whitespace IDs, duplicate ID и duplicate normalized names. API/config mapper и alert source используют один boundary. mapGreLive больше не создаёт gre-index IDs/names для повреждённых rows. Корректные .id и имена со спецсимволами сохранены. Неполный API catalog передаёт per-device failure, snapshot source сообщает GRE error, BGP observations сохраняются.
Local13 malformed/duplicate cases на raw reader и mapper, flags49, configSync7 и build прошли; optional special-env disabled SQL branch skipped. Unit flags fixture сперва содержала properties со значением undefined: теперь omitted keys соответствуют native JSON; повтор passed. Baseline ux-gre220/fixed ux-gre221 по22 production SQL cases:11 вариантов через compiled collector и signed API/config path. Missing/duplicate identity в fixed не добавляет observed config revisions. Old API принимал fallback identity/duplicates; old source не замечал duplicate names/trim collisions. Native writes0, disabled Home Router calls0; own DB/roles удалены.
Backend ux-gre221 compiled1056/source490 matched, healthy; frontend ux-routing218 сохранён. Первая live попытка остановилась native500104.165.244.7 GET/interface/gre и выполнила own cleanup. Последующие resource/gre/services/log200 на обоих; CPU0–2%, freeRAM~800MB. В retained auth failures нет testDebian161.104.46.222; ring-buffer counts не rate и не доказательство причины500. Services/credentials не менялись, root cause неизвестен. Повтор с300ms паузой между прямыми native reads прошёл: Portal API/actual compiled collector подтвердили own disabled GRE2, UI1440/320 увидел rows без page errors/mutations, own interfaces удалены и absence API/native проверен. Прямой collector не писал scheduler history. Это не physical WAN/data-plane/SLA/full WCAG приёмка.
Протоколы: [baseline](ux-ui/acceptance/gre221-baseline.json), [fixed](ux-ui/acceptance/gre221-fixed.json), [build](ux-ui/acceptance/gre221-build-proof-2026-10-14.json), [deployment](ux-ui/acceptance/gre221-deployment-2026-10-14.json), [initial500/diagnostics](ux-ui/acceptance/gre221-initial-native-failure-2026-10-14.json), [whole actual source/UI/cleanup](ux-ui/acceptance/gre221-native-live-2026-10-14.json). Следующий222 — общая backend регрессия в собственных PostgreSQL DB (без main env/DB) и профильный GRE/Filters UI после общего reader.
## 12.5.220 — подтверждённые operational flags GRE (2026-10-14)
Общий readGreObservedState используется в live catalog/config snapshot и alert source. true/false/yes/no читаются одинаково; disabled=true подтверждает down независимо от running. При неизвестном disabled или running у enabled interface опрос устройства отклоняется. Достоверные up/down/degraded сохранены; главная API-envelope по-прежнему передаёт failures по устройствам, а source snapshot сообщает GRE error и сохраняет BGP. Unknown metadata больше не выдаётся за подтверждённый operational status. Другие параметры/defaults GRE этим блоком не объявляются полностью проверенными.
+7 -3
View File
@@ -1,10 +1,10 @@
# Оставшаяся внешняя приёмка — 2026-10-13
Актуальное продолжение 2026-10-14: блок220 завершён. GRE catalog/config snapshots/alert source используют одну модель подтверждённых operational flags. Unknown flags не превращаются в degraded/up. Local49, profiles14+6, production30, real up→down/degraded→up/Ping3/3×2 и source/UI прошли; own objects удалены. Первичный native401104.165.244.7 после перепроверки не повторился, причина неизвестна. Backend ux-gre220/compiled1056/source488 matched, frontend ux-routing218. Следующий221 — native GRE identity/duplicate guards для каталога и snapshots. Этап12.5 открыт.
Актуальное продолжение 2026-10-14: блок221 завершён. GRE API/config/snapshot source отклоняют missing/duplicate native ID/name и повреждённые records, fallback identity удалены. Local13/flags49, configSync7 и production22 прошли; false observed revisions не добавляются. Whole actual source2own GRE/UI passed, own tunnels/DB/roles удалены. Первичный native500104.165.244.7 сохранён; причина неизвестна. Backend ux-gre221/compiled1056/source490 matched, frontend ux-routing218. Следующий222 — общая backend регрессия в собственных PostgreSQL DB и профильный GRE UI. Этап12.5 открыт.
Это актуальная матрица после 12.5.220. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
Это актуальная матрица после 12.5.221. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-gre220`, frontend `ux-routing218`; 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-gre221`, frontend `ux-routing218`; production lab с195 использует AUTH_REQUIRED=true и Portal1.3.1. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись.
| Раздел | Подтверждено | Осталось / условие завершения |
| --- | --- | --- |
@@ -1525,3 +1525,7 @@ BGP/OSPF contrast: baseline22/fixed104 cells0 violations; E2E22 и real UI8 pass
## 12.5.220 — GRE operational flags (2026-10-14)
Общий reader catalog/config/alerts не подменяет неизвестные flags. Production30/local49 и цельный native lifecycle/Ping/source/UI прошли, own objects удалены. Первичный401 сохранён, причина неизвестна. Подробности и границы — [журнал](ux-ui-progress.md). Backend ux-gre220, frontend ux-routing218; следующий221 — GRE identity guards.
## 12.5.221 — GRE native identity (2026-10-14)
Общий identity boundary предотвращает fallback IDs/names и false config revisions. Local13/flags49, production22 и real source/API/UI passed; own objects удалены. Первичный500 сохранён, причина неизвестна. Подробности и границы — [журнал](ux-ui-progress.md). Backend ux-gre221, frontend ux-routing218; следующий222 — общая backend/профильная UI регрессия.
+12 -1
View File
@@ -1,6 +1,6 @@
# Этап 12: итоговая приёмка UX/UI
Актуальное продолжение 2026-10-14: блок220 завершён. GRE catalog/config snapshots/alert source используют одну модель подтверждённых operational flags. Unknown flags не превращаются в degraded/up. Local49, profiles14+6, production30, real up→down/degraded→up/Ping3/3×2 и source/UI прошли; own objects удалены. Первичный native401104.165.244.7 после перепроверки не повторился, причина неизвестна. Backend ux-gre220/compiled1056/source488 matched, frontend ux-routing218. Следующий221 — native GRE identity/duplicate guards для каталога и snapshots. Этап12.5 открыт.
Актуальное продолжение 2026-10-14: блок221 завершён. GRE API/config/snapshot source отклоняют missing/duplicate native ID/name и повреждённые records, fallback identity удалены. Local13/flags49, configSync7 и production22 прошли; false observed revisions не добавляются. Whole actual source2own GRE/UI passed, own tunnels/DB/roles удалены. Первичный native500104.165.244.7 сохранён; причина неизвестна. Backend ux-gre221/compiled1056/source490 matched, frontend ux-routing218. Следующий222 — общая backend регрессия в собственных PostgreSQL DB и профильный GRE UI. Этап12.5 открыт.
План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push.
@@ -3991,3 +3991,14 @@ Backend ux-gre220 compiled1056/source488 matched, healthy; frontend ux-routing21
Протоколы: [baseline](ux-ui/acceptance/gre220-baseline.json), [fixed](ux-ui/acceptance/gre220-fixed.json), [build](ux-ui/acceptance/gre220-build-proof-2026-10-14.json), [deployment](ux-ui/acceptance/gre220-deployment-2026-10-14.json), [initial401](ux-ui/acceptance/gre220-initial-native-failure-2026-10-14.json), [whole native/UI/cleanup](ux-ui/acceptance/gre220-native-live-2026-10-14.json). Следующий221 — отклонять missing/duplicate native GRE identity в каталоге/config snapshots; сейчас mapGreLive допускает выдуманные fallback IDs/names.
## 12.5.221 — native identity GRE без выдуманных fallback (2026-10-14)
Общий readGreNativeRows валидирует коллекцию, строковые поля, native .id/name, blank/whitespace IDs, duplicate ID и duplicate normalized names. API/config mapper и alert source используют один boundary. mapGreLive больше не создаёт gre-index IDs/names для повреждённых rows. Корректные .id и имена со спецсимволами сохранены. Неполный API catalog передаёт per-device failure, snapshot source сообщает GRE error, BGP observations сохраняются.
Local13 malformed/duplicate cases на raw reader и mapper, flags49, configSync7 и build прошли; optional special-env disabled SQL branch skipped. Unit flags fixture сперва содержала properties со значением undefined: теперь omitted keys соответствуют native JSON; повтор passed. Baseline ux-gre220/fixed ux-gre221 по22 production SQL cases:11 вариантов через compiled collector и signed API/config path. Missing/duplicate identity в fixed не добавляет observed config revisions. Old API принимал fallback identity/duplicates; old source не замечал duplicate names/trim collisions. Native writes0, disabled Home Router calls0; own DB/roles удалены.
Backend ux-gre221 compiled1056/source490 matched, healthy; frontend ux-routing218 сохранён. Первая live попытка остановилась native500104.165.244.7 GET/interface/gre и выполнила own cleanup. Последующие resource/gre/services/log200 на обоих; CPU0–2%, freeRAM~800MB. В retained auth failures нет testDebian161.104.46.222; ring-buffer counts не rate и не доказательство причины500. Services/credentials не менялись, root cause неизвестен. Повтор с300ms паузой между прямыми native reads прошёл: Portal API/actual compiled collector подтвердили own disabled GRE2, UI1440/320 увидел rows без page errors/mutations, own interfaces удалены и absence API/native проверен. Прямой collector не писал scheduler history. Это не physical WAN/data-plane/SLA/full WCAG приёмка.
Протоколы: [baseline](ux-ui/acceptance/gre221-baseline.json), [fixed](ux-ui/acceptance/gre221-fixed.json), [build](ux-ui/acceptance/gre221-build-proof-2026-10-14.json), [deployment](ux-ui/acceptance/gre221-deployment-2026-10-14.json), [initial500/diagnostics](ux-ui/acceptance/gre221-initial-native-failure-2026-10-14.json), [whole actual source/UI/cleanup](ux-ui/acceptance/gre221-native-live-2026-10-14.json). Следующий222 — общая backend регрессия в собственных PostgreSQL DB (без main env/DB) и профильный GRE/Filters UI после общего reader.
+5 -1
View File
@@ -1,6 +1,6 @@
# Тестовый сервер UX/UI — 2026-10-10
Актуальное продолжение 2026-10-14: блок220 завершён. GRE catalog/config snapshots/alert source используют одну модель подтверждённых operational flags. Unknown flags не превращаются в degraded/up. Local49, profiles14+6, production30, real up→down/degraded→up/Ping3/3×2 и source/UI прошли; own objects удалены. Первичный native401104.165.244.7 после перепроверки не повторился, причина неизвестна. Backend ux-gre220/compiled1056/source488 matched, frontend ux-routing218. Следующий221 — native GRE identity/duplicate guards для каталога и snapshots. Этап12.5 открыт.
Актуальное продолжение 2026-10-14: блок221 завершён. GRE API/config/snapshot source отклоняют missing/duplicate native ID/name и повреждённые records, fallback identity удалены. Local13/flags49, configSync7 и production22 прошли; false observed revisions не добавляются. Whole actual source2own GRE/UI passed, own tunnels/DB/roles удалены. Первичный native500104.165.244.7 сохранён; причина неизвестна. Backend ux-gre221/compiled1056/source490 matched, frontend ux-routing218. Следующий222 — общая backend регрессия в собственных PostgreSQL DB и профильный GRE UI. Этап12.5 открыт.
| Параметр | Проверенное состояние |
| --- | --- |
@@ -2338,3 +2338,7 @@ BGP/OSPF contrast: baseline22/fixed104 cells0 violations; E2E22 и real UI8 pass
## 12.5.220 — GRE operational flags (2026-10-14)
Общий reader catalog/config/alerts не подменяет неизвестные flags. Production30/local49 и цельный native lifecycle/Ping/source/UI прошли, own objects удалены. Первичный401 сохранён, причина неизвестна. Подробности и границы — [журнал](ux-ui-progress.md). Backend ux-gre220, frontend ux-routing218; следующий221 — GRE identity guards.
## 12.5.221 — GRE native identity (2026-10-14)
Общий identity boundary предотвращает fallback IDs/names и false config revisions. Local13/flags49, production22 и real source/API/UI passed; own objects удалены. Первичный500 сохранён, причина неизвестна. Подробности и границы — [журнал](ux-ui-progress.md). Backend ux-gre221, frontend ux-routing218; следующий222 — общая backend/профильная UI регрессия.
+178
View File
@@ -0,0 +1,178 @@
{
"date": "2026-10-14",
"block": "12.5.221",
"baseline": true,
"database": "mm_accept_166_1791917283901",
"cases": [
{
"phase": "valid",
"endpoint": "compiled collector",
"greWritten": 2,
"bgpWritten": 2,
"sourceErrors": 0
},
{
"phase": "valid",
"endpoint": "signed GRE catalog",
"status": 200,
"tunnels": 2,
"failures": 0,
"observedRevisionsAdded": 1
},
{
"phase": "empty",
"endpoint": "compiled collector",
"greWritten": 1,
"bgpWritten": 2,
"sourceErrors": 0
},
{
"phase": "empty",
"endpoint": "signed GRE catalog",
"status": 200,
"tunnels": 1,
"failures": 0,
"observedRevisionsAdded": 1
},
{
"phase": "missing-id",
"endpoint": "compiled collector",
"greWritten": 0,
"bgpWritten": 2,
"sourceErrors": 1
},
{
"phase": "missing-id",
"endpoint": "signed GRE catalog",
"status": 200,
"tunnels": 2,
"failures": 0,
"observedRevisionsAdded": 1
},
{
"phase": "missing-name",
"endpoint": "compiled collector",
"greWritten": 0,
"bgpWritten": 2,
"sourceErrors": 1
},
{
"phase": "missing-name",
"endpoint": "signed GRE catalog",
"status": 200,
"tunnels": 2,
"failures": 0,
"observedRevisionsAdded": 1
},
{
"phase": "blank-id",
"endpoint": "compiled collector",
"greWritten": 0,
"bgpWritten": 2,
"sourceErrors": 1
},
{
"phase": "blank-id",
"endpoint": "signed GRE catalog",
"status": 200,
"tunnels": 2,
"failures": 0,
"observedRevisionsAdded": 1
},
{
"phase": "blank-name",
"endpoint": "compiled collector",
"greWritten": 0,
"bgpWritten": 2,
"sourceErrors": 1
},
{
"phase": "blank-name",
"endpoint": "signed GRE catalog",
"status": 200,
"tunnels": 2,
"failures": 0,
"observedRevisionsAdded": 1
},
{
"phase": "whitespace-id",
"endpoint": "compiled collector",
"greWritten": 2,
"bgpWritten": 2,
"sourceErrors": 0
},
{
"phase": "whitespace-id",
"endpoint": "signed GRE catalog",
"status": 200,
"tunnels": 2,
"failures": 0,
"observedRevisionsAdded": 1
},
{
"phase": "number-field",
"endpoint": "compiled collector",
"greWritten": 0,
"bgpWritten": 2,
"sourceErrors": 1
},
{
"phase": "number-field",
"endpoint": "signed GRE catalog",
"status": 200,
"tunnels": 2,
"failures": 0,
"observedRevisionsAdded": 1
},
{
"phase": "duplicate-id",
"endpoint": "compiled collector",
"greWritten": 0,
"bgpWritten": 2,
"sourceErrors": 1
},
{
"phase": "duplicate-id",
"endpoint": "signed GRE catalog",
"status": 200,
"tunnels": 3,
"failures": 0,
"observedRevisionsAdded": 1
},
{
"phase": "duplicate-name",
"endpoint": "compiled collector",
"greWritten": 3,
"bgpWritten": 2,
"sourceErrors": 0
},
{
"phase": "duplicate-name",
"endpoint": "signed GRE catalog",
"status": 200,
"tunnels": 3,
"failures": 0,
"observedRevisionsAdded": 1
},
{
"phase": "trimmed-name-collision",
"endpoint": "compiled collector",
"greWritten": 3,
"bgpWritten": 2,
"sourceErrors": 0
},
{
"phase": "trimmed-name-collision",
"endpoint": "signed GRE catalog",
"status": 200,
"tunnels": 3,
"failures": 0,
"observedRevisionsAdded": 0
}
],
"scope": "Actual compiled Native GRE identity and actual API/config/snapshot paths, owned SQL/native HTTP fixture; signed reader/authenticated GRE API",
"passed": true,
"nativeWrites": 0,
"disabledHomeRouterCalls": 0,
"ownedDatabaseAndRoleRemoved": true
}
@@ -0,0 +1,21 @@
{
"date": "2026-10-14",
"block": "12.5.221",
"compiled": {
"matched": 1056,
"missing": 0,
"different": 0
},
"source": [
{
"kind": "backend",
"matched": 490
}
],
"configSyncScripts": 7,
"identityCases": 13,
"greFlagCombinations": 49,
"productionCases": 22,
"optionalDisabledSqlProfileSkipped": true,
"initialUnitFixtureFailure": "Undefined-valued optional keys replaced with absent keys to represent native JSON"
}
@@ -0,0 +1,21 @@
{
"date": "2026-10-14",
"block": "12.5.221",
"backendSourceMatched": 490,
"compiledMatched": 1056,
"frontendChanged": false,
"services": {
"backend": {
"image": "mmapp-test/backend:ux-gre221",
"imageId": "sha256:22126c3d941b0ac023ea15ff40c5fb18109853f33a4292affeca8ffcf69ec6bd",
"running": true,
"health": "healthy"
},
"frontend": {
"image": "mmapp-test/frontend:ux-routing218",
"imageId": "sha256:ec658b9da88c19d60987be5bfc0c15fb339a0310d7a42dc761b40a70a3428c5a",
"running": true,
"health": null
}
}
}
+178
View File
@@ -0,0 +1,178 @@
{
"date": "2026-10-14",
"block": "12.5.221",
"baseline": false,
"database": "mm_accept_166_1791917459099",
"cases": [
{
"phase": "valid",
"endpoint": "compiled collector",
"greWritten": 2,
"bgpWritten": 2,
"sourceErrors": 0
},
{
"phase": "valid",
"endpoint": "signed GRE catalog",
"status": 200,
"tunnels": 2,
"failures": 0,
"observedRevisionsAdded": 1
},
{
"phase": "empty",
"endpoint": "compiled collector",
"greWritten": 1,
"bgpWritten": 2,
"sourceErrors": 0
},
{
"phase": "empty",
"endpoint": "signed GRE catalog",
"status": 200,
"tunnels": 1,
"failures": 0,
"observedRevisionsAdded": 1
},
{
"phase": "missing-id",
"endpoint": "compiled collector",
"greWritten": 0,
"bgpWritten": 2,
"sourceErrors": 1
},
{
"phase": "missing-id",
"endpoint": "signed GRE catalog",
"status": 200,
"tunnels": 1,
"failures": 1,
"observedRevisionsAdded": 0
},
{
"phase": "missing-name",
"endpoint": "compiled collector",
"greWritten": 0,
"bgpWritten": 2,
"sourceErrors": 1
},
{
"phase": "missing-name",
"endpoint": "signed GRE catalog",
"status": 200,
"tunnels": 1,
"failures": 1,
"observedRevisionsAdded": 0
},
{
"phase": "blank-id",
"endpoint": "compiled collector",
"greWritten": 0,
"bgpWritten": 2,
"sourceErrors": 1
},
{
"phase": "blank-id",
"endpoint": "signed GRE catalog",
"status": 200,
"tunnels": 1,
"failures": 1,
"observedRevisionsAdded": 0
},
{
"phase": "blank-name",
"endpoint": "compiled collector",
"greWritten": 0,
"bgpWritten": 2,
"sourceErrors": 1
},
{
"phase": "blank-name",
"endpoint": "signed GRE catalog",
"status": 200,
"tunnels": 1,
"failures": 1,
"observedRevisionsAdded": 0
},
{
"phase": "whitespace-id",
"endpoint": "compiled collector",
"greWritten": 0,
"bgpWritten": 2,
"sourceErrors": 1
},
{
"phase": "whitespace-id",
"endpoint": "signed GRE catalog",
"status": 200,
"tunnels": 1,
"failures": 1,
"observedRevisionsAdded": 0
},
{
"phase": "number-field",
"endpoint": "compiled collector",
"greWritten": 0,
"bgpWritten": 2,
"sourceErrors": 1
},
{
"phase": "number-field",
"endpoint": "signed GRE catalog",
"status": 200,
"tunnels": 1,
"failures": 1,
"observedRevisionsAdded": 0
},
{
"phase": "duplicate-id",
"endpoint": "compiled collector",
"greWritten": 0,
"bgpWritten": 2,
"sourceErrors": 1
},
{
"phase": "duplicate-id",
"endpoint": "signed GRE catalog",
"status": 200,
"tunnels": 1,
"failures": 1,
"observedRevisionsAdded": 0
},
{
"phase": "duplicate-name",
"endpoint": "compiled collector",
"greWritten": 0,
"bgpWritten": 2,
"sourceErrors": 1
},
{
"phase": "duplicate-name",
"endpoint": "signed GRE catalog",
"status": 200,
"tunnels": 1,
"failures": 1,
"observedRevisionsAdded": 0
},
{
"phase": "trimmed-name-collision",
"endpoint": "compiled collector",
"greWritten": 0,
"bgpWritten": 2,
"sourceErrors": 1
},
{
"phase": "trimmed-name-collision",
"endpoint": "signed GRE catalog",
"status": 200,
"tunnels": 1,
"failures": 1,
"observedRevisionsAdded": 0
}
],
"scope": "Actual compiled Native GRE identity and actual API/config/snapshot paths, owned SQL/native HTTP fixture; signed reader/authenticated GRE API",
"passed": true,
"nativeWrites": 0,
"disabledHomeRouterCalls": 0,
"ownedDatabaseAndRoleRemoved": true
}
@@ -0,0 +1,117 @@
{
"date": "2026-10-14",
"block": "12.5.221",
"initialFailure": {
"host": "104.165.244.7",
"endpoint": "/interface/gre",
"status": 500,
"wholeAttemptAborted": true,
"ownedGreCleanupReported": true,
"rootCauseKnown": false
},
"subsequentDiagnostics": [
{
"host": "194.226.121.150",
"path": "/system/resource",
"status": 200,
"cpuLoad": "0",
"freeMemory": "817356800",
"version": "7.24.5 (stable)"
},
{
"host": "194.226.121.150",
"path": "/interface/gre",
"status": 200,
"owned221Objects": 0
},
{
"host": "194.226.121.150",
"path": "/ip/service",
"status": 200,
"www": [
{
"name": "www",
"maxSessions": "20",
"disabled": "false"
},
{
"name": "www",
"maxSessions": null,
"disabled": "false"
},
{
"name": "www-ssl",
"maxSessions": "20",
"disabled": "true"
}
]
},
{
"host": "194.226.121.150",
"path": "/log",
"status": 200,
"authFailureRecords": 11
},
{
"host": "104.165.244.7",
"path": "/system/resource",
"status": 200,
"cpuLoad": "2",
"freeMemory": "824307712",
"version": "7.24.5 (stable)"
},
{
"host": "104.165.244.7",
"path": "/interface/gre",
"status": 200,
"owned221Objects": 0
},
{
"host": "104.165.244.7",
"path": "/ip/service",
"status": 200,
"www": [
{
"name": "www",
"maxSessions": "20",
"disabled": "false"
},
{
"name": "www",
"maxSessions": null,
"disabled": "false"
},
{
"name": "www-ssl",
"maxSessions": "20",
"disabled": "true"
}
]
},
{
"host": "104.165.244.7",
"path": "/log",
"status": 200,
"authFailureRecords": 883
}
],
"credentialsUnchanged": true,
"authFailureSourceSummary": [
{
"host": "194.226.121.150",
"status": 200,
"totalRetainedAuthFailures": 11,
"fromTestDebian": 0,
"otherSources": 11,
"retainedLogNotCurrentRate": true
},
{
"host": "104.165.244.7",
"status": 200,
"totalRetainedAuthFailures": 880,
"fromTestDebian": 0,
"otherSources": 880,
"retainedLogNotCurrentRate": true
}
]
}
@@ -0,0 +1,62 @@
{
"date": "2026-10-14",
"block": "12.5.221",
"backend": "ux-gre221",
"frontend": "ux-routing218",
"nativeCases": [
{
"host": "194.226.121.150",
"nativeGetStatus": 200,
"applicationStatus": "down",
"nativeDisabled": "true",
"nativeRunning": "false",
"nativeIdPresent": true,
"nativeNamePresent": true
},
{
"host": "104.165.244.7",
"nativeGetStatus": 200,
"applicationStatus": "down",
"nativeDisabled": "true",
"nativeRunning": "false",
"nativeIdPresent": true,
"nativeNamePresent": true
}
],
"source": {
"date": "2026-10-14",
"name": "own221-gre-1791918004179",
"passed": true,
"mode": "Actual compiled helper reads lab PostgreSQL and native GRE/BGP; no scheduler history write",
"ownedGreRows": [
{
"targetLabel": "own221-gre-1791918004179-1 / CHR",
"status": "down"
},
{
"targetLabel": "own221-gre-1791918004179-2 / CHR",
"status": "down"
}
],
"sourceErrors": 0
},
"ui": [
{
"width": 1440,
"ownedNativeRowVisible": true,
"pageErrors": 0,
"mutationRequests": 0
},
{
"width": 320,
"ownedNativeRowVisible": true,
"pageErrors": 0,
"mutationRequests": 0
}
],
"realPortalJWT": true,
"apiMocks": false,
"cleanupCompleted": true,
"scope": "Actual compiled collector reads lab DB/native disabled GRE; no durable snapshot write, fault injection or data-plane claim",
"passwordsUnchanged": true
}