feat(k1): stabilize LAB bridge and isolate onboard device integration

This commit is contained in:
DCCONSTRUCTIONS
2026-09-07 10:31:33 +03:00
parent 020a878915
commit 63ea2bed67
115 changed files with 13700 additions and 988 deletions
@@ -0,0 +1,269 @@
# K1 Bridge: архитектура, эксплуатационные сценарии и граница рефакторинга
Дата среза: 06.09.2026. Основание: просьба владельца полностью восстановить контекст K1 из Ops перед решением о рефакторинге. Это документальный и статический аудит текущей рабочей копии, а не новая аппаратная приёмка.
Исходный commit: `020a878915ea32c64963975447650fd8eee29071`. Рабочее дерево уже содержало изменения Node, K1, viewer и UI. При этом аудите исходники, прошивка, состояние сканера, службы и Ops не изменялись. Созданы только этот отчёт и индексы источников. Тесты, сборки, BLE-поиск, provisioning, START/STOP и отключения сети не запускались.
## 1. Вывод
Опасение о чрезмерной связности подтверждается кодом. `facade.py` — 35 996 строк; `connect()` — 1 971; `_adopt_existing_lan_connection()` — 1 224; `state()` — 1 028; `_reconcile_acquisition()` — 1 587. Экран `K1ProvisioningPipeline.tsx` — 3 732 строки, runtime hook — 2 135. В экран одновременно входят локальный черновик, выбор режима, scan, попытка provisioning, чтение статуса, восстановление, история физической команды и представление ошибок.
Однако это не означает, что все проверки лишние. Существенная часть сложности появилась после доказанных инцидентов: повторные команды после неизвестного результата, поздний ответ старого процесса, потеря сети после START, STOP без READY, восстановление камеры, гонка REST/WebSocket, удержание старого Rerun listener. Удалить эти проверки ради короткой функции подключения означало бы вернуть реальные дефекты.
Нужна декомпозиция владельцев и переходов при сохранении поведения. Уже существуют полезные границы: BLE transport, connection supervisor, network ledger, physical-command coordinator, recovery checkpoint, control transcript. Основной долг находится в их соединении внутри facade и в повторной интерпретации состояния разными UI.
Текущая ошибка подключения и архитектурный долг — связанные, но разные вопросы. Сам размер файла не доказывает причину ATT-ответа K1. Последние изменения улучшили диагноз и выход из ошибки; успешное подключение последней попытки ими не доказано.
## 2. Что найдено в Ops
Через прямой NODE.DC Ops MCP получены все 76 карточек MISSION CORE с описаниями и structured blocks. У 41 карточки есть метка `XGRIDS K1`; значительная часть — потребители уже записанного K1 evidence в LAB. Для 45 карточек получены все страницы активных комментариев: 128 комментариев, без оставшейся пагинации. Полный индекс названий, блоков, дат и comment IDs: [Ops index](2026-09-06-k1-bridge-ops-index.json).
Глубокая смысловая сверка выполнена для сетевого/control/recovery контура, Node и границ viewer. Индексация лабораторных карточек не выдаётся за повторный аудит всех алгоритмов CV.
| Карточка | Роль в этом разборе |
|---|---|
| MISSIONCOR-3 — Mission Core. Lixel K1 / XGRIDS Integration | Основная текущая приёмка K1; packet oracle, 14 операций, Bridge/Quick, сон/сеть, камера, baseline производительности |
| MISSIONCOR-49 — K1 · Проблемы сканирования | История отдельных сетевых, control, producer и Rerun отказов; физические повторные циклы; причины предыдущих регрессий |
| MISSIONCOR-76 — Mission Core Node · Архитектурные границы для бортового ПК | Владение устройствами на борту, связь Core/Node, reboot, журнал команд, Linux-паритет; уточнения владельца в комментариях |
| MISSIONCOR-66 — Mission Core. Канон интеграции Rerun | Границы live, Saved Sessions и LAB; запрет менять общий lifecycle ради локальной лаборатории |
| MISSIONCOR-74 — Additional Core · Переносимая кастомизация Rerun | Связанный реестр кастомизаций; индексирован, актуальные различия поверхностей прочитаны в свежем блоке #66 |
| MISSIONCOR-7 — Mission Core. Milestone — canonical K1 control and durable archive acceptance | Историческая физическая START/live/STOP/READY/archive приёмка |
| MISSIONCOR-10 — Operational Core — Real-time Record Limits | Исторические ограничения записи, durability, consumer backpressure; часть описания recovery устарела |
| MISSIONCOR-51 — Технический долг Mission Core | Отдельные полевые и вычислительные ограничения; не источник разрешения ослабить K1 safety |
| MISSIONCOR-1, -2, -5, -50 | Архитектурный и исторический контекст проекта, SDK и границ компонентов |
| MISSIONCOR-4 — Archive. NDC_xgrids-k1-connector — historical evidence | Раннее физическое evidence; явно архивная архитектура |
| MISSIONCOR-8, -11 и остальные K1 LAB-карточки | Downstream camera/perception/calibration/replay; учитывать как потребителей неизменного source-of-record |
В #76 комментарии 05.09 имеют решающее уточнение: первый бортовой K1 — **только Bridge**; существующий macOS/Quick остаётся тестовым путём. В теле старого baseline ещё написан последующий Linux Quick: это не новая задача. Все действия оператора должны проходить через GUI. Порядок пользовательских уточнений важнее старого шаблона карточки.
## 3. Восстановленная история и достоверность
| Дата / источник | Что действительно было подтверждено | Чего это не доказывает |
|---|---|---|
| 16.07, #3, #4 | BLE/Wi-Fi vertical slice; LixelGO/iPhone IP capture; MQTT/RTSP и packet map | IP capture не содержит BLE HCI; не является дампом самого 7f01 provisioning |
| 19.07, #7 | Canonical START/live/STOP/READY и durable archive на одном K1 | Linux, второй K1, другая FW, многочасовая эксплуатация |
| 28.07, #49 | Повторные Quick/Bridge циклы и вход после очистки cache | Успешное соединение не означало исправный Rerun receiver |
| 06.08, #49 | Bridge физически работал; отдельно диагностирован Quick/CoreWLAN helper regression | Нельзя переносить причину Quick helper на Bridge |
| 2122.08, #49/#3 | Устранены starvation, camera churn, Rerun admission и state-channel проблемы; подтверждён STOP + READY | Число unit tests не заменяет отдельную аппаратную приёмку каждого recovery |
| 23.08, #3 | Bridge → Quick → Bridge → Quick; сон во время запуска; сеть off → сон → wake → сеть on; камера и облако восстановились без повторного START/STOP | Полный reboot Node во время K1 записи, другая ОС/FW, универсальный SLA |
| 0506.09, #76 | Pairing, heartbeat offline/online при остановке Node-службы, сохранение identity, D455 этап | Аппаратный K1/Linux parity ещё не принят |
| 06.09, локальные incident-аудиты | Есть успешный Bridge, затем ATT-отказы; отдельно найден неверный camera evidence-root | Последние подключения и камера после исправления ещё не приняты новым чистым UI-прогоном |
Внутренний быстрый baseline — `eaad9de`, `20260822T105904Z_viewer_live`: START до индикации калибровки <5 с, калибровка 21 с, облако +2 с, правая камера +4 с; callback→publication p50/p95 23,839/41,541 ms. Это один физический run на коротком ledger, не гарантированная задержка любого подключения. Recovery-capable baseline `1001a31` имеет другие задержки и расширенные гарантии. Подробности: [internal baseline](../lab/010_K1_MISSION_CORE_INTERNAL_LIVE_BASELINE_20260822.redacted.md).
### Обнаруженные расхождения источников
1. Manifest содержит 77 сценариев: 51 `software-covered`, 17 `partial`, 9 `planned`. Это значения файла, не результат новых тестов. Например, переключение Bridge/Quick и sleep/wake ещё отмечены как требующие hardware evidence, хотя более поздняя #3 описывает физическую приёмку.
2. #10 пишет, что MQTT reconnect отсутствует; #3 и текущий recovery-код содержат ограниченное восстановление активного потока. Историческое ограничение нельзя применять как описание текущего пути.
3. #49 хранит старый статус незакрытой Rerun-приёмки; более поздняя #3 закрывает конкретный принятый beta-профиль. Приёмку следует связывать с датой, commit, платформой и точным сценарием.
4. В #3 краткий oracle-блок неточно объединяет финальный PCL и teardown около +0,951 с. Явная ERRATA в комментарии `c1ef70e2-d900-4d42-ad78-d460040a42d7` различает последний PCL PUBLISH +36,415 ms, pose +40,787 ms, RTSP media +54,057 ms и teardown около +951 ms. Эталон должен учитывать исправление.
5. Документ supervision описывает фиксированный шестисекундный scan; текущий scanner использует одно непрерывное окно 6→20 с при отсутствии K1. Это документальный drift после сегодняшнего изменения.
6. В документах есть разная формулировка browser close: завершение operator session и сохранение backend-owned capture. Это разные владельцы. Контракт CONN-62 и код не разрешают WebSocket disconnect инициировать scanner-команду. Для рефакторинга нужно явно зафиксировать отдельно draft, UI connection, lease и acquisition.
## 4. Bridge по слоям: нормальная последовательность
```mermaid
flowchart TD
UI[Оператор: найти, выбрать K1, ввести сеть] --> Intent[Один intent: runtime, mode, discovery generation, operation ID]
Intent --> Admission[Проверка владельца, текущего состояния и журналов]
Admission --> BLE[Точный BLE handle: GATT contract и 7f02 baseline]
BLE --> Journal[Сохранить границу dispatch до записи]
Journal --> Write[Одна 99-byte запись в 7f01]
Write --> Status[7f02: подтверждение требуемой сети и адреса]
Status --> Applied[Durable network_applied]
Applied --> Control[Read-only route, TCP, DeviceInfo bootstrap]
Control --> Ready[Текущая identity и готовность управления]
Ready --> Start[Отдельный явный START по каноническому диалогу]
Start --> Raw[Локальная исходная запись]
Start --> Preview[Ограниченный live preview: облако и камера]
```
Для LAB весь runtime принадлежит компьютеру оператора. Для борта путь до `Intent` проходит Core → аутентифицированный Node channel → Node broker → тот же K1 runtime. Радио, проверка маршрута к K1, secret provider и recorder принадлежат БК. Сеть Core может отличаться от локальной сети Node/K1.
### Провода протокола
Общий Bridge profile FW 3.0.2: service `7f00`, write `7f01`, status `7f02`. Frame — ровно 99 bytes: длина SSID, 32-byte slot, длина password, 64-byte slot, конечный zero. Mission Core использует один `with_response`, как в принятом macOS run. Это не Quick AP-enable, где отдельный 100-byte frame. Источники: [reviewed profile](../04_K1_WIFI_PROVISIONING_PROFILE.md), `ble/wifi_provisioning.py:193`, вызов в `facade.py:11747`.
`write_gatt_char` ACK доказывает транспортный факт, но не готовность MQTT. Состояние сети, локальный route, TCP endpoint, DeviceInfo identity, control и свежие sensor data — самостоятельные доказательства. Нельзя заменить их единым `connected` или принимать «точки пришли» за право на новую команду.
Прикладной transcript остаётся непрерывной MQTT-сессией: ordinals 0110 перед START; ordinal 11 — START; 12 — свежий initialized SCANNING; 1314 — завершающее read-only обновление. Подготовка включает согласованную синхронизацию времени и не является целиком read-only. Recovery использует inspection-only путь, который её не повторяет.
Критическая граница сна: после ordinal 12 physical resolve и recovery checkpoint сохраняются до 1314; public SCANNING/STOP удерживаются transition gate до завершения refresh. Потеря связи в этот момент не должна уничтожить уже доказанный START. Реализация: `protocol/application_session.py:1020`, `facade.py:24291`, тест `test_post_start_refresh_timeout_wakes_existing_read_only_recovery`.
## 5. Владельцы и состояния, которые нельзя смешать
| Владелец | Состояние / обязанность | Должен пережить |
|---|---|---|
| UI draft | Выбранный кандидат, SSID/пароль до отправки, локальное ожидание | Ничего, что могло бы восстановить право повторной записи после смены runtime |
| BLE arbiter | Один нативный radio owner, точный handle, cleanup | Отмену coroutine до фактического освобождения нативной операции |
| Network idempotency journal | Один operation ID и неизменность его запроса | Потерю HTTP-ответа и backend restart без повторного write |
| Network mutation ledger | prepared/dispatching/observing/terminal и доказательства 7f02 | Crash на границе отправки |
| Semantic topology store | Последняя подтверждённая конфигурация сети | Restart, но только как configured/offline, без live authority |
| Connection supervisor | Intent, host epoch, route/TCP/DeviceInfo, отдельные control/data planes | Короткую потерю сети через отзыв зависимых полномочий |
| Physical coordinator/ledger | START/STOP и доказанный либо неизвестный физический результат | Потерю сети, process crash и UI reset; не очищается вместе с формой |
| Active recovery checkpoint | Точная lineage acquisition/device/project/evidence и gaps | Поддерживаемый rebind/restart без нового START |
| Recorder/camera producer | Raw evidence и committed prefixes | Закрытие browser, медленного consumer, смену просмотрщика |
| Viewer | Disposable receiver, canvas/media transport, профиль отображения | Пересоздание consumer без управления сканером |
| Core/Node pairing | Постоянное доверие, heartbeat, binding | Обрыв канала и reboot; состояние K1 доказывается отдельно |
Часть журналов кажется дублированием только по названию: журнал сетевого запроса и журнал физического START отвечают на разные вопросы. Их физическое объединение без анализа crash consistency опасно. При этом хранение однотипных presentation-состояний в нескольких frontend-местах не даёт новых гарантий и является кандидатом на упрощение.
`state()` сейчас не чистая проекция: он выполняет локальную retirement/reconciliation работу и может инициировать уже разрешённый active-stream recovery. Это видно с `facade.py:7285`. Поэтому простое изменение частоты polling или перенос `state()` в новый UI может затронуть lifecycle. Чистую проекцию можно выделять только вместе с независимым владельцем reconciliation, сохранив все переходы и их порядок.
## 6. Матрица поведения, которое следует сохранить
Статусы ниже различают историческую физическую приёмку, наличие реализации/тестов и непроверенный Node parity. Наличие теста здесь не означает его нового запуска.
| Сценарий | Обязательное поведение | Основание / текущая граница |
|---|---|---|
| Первый scan не сразу видит включённый K1 | Одно ограниченное discovery; никаких скрытых connect/write; отдельное время первого candidate | `ble/scanner.py:1207`; сегодняшнее окно 6→20 с; свежая UI-приёмка нужна |
| Выбор устройства, ввод сети | Только локальный draft; никаких аппаратных действий от selection | #3, canon CONN-06/-70/-74; frontend fences |
| Apply | Не более одной reviewed сетевой mutation, exact captured handle | `wifi_provisioning.py:687`; durable callback перед write |
| Отказ до write | Прямо сообщить отсутствие отправки; освободить завершённую попытку | Network journal/ledger и error annotations |
| Потеря ACK или HTTP после write | Не повторять write; читать результат того же intent | `networkProvisioning.ts:105`; idempotency journal |
| K1 подключился к Wi-Fi, MQTT ещё не готов | network_applied сохраняется; read-only bootstrap не превращается во второй Apply | `facade.py:12001`, `_schedule_control_bootstrap_continuation:4439` |
| Неверная сеть / старый 7f02 | Не принимать чужой/старый target; ожидание и classifier должны согласоваться | `wifi_provisioning.py:709`, `_post_dispatch_network_target:35334`; найдено расхождение завершения polling |
| Повторный scan после STOP | Новый acquisition без stale camera/ingress/session | #49 field acceptance; `test_next_scan_retires_stale_terminal_live_perception_ingress_before_start` |
| Потеря только интернета при живой Node/K1 LAN | Локальный capture не зависит от Core preview; Core показывает потерю канала | #76 ownership; физический Linux K1 gate открыт |
| Потеря маршрута Node/K1 во время записи | Отозвать control; сохранить exact lineage; bounded read-only rebind | #3 23.08 macOS; `test_control_first_loss_freezes_topology_before_ephemeral_retirement` |
| Сеть off → sleep → wake → сеть on | Не повторять START; новый host epoch и новые identity/control proofs; вернуть data consumers | #3 физически принято на Mac; на Node отдельно |
| Потеря сети между ordinal 12 и 1314 | Сохранить durable START proof, выполнить существующий read-only recovery | `application_session.py:1020`, lifecycle test `test_post_start_refresh_timeout_wakes_existing_read_only_recovery` |
| K1 выключился во время калибровки | Не висеть в ложном ожидании; SCAN_OVER завершает локально, physical ambiguity сохраняется | #3 calibration-loss; `_reconcile_acquisition` |
| K1 вернулся READY после power cycle | Зафиксировать cessation, не объявлять успешный STOP и не начинать новый START | `test_active_stream_recovery_device_standby_never_restarts_scanner` |
| По прежнему IP отвечает другой K1/сервис | Не принимать endpoint за identity; не переписывать pin автоматически | `test_active_stream_recovery_wrong_identity_blocks_without_retry_or_camera_reopen` |
| Node/Core backend restart | Новое runtime поколение, сохранённая pairing/history; никакого replay команд | #76 heartbeat + separate K1 restart checkpoint |
| Restart активной K1 acquisition | Только доказательная rehydration; новый writer/gap, first PCL admission; старое evidence неизменно | `tests/test_xgrids_active_acquisition_restart_rehydration.py:326`; 22 тест-функции в файле; Node E2E ещё не принят |
| STOP ACK есть, READY нет | Не объявлять завершение; standby-unconfirmed/unknown, read-only reconciliation | #49; `test_stop_response_preserves_precise_unknown_outcome_through_predeadline_loss` |
| Принудительное локальное завершение | Закрыть только локальных владельцев; не посылать STOP; поздний recovery не оживляет их | `test_local_force_finish_cleanup_failure_is_visible_retryable_and_fences_late_success` |
| Две вкладки / поздний REST / смена mode | Старый runtime/revision не перетирает новый, команда не дублируется | `stateOrdering.ts`, lifecycle.ts; #3 REST/WS acceptance |
| Browser refresh/cache reset/закрытие | Не влияет на физическую команду и recorder; UI заново читает backend | #49/#76; CONN-62; отдельная clean-cache GUI проверка обязательна |
| Rerun не открылся, камера/данные живы | Viewer-only recovery; не переподключать K1 и не терять raw | #49 listener/admission; #3 live_receiver recovery |
| Камера пропала, MQTT жив | Camera-only recovery точного source/evidence epoch | `test_camera_stall_snapshot_does_not_reconnect_live_mqtt_runtime`, camera watchdog tests |
| Live consumer медленный / worker недоступен | Ограничивать preview, сохранять source-of-record и не менять scanner authority | #10, #66, #76; Node sustained-resource gate открыт |
| Переключение live → Data → LAB | Разные admission/lifecycle/settings policies; исправление Bridge не меняет эти профили | #66; `viewerProfile.ts`, отдельный аудит live camera root |
Восстановление нельзя свести к правилу «никаких повторов»: запрещены автоматические физические mutations, но ограниченные read-only наблюдения и consumer-only reconnect нужны и уже приняты. Для active-stream exception backoff задан 0,5/1/2/4/5 с с пределом интервала; автоматического terminal timeout нет. Exact READY/SCAN_OVER/fault, изменение lineage и явное локальное завершение имеют разные исходы. Источник: `docs/20_K1_CONNECTION_SUPERVISION_CANON.md:574`.
## 7. Конкретные проблемы и риски текущей структуры
### F1 — Перегруженный coordinator и зависимость проекции от lifecycle
**Подтверждено статически.** `facade.py` соединяет protocol, OS/network, durable stores, process/native leases, acquisition/camera, recovery, ошибки и UI policy. `connect()` содержит Bridge, Direct, Quick, host association, reset/retirement и child bootstrap. В `state()` совмещены чтение и упорядоченное локальное завершение.
Практический риск: изменение unrelated presentation/polling влияет на момент cleanup или recovery; вынесенный callback может поменять порядок захвата gate и публикации proof. Размер файла сам по себе не обосновывает удаление guard. Первый допустимый structural шаг — выделение неизменных типов/проекций и изолированных функций с сохранением вызовов и их порядка.
### F2 — Нижний и верхний уровни по-разному заканчивают station observation
**Подтверждённое расхождение критериев; полевой причинный статус открыт.** `wifi_provisioning.py:725` завершает polling при любом адресе, кроме `None` и AP fallback. Верхний слой `facade.py:11792` проверяет requested network и допускаемый post-dispatch target. Если первая post-write выборка ещё описывает прежнюю LAN, helper больше не ждёт следующую, даже при оставшемся 45-second budget.
Для исправления потребуется единый reviewed terminal predicate либо передаваемый transport-слою observation criterion. До изменения нужен сценарий «старая LAN → переходный status → запрошенная LAN» с одной записью и несколькими чтениями. Это не объяснение сегодняшнего ATT4: ATT-исключение возникает на write, до этого polling.
### F3 — UI повторно собирает смысл операции и скрывает полезный контекст
**Подтверждено кодом и пользовательскими скриншотами.** Экран держит отдельные local attempt, candidate-unavailable, saved reconnect, applied recovery и physical recovery representations; один failure записывается в несколько presentation slots. Ошибка дублируется, а SSID из `ProvisioningAttemptPresentation` не показан в итоговой ошибке. Primary reconnect фактически выполняет проверку существующего состояния, а исправление сети требует другого пути.
Из успешного private evidence известна подтверждённая сеть; на раннем скриншоте введено другое написание. SSID последней неуспешной операции не сохранён, поэтому обвинять конкретно последний ввод нельзя. UI должен позволять проверить собственный ввод в текущем локальном intent, без публикации SSID в Ops/общие логи и без восстановления password. Требуется согласовать это с прежней формулировкой secret-free error contract, которая запрещает вставлять SSID из backend exception.
Кандидат на упрощение: одна typed presentation projection из server attempt + текущего локального draft, один операторский error и один явно названный следующий шаг. Backend policy остаётся authority.
### F4 — Новый Node-путь теряет часть уже существующего recovery-контракта
**Подтверждено статически; аппаратный Node/K1 запуск не выполнен.** `NodeBridge` переиспользует общий service — это правильная основа. Но `project()` (`node_bridge.py:55`) экспортирует краткие `connected`, `ready_to_start`, `phase`, candidates и runtime; отсутствуют exact connection attempt, typed failure, safe-next-action, child bootstrap progress и snapshot revision.
`network.provision` возвращает durable network ACK до завершения read-only bootstrap. `DeviceEnrollmentWindow.tsx` получает один projected result и не подписывается на последующее enrollment state; он может показать «связь пока не подтверждена», хотя bootstrap продолжает работать. Это не обязательно отказ соединения. Verify требует `device_id` в текущих candidates (`node_bridge.py:116`): cold saved reconnect после restart не эквивалентен принятому LAB пути.
Python `/operation` сворачивает все exceptions в 409 с одной фразой; Go `call()` заменяет non-200 ещё одной общей ошибкой, а `execute()` классифицирует её как unknown. Это безопасно по запрету replay, но стирает различие между stale-before-dispatch, отказом устройства и unknown-after-dispatch. Backend typed diagnostics, улучшенные в LAB, не доходят до Node UI.
Не следует копировать 3 732-строчный LAB-компонент в Node. Нужно довести общий typed operation/recovery контракт через транспортные оболочки и оставить две компактные поверхности над одной семантикой.
### F5 — Несогласованные deadlines между Node и K1 runtime
**Подтверждено статически; воспроизведение не проводилось.** Node UI выдаёт request deadline 170 с; Core/Go допускают до 180 с; Go HTTP client имеет 185 с, но сам вызов ограничен command deadline; общий `network.provision` journal задаёт 240 с. Timeout доставки может наступить до terminal outcome нижнего уровня. Политика unknown/no-auto-retry корректна, но оператору недоступно полноценное наблюдение той же операции после таймаута через урезанную проекцию.
Нужно разделять срок допуска до отправки, ожидание transport-ответа и наблюдение принятой операции. Увеличить все timeout не решает владение и корреляцию.
### F6 — Матрица приёмки и freezes плохо отражают актуальную систему
**Подтверждено.** Manifest ссылается в основном на целые test files, а не на конкретный test/scenario/physical run. Один lifecycle файл содержит 446 test-функций. `planned` в старом manifest не доказывает отсутствие реализации сегодня; `software-covered` не доказывает Node parity.
Guardrail закреплён на `c041a569...` и защищает packet oracle/frozen paths. Он полезен как защита от случайного protocol drift, но не как единственный критерий нового рефакторинга. Отдельные сегодняшние source changes уже выходят за старый файл-freeze. Нельзя просто переснять hashes, объявив поведение сохранённым.
### F7 — Профильность Rerun частично отделяет lifecycle, но не всю кастомизацию
**Подтверждено текущим кодом и предыдущим аудитом.** Есть разные live/recorded/LAB profile kinds и remount boundary. Но в Control Station общий `App.tsx:183` хранит `sceneSettings`; live и Saved Sessions используют общий канал настроек, тогда как LAB имеет отдельные result settings. Поэтому формулировка «все три профиля полностью изолированы» сейчас слишком сильна.
Это самостоятельный долг, не основание трогать viewer в рефакторинге Bridge. Протокол подключения, live camera producer и presentation-профиль нужно принимать отдельно. Исправление camera evidence-root сегодня также не является изменением Wi-Fi протокола.
## 8. Обязательные границы будущего рефакторинга
Это предложение для последующего решения, не начатая реализация.
1. Сохранить текущий рабочий snapshot и сопоставить каждый обязательный scenario с точным existing test, physical run и платформой. Отдельно перечислить Node-only проверки. Исторические версии Ops не переписывать как новую приёмку.
2. Выделить один typed outcome: `not_dispatched`, `network_applied`, `control_ready`, `outcome_unknown`, конкретный отказ; не выводить результат из HTTP-кода. Во всех оболочках сохранять operation/runtime/target correlation и допустимое действие.
3. Разделить normal Bridge provisioning и recovery orchestration. Нормальный путь может быть коротким; recovery обязан сохранять explicit target, physical ledger и ownership. Quick остаётся отдельной принятой strategy; его не переносить на борт и не удалять из LAB.
4. Разделить state projection и reconciliation owner, только после фиксации существующего порядка переходов и lock ownership. Переносить по одной обязанности, без одновременного изменения protocol, camera и viewer.
5. Упростить UI на основе общей семантики результата. Компактность достигается уменьшением повторной интерпретации, а не скрытием unknown или заменой всех отказов словом «подключение».
6. После каждого узкого изменения — соответствующая автоматическая регрессия, затем отдельный физический UI-сценарий. По указанию владельца перед каждым аппаратным/UI-прогоном очистить cache; обычный reload не засчитывать. Сейчас очищенный прогон не выполнен: browser tool не предоставляет доступную очистку.
7. Производительность измерять на одном и том же сценарии и ledger: click→BLE discovery, connect, write-return, status proof, network ACK, route/TCP/DeviceInfo, START, first PCL, first camera. У каждой стадии свой бюджет; таймер ожидания без стадии недостаточен.
Неприкосновенны: exact wire frame/command order, одна mutation на intent, durability до dispatch, запрет command replay, identity/runtime/host-epoch fences, отделение record от preview, gaps при restart, reader-only recovery и сохранение других Rerun профилей. Менять эти контракты можно только отдельным обоснованным решением, а не попутно при разрезании файлов.
## 9. Что пока нельзя утверждать
- Причина всех сегодняшних ATT-ошибок не установлена единым доказательством. FW-specific interpretation ATT4/6 полезна, но не заменяет exact entered-network evidence последнего intent.
- Найденный ранний выход polling — статически установленный риск другого этапа; он не был воспроизведён на физическом K1.
- Холодный reboot БК на несколько минут во время K1 capture не принят этим аудитом. В коде есть restart rehydration, в Ops принят heartbeat/recovery на отдельных конфигурациях; их Linux end-to-end композиция требует своей приёмки.
- Все 77 scenario не перепроверены аппаратно и все тесты не перезапущены. Список нужен для предотвращения регрессии, а не для новой зелёной отметки.
- Рефакторинг не начат. Нет изменения пакета, установки на Mini, restart сервиса или публикации в Ops.
## 10. Проверяемые источники
- [Полный индекс Ops](2026-09-06-k1-bridge-ops-index.json): все 76 карточек, 41 K1 label, 128 полученных активных комментариев; основные semantic источники указаны выше.
- [Hashes текущего кода](2026-09-06-k1-bridge-code-snapshot.json): точные bytes критических модулей и test-файлов на момент чтения, без proprietary evidence или credentials.
- [LixelGO IP protocol observation](../lab/002_LIXELGO_IPHONE_LOCAL_PROTOCOL_20260716.redacted.md), [Wi-Fi profile](../04_K1_WIFI_PROVISIONING_PROFILE.md).
- [Connection supervision canon](../20_K1_CONNECTION_SUPERVISION_CANON.md), [acceptance manifest](../k1-connection-acceptance.manifest.json), [recovery runbook](../runbooks/K1_CONNECTION_RECOVERY.md), [physical recovery ADR](../adr/0015-k1-physical-state-recovery.md).
- [Bridge incident](2026-09-06-k1-bridge-connection-incident.md), [station reply semantics](2026-09-06-k1-station-reply-semantics.md), [live camera root](2026-09-06-k1-live-reference-camera-root.md), [Node Bridge implementation and open gates](2026-09-06-node-k1-bridge-implementation.md).
Чтение Ops и исходников позволило восстановить рабочие гарантии и найти конкретные границы риска. Следующее решение должно выбирать одну такую границу и её приёмку; переписывание всего K1-контура сразу не имеет достаточного доказательного основания.
## 11. Уточнение цели: необязательная интеграция устройства и переносимость
Дополнительный запрос владельца: K1 должен быть необязательной интеграцией; macOS — первая принимаемая платформа, Ubuntu — следующая проверка переносимости. Другие модели XGRIDS не должны требовать встраивания их протоколов в Core. Ниже — архитектурное предложение, не новая реализация или аппаратная приёмка.
### Что уже отделено, а что ещё нет
- Есть manifest `DevicePlugin`, отдельный frontend пакета `plugins/xgrids-k1`, backend в `src/k1link/device_plugins/xgrids_k1`, нейтральный SDK и загрузчик с проверкой версии, набора действий и handshake. Это реальная существующая основа, которую следует сохранить.
- [Composition frontend](../../apps/control-station/src/composition/devicePlugins.ts) явно включает K1 в сборку. Это правильное место выбора поставляемых модулей, но сейчас выбор статический; независимо устанавливаемый frontend этим не доказан.
- [Backend composition](../../src/k1link/web/device_plugin_composition.py) допускает только `transitional-in-process`. [ADR 0011](../adr/0011-laboratory-plugin-runtime-handshake-and-transport-seam.md) прямо исключает из текущих гарантий crash containment, supervisor, portable media IPC и установку пакетов. Наличие runtime transport не означает, что отдельный процесс уже работает.
- [Общий pyproject](../../pyproject.toml) включает BLE/MQTT-зависимости и K1 CLI. Общий Python wheel содержит весь `src/k1link`. Поэтому независимое удаление драйвера пока нельзя считать принятой возможностью.
- [SensorWorkspace](../../packages/sensor-ui/src/SensorWorkspace.tsx) напрямую импортирует `K1Detail` и выбирает его по `device.kind === 'k1'`. Это конкретная зависимость общей поверхности от устройства; её место — регистрация визуального расширения интеграцией.
- Анализ Python import graph обнаружил 14 модулей `compute`, прямо импортирующих K1 analysis/protocol/replay. Это главным образом работа с данными, а не управление радио. Их нельзя механически удалять вместе с драйвером: нужны отдельные границы чтения архивов и проверка LAB.
- [NodeBridge](../../src/k1link/device_plugins/xgrids_k1/node_bridge.py) уже использует тот же compatibility service с Linux-адаптерами. Следовательно, второй реализации всей K1-логики сейчас нет и создавать её не требуется. Однако полноценная Ubuntu-приёмка и общий контракт проекции результата ещё не завершены.
Размер и связность `facade.py` — проблема внутреннего устройства плагина. Прямые зависимости общей установки, сенсорного UI и вычислительных модулей — проблема его внешней границы. Перенос одного большого файла в новую папку не решит ни ту, ни другую автоматически. Историческое имя Python namespace `k1link` само по себе не является доказательством зависимости от оборудования.
### Предлагаемая ответственность
| Слой | Ответственность |
| --- | --- |
| Mission Core / Node host | Реестр интеграций и execution node, разрешения, общий жизненный цикл операций, хранение evidence, маршрутизация нормализованных потоков, оболочка UI и профили просмотра. |
| K1 domain | Точный протокол BLE/MQTT/RTSP, совместимость модели и прошивки, порядок подключения и acquisition, интерпретация ответов, восстановление и доказательства физического состояния K1. |
| Адаптер платформы | BLE backend, наблюдение сетевого интерфейса и маршрута, доступ к секретам, запуск и остановка локального runtime. Реализации macOS и Ubuntu могут различаться. |
| Представление интеграции | Поля подключения, возможности, настройки и статусы K1 через общий SDK; регистрация в host вместо веток K1 в общих компонентах. |
| Чтение данных | K1 raw codec как явно объявленная зависимость чтения; нормализованные сохранённые данные читаются без активного драйвера оборудования. Совместимость существующих LAB проверяется отдельно. |
Переносимую логику разумно сохранить на текущем Python, отделяя платформенные вызовы через небольшие интерфейсы. Требование переносимости относится к поведению и контракту, а не к одному бинарнику для всех ОС. SDK уже экспортирует JSON Schema; смена языка возможна позднее при доказанной необходимости, но сейчас добавила бы повторную проверку протокола и recovery.
Целевая граница исполнения — отдельный runtime интеграции на том компьютере, рядом с которым находится устройство: локально для LAB, на БК для бортового сценария. Core обращается к выбранному execution node; Wi-Fi оператора не подменяет Wi-Fi около БК. Контракт должен покрывать не только команды, но и события операции, состояние, evidence и media. Объявление permissions в manifest не заменяет их фактическое ограничение.
Плагин имеет общий протокол взаимодействия с host, но собственную семантику устройства. Core не должен знать BLE UUID или порядок команд K1. И наоборот, плагин не должен владеть профилем лабораторного Rerun, глобальной навигацией или реестром аппаратов. Другие модели XGRIDS получают явные model/firmware profiles; общие vendor-компоненты выделяются по подтверждённому совпадению поведения, а не по одному бренду. Обобщение на одновременную работу нескольких устройств — отдельная приёмка, не свойство текущего single-session runtime.
### Последовательность и критерии завершения
1. Закрепить существующие сценарии и одинаковый контракт результата/событий для LAB и Node. Получение HTTP-ответа и физическое завершение операции остаются разными событиями.
2. Выделить внутри K1 provisioning, наблюдение/recovery и проекцию состояния, сохранив durable dispatch boundary, владельца операции, блокировки и порядок команд. Не пересобирать одновременно acquisition, камеру и Rerun.
3. Устранить прямые зависимости общих компонентов, разделить поставку драйвера и чтение записей. Проверить запуск Core без установленного K1 и работу других устройств и доступных архивов.
4. Реализовать процессную границу за существующим transport с явными контрактами событий/media. Проверить зависание и падение плагина: Core остаётся доступным, состояние устройства честно меняется, другая запись не прерывается. Перезапуск runtime не переотправляет provisioning, START или STOP автоматически; сначала восстанавливаются журнал и наблюдаемое состояние.
5. Принять macOS, затем Ubuntu Bridge на том же domain-коде. Перенос считается доказанным, когда меняются адаптеры и упаковка, а не K1-логика или Core. Допустимо начать с явного состава сборки; динамическая установка UI, магазин плагинов и hot reload не нужны для первой проверки границы.
Отдельные обязательные критерии: восстановление связи после потери сети, безопасное обнаружение рестарта устройства и БК, отсутствие повторной физической команды, отсутствие регрессии LAB/recorded/live профилей Rerun. Каждый аппаратный прогон выполняется через UI с предварительной очисткой браузерного кэша по указанию владельца. В этом дополнении выполнены только чтение кода и документирование; проверок на устройстве не было.
@@ -0,0 +1,108 @@
{
"schema_version": "missioncore.bridge-analysis-code-snapshot/v1",
"base_commit": "020a878915ea32c64963975447650fd8eee29071",
"working_tree": "dirty; source code not changed by this analysis",
"analysis": "Static inspection and existing evidence only; no tests executed",
"files": [
{
"path": "src/k1link/device_plugins/xgrids_k1/facade.py",
"sha256": "24c4ec6c33c4d21a99065c4146df12bdc2d81efd5a9da769c077b1ab4bea896d",
"lines": 35996
},
{
"path": "src/k1link/device_plugins/xgrids_k1/connection_supervisor.py",
"sha256": "2adbd7d61c34816d5be9a66baa788634408df7dc0e4a0d21520241d5e965806a",
"lines": 2252
},
{
"path": "src/k1link/device_plugins/xgrids_k1/ble/wifi_provisioning.py",
"sha256": "f169a78f66e71d9c85d503e174d5f19f19e010aa04db3fe3ba8cf032d809de55",
"lines": 818
},
{
"path": "src/k1link/device_plugins/xgrids_k1/ble/scanner.py",
"sha256": "ea6f1fed6db5b3b98735779e14237e0eaf40a3cbe2d0cd8f3196e95308c12153",
"lines": 1306
},
{
"path": "src/k1link/device_plugins/xgrids_k1/protocol/application_session.py",
"sha256": "f91367b19b3f85ce2b47279acbdeff969cd5a10b64bc5ceb90b843840a1e6b26",
"lines": 1930
},
{
"path": "src/k1link/device_plugins/xgrids_k1/node_bridge.py",
"sha256": "550f34cf5b947131ca1067a1dbe74731ca7a7601cb42bbb5a9c1a08ebfd9179b",
"lines": 248
},
{
"path": "src/k1link/device_plugins/xgrids_k1/linux_host.py",
"sha256": "c2e3fa6884e97c9cbd7bbf29cfb1260e539f61fd2e7d18e0dc3c1ab94c59d4d4",
"lines": 214
},
{
"path": "src/k1link/fleet/device_enrollment.py",
"sha256": "9856fd5638aaef88317ec82fa482b6e358efb5fd57deecf61fb9e9d0e14c4fa4",
"lines": 173
},
{
"path": "apps/node-agent/internal/node/device_enrollment.go",
"sha256": "9e92379ed9e262d6ee4b16c754df7852d129bf6daec324140e85a4b15af9c5e0",
"lines": 323
},
{
"path": "plugins/xgrids-k1/frontend/src/components/K1ProvisioningPipeline.tsx",
"sha256": "d6205ce8be004238791471fb7c3eb2eccb0676bedb81a06514771c26419cc0ab",
"lines": 3732,
"hook_mentions": {
"useState": 17,
"useEffect": 13,
"useRef": 10
}
},
{
"path": "plugins/xgrids-k1/frontend/src/useXgridsK1Runtime.ts",
"sha256": "dcc0c1e5ca1c841e963ddc18d24628799ec97d036bc1095eb6f22124cf000565",
"lines": 2135
},
{
"path": "plugins/xgrids-k1/frontend/src/networkProvisioning.ts",
"sha256": "45ba4eaf111b1cf8fa0945884aaf95e3920a274cd1d7f0e4797f8da2ee7fd204",
"lines": 241
},
{
"path": "packages/sensor-ui/src/DeviceEnrollmentWindow.tsx",
"sha256": "de02c9443f606bd0d73ec476c34f095c05851005497426431271dbe1ba36f064",
"lines": 49,
"hook_mentions": {
"useState": 10,
"useEffect": 1,
"useRef": 1
}
},
{
"path": "packages/sensor-ui/src/enrollment.ts",
"sha256": "ad97fbe9ecc1b6567d8ee67d6d32c4cc39a31ce95bf0eb48ef9cbc40a884a16f",
"lines": 37
},
{
"path": "docs/k1-connection-acceptance.manifest.json",
"sha256": "477615cdd34685bc10d9815de0c9642fbdfcdea2eb05e04b5e07b7c87a2e9854",
"lines": 93
},
{
"path": "tests/test_xgrids_acquisition_lifecycle.py",
"sha256": "97728c489551f16a6ccf2606a19ca077f5a2926adc67681f367666188df008ef",
"lines": 34794
},
{
"path": "tests/test_xgrids_active_acquisition_restart_rehydration.py",
"sha256": "3fc85fba32eab54fda12625ac77d54f7e3ae8a454708b4167b71e0ffd4271d61",
"lines": 2482
},
{
"path": "tests/test_node_k1_bridge.py",
"sha256": "25af4bd6d40b8df487b231e599730117b62598a78b838d20e5443395920c2c48",
"lines": 204
}
]
}
@@ -0,0 +1,140 @@
# K1 Bridge: provisioning recovery and first-scan discovery
Scope: the operator-local K1 connection in LAB on canonical Core `8000`.
The onboard installation is a separate pending acceptance task.
The later [firmware callback audit](2026-09-06-k1-station-reply-semantics.md)
explains the two fresh 19:22/19:23 UTC failures and supersedes the earlier
generic ATT interpretation below with a bounded FW 3.0.2 station diagnosis.
## Evidence and diagnosis
Three explicit Bridge attempts returned HTTP 502 on 2026-09-06 at
18:04:50, 18:05:13 and 18:05:43 UTC. All failed at `gatt-write` with
`BleakGATTProtocolError`, ATT 4 `INVALID_PDU`. Each attempted one 99-byte
write-with-response; acknowledgement and a joined Wi-Fi address were absent.
The characteristic advertised `read, write`; the observed write-without-response
capacity was 253 bytes. This capacity alone does not prove a successful ATT write.
A separately admitted, exact-target Bridge `connection.verify` at 18:15 UTC
read K1 state without a provisioning write or host Wi-Fi switch. K1 answered,
but supplied no shared-LAN address (`connection-verify-address-unavailable`).
A preceding request was rejected at input validation because of the diagnostic
operation identifier format; it performed no device I/O.
The same ATT error exists in August's private logs. The accepted August Bridge
record uses the same Bleak 3.0.2, write mode, frame length and capacity. Successful
historical writes also include an empty network baseline, so an empty baseline
does not justify adding an AP-enable command or rejecting Bridge in advance.
The exact reason for the peripheral's rejection remains unproved; a wrong Wi-Fi
password, frame-format regression or MTU failure must not be asserted from this
error alone. No alternate transport mode, frame, retry, START or STOP was sent.
Private evidence is retained under the ignored incident directory, with UTC and
monotonic timestamps, redacted events and SHA-256 artifact index. Credentials and
raw packet contents are absent from this report and source changes.
## Software causes and refactor
- The Connect hook had three overlapping result/error branches. Its failed
HTTP path accepted the server snapshot but returned `observedState: null` to
the form; the form therefore lost the exact operation and its ATT diagnosis.
- A completed Bluetooth search suppressed the recovery surface even after the
subsequent, click-owned Connect failed. The disabled credential form instead
promised a future safe continuation despite there being no running operation.
- Recovery guidance claimed an automatic recovery or a completed UI reset that
had not occurred. The lead status fell back to idle despite a terminal failure.
- Durable JSON logging omitted the write-mode, frame-size and GATT-property
fields already supplied by the BLE implementation.
`networkProvisioning.ts` now owns the single submission, bounded observation of
that exact idempotency key, monotonic snapshot selection and reviewed failure
copy. An existing operation is only observed, never resubmitted. The runtime
hook retains exact applied-network/control-authority checks and React action
ownership. The form renders the returned operation and allows explicit recovery
after its failed attempt even when its preceding search is complete. It still
clears the credential immediately upon submission.
The frozen BLE frames and canonical MQTT dialogue are unchanged. Ops reference:
MISSIONCOR-3, “Mission Core. Lixel K1 / XGRIDS Integration”, and
`docs/lab/002_LIXELGO_IPHONE_LOCAL_PROTOCOL_20260716.redacted.md`.
That iPhone capture begins at the IP stack and does not contain Bluetooth HCI.
The reviewed BLE profile remains `docs/04_K1_WIFI_PROVISIONING_PROFILE.md`.
## Validation and remaining acceptance
Focused frontend: 227 passed, including new behavioral cases for HTTP 502,
existing-key non-replay, lost-response success, pending settlement, stale REST
versus newer WebSocket, superseded intent and unrelated journal rows. The
rendered regression covers completed search followed by failed Connect and
requires enabled recovery choices instead of the trapped form.
Full frontend suite: 772 passed sequentially; TypeScript and final production build passed. Application architecture: 4 passed.
BLE and persistent-diagnostic tests: 34 passed. Canonical guardrail: four
immutable LixelGO captures verified in their original checkout, frozen
protocol/Bridge contour unchanged, 32 synthetic sentinels passed. The convenience
script initially failed because raw captures are deliberately absent from the
active worktree; its original-location integrity check passed without copying
or changing the captures. Ruff and diff checks passed.
A later runtime journal records an explicit successful Bridge write at 18:42 UTC,
followed by application-control acceptance, canonical START and the first point
frame, then a confirmed STOP at 18:44 UTC. These actions occurred while the
assistant was editing/testing discovery; they were not dispatched by this
investigation. The success followed the recovery-UI refactor and preceded the
new discovery implementation. It proves a subsequent successful physical
connection, not the cause or permanent resolution of the intermittent ATT error.
Do not automatically replay the earlier failed attempts.
## First-scan miss
The operator reported that an already active K1 is absent from the first search
and appears on the second. This was reproduced on the original six-second
implementation: 18:32 UTC returned four devices and zero K1 candidates; a second
explicit scan at 18:35 UTC returned five devices including the expected K1.
These observations do not distinguish radio advertisement latency from macOS
state and do not establish that the camera was powered off.
Discovery now opens one native Bleak scanner context. It listens for an initial
six-second window and, if no K1 name has appeared, continues the same context
up to a twenty-second bound, stopping on a later K1 candidate. There is no hidden
second scan, GATT connection, provisioning retry or cache-based candidate
promotion. Name matching only ends discovery; compatibility and connection
still require their separate evidence. The owner arbiter, generation revocation,
native-handle capture and cancellation cleanup remain in place. A caller's
explicit shorter duration is respected.
The shared frontend request/countdown, backend default and Node Bridge source
use the twenty-second bound. Completed operations and private structured logs
now include scanner startup, total elapsed time, first-candidate time and whether
the initial window was extended. They contain no Wi-Fi credentials.
After the new canonical process started, the first explicit UI search at
18:51 UTC found the expected K1. Native startup took 766 ms, first K1 detection
2023 ms from scanner construction, and total discovery 6768 ms. Extension was
not needed. The browser showed the fresh candidate and the arbiter returned idle.
This is one successful process-restart test; the browser cache was not cleared.
The operator requested a separate test after browser-cache clearing. That
acceptance is pending: the available in-app browser automation exposes no cache
clearing capability, and the clear-browsing-data keyboard shortcut had no effect.
The operator was asked to clear it; no cache clearing is claimed.
Additional discovery validation: 73 backend tests passed (scanner, owner arbiter,
persistent diagnostics and Node Bridge), seven facade discovery tests passed,
138 focused frontend tests passed, and architecture checks passed. TypeScript
and production build passed. Final focused discovery checks, Ruff, whitespace
and the frozen protocol/Bridge comparison passed. The previously completed full
772-test frontend pass applies to the recovery refactor; only affected suites
were rerun for the subsequent discovery change.
Canonical Core remains on port 8000; no listener exists on 8765. Onboard source
is synchronized separately; this investigation does not install or rebuild the
pending Node package.
## Ops publication
The existing Ops canon was read. Automatic approval review rejected the attempted
card update because consultation was authorized but publication of internal
technical details was not. No Ops card was changed; this local report is the
reviewable result pending explicit publication authorization.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,129 @@
# K1 live reference comparison and missing camera
Scope: operator-local K1 Bridge data acquisition on canonical Mission Core 8000.
No new physical START, STOP, provisioning or RTSP request was sent during this
investigation. The comparison uses the existing 6 September capture.
## Authoritative references found in Ops
MISSIONCOR-3, “Mission Core. Lixel K1 / XGRIDS Integration”, block “Внутренние
эталоны Mission Core” and its comment dated 22 August 2026 identify:
- Fast reference A: eaad9de, 20260822T105904Z_viewer_live. START to calibration
under 5 seconds; calibration 21 seconds; after calibration cloud 2 seconds,
right camera 4 seconds; STOP to physical onset 1 second.
- Recovery reference B: 1001a31, 20260822T130323Z_viewer_live. START 14 seconds;
calibration 22 seconds; cloud +1 second, camera +8 seconds; STOP onset 7 seconds.
The linked canonical report is
[Lab 010](../lab/010_K1_MISSION_CORE_INTERNAL_LIVE_BASELINE_20260822.redacted.md).
These are measured internal references for one K1 FW 3.0.2 and Bridge/direct LAN,
not a transferable SLA or a standalone Rerun preset. A used a short physical
ledger; B retained mature recovery history. Source quality was not reduced.
MISSIONCOR-66, “Mission Core. Канон интеграции Rerun”, and MISSIONCOR-74,
“Additional Core · Переносимая кастомизация Rerun”, define the separate
presentation contracts:
| Surface | Current profile | Clock and media |
| --- | --- | --- |
| Live acquisition | live-acquisition | stream_time; upstream live Rerun; right camera through the independent RTSP → durable fMP4 → MSE path |
| Saved Sessions / Data | recorded-session | session_time; progressive recorded admission; RecordedFmp4Player follows the shared playback clock |
| Canonical LAB result | laboratory-result | session_time; result-specific settings; merged RRD with native AssetVideo/VideoFrameReference; separate presentation gate |
The live receiver does not inherit the LAB full-readiness gate. Changing point
size, accumulation or blueprint cannot repair a camera producer that never
started. The current live display settings include accumulation 47 seconds,
point size 1 and height/viridis; no evidence identifies those visual values as
part of the fast reference, so they were not arbitrarily reset.
### Settings isolation limitation found during the audit
Distinct profile kinds, clocks and media admission do not prove complete
settings isolation. App.tsx still owns one sceneSettings/displayDraft pair for
live and Data, and useWorkspaceLayoutProfile() loads and saves one
observation.spatial profile containing scene settings. The normal settings
committer suppresses backend writes while recorded replay is presented, but it
still updates the common in-memory settings. The persisted layout restore/apply
path also has no profile-kind namespace. LAB uses its own resultId-scoped draft
and durable view profile.
Therefore this audit confirms distinct presentation contracts and LAB settings
ownership, not full live/Data settings isolation. No shared settings, layout,
Rerun renderer or replay code was changed for this camera repair. The focused
profile tests below do not cover the remaining live/Data settings coupling.
Separating that storage and state requires its own transition/race and browser
regression checks; it must not be folded into a camera-path fix implicitly.
## Observed 6 September failure
Existing session 20260906T184240Z_viewer_live:
| Metric | Fast A | Recovery B | Recent run |
| --- | ---: | ---: | ---: |
| MQTT callback → publication p50 | 23.839 ms | 83.934 ms | 93.151 ms |
| MQTT callback → publication p95 | 41.541 ms | 223.589 ms | 208.989 ms |
| Preview drops | 0 | 70 | 58 |
| Point decode errors | 0 | 0 | 0 |
| Camera archive | complete | complete | absent |
Run lengths differ; drop counts are not normalized performance rates. Device
calibration onset and first visible pixels were not independently measured in
the recent run, so the historical operator timings are not falsely compared to
backend timestamps. The recent run published 1,028,061 points in 386 PCL frames.
At 18:43:15 UTC the browser admitted a Rerun store; this alone is not proof of
visible point pixels. Between 18:43:20 and 18:43:50 the backend logged 22 failed
post-authoritative-PCL camera activations. First-PCL admission took 434 ms.
No camera producer activation success or camera media artifact exists in this
session. The private formatter discarded exception details, preventing recovery
of each historical exception stack from those records.
## Reproduced storage defect and bounded repair
The running checkout is separate from MISSIONCORE_DATA_DIR. Acquisition uses
resolve_missioncore_evidence_dir(), but XgridsK1CameraGateway previously confined
session paths to repository_root. The actual session is outside the checkout.
An offline call using the real existing session directory deterministically
raised “camera recording root must stay inside the repository” before authority
reservation, FFmpeg preparation or network I/O. Camera remained idle, matching
the observed pre-producer failure. This mismatch necessarily blocks recording
at the configured path even though the original exception stacks were lost.
The gateway now receives an explicit evidence_root from the existing service
composition. It confines both acquisition-owned and selected-preview recording
to that root after resolving paths. It rejects sibling directories and symlink
escapes. The source checkout remains the FFmpeg-binary lookup root; the fallback
for standalone gateway callers preserves their existing repository confinement.
No RTSP arguments, video quality, stream choice, camera producer lifecycle,
START/STOP, MQTT dialogue, Rerun blueprint or LAB/archive viewer policy changed.
Private exception diagnostics now retain only the exception class and final
filename/line/function. They omit exception text, locals, source lines and
absolute paths. This makes future failures attributable without leaking data.
## Validation and remaining physical acceptance
- Camera gateway suite: 42 passed, including seven new external-root,
composition-wiring and path-confinement cases. Synthetic FFmpeg produced
and archived media in the configured external directory.
- Camera acquisition lifecycle: 37 passed.
- Persistent diagnostics suite: 8 passed, including exception-location redaction.
- Focused frontend profile, environment, LAB view profile and recorded-camera
journal checks: 14 passed. These are contract-level checks, not physical
playback acceptance or proof of complete settings isolation.
- Mypy on camera.py and runtime_diagnostics.py: passed.
- Ruff and git diff --check: passed.
- Protocol, BLE provisioning/AP, physical ledger/coordinator, MQTT,
connection supervisor, runtime and archive remain identical to c041a56.
- The broad frozen-contour guard also includes camera.py, so it now deliberately
detects this narrow camera storage change. Its baseline was not advanced or
weakened. This is not a claim that the full freeze check passes unchanged.
The canonical idle service was refreshed and its replacement was confirmed
ready on port 8000 at 19:10:22 UTC; no frontend rebuild was required. A new
operator-started physical run is still
needed to verify right-camera appearance, durable media and browser playback.
The existing failed session is preserved and is not retroactively repaired.
Ops was consulted read-only. This local report was not published to a card.
@@ -0,0 +1,95 @@
# K1 station replies: misleading ATT error and fresh failures
Scope: the two operator-started Bridge attempts on Core 8000 following the
operator-reported browser cache clear. No device write, START/STOP, Wi-Fi switch,
subnet scan or firmware execution was performed by this investigation.
## Fresh evidence
| Attempt, UTC | K1 first advertisement | Complete scan | Connect operation | Reply |
| --- | ---: | ---: | ---: | --- |
| 19:22:25 | 1.308 s | 6.366 s | 51.399 s | ATT 4 INVALID_PDU |
| 19:23:55 | 2.208 s | 6.023 s | 7.587 s | ATT 4 INVALID_PDU |
Both attempts reached the validated GATT contract, baseline status read and
one 99-byte write-with-response. Neither write was acknowledged as successful.
The observed command capacity was 253 bytes. Empty passwords are rejected
before this path; presence does not prove correctness. Individual connect and
write durations were not recorded, so total latency is not falsely attributed
entirely to the Wi-Fi operation or entirely to CoreBluetooth.
Private snapshots, operation identities and the disassembly are retained under
the ignored `.runtime/k1-connect-incident-20260906/fresh-1922/` directory with
0600 files, UTC/monotonic timestamps and a SHA-256 artifact index.
## Firmware-level explanation
The reviewed official FW 3.0.2 artifact is the immutable source documented in
[Lab 004](../lab/004_K1_FW302_AP_CREDENTIAL_PROVIDER_20260720.redacted.md).
Its extracted `lixel_nman` executable is 323,464 bytes, SHA-256
`aead745e4e0073ae99e84e851e5560161f2d560d5804f21e5268116efbb1dc42`,
ELF build ID `205fb546e44667ca0e74159a7672269a81b754f5`.
Bounded offline AArch64 disassembly established:
1. At 0x14588 the service registers characteristic 7f01, with write callback
0x17e48 selected at 0x14598.
2. Its station branch calls `wifi_connect` at 0x18034 → 0x15440. The return
value is retained in w28 at 0x18038 and passed as the write-result error at
0x17fc4 → 0x291a8.
3. `wifi_connect` returns 4 in its network-not-found branches: 0x16190 and
0x16a2c. Adjacent diagnostics refer explicitly to the SSID not being found.
4. The branch matching NetworkManager's credentials-required output returns
6 at 0x1641c. This describes an unsuccessful credential path, not proof
of the exact incorrect character or how the form was populated.
Thus the device's application return codes collide with generic ATT names:
4 is displayed as INVALID_PDU and 6 as REQUEST_NOT_SUPPORTED. The existing
Mission Core message discarded the reviewed device meaning. Neither changing
the 99-byte layout nor switching write mode follows from these observations.
The callback performs Wi-Fi work before returning, so its response is not merely
an instantaneous transport acknowledgment.
This is an interpretation under the selected exact firmware profile. It is not
a universal ATT error mapping, proof of the live firmware before DeviceInfo,
or confirmation that the network configuration remained unchanged. The actual
SSID/radio condition still needs a corrected operator-run connection test.
The old screenshot displays a network name whose exact spelling was queried;
this report does not assume that spelling is a typo or assume K1 can see it.
## Bounded correction
`wifi_failure.py` owns the pure profile-scoped classification. The service uses
it only for Bridge/Direct, selected FW 3.0.2, an annotated gatt-write failure,
a dispatched/unconfirmed 99-byte write-with-response and BleakGATTProtocolError.
It retains the original exception code and raw ATT facts. Quick Connect,
baseline-read failures, other firmware and unrelated errors retain their
original diagnostics. Retry flags, mutation ledger and ambiguity remain intact.
The frontend shares the station guidance between the form and recovery panel.
It offers “Указать сеть Wi-Fi заново” through the existing explicit reset and
scan flow. The pending stage explains K1 Wi-Fi connection. It does not populate,
store, inspect or change the password. The GATT helper, packet bytes, MQTT,
physical command control and all Rerun profiles are unchanged in this increment.
## Validation
- 19 focused backend cases passed: 16 classification boundary cases, two
service-level reply cases and the existing ambiguous-write recovery case.
They verify one submission, retained raw codes, failed/unconfirmed state and
an unresolved ledger rather than promoting an error to success.
- 24 classification/diagnostic cases passed; mypy and Ruff passed.
- 775 frontend tests passed, including rendered station guidance and the
explicit same-device network setup action. Architecture: 4 passed.
- Production typecheck and build passed. The replacement canonical Core was
confirmed ready at 2026-09-06T19:41:10.665304+00:00.
- Browser check: the real Core 8000 Test Devices page loaded; normal and expanded
sizes worked. The page was left open. No BLE action was clicked. The new error
messages were covered by rendered tests, not represented as a new hardware run.
- Hardware Wi-Fi acceptance remains pending the exact target SSID and an
operator-started attempt. macOS networksetup did not provide the current SSID;
its output was not treated as proof that the host network is down.
Ops MISSIONCOR-3 was read through the direct MCP. Its iPhone network capture
starts at IP and does not contain Bluetooth HCI; it cannot establish the meaning
of this GATT callback. No Ops card was written.
@@ -0,0 +1,164 @@
# K1 Bridge on the onboard computer
Authority: the owner's 2026-09-06 request and two annotated Fleet screenshots.
The earlier Node context is historical evidence, not a new instruction. X4 work
is paused while its battery charges. Hardware acceptance below remains pending
until the owner powers on K1 and supplies the target WLAN in the application.
## Product surface decision
The operator occasionally adds a wireless sensor to one selected onboard
computer. The primary entity is that computer's device inventory. Discovery,
network observations, credentials and device control belong to that computer;
the operator's Mission Core is a paired remote console. Node's local console
uses the same form and backend contract.
Selected composition: a plus beside refresh in the device inventory opens the
canonical modal Window, titled «Подключение устройства к БК». The target board
is visible before discovery or credentials. K1 is selected from an explicit
scan on that board. The operator chooses a board-observed WLAN or enters an
SSID, then submits one Bridge operation. Success requires observed device
connectivity; dispatch alone is never shown as connected.
An independent Fleet connection workspace was considered: it separates the
action from its board and can imply operator-local radios. A new primary root
was also unnecessary. The owner explicitly selected the inventory plus and
authorized moving the existing operator-local connection workspace into LAB,
with all its current modes and functions, under «Тестовые устройства».
The stable workspace ID is retained for saved navigation.
This is domain content in admitted list/detail and modal compositions. Reuse
`Window`, `WindowFooterActions`, `TextField` (including password), `Select`,
`Button`, `IconButton`, `ResourceRow`, `SettingsCard`, `StatusBadge`,
`ActivityIndicator`, `ToastStack`; icons `plus`, `refresh`, `network`, `camera`,
`eye`, `settings`, `close`, `play`, `stop`. These exist in the sibling Design
Guideline registries. No new generic visual entity is introduced.
State grammar: board unavailable; service unavailable; ready to scan;
scanning; no candidates; candidate selected; network list unavailable with
manual entry; ready to connect; connecting; observed connected; failed before
write; outcome unknown after possible write. A stale discovery/runtime or
changed board invalidates the form. Closing the modal clears its password.
Closing after dispatch does not claim cancellation of a physical operation.
## Execution and security boundaries
Only Bridge is admitted on Node. Reuse the reviewed firmware 3.0.2 profile and
99-byte 7f01 operation with 7f02 observation and exact endpoint verification.
No Quick Connect, host association, AP enable, subnet scan or firmware action
is exposed by the Node adapter. BLE notifications, where used by the existing
profile, can entail the standard temporary CCCD subscription write.
Use the existing authenticated Node/Core channel. A device-enrollment command
targets the paired node and a worker runtime/discovery generation, even before
a device session exists. Wi-Fi credentials must not enter the existing durable
sensor-command journal, Fleet database, error text, evidence or argv. Pending
secret payloads are short-lived memory only. A restart, expired command or
uncertain dispatch never retries a provisioning write with a new identity.
The Node worker is the sole hardware owner for both consoles.
Linux adapters must observe BlueZ and NetworkManager on the board. Host-route
verification uses the kernel route to the exact K1 address; a tunnel/default
route cannot silently qualify as the required local Bridge path. Existing
macOS adapter behavior and the accepted local LAB workflows are retained.
## Rerun profile boundary
| Profile | Source / clock | Settings authority | Lifetime |
| --- | --- | --- | --- |
| Live acquisition | Current board camera/LiDAR / stream_time | Live preview settings | Active acquisition and execution binding |
| Recorded session | Immutable admitted recording / session_time | Session replay, trajectory, time and playback settings | Recording identity |
| Laboratory result | Immutable admitted result and recording / session_time | Result-specific scene, evidence layers, diagnostic selections | Result identity |
The native renderer and recorded data pipeline can be shared, but each profile
has an explicit discriminator. Crossing profiles remounts the renderer so its
refs, subscriptions and pending recovery cannot leak into another profile.
LAB uses its own factory and result identity. Source recording/application IDs
remain unchanged: profile separation does not rewrite evidence lineage.
The Node publishes native RRD through ordered WebRTC data channels and the
existing camera gateway publishes H.264/fMP4 through a second channel. Signalling
uses paired sensor operations; ICE admits private LAN/Tailscale host candidates
only, with no STUN/TURN. The Node Rerun sink opens no gRPC port. Both hosts inject
the existing isolated native Rerun renderer into the shared sensor UI.
Two viewers at most are admitted. Decoded preview envelopes use latest-value
queues; encoded RRD bytes are never dropped within an open recording. Slow
consumers are closed. Camera preview leases share the canonical recording
producer. Closing a viewer releases its peer and delivery lease; acquisition
STOP remains a separate explicit operator action. These bounds concern delivery;
sustained native-viewer CPU/GPU/memory acceptance requires the real board test.
Node live settings (point size, accumulation, color, palette, points, trajectory,
grid) invoke only `viewer.settings.update` behind `profile=live-acquisition`.
Recorded and LAB profiles keep their own controls. LAB result changes remount
the renderer as well as changes between the three profile kinds.
## Validation and current acceptance
- Focused backend tests: board binding, expiration, replay prevention, secret
non-persistence, restart/unknown outcomes and Linux route classifications.
- Architecture gate; frontend typecheck, unit tests and build sequentially.
- Node package build and installation provenance; service health after reboot.
- Browser: both plus controls, target board, scan/empty/error states, manual
SSID, password clearing, keyboard Escape, normal/expanded live viewer.
- Real K1: one explicit scan/select/Bridge, actual DeviceInfo verification,
device appears in both inventories; camera and LiDAR acquisition/stop;
reconnect and restart; regression of local LAB and recorded replay.
No hardware acceptance or successful deployment is claimed by this document.
## Completed validation
- Core architecture gate: 4/4; complete frontend suite: 764/764 after updating
navigation expectations. Core and Node typechecks and production builds pass.
- Node Go tests pass, including redacted enrollment journal, one dispatch per
intent, restart outcome unknown, wrong node/expired request/host mutation deny.
- Focused Python Fleet, pairing, Node SDK, existing BLE scanner and Rerun tests
pass (one existing optional case skipped). Empty Node adapter construction,
public state and teardown pass locally, with no hardware discovery/listener.
- Six WebRTC tests pass, including an actual bounded loopback data-channel
roundtrip. Missing camera preserves the RRD channel; rejected offers release
the peer. Native RRD sink emits an RRF2 header without opening gRPC.
- All 51 Linux wheel hashes and archive members were checked. The only admitted
.pth is Rerun's literal package-directory declaration; bootstrap adds that
exact directory without executing path hooks. Ubuntu 24.04/amd64/Python 3.12
and fixed root-owned runtime paths are enforced. Installation checks idle
acquisition and stops the old K1 worker before replacing its modules.
- Canonical Core checkout was updated and its exact launch agent restarted.
`/api/liveness` and the new enrollment API pass; the paired board remains
online. Exactly one integrated backend remains on 8000, none on 8765.
- In-app browser: Fleet no longer contains the old connection workspace; LAB
opens Test devices with the existing K1 scenario. The vehicle inventory plus
opens the board-scoped modal. Unavailable-service state and Escape were
verified against the real paired board, which still runs the prior Node.
## Package and remaining work
Prepared package: `apps/node-agent/build/mission-core-node_0.7.0_amd64.deb`.
Size: 429540264 bytes. SHA-256:
`10b1164b57c31cd3edcced8653faf14cb9dc459db06442f0b417bc7f23bd4e76`.
The adjacent provenance includes base revision, exact source hashes, pinned Go
and Design Guideline identity and the locked wheel manifest. Maintainer scripts
inside the package match the reviewed sources.
The automatic approval reviewer rejected a proposed source-only Linux smoke
transfer because the board address was considered insufficiently authorized.
No proprietary source/package or application key was sent after that rejection.
Only dependency wheels and their build fetch script were staged in a separate
Downloads directory earlier; no board service/system installation has occurred.
An explicit confirmation of the exact target, package and encrypted credential
migration was requested after the concrete package was ready.
After that confirmation: install the package with the board administrator's
normal authentication, import the existing application key via protected stdin
and `systemd-creds`, verify Linux worker/BlueZ/NetworkManager and both UIs, then
ask the owner to power K1 for the admitted Bridge and acquisition tests. Wi-Fi
credentials are entered in the application only. Do not claim hardware, reboot,
expanded live-view, camera/LiDAR or sustained-resource acceptance before these
checks. Board source archives are retained by the canonical K1 runtime; Node's
recorded-session browse/export surface is not introduced in this change.
Direct Ops tools were unavailable in this session. This local audit records
profile distinctions and engineering evidence; no live Ops card update is
claimed. The user's historical attachments were context, not authorization.
@@ -0,0 +1,62 @@
# K1 LAB acceptance and onboard continuation
## Accepted local baseline
The owner confirmed a successful local LAB connection after correcting the
network name. Core independently recorded `network_applied` and DeviceInfo /
control ready in 8.298 seconds. In the next operator-run session the owner
reported the usual approximately 20-second calibration, prompt point-cloud
appearance, visible signal loss after disabling Wi-Fi and successful recovery
after enabling it again (approximately 20 seconds of waiting). These recovery
timings are operator observations, not newly instrumented latency measurements.
They qualify that local run, not every outage duration, a Node reboot or Ubuntu.
The owner requested committing and pushing the working implementation and
resuming K1 Bridge on the paired onboard computer. The backend connection
read-model extraction, plugin-owned sensor UI, BLE discovery fixes, station
error messages, camera evidence-root handling, Node enrollment and separate
viewer-profile discriminators are included in this checkpoint. Earlier audit
documents retain their original time-scoped validation and limitations.
## Spatial scene placement
The owner explicitly moved the local test-device live scene from Control to
LAB. Its operator job is inspection of the current local test stream; source
selection, acquisition and live settings retain their existing ownership.
Keeping it under Control would imply the future operational board view; a new
root or duplicate viewer would add an unnecessary product surface. The existing
workspace is therefore registered under LAB with its stable `spatial-scene` ID,
renderer, settings and links intact. Existing sidebar, workspace shell and
`globe` icon are reused; no new Design Guideline primitive is introduced.
Only obsolete Control quick links to that scene are retired on settings read.
Custom page copy, media and unrelated links remain intact. Default Control
shortcuts become cameras and map. Home and LAB links can still open the same
scene. This is an owner-approved relocation, with no Rerun parameter change.
## Validation and onboard candidate
- Environment migration, Node bridge, connection read-model and Fleet
enrollment tests passed: 36 cases. Ruff passed for the environment changes.
- Go package tests passed with the built Node UI embedded and an isolated
build cache. No system toolchain or package installation was needed.
- Core architecture/type checks passed. Full frontend run: 784/786 passed;
the two failures were old LAB workspace-list expectations. Those expectations
were updated and both affected suites passed; no production change followed.
- Node 0.7.1 is the next package version so the earlier 0.7.0 candidate is not
silently replaced. Build provenance now covers the moved K1 frontend sources.
The 51 K1 and 29 RealSense wheel hashes were verified before reuse.
The current paired board was resolved from authenticated Core Fleet data and
its SSH host key matched the previously trusted Mini key. It runs Node 0.6.11.
Administrative installation requires the owner's normal Ubuntu authentication;
non-interactive sudo is unavailable. No password is requested in chat.
The exact package, installation result, source commit and final Core UI
delivery are recorded in a subsequent release addendum after the build.
Device preparation and real Bridge acquisition must still be accepted through
both interfaces on that board. Operator and board WLANs remain independent;
the board owns Bluetooth, network observation, K1 commands and raw data.
Ops direct tools are absent in this session. This local report is prepared for
the K1 and Node cards; no Ops publication is claimed.
@@ -0,0 +1,145 @@
{
"stage": "k1-plugin-boundary-r1",
"source_files": [
{
"path": "src/k1link/device_plugins/xgrids_k1/facade.py",
"sha256": "4d5144ffa261b4e436be037ed421a9a48386485ff81128aa2cf95ca6efe9f469",
"lines": 35549
},
{
"path": "src/k1link/device_plugins/xgrids_k1/connection_attempt.py",
"sha256": "0619c1555c182bb635272c66c998e457d3fb1378d221b440449b5b274fc2a27d",
"lines": 495
},
{
"path": "src/k1link/device_plugins/xgrids_k1/node_bridge.py",
"sha256": "11a58900ca1a8553e4301fa4a2cc61717b06a2b2ee18de412decf8c65ab2acdf",
"lines": 319
},
{
"path": "tests/test_k1_connection_read_model.py",
"sha256": "f3476dad252c9efb536fb6c1e7e84154e7704bae063fe7f1abd4fe536c4ed585",
"lines": 170
},
{
"path": "apps/control-station/src/core/device-plugins/contracts.ts",
"sha256": "c01d9f81d554d7c3155576eee148cf62f3ddbb891ef718c259ef5e4ad0190070",
"lines": 125
},
{
"path": "apps/control-station/src/workspaces/fleet/VehicleSensors.tsx",
"sha256": "d0922fe89b0e9162c045cbf6163737b789af0aa1239034b7bd7596fa9a961897",
"lines": 24
},
{
"path": "apps/control-station/tsconfig.app.json",
"sha256": "29297ad53cb440fe8bb0d399aacbf8d192d1df2bd87cc264537b6444fb15f3cd",
"lines": 52
},
{
"path": "apps/control-station/vite.config.ts",
"sha256": "92dab3df33e06c5e9302d1bacc9f9526b64341fa55a0cd466479b4c9505d4cd3",
"lines": 134
},
{
"path": "apps/control-station/test/sensorEnrollment.test.mjs",
"sha256": "b5d8d3ad4a4baaa5e16c40cfd5ec710c7800625bfdd53a3fa6e310d3b3352f8f",
"lines": 102
},
{
"path": "apps/node-agent/ui/src/NodeSensors.tsx",
"sha256": "e26493746c8a50e604a1e3f8bc75c3ba7cfdafd1a70b9750a1375f8d5bc14300",
"lines": 7
},
{
"path": "apps/node-agent/ui/tsconfig.json",
"sha256": "0bb5f9df862a6ce20e2877a947a0741e7e1c823939ac01dc4f76a449a72a290a",
"lines": 34
},
{
"path": "apps/node-agent/ui/vite.config.js",
"sha256": "682f1d5d691a74a7e3e5c15d38bb522455017b8f873fb424b374859e84d42a84",
"lines": 17
},
{
"path": "packages/sensor-ui/src/contracts.ts",
"sha256": "da575e7fd018e5bf6802c0ae5a706f05dda436587b7088eef2c009e56a7ade51",
"lines": 44
},
{
"path": "packages/sensor-ui/src/enrollment.ts",
"sha256": "2c510e2ae8597953b08ab017f89663f149a95b08b7c416708476f2fe5dc3cfd8",
"lines": 20
},
{
"path": "packages/sensor-ui/src/extensions.ts",
"sha256": "db930b121cf828f7e5e6a533f51b2eae40d99e383a6be6b37e16e28ea2271de3",
"lines": 29
},
{
"path": "packages/sensor-ui/src/pluginSdk.ts",
"sha256": "349f87b22d49e63a9b7f5ffc8fd1b05be424d9a3b918f232a491b333ae08ce8a",
"lines": 4
},
{
"path": "packages/sensor-ui/src/SensorWorkspace.tsx",
"sha256": "30f91a8d313e07c36f7b8577317cadece18467ece9af093abf76b3d59b14fd78",
"lines": 43
},
{
"path": "plugins/xgrids-k1/frontend/src/plugin.ts",
"sha256": "bc3a062cd9408622de39eb00bd60b0e9e701095eba1483d2a22874f1630fde07",
"lines": 17
},
{
"path": "plugins/xgrids-k1/frontend/src/api.ts",
"sha256": "df61477546658836b3472d423c5366c6c6e0de0cb08a178d7c9e31b93edf1e2e",
"lines": 2078
},
{
"path": "plugins/xgrids-k1/frontend/src/connectionAttempt.ts",
"sha256": "ff28453215c6c6f4a23d969b06914fd191e376da87fd72f3a4e44cb8d7a61286",
"lines": 17
},
{
"path": "plugins/xgrids-k1/frontend/src/components/K1OperatorError.tsx",
"sha256": "27bed30edf1397617bfcf51cea0a2783b6d7cdd43ece6469d092b89559f1e154",
"lines": 231
},
{
"path": "plugins/xgrids-k1/frontend/src/sensors/K1Detail.tsx",
"sha256": "b02130ff22bbd81d7a409001dfde21df682b20229a4f08d600d3e8f412a4acde",
"lines": 25
},
{
"path": "plugins/xgrids-k1/frontend/src/sensors/enrollment.ts",
"sha256": "ca29b7f3f5b33e6bf582748e86bc85eceef6c634ea877f494e27694177a46191",
"lines": 124
},
{
"path": "plugins/xgrids-k1/frontend/src/sensors/K1LiveSettings.tsx",
"sha256": "dc7ce392e92240766b69c3db25cd500d6752917a675acdf57c53a65c6a34f6a4",
"lines": 28
},
{
"path": "plugins/xgrids-k1/frontend/src/sensors/plugin.ts",
"sha256": "89bab0bd0955dbc85820b32194ea0d3b3d03fbb0567297fb8527db9543ccf705",
"lines": 7
},
{
"path": "plugins/xgrids-k1/frontend/src/sensors/runtime.ts",
"sha256": "f813948f12b9eb86ee7f5c70dbbbe33cc785ce7e4c941d8059a3d1ed82829de5",
"lines": 7
},
{
"path": "plugins/xgrids-k1/frontend/src/sensors/K1LiveView.tsx",
"sha256": "0493ff5b09f191fdb03bc14f635ec67289100f9e6927f6dd3d124f0f0656babe",
"lines": 63
},
{
"path": "plugins/xgrids-k1/frontend/src/sensors/DeviceEnrollmentWindow.tsx",
"sha256": "59dedf270b7a5b4fad3bb75b47b4ad4cac964a54aa4d73075c38b7f6860a3ff8",
"lines": 62
}
]
}
@@ -0,0 +1,147 @@
# K1 plugin boundary R1 — implementation
Scope: the first independently verifiable increment of the approved plugin
refactor. This report does not declare the whole architecture migration or
the physical Bridge incident resolved.
## Result
The shared sensor workspace no longer imports K1, selects K1-specific controls,
or owns K1 control-operation deadlines. K1 detail, live settings, live renderer
and Bridge enrollment are under `plugins/xgrids-k1/frontend/src/sensors`.
Control Station receives their optional contributions from the installed
`DeviceUiPlugin` registry; Node composes the same integration explicitly.
Both hosts resolve `@mission-core/sensor-sdk` to the same portable host surface.
Missing and ambiguous renderers fail closed; absence does not substitute the
camera detail controls for an unsupported device. Existing camera behavior is
retained through the host's default camera detail.
Four functions were mechanically extracted from `facade.py` into
`connection_attempt.py`. Their ASTs match the pre-change working-tree copy
exactly, including runtime/target/parent/lease/host-epoch proof checks. The
facade is now 35,549 lines; this is a first responsibility boundary, not a claim
that the remaining service is small or process-isolated.
Node now exports the existing connection attempt, snapshot revision, runtime
start time and current permitted enrollment actions. The compact projection
does not forward diagnostics, timeline payloads or exception text. After an
invocation error the driver may read the exact journaled operation once; it
never resends the physical command. Explicit host admission rejection is
distinguished from an unknown post-dispatch outcome. Secrets are removed from
input references even on pre-dispatch rejection; this does not claim secure
erasure of immutable language/runtime copies.
The enrollment observer distinguishes delivery completion from network and
control completion. It sends one POST, resolves lost responses by the same
operation ID, follows the exact owned bootstrap, ignores older snapshots,
stops observing across a runtime replacement, and stops client observation on
window closure without cancelling/replaying physical commands. Read-only
observation has a bounded budget beyond the existing delivery deadline; it
does not extend command admission. Polling in the open window keeps host
availability and backend authority visible. A changed runtime/discovery/mode
invalidates the selected device and credential draft.
## Validation
- All 629 cases in the existing acquisition lifecycle suite passed after the
extraction, including the existing recovery and physical-authority checks.
- The final Python read-model, Node bridge and Fleet enrollment set passed:
22 cases, covering projection privacy, exact operation correlation,
no-resubmit behavior, secret lifetime and pre-dispatch rejection.
- The full frontend unit suite passed: 784 tests. After the final observer and
presentation changes, 41 focused enrollment/boundary/architecture tests passed, including
two additional cases for current authority and delayed results during an
observed board outage.
- The architecture test passed. Control Station and Node TypeScript checks
and production builds passed. Jobs were run without a second backend or
Docker startup. The Node dependency install used the local npm cache with
scripts disabled. Full frontend tests emitted existing sandbox HMR/listener
warnings; the unit tests do not establish browser or hardware acceptance.
- Ruff and `git diff --check` passed. The four extracted functions retain equal
ASTs after formatting. Existing dirty working-tree changes were preserved.
The canonical LaunchAgent was restarted only after `/api/state` showed idle
capture/control and no accepted/running operation. The configured persistent
data directory remained outside the checkout. The replacement serves
`127.0.0.1:8000`, reports liveness `alive`, runtime
`snapshot-runtime-fefcc6209249e845738886a72813e13c`, and idle source state.
No Mission Core backend was found listening on 8765.
## Limits and next acceptance gate
No physical K1 command or new capture was issued in this increment. Browser
hardware acceptance remains pending the owner's required cache clearing; the
available browser tools do not expose that operation. A new tab or reload was
not counted as a cleared-cache test. The Node UI is built locally, but this
increment was not installed on the Ubuntu mini-PC.
The BLE packet format, provisioning dispatch/polling behavior, acquisition
command order, camera producer and Rerun profile settings were not modified.
In particular, this work does not prove resolution of the earlier ATT failure
or the separately identified early network-status polling risk.
Backend optional installation, dependency separation, isolated macOS runtime,
portable media IPC, archive-codec separation and full Node reboot acceptance
remain subsequent stages. Multiple simultaneous device sessions and a
multi-provider enrollment picker are not supplied by this increment.
Before the next deeper lifecycle extraction, accept one cleared-cache UI
Bridge connection and its exact operation stages on the prepared Mac; keep
network/control unknown states honest and compare recovery against the prior
Ops scenarios. Ubuntu Bridge requires its own physical acceptance before any
claim of platform parity. LAB/recorded/live Rerun profiles remain separate
acceptance dimensions.
References: [architecture audit](2026-09-06-k1-bridge-architecture-review.md),
[Ops inventory](2026-09-06-k1-bridge-ops-index.json),
[changed source hashes](2026-09-07-k1-plugin-boundary-r1-sources.json).
The implementation and open hardware checks were added to Ops card #3,
“Mission Core. Lixel K1 / XGRIDS Integration”, as 12 titled R1 blocks. The
17 existing blocks and historical card status were preserved.
## Owner's fresh Chrome test after R1
The owner reported clearing Chrome's cache, scanning, selecting K1 and
submitting the network form. The canonical service journal records discovery
completing in 12.395 seconds and the network attempt running from
2026-09-06 21:16:38.081 UTC to 21:16:49.307 UTC (11.226 seconds).
It failed at `gatt-write`, after one dispatched 99-byte write-with-response,
with `BleakGATTProtocolError`, ATT 4 (`INVALID_PDU`). Advertised characteristic
properties were read/write; the reported command capacity was 253 bytes.
The write was not confirmed. Neither post-write status polling nor MQTT
bootstrap was reached. This failure does not implicate the separate early
status-poll termination risk, camera path or Rerun profiles.
The selected FW 3.0.2 profile classifies this reply as
`k1-wifi-network-not-found`, based on the previously reviewed firmware callback.
This attempt itself did not obtain live DeviceInfo and cannot establish the
actual firmware or exact cause of network invisibility. The submitted network
name is deliberately not retained in the server journal. The exact SSID was
requested from the owner; no password was requested or retained. No spelling
error or radio incompatibility is assumed.
Source review confirmed that the form captures the password before clearing
React state, and the station path passes it to the existing frame encoder.
The existing LAB form trims SSID edges; whether that affects this attempt is
unknown. No provisioning behavior was changed on this evidence. The focused
Wi-Fi provisioning and firmware failure suites passed (43 tests); hardware
acceptance remains failed/pending diagnosis.
Sanitized operation events, UTC and monotonic observation timestamps, owner
test notes and an artifact hash are retained outside Git in the ignored
`.runtime/k1-connect-incident-20260906/fresh-2116/` directory. The agent issued
only local state/liveness reads, with no new device command or service restart.
Core 8000 remained alive. A fresh Ops card read timed out; this addendum has
not yet been copied to Ops.
## Subsequent successful connection and refusal guidance
The owner subsequently confirmed a mistyped network name and a successful
connection after correcting it. Core independently reports network applied
and DeviceInfo/control ready in 8.298 seconds on 2026-09-07. The previous
failure is explained for this incident; its historical outcome is not rewritten.
The UI now names Wi-Fi failure prominently and asks to check the network name
and password. See [the bounded correction and evidence](2026-09-07-k1-wifi-refusal-ui.md).
This accepts that connection attempt, not stream, reboot, Ubuntu or fresh-cache
visual acceptance of the subsequent wording change.
@@ -0,0 +1,57 @@
# K1 Wi-Fi refusal: operator diagnosis and UI correction
The owner confirmed that the previously entered Wi-Fi network name was wrong
and that connection succeeded after correcting it. The canonical Core journal
independently records a successful Bridge attempt on 2026-09-07, from
07:01:10.203 UTC to 07:01:18.501 UTC: `network_applied`,
`device-info-confirmed`, control `ready` (8.298 seconds).
The earlier ATT 4 response is therefore consistent with the exact reviewed
FW 3.0.2 station callback's network-not-found branch. The response establishes
the device's reported failure, not independently which character the operator
typed incorrectly. ATT 6 remains the separate credentials-required branch;
neither is inferred from a generic timeout or lost response.
## Implementation
The existing plugin-owned `networkFailurePresentation.ts` keeps distinct public
codes and specific reasons, while both messages now explicitly ask the operator
to check the network name and password. `K1OperatorError.tsx` gives those exact
station refusals the title “Не удалось подключить K1 к WiFi”. Existing LAB and
onboard enrollment consumers share the text. Unclassified Bluetooth errors,
timeouts and unknown outcomes keep their separate presentation.
This increment changes presentation only. No BLE frame, station callback
classification, ledger, retry authorization, acquisition command, connection
recovery or Rerun setting was changed. The historical failed attempt remains
failed/unconfirmed; the later successful attempt supplies its own authority.
## Validation and delivery
- Existing rendered recovery, provisioning and onboard enrollment assertions
were updated for the explicit Wi-Fi cause and credential guidance.
- Architecture checks passed (4 cases); the full frontend suite passed
(786 cases), including the existing no-resubmit and unknown-outcome coverage.
- Control Station and Node UI TypeScript checks and production builds passed.
Builds/tests were sequential; no Docker or extra backend was started.
- The Core UI was built in a temporary directory, then published with HTML last
and prior hashed assets retained for already open tabs. HTTP verification of
`/assets/app-B5NeGPU1.js` confirmed both the new title and guidance; the HTML
response has `Cache-Control: no-store`.
- Core 8000 remained alive with the same runtime identity and the K1 control
session ready. The backend was not restarted. No backend listens on 8765.
- The Node UI was built locally; this increment was not installed on Ubuntu.
No new device failure or browser hardware attempt was induced. Browser cache
clearing was not performed by the agent and no fresh-cache visual acceptance
is claimed. The user's successful attempt and rendered tests are distinct
evidence sources. Stream, power-loss and Ubuntu acceptance are not established
by this connection-only success.
Sanitized success evidence, owner notes, UTC/monotonic observation timestamps
and SHA-256 artifact metadata are retained outside Git in the ignored
`.runtime/k1-connect-incident-20260906/success-20260907/` directory. No Wi-Fi
password or submitted network name was retained.
The direct Ops tools are absent from this turn's tool inventory, and no tool
discovery endpoint is exposed. This report is prepared for card #3; it has not
been written to Ops. Legacy Ops widgets and raw API workarounds were not used.