diff --git a/backend/package.json b/backend/package.json index 57e96ddd..c46f4e26 100644 --- a/backend/package.json +++ b/backend/package.json @@ -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", diff --git a/backend/scripts/acceptance/gre-native-identity-sql.mjs b/backend/scripts/acceptance/gre-native-identity-sql.mjs new file mode 100644 index 00000000..be64cb06 --- /dev/null +++ b/backend/scripts/acceptance/gre-native-identity-sql.mjs @@ -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;in.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 diff --git a/backend/src/services/gre-live.ts b/backend/src/services/gre-live.ts index 812e2292..98da3072 100644 --- a/backend/src/services/gre-live.ts +++ b/backend/src/services/gre-live.ts @@ -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() - 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) ?? [] diff --git a/backend/src/services/gre-native-identity.test.ts b/backend/src/services/gre-native-identity.test.ts new file mode 100644 index 00000000..737a09da --- /dev/null +++ b/backend/src/services/gre-native-identity.test.ts @@ -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') diff --git a/backend/src/services/gre-observed-state.ts b/backend/src/services/gre-observed-state.ts index acc035cc..ea5fae8b 100644 --- a/backend/src/services/gre-observed-state.ts +++ b/backend/src/services/gre-observed-state.ts @@ -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> { + 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> +} + /** 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) diff --git a/backend/src/services/gre-tunnels-live.test.ts b/backend/src/services/gre-tunnels-live.test.ts index b17dd4a0..ca0765e3 100644 --- a/backend/src/services/gre-tunnels-live.test.ts +++ b/backend/src/services/gre-tunnels-live.test.ts @@ -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++ diff --git a/backend/src/services/gre-tunnels-live.ts b/backend/src/services/gre-tunnels-live.ts index 22199ea2..99d34480 100644 --- a/backend/src/services/gre-tunnels-live.ts +++ b/backend/src/services/gre-tunnels-live.ts @@ -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 { const client = MikrotikClient.fromServer(server) - const greRaw = readInternetPathRows(await client.get("/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("/interface/gre")) return greRaw.map((g, idx) => mapGreAlertRow(server, g, idx)) } diff --git a/docs/ux-ui-external-acceptance.md b/docs/ux-ui-external-acceptance.md index 50974c93..69517b8c 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: блок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 регрессия. diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index 8a5d14b0..32a733fe 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -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). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат. diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index 3e503465..4a0d9b9a 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -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 этим блоком не объявляются полностью проверенными. diff --git a/docs/ux-ui-remaining-acceptance.md b/docs/ux-ui-remaining-acceptance.md index a69cec32..2817c250 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: блок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 регрессия. diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index 0eef8277..ee22f7c6 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: блок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. + diff --git a/docs/ux-ui-test-server.md b/docs/ux-ui-test-server.md index e422032b..063c5830 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: блок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 регрессия. diff --git a/docs/ux-ui/acceptance/gre221-baseline.json b/docs/ux-ui/acceptance/gre221-baseline.json new file mode 100644 index 00000000..07181e63 --- /dev/null +++ b/docs/ux-ui/acceptance/gre221-baseline.json @@ -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 +} diff --git a/docs/ux-ui/acceptance/gre221-build-proof-2026-10-14.json b/docs/ux-ui/acceptance/gre221-build-proof-2026-10-14.json new file mode 100644 index 00000000..f1c0e246 --- /dev/null +++ b/docs/ux-ui/acceptance/gre221-build-proof-2026-10-14.json @@ -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" +} diff --git a/docs/ux-ui/acceptance/gre221-deployment-2026-10-14.json b/docs/ux-ui/acceptance/gre221-deployment-2026-10-14.json new file mode 100644 index 00000000..d2390258 --- /dev/null +++ b/docs/ux-ui/acceptance/gre221-deployment-2026-10-14.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 + } + } +} diff --git a/docs/ux-ui/acceptance/gre221-fixed.json b/docs/ux-ui/acceptance/gre221-fixed.json new file mode 100644 index 00000000..267c3a65 --- /dev/null +++ b/docs/ux-ui/acceptance/gre221-fixed.json @@ -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 +} diff --git a/docs/ux-ui/acceptance/gre221-initial-native-failure-2026-10-14.json b/docs/ux-ui/acceptance/gre221-initial-native-failure-2026-10-14.json new file mode 100644 index 00000000..a082867d --- /dev/null +++ b/docs/ux-ui/acceptance/gre221-initial-native-failure-2026-10-14.json @@ -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 + } + ] +} diff --git a/docs/ux-ui/acceptance/gre221-native-live-2026-10-14.json b/docs/ux-ui/acceptance/gre221-native-live-2026-10-14.json new file mode 100644 index 00000000..da8ce483 --- /dev/null +++ b/docs/ux-ui/acceptance/gre221-native-live-2026-10-14.json @@ -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 +}