diff --git a/backend/src/services/traffic-flow-ripe.test.ts b/backend/src/services/traffic-flow-ripe.test.ts index 9e9689cf..69e56e02 100644 --- a/backend/src/services/traffic-flow-ripe.test.ts +++ b/backend/src/services/traffic-flow-ripe.test.ts @@ -220,3 +220,40 @@ resetRipeCacheForTests() assert.equal(lookupRipeCached("2606:4700:4700::1111"), null, "Invalidation clears IPv6 indexes") console.log("traffic-flow-ripe.test.ts: ok (including IPv6 indexes and database pause/resume)") + +for (const { name, location, expected } of [ + { name: "normal", location: { country: "US", latitude: 37.4, longitude: -122.1 }, expected: [37.4, -122.1] }, + { name: "equator", location: { country: "BR", latitude: 0, longitude: -50 }, expected: [0, -50] }, + { name: "meridian", location: { country: "GB", latitude: 51, longitude: 0 }, expected: [51, 0] }, + { name: "explicit zero", location: { country: "GH", latitude: 0, longitude: 0 }, expected: [0, 0] }, + { name: "unknown placeholder", location: { country: "?", latitude: 0, longitude: 0 }, expected: [null, null] }, + { name: "missing country placeholder", location: { latitude: 0, longitude: 0 }, expected: [null, null] }, + { name: "unknown country real coordinates", location: { country: "?", latitude: 1, longitude: 2 }, expected: [1, 2] }, + { name: "missing coordinates", location: { country: "US" }, expected: [null, null] }, + { name: "null coordinates", location: { country: "US", latitude: null, longitude: null }, expected: [null, null] }, + { name: "empty strings", location: { country: "US", latitude: "", longitude: "" }, expected: [null, null] }, + { name: "boolean", location: { country: "US", latitude: false, longitude: false }, expected: [null, null] }, + { name: "numeric strings", location: { country: "US", latitude: "0", longitude: "0" }, expected: [null, null] }, + { name: "partial", location: { country: "US", latitude: 37 }, expected: [null, null] }, + { name: "invalid latitude", location: { country: "US", latitude: 91, longitude: 1 }, expected: [null, null] }, + { name: "invalid longitude", location: { country: "US", latitude: 1, longitude: -181 }, expected: [null, null] }, + { name: "boundary", location: { country: "US", latitude: -90, longitude: 180 }, expected: [-90, 180] }, +]) { + resetRipeCacheForTests() + disableRipePersistForTests() + setRipeFetchForTests(async input => { + const url = String(input) + const body = url.includes("network-info") ? { data: { prefix: "8.8.8.0/24", asns: [15169] } } + : url.includes("maxmind-geo-lite") ? { data: { located_resources: [{ locations: [location] }] } } + : { data: { holder: "GOOGLE, US" } } + return new Response(JSON.stringify(body), { status: 200 }) + }) + enqueueRipeMisses(["8.8.8.8"]) + await flushRipeQueueForTests() + const result = lookupRipeCached("8.8.8.8") + assert.equal(result?.ok, true, name) + assert.equal(result?.asn, 15169, name) + assert.deepEqual([result?.lat, result?.lng], expected, name) + if (location.country === "?" || !("country" in location)) assert.equal(result?.country, "US", "ASN country fallback is independent of coordinate validity") +} +console.log("traffic-flow-ripe.test.ts: coordinate provenance cases passed") diff --git a/backend/src/services/traffic-flow-ripe.ts b/backend/src/services/traffic-flow-ripe.ts index 664f2c54..fbb7941b 100644 --- a/backend/src/services/traffic-flow-ripe.ts +++ b/backend/src/services/traffic-flow-ripe.ts @@ -383,18 +383,22 @@ function pickGeo(data: unknown): { country: string; lat: number | null; lng: num const d = data as { data?: { located_resources?: Array<{ - locations?: Array<{ country?: string; latitude?: number; longitude?: number }> + locations?: Array<{ country?: string; latitude?: unknown; longitude?: unknown }> }> } } const loc = d?.data?.located_resources?.[0]?.locations?.[0] const country = resolveRipeCountry(String(loc?.country ?? ""), 0, "") - const lat = loc?.latitude == null ? null : Number(loc.latitude) - const lng = loc?.longitude == null ? null : Number(loc.longitude) + const coordinate = (value: unknown, limit: number) => typeof value === "number" && Number.isFinite(value) && Math.abs(value) <= limit ? value : null + const lat = coordinate(loc?.latitude, 90) + const lng = coordinate(loc?.longitude, 180) + // RIPE can return unknown country + 0/0 as a placeholder. ASN country fallback + // must not turn that placeholder into a confirmed map location. + const located = lat !== null && lng !== null && (Boolean(country) || lat !== 0 || lng !== 0) return { country: country || "—", - lat: Number.isFinite(lat) ? lat : null, - lng: Number.isFinite(lng) ? lng : null, + lat: located ? lat : null, + lng: located ? lng : null, } } diff --git a/docs/ux-ui-external-acceptance.md b/docs/ux-ui-external-acceptance.md index 379d4ff4..b7465b61 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-11: завершён12.5.142 — исправлены специальные IPv6, mapped host identity и IPv6 RIPE-кеш с /128. Профильные проверки,6 SQL-модулей в собственной БД,984 compiled outputs и live RIPE двух IPv6 passed; runtime backend ux-ipv6142/frontend ux-traffic141. Далее143 — проверить неизвестные координаты RIPE, затем IPv6 flow pipeline.12.5 открыт. +Актуальное продолжение 2026-10-11: завершён12.5.143 — неизвестные координаты RIPE сохраняются null без потери ASN/страны;16 cases,3 SQL-модуля,984 outputs и live placeholder→null passed. Backend ux-geo143/frontend ux-traffic141. Далее144 — IPv6 IPFIX UDP→worker→SQL;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). @@ -696,7 +696,7 @@ flowchart LR ## 12.5.77 — основной блок IPsec (2026-10-09) -Актуальное продолжение 2026-10-11: завершён12.5.142 — исправлены специальные IPv6, mapped host identity и IPv6 RIPE-кеш с /128. Профильные проверки,6 SQL-модулей в собственной БД,984 compiled outputs и live RIPE двух IPv6 passed; runtime backend ux-ipv6142/frontend ux-traffic141. Далее143 — проверить неизвестные координаты RIPE, затем IPv6 flow pipeline.12.5 открыт. +Актуальное продолжение 2026-10-11: завершён12.5.143 — неизвестные координаты RIPE сохраняются null без потери ASN/страны;16 cases,3 SQL-модуля,984 outputs и live placeholder→null passed. Backend ux-geo143/frontend ux-traffic141. Далее144 — IPv6 IPFIX UDP→worker→SQL;12.5 открыт. Подробный объём, протоколы, схема и ограничения: [12.5.77 в журнале](ux-ui-progress.md). @@ -1531,3 +1531,13 @@ Changed-file ESLint, backend production build и IP/RIPE profile tests прош Первый Docker запуск указал отсутствующий Dockerfile.backend; исправлен путь backend/Dockerfile, build passed. Все984 emitted production outputs candidate совпали с local build. Реальный RIPE API дал6 HTTP200 для Google и Cloudflare IPv6; cached sibling/expanded address и special/invalid controls дали0 новых requests. Service process отдельный, persistence выключена. Backend ux-ipv6142 развёрнут, /health и Docker health healthy; frontend ux-traffic141 сохранён. Live /traffic1440/320 passed без srv1/400/pageerror/overflow. Rollback env/image сохранены, сетевые настройки и пароли не менялись. [SQL/live RIPE и границы](ux-ui/acceptance/flow-ipv6-ripe-2026-10-11.json), [browser smoke](ux-ui/acceptance/traffic-ipv6-smoke-2026-10-11.json). Это не доказывает IPv6 WAN connectivity или полный exporter→ingest→SQL→UI pipeline. При live RIPE выявлены координаты0/0 для Cloudflare; следующий143 — проверить, не подменяются ли отсутствующие значения нулями. Далее — IPv6 flow pipeline.12.5 открыт. + +## 12.5.143 — неизвестные координаты RIPE без ложной точки на карте (2026-10-11) + +При real RIPE IPv6 acceptance142 Cloudflare получил координаты0/0 после ответа upstream с country=? и latitude/longitude=0. Эти данные не подтверждают положение узла. Исправлен pickGeo: принимается только полная пара finite numeric coordinates в диапазонах±90/±180; empty strings, boolean, numeric strings, missing/partial/out-of-range coordinates дают null pair. Unknown-country0/0 также даёт null. Действительный ноль на экваторе/меридиане и явно заданный known-country0/0 сохраняются. ASN country fallback не превращает неизвестную точку в подтверждённые координаты. + +16 новых end-to-end service cases в RIPE profile прошли локально и в production candidate. ESLint, backend build,984 compiled outputs comparison passed. Три затронутых SQL-потребителя (analytics, map-hops, facts-rebuild) прошли в собственной temporary PostgreSQL DB; она удалена с owner check, working DB не использовалась fixtures. Real RIPE снова дал6 HTTP200 для Google/Cloudflare IPv6. Upstream unknown-country0/0 сохранён в протоколе вместе с service lat/lng=null, ASN/country retained. Cache controls не сделали новых queries. + +Backend ux-geo143 развёрнут, health healthy, frontend ux-traffic141 сохранён; read-only traffic PC/mobile smoke выполнен отдельно. Rollback env/image сохранены. [Полный протокол](ux-ui/acceptance/ripe-unknown-coordinates-2026-10-11.json), [browser smoke](ux-ui/acceptance/traffic-geolocation-smoke-2026-10-11.json), [RIPE API](https://stat.ripe.net/docs/data-api/api-endpoints/maxmind-geo-lite). + +Unknown0/0 policy основана на наблюдённом live ответе, а не на заявлении о поведении всех providers. Старые persisted entries с уже потерянной raw provenance не переписываются: исправлены новые fetches, прошлый cache живёт по прежнему TTL. Изменений schema/UI/theme или настроек CHR нет, пароли не менялись. Полная физическая точность геолокации и licensed GeoLite2 file acceptance не заявляются. Следующий144 — реальный IPv6 IPFIX UDP→collector worker→SQL в изолированной БД.12.5 открыт. diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index 6078e511..bd9aa1fb 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -1,12 +1,12 @@ # План реализации UX/UI по пятичасовым окнам -Актуальное продолжение 2026-10-11: завершён12.5.142 — исправлены специальные IPv6, mapped host identity и IPv6 RIPE-кеш с /128. Профильные проверки,6 SQL-модулей в собственной БД,984 compiled outputs и live RIPE двух IPv6 passed; runtime backend ux-ipv6142/frontend ux-traffic141. Далее143 — проверить неизвестные координаты RIPE, затем IPv6 flow pipeline.12.5 открыт. +Актуальное продолжение 2026-10-11: завершён12.5.143 — неизвестные координаты RIPE сохраняются null без потери ASN/страны;16 cases,3 SQL-модуля,984 outputs и live placeholder→null passed. Backend ux-geo143/frontend ux-traffic141. Далее144 — IPv6 IPFIX UDP→worker→SQL;12.5 открыт. Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-10. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат. ## Актуальная точка продолжения — 2026-10-10 -Актуальное продолжение 2026-10-11: завершён12.5.142 — исправлены специальные IPv6, mapped host identity и IPv6 RIPE-кеш с /128. Профильные проверки,6 SQL-модулей в собственной БД,984 compiled outputs и live RIPE двух IPv6 passed; runtime backend ux-ipv6142/frontend ux-traffic141. Далее143 — проверить неизвестные координаты RIPE, затем IPv6 flow pipeline.12.5 открыт. +Актуальное продолжение 2026-10-11: завершён12.5.143 — неизвестные координаты RIPE сохраняются null без потери ASN/страны;16 cases,3 SQL-модуля,984 outputs и live placeholder→null passed. Backend ux-geo143/frontend ux-traffic141. Далее144 — IPv6 IPFIX UDP→worker→SQL;12.5 открыт. Этапы0–11 завершены в локальных границах журнала. История выполненных внешних блоков и доказательства сохранены в [журнале](ux-ui-stage-12.md) и [протоколе](ux-ui-external-acceptance.md). Реальная Telegram доставка, Portal SSO, multi-instance и оставшиеся профильные сетевые ограничения открыты. @@ -371,6 +371,6 @@ flowchart LR ## 12.5.77 — основной блок IPsec (2026-10-09) -Актуальное продолжение 2026-10-11: завершён12.5.142 — исправлены специальные IPv6, mapped host identity и IPv6 RIPE-кеш с /128. Профильные проверки,6 SQL-модулей в собственной БД,984 compiled outputs и live RIPE двух IPv6 passed; runtime backend ux-ipv6142/frontend ux-traffic141. Далее143 — проверить неизвестные координаты RIPE, затем IPv6 flow pipeline.12.5 открыт. +Актуальное продолжение 2026-10-11: завершён12.5.143 — неизвестные координаты RIPE сохраняются null без потери ASN/страны;16 cases,3 SQL-модуля,984 outputs и live placeholder→null passed. Backend ux-geo143/frontend ux-traffic141. Далее144 — IPv6 IPFIX UDP→worker→SQL;12.5 открыт. Подробный объём, протоколы, схема и ограничения: [12.5.77 в журнале](ux-ui-progress.md). diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index f8aa7ce8..3b3682cf 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -1,6 +1,6 @@ # UX/UI: журнал реализации -Актуальное продолжение 2026-10-11: завершён12.5.142 — исправлены специальные IPv6, mapped host identity и IPv6 RIPE-кеш с /128. Профильные проверки,6 SQL-модулей в собственной БД,984 compiled outputs и live RIPE двух IPv6 passed; runtime backend ux-ipv6142/frontend ux-traffic141. Далее143 — проверить неизвестные координаты RIPE, затем IPv6 flow pipeline.12.5 открыт. +Актуальное продолжение 2026-10-11: завершён12.5.143 — неизвестные координаты RIPE сохраняются null без потери ASN/страны;16 cases,3 SQL-модуля,984 outputs и live placeholder→null passed. Backend ux-geo143/frontend ux-traffic141. Далее144 — IPv6 IPFIX UDP→worker→SQL;12.5 открыт. Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md). @@ -36,7 +36,17 @@ | 10 | Маршрутизация, диагностика, карта и терминал | Завершён в границах локальной приёмки 2026-10-01; блоки 10.1–10.71, внешние ограничения в 10.71 | | 11А | Пользователи, контейнеры, сертификаты и резервные копии | Завершён в границах локальной приёмки 2026-10-01; 11А.1–11А.33, внешние ограничения в 11А.33 | | 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 | -| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–142 и ограничения ниже. Runtime backend ux-ipv6142/frontend ux-traffic141. | +| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–143 и ограничения ниже. Runtime backend ux-geo143/frontend ux-traffic141. | + +## 12.5.143 — неизвестные координаты RIPE без ложной точки на карте (2026-10-11) + +При real RIPE IPv6 acceptance142 Cloudflare получил координаты0/0 после ответа upstream с country=? и latitude/longitude=0. Эти данные не подтверждают положение узла. Исправлен pickGeo: принимается только полная пара finite numeric coordinates в диапазонах±90/±180; empty strings, boolean, numeric strings, missing/partial/out-of-range coordinates дают null pair. Unknown-country0/0 также даёт null. Действительный ноль на экваторе/меридиане и явно заданный known-country0/0 сохраняются. ASN country fallback не превращает неизвестную точку в подтверждённые координаты. + +16 новых end-to-end service cases в RIPE profile прошли локально и в production candidate. ESLint, backend build,984 compiled outputs comparison passed. Три затронутых SQL-потребителя (analytics, map-hops, facts-rebuild) прошли в собственной temporary PostgreSQL DB; она удалена с owner check, working DB не использовалась fixtures. Real RIPE снова дал6 HTTP200 для Google/Cloudflare IPv6. Upstream unknown-country0/0 сохранён в протоколе вместе с service lat/lng=null, ASN/country retained. Cache controls не сделали новых queries. + +Backend ux-geo143 развёрнут, health healthy, frontend ux-traffic141 сохранён; read-only traffic PC/mobile smoke выполнен отдельно. Rollback env/image сохранены. [Полный протокол](ux-ui/acceptance/ripe-unknown-coordinates-2026-10-11.json), [browser smoke](ux-ui/acceptance/traffic-geolocation-smoke-2026-10-11.json), [RIPE API](https://stat.ripe.net/docs/data-api/api-endpoints/maxmind-geo-lite). + +Unknown0/0 policy основана на наблюдённом live ответе, а не на заявлении о поведении всех providers. Старые persisted entries с уже потерянной raw provenance не переписываются: исправлены новые fetches, прошлый cache живёт по прежнему TTL. Изменений schema/UI/theme или настроек CHR нет, пароли не менялись. Полная физическая точность геолокации и licensed GeoLite2 file acceptance не заявляются. Следующий144 — реальный IPv6 IPFIX UDP→collector worker→SQL в изолированной БД.12.5 открыт. ## 12.5.142 — специальные IPv6 и рабочий кеш RIPE (2026-10-11) diff --git a/docs/ux-ui-remaining-acceptance.md b/docs/ux-ui-remaining-acceptance.md index 923b94a9..4c4a0dee 100644 --- a/docs/ux-ui-remaining-acceptance.md +++ b/docs/ux-ui-remaining-acceptance.md @@ -1,8 +1,8 @@ # Оставшаяся внешняя приёмка — 2026-10-11 -Это актуальная матрица после 12.5.142. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии. +Это актуальная матрица после 12.5.143. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии. -Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-ipv6142`, frontend `ux-traffic141`; production lab использует AUTH_REQUIRED=false. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись. +Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-geo143`, frontend `ux-traffic141`; production lab использует AUTH_REQUIRED=false. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись. | Раздел | Подтверждено | Осталось / условие завершения | | --- | --- | --- | @@ -18,7 +18,7 @@ | Recursive routes | Production PUT/change/own history restore88, disabled guards, partial/unknown/applied outcomes, live формы/контекст | Автоматический rollback/retry, durable receipts и несколько экземпляров отсутствуют; PUT не является idempotent noop. Полная произвольная legacy catalog миграция не принята | | Diagnostics/terminal | Allowlist/native reads, Ping/trace/route/MTU/DNS, actual avg-rtt, disabled guards, JWT/live PC/mobile89; real default memory tail/log limits/API/UI133 | DNS использует backend resolver; MTU — ограниченная оценка, не гарантированный максимум. Stop ожидания не доказывает остановку RouterOS команды; arbitrary CLI и physical disconnect не приняты | | Portal/интеграции | Локальные и isolated JWT guards; UI/API error states в границах журналов | Настоящий Portal issuer/login/отзыв прав; RIPE SDK two-IP/no-fetch passed140, полный flow pipeline/local licensed MaxMind/S3/container/certificate jobs и Gitea delivery требуют отдельных тестовых endpoints/аккаунтов. Не отправлять сообщения и не публиковать релиз без соответствующего поручения | -| Общая приёмка | Локальная12.3.83; UI1244/context221/compiled984 parity139; профильные build/E2E/live проверки последующих блоков | Traffic initial live mock-id gate passed141; IPv6 special ranges/cache passed142; unknown RIPE coordinates143; последующие изменения проверять отдельно; scoped axe не означает полную WCAG сертификацию | +| Общая приёмка | Локальная12.3.83; UI1244/context221/compiled984 parity139; профильные build/E2E/live проверки последующих блоков | Traffic initial live mock-id gate passed141; IPv6 special ranges/cache passed142; unknown RIPE coordinates passed143; IPv6 flow pipeline144; последующие изменения проверять отдельно; scoped axe не означает полную WCAG сертификацию | Доказательства и точные ограничения: [полная история этапа12](ux-ui-stage-12.md), [внешний протокол](ux-ui-external-acceptance.md), [BGP93](ux-ui/acceptance/bgp-failover-2026-10-10.json), [паспорт стенда](ux-ui-test-server.md). Невыполненные пункты не являются все блокерами данного лабораторного сценария: для выпуска необходимо явно определить требуемые конфигурации и принять их либо согласовать ограничение. @@ -446,3 +446,13 @@ Changed-file ESLint, backend production build и IP/RIPE profile tests прош Первый Docker запуск указал отсутствующий Dockerfile.backend; исправлен путь backend/Dockerfile, build passed. Все984 emitted production outputs candidate совпали с local build. Реальный RIPE API дал6 HTTP200 для Google и Cloudflare IPv6; cached sibling/expanded address и special/invalid controls дали0 новых requests. Service process отдельный, persistence выключена. Backend ux-ipv6142 развёрнут, /health и Docker health healthy; frontend ux-traffic141 сохранён. Live /traffic1440/320 passed без srv1/400/pageerror/overflow. Rollback env/image сохранены, сетевые настройки и пароли не менялись. [SQL/live RIPE и границы](ux-ui/acceptance/flow-ipv6-ripe-2026-10-11.json), [browser smoke](ux-ui/acceptance/traffic-ipv6-smoke-2026-10-11.json). Это не доказывает IPv6 WAN connectivity или полный exporter→ingest→SQL→UI pipeline. При live RIPE выявлены координаты0/0 для Cloudflare; следующий143 — проверить, не подменяются ли отсутствующие значения нулями. Далее — IPv6 flow pipeline.12.5 открыт. + +## 12.5.143 — неизвестные координаты RIPE без ложной точки на карте (2026-10-11) + +При real RIPE IPv6 acceptance142 Cloudflare получил координаты0/0 после ответа upstream с country=? и latitude/longitude=0. Эти данные не подтверждают положение узла. Исправлен pickGeo: принимается только полная пара finite numeric coordinates в диапазонах±90/±180; empty strings, boolean, numeric strings, missing/partial/out-of-range coordinates дают null pair. Unknown-country0/0 также даёт null. Действительный ноль на экваторе/меридиане и явно заданный known-country0/0 сохраняются. ASN country fallback не превращает неизвестную точку в подтверждённые координаты. + +16 новых end-to-end service cases в RIPE profile прошли локально и в production candidate. ESLint, backend build,984 compiled outputs comparison passed. Три затронутых SQL-потребителя (analytics, map-hops, facts-rebuild) прошли в собственной temporary PostgreSQL DB; она удалена с owner check, working DB не использовалась fixtures. Real RIPE снова дал6 HTTP200 для Google/Cloudflare IPv6. Upstream unknown-country0/0 сохранён в протоколе вместе с service lat/lng=null, ASN/country retained. Cache controls не сделали новых queries. + +Backend ux-geo143 развёрнут, health healthy, frontend ux-traffic141 сохранён; read-only traffic PC/mobile smoke выполнен отдельно. Rollback env/image сохранены. [Полный протокол](ux-ui/acceptance/ripe-unknown-coordinates-2026-10-11.json), [browser smoke](ux-ui/acceptance/traffic-geolocation-smoke-2026-10-11.json), [RIPE API](https://stat.ripe.net/docs/data-api/api-endpoints/maxmind-geo-lite). + +Unknown0/0 policy основана на наблюдённом live ответе, а не на заявлении о поведении всех providers. Старые persisted entries с уже потерянной raw provenance не переписываются: исправлены новые fetches, прошлый cache живёт по прежнему TTL. Изменений schema/UI/theme или настроек CHR нет, пароли не менялись. Полная физическая точность геолокации и licensed GeoLite2 file acceptance не заявляются. Следующий144 — реальный IPv6 IPFIX UDP→collector worker→SQL в изолированной БД.12.5 открыт. diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index cb9c99d5..4f2bbfd3 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -1,6 +1,6 @@ # Этап 12: итоговая приёмка UX/UI -Актуальное продолжение 2026-10-11: завершён12.5.142 — исправлены специальные IPv6, mapped host identity и IPv6 RIPE-кеш с /128. Профильные проверки,6 SQL-модулей в собственной БД,984 compiled outputs и live RIPE двух IPv6 passed; runtime backend ux-ipv6142/frontend ux-traffic141. Далее143 — проверить неизвестные координаты RIPE, затем IPv6 flow pipeline.12.5 открыт. +Актуальное продолжение 2026-10-11: завершён12.5.143 — неизвестные координаты RIPE сохраняются null без потери ASN/страны;16 cases,3 SQL-модуля,984 outputs и live placeholder→null passed. Backend ux-geo143/frontend ux-traffic141. Далее144 — IPv6 IPFIX UDP→worker→SQL;12.5 открыт. План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push. @@ -2836,3 +2836,13 @@ Changed-file ESLint, backend production build и IP/RIPE profile tests прош Первый Docker запуск указал отсутствующий Dockerfile.backend; исправлен путь backend/Dockerfile, build passed. Все984 emitted production outputs candidate совпали с local build. Реальный RIPE API дал6 HTTP200 для Google и Cloudflare IPv6; cached sibling/expanded address и special/invalid controls дали0 новых requests. Service process отдельный, persistence выключена. Backend ux-ipv6142 развёрнут, /health и Docker health healthy; frontend ux-traffic141 сохранён. Live /traffic1440/320 passed без srv1/400/pageerror/overflow. Rollback env/image сохранены, сетевые настройки и пароли не менялись. [SQL/live RIPE и границы](ux-ui/acceptance/flow-ipv6-ripe-2026-10-11.json), [browser smoke](ux-ui/acceptance/traffic-ipv6-smoke-2026-10-11.json). Это не доказывает IPv6 WAN connectivity или полный exporter→ingest→SQL→UI pipeline. При live RIPE выявлены координаты0/0 для Cloudflare; следующий143 — проверить, не подменяются ли отсутствующие значения нулями. Далее — IPv6 flow pipeline.12.5 открыт. + +## 12.5.143 — неизвестные координаты RIPE без ложной точки на карте (2026-10-11) + +При real RIPE IPv6 acceptance142 Cloudflare получил координаты0/0 после ответа upstream с country=? и latitude/longitude=0. Эти данные не подтверждают положение узла. Исправлен pickGeo: принимается только полная пара finite numeric coordinates в диапазонах±90/±180; empty strings, boolean, numeric strings, missing/partial/out-of-range coordinates дают null pair. Unknown-country0/0 также даёт null. Действительный ноль на экваторе/меридиане и явно заданный known-country0/0 сохраняются. ASN country fallback не превращает неизвестную точку в подтверждённые координаты. + +16 новых end-to-end service cases в RIPE profile прошли локально и в production candidate. ESLint, backend build,984 compiled outputs comparison passed. Три затронутых SQL-потребителя (analytics, map-hops, facts-rebuild) прошли в собственной temporary PostgreSQL DB; она удалена с owner check, working DB не использовалась fixtures. Real RIPE снова дал6 HTTP200 для Google/Cloudflare IPv6. Upstream unknown-country0/0 сохранён в протоколе вместе с service lat/lng=null, ASN/country retained. Cache controls не сделали новых queries. + +Backend ux-geo143 развёрнут, health healthy, frontend ux-traffic141 сохранён; read-only traffic PC/mobile smoke выполнен отдельно. Rollback env/image сохранены. [Полный протокол](ux-ui/acceptance/ripe-unknown-coordinates-2026-10-11.json), [browser smoke](ux-ui/acceptance/traffic-geolocation-smoke-2026-10-11.json), [RIPE API](https://stat.ripe.net/docs/data-api/api-endpoints/maxmind-geo-lite). + +Unknown0/0 policy основана на наблюдённом live ответе, а не на заявлении о поведении всех providers. Старые persisted entries с уже потерянной raw provenance не переписываются: исправлены новые fetches, прошлый cache живёт по прежнему TTL. Изменений schema/UI/theme или настроек CHR нет, пароли не менялись. Полная физическая точность геолокации и licensed GeoLite2 file acceptance не заявляются. Следующий144 — реальный IPv6 IPFIX UDP→collector worker→SQL в изолированной БД.12.5 открыт. diff --git a/docs/ux-ui-test-server.md b/docs/ux-ui-test-server.md index 7b5d9e46..0cbc26fb 100644 --- a/docs/ux-ui-test-server.md +++ b/docs/ux-ui-test-server.md @@ -1,6 +1,6 @@ # Тестовый сервер UX/UI — 2026-10-10 -Сервер161.104.46.222: завершён12.5.142 — исправлены специальные IPv6, mapped host identity и IPv6 RIPE-кеш с /128. Профильные проверки,6 SQL-модулей в собственной БД,984 compiled outputs и live RIPE двух IPv6 passed; runtime backend ux-ipv6142/frontend ux-traffic141. Далее143 — проверить неизвестные координаты RIPE, затем IPv6 flow pipeline.12.5 открыт. +Сервер161.104.46.222: завершён12.5.143 — неизвестные координаты RIPE сохраняются null без потери ASN/страны;16 cases,3 SQL-модуля,984 outputs и live placeholder→null passed. Backend ux-geo143/frontend ux-traffic141. Далее144 — IPv6 IPFIX UDP→worker→SQL;12.5 открыт. | Параметр | Проверенное состояние | | --- | --- | @@ -14,7 +14,7 @@ | PostgreSQL | postgres:18-alpine,18.6;mmapp-test-postgres healthy;SELECT version() passed | | Данные | Отдельный Compose project mmapp-test,том mmapp-test_mmapp-pgdata и каталог ./data | | Доступ | PostgreSQL не публикуется;frontend3000/backend8000 привязаны к127.0.0.1 после запуска | -| Образы приложения | mmapp-test/backend:ux-ipv6142 и mmapp-test/frontend:ux-traffic141; запущены | +| Образы приложения | mmapp-test/backend:ux-geo143 и mmapp-test/frontend:ux-traffic141; запущены | Compose SHA256:3052bd6ab5d9865f97605cebfb5ff27a4410cd3357b81153a65a87fb7c92582f. OptionalBACKEND_IMAGE_TAG позволяетобновлятьbackendотдельно. Локальный и удалённый файлы совпали. Шаблоны: [Compose](../deploy/docker-compose.test.yml), [env без секретов](../deploy/env.test.example). @@ -1262,3 +1262,13 @@ Changed-file ESLint, backend production build и IP/RIPE profile tests прош Первый Docker запуск указал отсутствующий Dockerfile.backend; исправлен путь backend/Dockerfile, build passed. Все984 emitted production outputs candidate совпали с local build. Реальный RIPE API дал6 HTTP200 для Google и Cloudflare IPv6; cached sibling/expanded address и special/invalid controls дали0 новых requests. Service process отдельный, persistence выключена. Backend ux-ipv6142 развёрнут, /health и Docker health healthy; frontend ux-traffic141 сохранён. Live /traffic1440/320 passed без srv1/400/pageerror/overflow. Rollback env/image сохранены, сетевые настройки и пароли не менялись. [SQL/live RIPE и границы](ux-ui/acceptance/flow-ipv6-ripe-2026-10-11.json), [browser smoke](ux-ui/acceptance/traffic-ipv6-smoke-2026-10-11.json). Это не доказывает IPv6 WAN connectivity или полный exporter→ingest→SQL→UI pipeline. При live RIPE выявлены координаты0/0 для Cloudflare; следующий143 — проверить, не подменяются ли отсутствующие значения нулями. Далее — IPv6 flow pipeline.12.5 открыт. + +## 12.5.143 — неизвестные координаты RIPE без ложной точки на карте (2026-10-11) + +При real RIPE IPv6 acceptance142 Cloudflare получил координаты0/0 после ответа upstream с country=? и latitude/longitude=0. Эти данные не подтверждают положение узла. Исправлен pickGeo: принимается только полная пара finite numeric coordinates в диапазонах±90/±180; empty strings, boolean, numeric strings, missing/partial/out-of-range coordinates дают null pair. Unknown-country0/0 также даёт null. Действительный ноль на экваторе/меридиане и явно заданный known-country0/0 сохраняются. ASN country fallback не превращает неизвестную точку в подтверждённые координаты. + +16 новых end-to-end service cases в RIPE profile прошли локально и в production candidate. ESLint, backend build,984 compiled outputs comparison passed. Три затронутых SQL-потребителя (analytics, map-hops, facts-rebuild) прошли в собственной temporary PostgreSQL DB; она удалена с owner check, working DB не использовалась fixtures. Real RIPE снова дал6 HTTP200 для Google/Cloudflare IPv6. Upstream unknown-country0/0 сохранён в протоколе вместе с service lat/lng=null, ASN/country retained. Cache controls не сделали новых queries. + +Backend ux-geo143 развёрнут, health healthy, frontend ux-traffic141 сохранён; read-only traffic PC/mobile smoke выполнен отдельно. Rollback env/image сохранены. [Полный протокол](ux-ui/acceptance/ripe-unknown-coordinates-2026-10-11.json), [browser smoke](ux-ui/acceptance/traffic-geolocation-smoke-2026-10-11.json), [RIPE API](https://stat.ripe.net/docs/data-api/api-endpoints/maxmind-geo-lite). + +Unknown0/0 policy основана на наблюдённом live ответе, а не на заявлении о поведении всех providers. Старые persisted entries с уже потерянной raw provenance не переписываются: исправлены новые fetches, прошлый cache живёт по прежнему TTL. Изменений schema/UI/theme или настроек CHR нет, пароли не менялись. Полная физическая точность геолокации и licensed GeoLite2 file acceptance не заявляются. Следующий144 — реальный IPv6 IPFIX UDP→collector worker→SQL в изолированной БД.12.5 открыт. diff --git a/docs/ux-ui/acceptance/ripe-unknown-coordinates-2026-10-11.json b/docs/ux-ui/acceptance/ripe-unknown-coordinates-2026-10-11.json new file mode 100644 index 00000000..086f2ef2 --- /dev/null +++ b/docs/ux-ui/acceptance/ripe-unknown-coordinates-2026-10-11.json @@ -0,0 +1,137 @@ +{ + "date": "2026-10-11", + "block": "12.5.143", + "runtime": { + "backend": "ux-geo143", + "frontend": "ux-traffic141", + "backendHealthy": true + }, + "checks": { + "coordinateCasesPassed": 16, + "completeRipeProfilePassedLocallyAndInCandidate": true, + "changedFilesEslintPassed": true, + "backendBuildPassed": true, + "compiledOutputsMatched": 984, + "isolatedSqlModulesPassed": 3, + "ownTemporaryDatabaseRemoved": true + }, + "sql": { + "date": "2026-10-11", + "name": "mm_accept_143_1791710809060", + "tests": [ + { + "module": "traffic-flow-analytics", + "passed": true, + "output": "traffic-flow-analytics.test.ts: ok\nISOLATED_SQL_TEST_COMPLETE\n" + }, + { + "module": "traffic-flow-map-hops", + "passed": true, + "output": "traffic-flow-map-hops.test.ts: pickMapServices ok\ntraffic-flow-map-hops.test.ts: hops ok\ntraffic-flow-map-hops.test.ts: ok\nISOLATED_SQL_TEST_COMPLETE\n" + }, + { + "module": "traffic-flow-facts-rebuild", + "passed": true, + "output": "traffic-flow-facts-rebuild.test.ts: ok\nISOLATED_SQL_TEST_COMPLETE\n" + } + ], + "databaseRemoved": true + }, + "liveRipe": { + "date": "2026-10-11", + "runtime": "ux-ipv6143", + "requests": [ + { + "path": "/data/network-info/data.json", + "resource": "2606:4700:4700::1111", + "status": 200 + }, + { + "path": "/data/network-info/data.json", + "resource": "2001:4860:4860::8888", + "status": 200 + }, + { + "path": "/data/maxmind-geo-lite/data.json", + "resource": "2606:4700:4700::/48", + "status": 200 + }, + { + "path": "/data/as-overview/data.json", + "resource": "AS13335", + "status": 200 + }, + { + "path": "/data/maxmind-geo-lite/data.json", + "resource": "2001:4860::/32", + "status": 200 + }, + { + "path": "/data/as-overview/data.json", + "resource": "AS15169", + "status": 200 + } + ], + "cases": [ + { + "ip": "2001:4860:4860::8888", + "prefix": "2001:4860::/32", + "asn": 15169, + "country": "US", + "lat": 37.09024, + "lng": -95.712891, + "holder": "GOOGLE - Google LLC", + "ok": true, + "fetchedAt": 1791710819190 + }, + { + "ip": "2606:4700:4700::1111", + "prefix": "2606:4700:4700::/48", + "asn": 13335, + "country": "US", + "lat": null, + "lng": null, + "holder": "CLOUDFLARENET - Cloudflare, Inc.", + "ok": true, + "fetchedAt": 1791710819069 + } + ], + "geolocationSources": [ + { + "resource": "2606:4700:4700::/48", + "location": { + "country": "?", + "city": "", + "latitude": 0, + "longitude": 0, + "covered_percentage": 100, + "resourceCount": 1 + } + }, + { + "resource": "2001:4860::/32", + "location": { + "country": "US", + "city": "", + "latitude": 37.09024, + "longitude": -95.712891, + "covered_percentage": 100, + "resourceCount": 155 + } + } + ], + "cacheHitsAndSpecialNoFetch": true, + "unknownCoordinatesPreserved": true, + "success": true + }, + "bounds": [ + "Unknown country plus numeric0/0 observed directly from RIPE and treated conservatively as unknown location; this policy is based on live evidence, not a documented guarantee for all providers", + "Coordinates accept finite numeric latitude[-90,90]/longitude[-180,180]; missing/partial/empty/string/boolean/out-of-range values give null pair", + "Independent legitimate zero latitude/longitude and explicit known-country0/0 retained; ASN/country fallback does not establish coordinates", + "Existing persisted entries whose raw geolocation provenance was already lost are not rewritten; fresh lookups corrected, old cache follows existing TTL", + "No schema/UI/theme/device/password changes; working database fixtures untouched; no real GeoLite2 licensed file acceptance or physical location accuracy guarantee" + ], + "references": [ + "https://stat.ripe.net/docs/data-api/api-endpoints/maxmind-geo-lite" + ] +} diff --git a/docs/ux-ui/acceptance/traffic-geolocation-smoke-2026-10-11.json b/docs/ux-ui/acceptance/traffic-geolocation-smoke-2026-10-11.json new file mode 100644 index 00000000..782aa53b --- /dev/null +++ b/docs/ux-ui/acceptance/traffic-geolocation-smoke-2026-10-11.json @@ -0,0 +1,84 @@ +{ + "date": "2026-10-11", + "block": "12.5.143", + "runtime": { + "backend": "ux-geo143", + "frontend": "ux-traffic141" + }, + "checks": { + "readOnlyFrontendUnchanged": true, + "backendHealthy": true, + "backendProductionBuildPassed": true + }, + "cases": [ + { + "width": 1440, + "httpStatus": 200, + "api": [ + { + "path": "/api/traffic/bound-interfaces", + "status": 200 + }, + { + "path": "/api/traffic/users", + "status": 200 + }, + { + "path": "/api/traffic/servers", + "status": 200 + }, + { + "path": "/api/traffic/servers/1/live", + "status": 200 + }, + { + "path": "/api/traffic/servers/1/interfaces", + "status": 200 + }, + { + "path": "/api/traffic/servers/1", + "status": 200 + } + ], + "pageErrors": [], + "noOverflow": true + }, + { + "width": 320, + "httpStatus": 200, + "api": [ + { + "path": "/api/traffic/bound-interfaces", + "status": 200 + }, + { + "path": "/api/traffic/users", + "status": 200 + }, + { + "path": "/api/traffic/servers", + "status": 200 + }, + { + "path": "/api/traffic/servers/1/live", + "status": 200 + }, + { + "path": "/api/traffic/servers/1", + "status": 200 + }, + { + "path": "/api/traffic/servers/1/interfaces", + "status": 200 + } + ], + "pageErrors": [], + "noOverflow": true + } + ], + "bounds": [ + "Read-only initial loading with real current lab catalog; no full NetFlow ingestion or throughput claim", + "Catalog failure and pending loading checked by controlled E2E; numeric live server details/interfaces/SSE enabled only after confirmed catalog ownership", + "Interface filter remains selected on range changes; backend source switch invalidates prior catalog ownership" + ] +}