Files
NODEDC_MISSION_CORE/docs/audits/2026-09-06-k1-bridge-architecture-review.md
T

54 KiB
Raw Blame History

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.

Глубокая смысловая сверка выполнена для сетевого/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.

Обнаруженные расхождения источников

  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 по слоям: нормальная последовательность

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, 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 и исходников позволило восстановить рабочие гарантии и найти конкретные границы риска. Следующее решение должно выбирать одну такую границу и её приёмку; переписывание всего K1-контура сразу не имеет достаточного доказательного основания.

11. Уточнение цели: необязательная интеграция устройства и переносимость

Дополнительный запрос владельца: K1 должен быть необязательной интеграцией; macOS — первая принимаемая платформа, Ubuntu — следующая проверка переносимости. Другие модели XGRIDS не должны требовать встраивания их протоколов в Core. Ниже — архитектурное предложение, не новая реализация или аппаратная приёмка.

Что уже отделено, а что ещё нет

  • Есть manifest DevicePlugin, отдельный frontend пакета plugins/xgrids-k1, backend в src/k1link/device_plugins/xgrids_k1, нейтральный SDK и загрузчик с проверкой версии, набора действий и handshake. Это реальная существующая основа, которую следует сохранить.
  • Composition frontend явно включает K1 в сборку. Это правильное место выбора поставляемых модулей, но сейчас выбор статический; независимо устанавливаемый frontend этим не доказан.
  • Backend composition допускает только transitional-in-process. ADR 0011 прямо исключает из текущих гарантий crash containment, supervisor, portable media IPC и установку пакетов. Наличие runtime transport не означает, что отдельный процесс уже работает.
  • Общий pyproject включает BLE/MQTT-зависимости и K1 CLI. Общий Python wheel содержит весь src/k1link. Поэтому независимое удаление драйвера пока нельзя считать принятой возможностью.
  • SensorWorkspace напрямую импортирует K1Detail и выбирает его по device.kind === 'k1'. Это конкретная зависимость общей поверхности от устройства; её место — регистрация визуального расширения интеграцией.
  • Анализ Python import graph обнаружил 14 модулей compute, прямо импортирующих K1 analysis/protocol/replay. Это главным образом работа с данными, а не управление радио. Их нельзя механически удалять вместе с драйвером: нужны отдельные границы чтения архивов и проверка LAB.
  • NodeBridge уже использует тот же 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 с предварительной очисткой браузерного кэша по указанию владельца. В этом дополнении выполнены только чтение кода и документирование; проверок на устройстве не было.