From 05bdab24a5c63a176ec5c4aaf7bf573bdd33d496 Mon Sep 17 00:00:00 2001 From: DCCONSTRUCTIONS Date: Thu, 16 Jul 2026 19:44:16 +0300 Subject: [PATCH] docs: record Mission Core K1 architecture milestone --- README.md | 72 +++- apps/control-station/README.md | 43 ++- docs/00_TECHNICAL_AUDIT.md | 15 +- docs/01_IMPLEMENTATION_PLAN.md | 8 +- docs/05_K1_MQTT_STREAM_PROFILE.md | 18 +- docs/07_MISSION_CORE_MONOREPO.md | 209 +++++++++-- ...lpha2-and-experimental-device-lifecycle.md | 338 ++++++++++++++++++ plugins/xgrids-k1/README.md | 37 +- 8 files changed, 659 insertions(+), 81 deletions(-) create mode 100644 docs/adr/0004-plugin-sdk-v0alpha2-and-experimental-device-lifecycle.md diff --git a/README.md b/README.md index 0b1785e..f35e57d 100644 --- a/README.md +++ b/README.md @@ -8,16 +8,30 @@ extracted, and device plugins. The first proven hardware vertical is the XGRIDS/LixelKity K1 plugin. On firmware 3.0.2 the host provisions the scanner onto an existing LAN without LixelGO, -connects to its MQTT broker, durably captures the raw stream, decodes point cloud -and pose, and renders the real cloud plus trajectory through an embedded -self-hosted Rerun Web Viewer. The former Foxglove bridge remains only as a -legacy regression module. +connects to its MQTT broker, persists each raw frame before preview work, decodes +point cloud and pose, and renders the real cloud plus trajectory through an +embedded self-hosted Rerun Web Viewer. Capture files are `fsync`ed on clean +close; per-frame power-loss durability is not claimed. The former Foxglove +bridge remains only as a legacy regression module. The repository is intentionally migrating in stages. The current `src/k1link` package is the compatibility implementation of the first plugin path; vendor transport and codecs will move behind `plugins/xgrids-k1` and the Mission Core Plugin SDK without changing the verified wire protocol or raw evidence format. +Plugin SDK v0alpha2 now provides executable, vendor-neutral identity, session, +operation, stream, evidence and compatibility contracts. The meanings remain a +local experimental vocabulary rather than a mutation of NODE.DC Platform +Ontology. The current runtime is still transitional and in process: it uses an +explicitly injected K1 normalizer to produce transport-neutral local consumer +views; portable SDK stream envelopes, process isolation, durable operations, +multi-device routing and the remote Edge split remain later gates. + +The current runtime cannot read K1 firmware automatically. It keeps the exact +profile inactive until the operator explicitly attests firmware `3.0.2` and +direct-LAN topology; state records that basis as `operator-attested`, not as +device-derived evidence. + The repository now contains one narrowly gated state-changing command: `ble wifi-configure`. It accepts only the reviewed firmware-3 provisioning profile and requires explicit `--confirm-write`; the Wi-Fi password is collected @@ -29,7 +43,16 @@ Nothing changes router settings, firmware or global Python packages. - one XGRIDS/LixelKity K1; - one Apple Silicon MacBook running macOS; - one ordinary TP-Link Deco/mesh network used by other devices; -- no LixelGO, phone, Linux host, dedicated AP, OpenWrt, or vendor SDK. +- the proven baseline used no LixelGO, phone, Linux host, dedicated AP, + OpenWrt, or vendor SDK. + +An owner-controlled iPhone with LixelGO is now available for a separate, +evidence-only observation stage. It does not invalidate the no-phone baseline +and is not a runtime dependency. The decision and gated runbook are +[`ADR 0005`](docs/adr/0005-owner-controlled-lixelgo-iphone-observation.md) and +[`docs/08_LIXELGO_IPHONE_OBSERVATION.md`](docs/08_LIXELGO_IPHONE_OBSERVATION.md). +The Mac capture environment is isolated under +`plugins/xgrids-k1/lab/iphone-capture/` and does not require full Xcode. The ordinary router is sufficient for the first gates. We first observe the existing LAN without changing it. A Guest/IoT SSID is optional and may be @@ -45,10 +68,11 @@ The project has three independent gates: 3. Without LixelGO, K1 can be associated with Wi-Fi and a proprietary data session can be opened. -All three gates are now proven on the tested unit. The external stream is plain +All three gates are now proven on the tested unit. The spatial stream is plain MQTT 3.1.1 on TCP 1883. Firmware-3 `lio_pcl` is protobuf wrapped in a raw LZ4 -block, and `lio_pose` is an uncompressed protobuf. Raw panoramic camera access is -still unproven and is not implied by point-cloud success. +block, and `lio_pose` is an uncompressed protobuf. Owner-operated LixelGO capture +also proved separate left/right RTSP/H.264 camera previews on TCP 8554. This is a +compressed preview contract, not proof of full-resolution raw camera access. ## Local environment @@ -58,7 +82,7 @@ repositories. ```bash cd /Users/dcconstructions/Downloads/mnt/NODEDC/NODEDC_MISSION_CORE -uv sync --group dev +uv sync --frozen --group dev uv run k1link doctor uv run pytest ``` @@ -74,9 +98,10 @@ Install, type-check, build and serve the complete local application from the repository root: ```bash -uv sync --group dev +uv sync --frozen --group dev cd apps/control-station -npm install +npm ci +npm run test:unit npm run typecheck npm run build cd ../.. @@ -89,7 +114,14 @@ still provides real CoreBluetooth discovery, one operator-triggered reviewed BLE Wi-Fi provisioning write, read-only MQTT live capture, native `.k1mqtt` and reviewed-TSV replay, raw-first evidence storage and measured preview metrics. Physical K1 scanning is still started and stopped by the verified double-click; -the connector publishes no modeling command. +the connector publishes no modeling command. The observed LixelGO action mapping +remains descriptive and write-disabled. + +This locked bootstrap is repeatable in the current workspace, not yet a +standalone release install. The frontend consumes sibling `file:` packages from +`NODEDC_DESIGN_GUIDELINE`; `package-lock.json` does not pin that checkout's Git +revision or content hash. Publishing/vendoring those packages or enforcing an +immutable donor revision remains a packaging and CI prerequisite. The Observation spatial workspace embeds the open-source Rerun Web Viewer inside the Mission Core shell. It can open a compatible RRD file over HTTP(S) or a @@ -102,9 +134,12 @@ the resulting URL through control-plane state. Later sessions reset their session-local scene and metrics and reuse that process-wide stream; this avoids restarting the native listener while the embedded browser remains connected. Unless an operator has entered a manual source, the React application assigns -that URL to the embedded viewer. The complete runtime path is K1 MQTT → raw-first evidence -capture → bounded latest-wins preview queue → reviewed protobuf/LZ4 decoders → -Rerun `Points3D`, `Transform3D` and `LineStrips3D` → embedded Web Viewer. +that URL to the embedded viewer. The complete runtime path is K1 MQTT → raw-first +evidence capture → bounded latest-wins preview queue → explicitly injected K1 +protobuf/LZ4 normalizer → transport-neutral decoded local views → Rerun +`Points3D`, `Transform3D` and `LineStrips3D` → embedded Web Viewer. Rerun does +not inspect K1 topics or raw payloads. These local decoded views are not yet the +portable Plugin SDK wire envelopes. The default Rerun blueprint shows a 12-second sliding accumulation of real point frames. Product controls are connected for point size, intensity/height/distance @@ -115,9 +150,9 @@ point cloud, trajectory, camera frame or latency value is generated. A powered-device checkpoint passed 80 real MQTT messages through the current Rerun runtime: 38 point-cloud frames, 42 pose frames, 2,775 points in the last -cloud and zero decode errors. Raw panoramic camera frames remain absent. Rerun -`capture_time` is the Mac receive timestamp, not a proven K1 sensor timestamp or -photon-to-screen measurement. +cloud and zero decode errors. The later RTSP camera preview is not yet wired into +the Rerun/runtime path. Rerun `capture_time` is the Mac receive timestamp, not a +proven K1 sensor timestamp or photon-to-screen measurement. The old Foxglove implementation is retained only in `src/k1link/viewer/foxglove_bridge.py` and its regression tests. The current @@ -179,6 +214,7 @@ present. - [Mission Core monorepo and plugin boundary](docs/07_MISSION_CORE_MONOREPO.md) - [Monorepo architecture decision](docs/adr/0002-mission-core-monorepo.md) - [Device plugin UI and runtime boundary](docs/adr/0003-device-plugin-ui-and-runtime-boundary.md) +- [Plugin SDK v0alpha2 and experimental device lifecycle](docs/adr/0004-plugin-sdk-v0alpha2-and-experimental-device-lifecycle.md) - [Redacted live lab report](docs/lab/001_K1_LIVE_MQTT_20260715.redacted.md) - [Session manifest schema](schemas/session-manifest.schema.json) - [Reference input provenance](docs/reference/README.md) diff --git a/apps/control-station/README.md b/apps/control-station/README.md index 52acef5..c8d7e22 100644 --- a/apps/control-station/README.md +++ b/apps/control-station/README.md @@ -16,7 +16,7 @@ device adapter, но структура интерфейса от него не | Контур | Состояние | Что это означает | | --- | --- | --- | | Mission Core fixed shell | Реализован | Header, навигация по разделам, рабочая поверхность, окна и инспекторы работают в одном приложении. | -| Device plugin registry | Реализован, v1alpha1 | До выбора модели provider остаётся inert и не делает I/O; каждый manifest объявляет ровно одну модель, custom `device.connection` UI key и backend factory. Frontend component подключается одним reviewed import в composition root. | +| Device plugin registry | Реализован, v1alpha1 + v1alpha2 | До выбора модели provider остаётся inert и не делает I/O. v1alpha1 сохраняет одну модель; v1alpha2 допускает одну или несколько моделей и требует profile coverage каждой. Custom `device.connection` UI key и backend factory подключаются одним reviewed import в composition root. | | Локальный control plane | Реализован | React получает состояние и выполняет операции через FastAPI REST и WebSocket на loopback. | | K1 BLE → Wi-Fi | Реализован | Реальный BLE-поиск всех видимых устройств и одна подтверждённая provisioning-запись выбранному устройству. | | K1 live/replay MQTT | Реализован | Read-only приём, raw-first сохранение, декодирование облака точек и позы, реальные метрики. | @@ -36,12 +36,13 @@ device adapter, но структура интерфейса от него не K1 MQTT :1883, read-only └── raw .k1mqtt + metadata + SHA-256 сохраняются первыми └── bounded latest-wins preview queue (32 сообщения) - └── проверенный protobuf/LZ4 decoder - └── Rerun Points3D + Transform3D + LineStrips3D - └── gRPC/proxy TCP 9876 - └── rerun_grpc_url в REST/WebSocket state - └── встроенный @rerun-io/web-viewer - └── пространственная сцена Mission Core + └── явно внедрённый K1 protobuf/LZ4 normalizer + └── transport-neutral DecodedPointCloudView / DecodedPoseView + └── Rerun Points3D + Transform3D + LineStrips3D + └── gRPC/proxy TCP 9876 + └── rerun_grpc_url в REST/WebSocket state + └── встроенный @rerun-io/web-viewer + └── пространственная сцена Mission Core Mission Core Control Station ←→ REST /api/v1/device-plugins/* + plugin-scoped WebSocket events @@ -108,10 +109,11 @@ layout пока остаются интерфейсным контрактом. ```bash cd /Users/dcconstructions/Downloads/mnt/NODEDC/NODEDC_MISSION_CORE -uv sync --group dev +uv sync --frozen --group dev cd apps/control-station -npm install +npm ci +npm run test:unit npm run typecheck npm run build @@ -124,6 +126,12 @@ uv run k1link serve предусмотрен, потому что endpoint подключения кратковременно принимает пароль Wi-Fi. +Этот locked bootstrap повторяем в текущем workspace, но он ещё не является +standalone release install. Локальные `file:` зависимости берутся из соседнего +`NODEDC_DESIGN_GUIDELINE`, а lockfile не фиксирует Git revision или content hash +этого checkout. До CI/portable packaging эти пакеты нужно опубликовать, +завендорить либо проверять по неизменяемой donor revision. + Для разработки интерфейса после запуска backend: ```bash @@ -142,7 +150,10 @@ Vite слушает `http://127.0.0.1:5173` и проксирует весь `/a 2. Выбрать **Парк → Локальное устройство**. 3. В каталоге моделей выбрать **XGRIDS LixelKity K1**. Только после этого активируется runtime и монтируется custom UI XGRIDS-плагина. -4. Включить K1, дождаться стабильного индикатора и подтвердить это в форме. +4. Включить K1, дождаться стабильного индикатора, вручную сверить firmware + `3.0.2` и direct-LAN топологию, затем подтвердить это в форме. Mission Core + не читает firmware с устройства; такое подтверждение остаётся + `operator-attested`, а не device-derived evidence. 5. Нажать **Показать все BLE-устройства**. Интерфейс показывает полный результат шестисекундного поиска; метка совместимости является подсказкой, выбор делает оператор. @@ -211,7 +222,7 @@ Frontend XGRIDS-плагина использует только v1alpha namespa `/api/viewer/settings` сохранены как deprecated compatibility shims внутри XGRIDS backend contribution. -В v1alpha1 backend composition загружается из manifests, а frontend plugins +В v1alpha1/v1alpha2 backend composition загружается из manifests, а frontend plugins статически включаются в сборку через `src/composition/devicePlugins.ts`. Автоматической runtime-сверки двух installed sets пока нет: их соответствие — проверяемое требование сборки до появления подписанных plugin bundles и startup @@ -219,9 +230,17 @@ compatibility handshake. Поля manifest `permissions`, `mutating` и `secretFields` пока являются декларативными метаданными; generic host валидирует их форму и action allowlist, но ещё не реализует на их основе RBAC, подтверждения оператора или secret-vault substitution. +Точный K1 compatibility profile остаётся неактивным, пока оператор явно не +подтвердит firmware `3.0.2` и direct-LAN топологию. Runtime не получает версию +firmware с устройства и явно помечает основание как `operator-attested`. +Ошибки валидации namespaced action endpoint и deprecated `/api/connect` +возвращаются как общий `422` без исходных значений запроса, поэтому отклонённое +значение credential-поля не отражается клиенту. Legacy `/api/state` и изменяющие состояние ответы могут вернуть snapshot напрямую или -как `{ "state": { ... } }`. Текущие поля включают `phase`, `message`, `devices`, +как `{ "state": { ... } }`. v1alpha2 snapshot отдельно содержит `device_ref`, +`device_session`, `acquisition`, `operations`, compatibility/calibration/sensor +facts и старые presentation-поля `phase`, `message`, `devices`, `selected_device_id`, `k1_ip`, `source_mode`, `metrics`, `rerun_grpc_url` и `viewer_settings`. Legacy-поля `foxglove_ws_url`/`foxglove_viewer_url` остаются пустыми. OpenAPI доступен по `/api/docs`. diff --git a/docs/00_TECHNICAL_AUDIT.md b/docs/00_TECHNICAL_AUDIT.md index a201a94..151221f 100644 --- a/docs/00_TECHNICAL_AUDIT.md +++ b/docs/00_TECHNICAL_AUDIT.md @@ -1,6 +1,6 @@ # Technical audit -Status: live feasibility validated, 2026-07-15. +Status: live feasibility and camera-preview transport validated, 2026-07-16. ## Executive finding @@ -11,9 +11,10 @@ physical-button scan and decoded both point-cloud and pose reports. All 1,140 captured `lio_pcl` frames decoded as raw-LZ4 protobuf blocks, yielding 4,165,862 points. All 1,215 `lio_pose` messages decoded, and the resulting -approximately 1.566 m displacement matched the controlled movement. The -remaining feasibility question is the panoramic camera branch, not the core -realtime LiDAR/pose path. +approximately 1.566 m displacement matched the controlled movement. A later +owner-operated LixelGO/iPhone capture also observed left/right RTSP/H.264 camera +preview and the remote start/stop wire mapping. The remaining camera work is a +bounded Mission Core receiver, not transport discovery. The supplied Bible is useful as an OSINT dossier. It is not an executable plan for the actual stand because many experiments assume a phone and LixelGO. The @@ -52,12 +53,14 @@ the local LAS alone: current LixelStudio materials describe K1 remote control and realtime point-cloud streaming over USB/Wi-Fi. LixelStudio is Windows-only and this does not disclose or guarantee access to the protocol from macOS. It does, however, justify keeping realtime point cloud as the primary stream target. -No comparable official evidence proves an exportable raw panorama/camera stream. +The physical Lab 002 capture now provides stronger evidence than the prior +official-material inference: a compressed left/right preview leaves the device. +It still does not prove a full-resolution raw panorama stream. ### Still unsupported until measured - Local `map.las` layout resembles the network stream. -- Raw or stitched panoramic camera frames leave the device. +- Full-resolution raw or pre-stitched panoramic camera frames leave the device. - The upper 24 bits of point `rgbi` are usable packed RGB. - MQTT application commands can be published safely without first reproducing device/session header state and response handling. diff --git a/docs/01_IMPLEMENTATION_PLAN.md b/docs/01_IMPLEMENTATION_PLAN.md index b34570f..126f1c8 100644 --- a/docs/01_IMPLEMENTATION_PLAN.md +++ b/docs/01_IMPLEMENTATION_PLAN.md @@ -15,12 +15,14 @@ Each gate produces evidence and an explicit GO, PAUSE or BLOCKED result. | Stage 4 artifacts/flows | GO — bounded capture, hashes and negative control | | Stage 5 point cloud | GO — raw-LZ4 protobuf, 1,140 live frames decoded | | Stage 5 pose | GO — 1,215 live frames decoded and motion-correlated | -| Stage 5 camera | PAUSE — no independent frame/video stream observed | +| Stage 5 camera | GO (discovery) — left/right RTSP/H.264 preview observed; runtime adapter pending | | Stage 6 live viewer | GO — React console, Foxglove cloud/path and Mac latency metrics | USB project copying remains optional ground truth rather than a blocker for the -now-verified network path. MQTT control publishing remains deliberately deferred -because the physical button is a known-safe start/stop mechanism. +now-verified network path. Owner-operated LixelGO traffic verifies the MQTT +start/stop mapping and RTSP camera transport. MQTT control publishing remains +deliberately deferred because complete request/save/rollback semantics are not +yet modeled and the physical button remains a known-safe fallback. The Stage 6 alpha uses a bounded raw-first bridge: loss in the visualization queue cannot discard MQTT evidence. Acceptance is replay of the full captured diff --git a/docs/05_K1_MQTT_STREAM_PROFILE.md b/docs/05_K1_MQTT_STREAM_PROFILE.md index 6ebe876..3cde9fb 100644 --- a/docs/05_K1_MQTT_STREAM_PROFILE.md +++ b/docs/05_K1_MQTT_STREAM_PROFILE.md @@ -131,12 +131,17 @@ orientation is exposed as `(x,y,z,w)`. Unknown tail fields are retained. ## Application control boundary -Static analysis identified the application start/stop topic as +Static analysis first identified the application start/stop topic as `lixel/application/request/modeling`, QoS 2, with a protobuf `ModelingRequest`. Start action is 1 and stop action is 2. A valid request also contains a device/session/OpenAPI header and, for start, project/record/scan/mount settings. +Lab 002 then observed both mappings in owner-operated LixelGO traffic and +correlated them with device success responses and physical start/stop of the +high-rate streams. This promotes the mapping from a static hint to descriptive +wire evidence; it does not authorize replay or publishing. + Publishing is deliberately not implemented. The physical double-click provides a verified autonomous start/stop path and avoids inventing session headers or changing scan settings. A future publisher requires a separate reviewed profile, @@ -156,9 +161,10 @@ Default defensive limits are applied before allocation or iteration: Per-frame failures do not justify firmware writes or speculative recovery. Always preserve the raw MQTT record and report the decoder error separately. -## Current camera result +## Camera result outside MQTT -No independent camera, panorama, JPEG, H.264 or RTSP topic was observed in this -run. `lio_pcl` may carry intensity or packed color information, but that does not -prove access to the two raw panoramic camera streams. Camera discovery remains a -separate evidence gate. +No camera payload was observed in the report-topic MQTT run documented above. +The later LixelGO/iPhone experiment found the preview on a separate transport: +left/right RTSP sessions on TCP `8554` with interleaved RTP/TCP and H.264. This +proves compressed preview availability, not full-resolution raw frames or +camera calibration. See Lab 002. diff --git a/docs/07_MISSION_CORE_MONOREPO.md b/docs/07_MISSION_CORE_MONOREPO.md index 3a62220..22880d7 100644 --- a/docs/07_MISSION_CORE_MONOREPO.md +++ b/docs/07_MISSION_CORE_MONOREPO.md @@ -1,42 +1,193 @@ # NODEDC MISSION CORE monorepo -This document maps the renamed repository to the target Mission Core -architecture without claiming that unfinished package boundaries already -exist. +This document maps the current repository to the target Mission Core +architecture without claiming that deferred package and process boundaries +already exist. The accepted device-lifecycle decision is recorded in +[`ADR 0004`](adr/0004-plugin-sdk-v0alpha2-and-experimental-device-lifecycle.md). ## Current layout | Path | Current responsibility | Target responsibility | | --- | --- | --- | | `apps/control-station/` | React shell, device workflow, spatial scene | Vendor-neutral operator application | -| `src/k1link/web/` | Local FastAPI control plane and K1 session orchestration | Generic host APIs plus plugin supervisor | -| `src/k1link/viewer/` | Replay runtime and Rerun adapter | Canonical scene sink and presentation adapter | -| `src/k1link/ble/` | K1 BLE discovery and reviewed provisioning | `plugins/xgrids-k1` | -| `src/k1link/mqtt/` | Raw-first K1 MQTT capture | `plugins/xgrids-k1` behind EvidenceStore | -| `src/k1link/protocol/` | Firmware-scoped K1 codecs | `plugins/xgrids-k1` | -| `packages/plugin-sdk/` | Contract placeholder | Versioned manifests, capabilities, lifecycle, schemas | -| `plugins/xgrids-k1/` | Extraction boundary and documentation | Independently versioned device plugin | +| `packages/plugin-sdk/` | Installable v0alpha2 Pydantic contracts and JSON Schema export | Portable host/plugin identity, lifecycle, stream and evidence boundary | +| `plugins/xgrids-k1/` | v1alpha2 manifest, exact compatibility profile, fail-closed loader and extraction documentation | Independently versioned XGRIDS device plugin | +| `src/k1link/web/` | Local FastAPI host, in-memory operation/acquisition lifecycle and transitional XGRIDS facade | Generic host APIs plus isolated plugin supervisor | +| `src/k1link/mqtt/` | Raw-first K1 MQTT capture | Plugin-owned transport behind an EvidenceStore | +| `src/k1link/protocol/` | Firmware-scoped K1 codecs and explicit vendor normalizer | Plugin-owned protocol adapter | +| `src/k1link/data_plane/` | Transport-neutral decoded in-process consumer views | Local projections hydrated from portable SDK envelopes | +| `src/k1link/viewer/` | Rerun consumer, visualization runtime and legacy Foxglove regression module | Replaceable canonical scene sink and presentation adapters | +| `docs/domain-model/` | Versioned experimental Mission Core vocabulary | Promotion source for meanings proven across devices/services | -## Extraction order +## Implemented boundary -1. Define canonical PointCloud, Pose, DeviceStatus, RawEnvelope, and metrics - contracts in the Plugin SDK. -2. Make Rerun consume canonical frames rather than K1 MQTT topics. -3. Extract codecs and replay parity fixtures. -4. Extract raw capture with fault isolation between evidence and preview. -5. Extract BLE/Wi-Fi provisioning and MQTT into a least-privilege edge worker. -6. Replace K1-specific host routes and state with plugin/device-session APIs. -7. Drive the connection wizard from plugin capabilities and schemas. -8. Re-run native replay and the physical BLE → Wi-Fi → MQTT → scene acceptance. +The current K1 live/replay data path is: + +```text +raw K1 transport + -> raw-first evidence capture + -> bounded preview queue + -> XGRIDS normalizer injected by the plugin facade + -> DecodedPointCloudView / DecodedPoseView + -> Rerun bridge + -> embedded local viewer +``` + +Rerun does not import K1 protocol code, inspect MQTT topics or receive raw +payloads. `VisualizationRuntime` cannot choose a vendor decoder implicitly; its +composition owner injects one. The local `Decoded*View` classes are in-process +consumer projections, not the portable Plugin SDK wire envelopes. + +The Plugin SDK v0alpha2 separately defines model/device/session identity, +operation events, acquisition-related session state, canonical stream +envelopes, payload handles, evidence lineage and compatibility assessments. It +is importable through the root editable path dependency and independently +buildable from `packages/plugin-sdk`. The hot K1 path does not yet instantiate +all SDK envelopes or use the SDK `EvidenceStore` protocol. + +## Experimental vocabulary, not Platform Ontology + +The local vocabulary distinguishes: + +```text +model != device != transport alias != device session +acquisition != operation != source/channel != evidence +acknowledgement != completion +raw evidence != decoded data != viewer state +``` + +These meanings remain versioned under `docs/domain-model/` and do not mutate or +create a runtime dependency on NODE.DC Platform Ontology. Promotion requires a +second device family or another platform service using the same stable meaning, +an owner, lifecycle, compatibility policy and migration tests. + +## Plugin manifest and compatibility + +The catalog accepts existing v1alpha1 manifests and additive v1alpha2 manifests. +v1alpha1 remains a one-model contract; v1alpha2 accepts one or more models and +requires every model to be covered by a reviewed, plugin-local, path-confined +compatibility profile. The current XGRIDS profile matches exactly: + +- XGRIDS LixelKity K1; +- firmware `3.0.2`; +- direct-LAN topology; +- one retained physical laboratory evidence scope. + +Unknown or unattested firmware fails closed. The current runtime does not read +firmware from the device: it activates this limited, read-only profile only +after explicit operator attestation of firmware `3.0.2` and direct-LAN topology, +and records the basis as `operator-attested`. That claim is not device-derived +proof. The profile independently records observation, decode, replay, +physical-verification and write evidence. Loading it cannot authorize a +transport mutation. Application-command publishing remains disabled; the +separately reviewed BLE Wi-Fi provisioning write retains its own explicit +operator gate. + +## Semantic lifecycle + +The transitional facade now creates separate provisional device, +device-session, acquisition and operation IDs. Acquisition can be prepared, +wait for receiver readiness, then wait for an external physical start, and become acquiring only after real point +data, wait for an externally confirmed stop, finalize and complete. Capture-only +stop reports the K1 physical state as unknown. + +The operation journal is bounded and in memory. It records IDs, idempotency, +declared deadlines, progress and terminal results without action parameters or secrets. +It is not durable, distributed or recoverable after process restart. + +Semantic acquisition actions coexist with legacy `stream.*` actions for +v1alpha1/UI compatibility. On this profile, start and stop operate the local +receiver and operator workflow only. K1 scanning is still started and stopped +by physical double-click; no modeling request is published. + +Left/right camera preview transport, endpoint paths and H.264 framing are now +observed under the exact compatibility profile. The read-only runtime adapter is +not implemented yet. Device status and heartbeat remain raw observed channels +without semantic decoders. Device calibration command and sensor-to-vehicle +extrinsics are unavailable. + +## Remaining extraction order + +1. Preserve native replay parity and repeat the exact-profile physical + point/pose regression through the explicit normalizer boundary. +2. Map portable SDK stream/evidence contracts onto the hot path without + replacing raw evidence or changing consumer output. +3. Extract codecs, normalizer and raw capture physically behind the XGRIDS + plugin package boundary. +4. Add process isolation and a durable operation/evidence supervisor. +5. Replace compatibility routes and singleton state with multi-device session + routing. +6. Split Edge execution from the Control Station behind authenticated transport. +7. Implement the now-observed read-only RTSP/H.264 camera adapter; keep the + modeling-command publisher disabled until its separate safety gate closes. ## Invariants -- Raw evidence is durable before preview work. -- Mission Core starts when the XGRIDS manifest is absent. A formal - disabled/quarantine state for an installed plugin is a future supervisor feature. -- The plugin cannot add global navigation or arbitrary CSS. -- Vendor writes and commands must ultimately require declared capabilities and - operator gates. In v1alpha1 `permissions`, `mutating`, and `secretFields` are - validated metadata only; concrete adapters still enforce request safety. -- The visualization engine can be replaced without changing device plugins. -- Real captures and credentials remain outside Git. +- Raw evidence is persisted before preview work and is never overwritten by a + decode result. +- Mission Core starts when the XGRIDS manifest is absent. Formal installed + plugin quarantine remains a future supervisor feature. +- Core code does not branch on XGRIDS IDs, fields, topics or firmware. +- A plugin cannot add global navigation or arbitrary CSS. +- A transport alias never becomes a stable device identity merely because it + appears in a vendor header. +- Acknowledgement, first data, operator confirmation and physical completion + remain distinct evidence levels. +- Vendor writes require a declared capability, exact compatibility evidence, + host authorization and an operator gate. Manifest metadata and profiles alone + grant none of these. +- The visualization engine can be replaced without changing vendor transports + or codecs. +- Real captures, credentials, device identities, maps and router metadata remain + outside Git. + +## Deferred, not implemented + +- isolated/signed plugin processes and crash containment; +- durable operation journal, restart recovery, vault and RBAC; +- persistent device identity and concurrent multi-device routing; +- complete SDK-envelope/EvidenceStore hot-path integration; +- remote Edge split, authenticated WAN relay and fleet orchestration; +- automatic K1 start/stop/calibration commands; +- camera discovery, decoding, synchronization and visualization. + +## Workspace-repeatable locked architecture gate + +From the repository root: + +```bash +uv sync --frozen --group dev +uv run pytest \ + tests/test_plugin_sdk_v0alpha2_contracts.py \ + tests/test_plugin_catalog.py \ + tests/test_xgrids_compatibility_profile.py \ + tests/test_device_lifecycle.py \ + tests/test_xgrids_acquisition_lifecycle.py \ + tests/test_canonical_pipeline.py \ + tests/test_rerun_bridge.py \ + tests/test_web_validation_security.py +uv run ruff check . +uv run mypy +uv run mypy --strict packages/plugin-sdk/python/missioncore_plugin_sdk +uv run python plugins/xgrids-k1/profile_loader.py +uv run python -m missioncore_plugin_sdk.v0alpha2 \ + > /tmp/missioncore-plugin-sdk-v0alpha2.schemas.json + +cd apps/control-station +npm ci +npm run test:unit +npm run typecheck +npm run build +``` + +These checks perform no BLE scan, network capture, router mutation or K1 +application write. The next physical and communication gates are specified in +ADR 0004. + +This gate is repeatable in the current workspace, not standalone reproducible. +The frontend consumes mutable sibling `file:` dependencies from +`NODEDC_DESIGN_GUIDELINE`; its lockfile does not pin the donor checkout's Git +revision or content hash. CI and portable packaging remain blocked until those +packages are published or vendored, or an immutable donor revision and content +verification are enforced. The current root Python wheel also omits the plugin +manifests/profile loader and built frontend, so it is not a deployment bundle. diff --git a/docs/adr/0004-plugin-sdk-v0alpha2-and-experimental-device-lifecycle.md b/docs/adr/0004-plugin-sdk-v0alpha2-and-experimental-device-lifecycle.md new file mode 100644 index 0000000..977db78 --- /dev/null +++ b/docs/adr/0004-plugin-sdk-v0alpha2-and-experimental-device-lifecycle.md @@ -0,0 +1,338 @@ +# ADR 0004: Plugin SDK v0alpha2 and experimental device lifecycle + +- Status: accepted +- Date: 2026-07-16 +- Supersedes: the next-milestone assumptions in ADR 0003; it does not replace + the v1alpha1 compatibility boundary + +## Context + +ADR 0003 isolated the first concrete device integration from the Mission Core +shell, but the backend still exposed one aggregate runtime and the repository +had no executable, vendor-neutral definitions for identity, sessions, +operations, acquisitions, streams, or evidence. The verified K1 path also fed +vendor MQTT topics and decoder results directly into visualization code. + +The next research stage will use an owner-controlled iPhone and LixelGO to +observe additional K1 communication. Before that experiment, Mission Core needs +stable local meanings and dependency boundaries so that discovered camera or +command mechanics do not spread vendor assumptions through the host. + +The domain is still experimental. Only one K1, firmware `3.0.2`, one direct-LAN +topology, point cloud, pose, raw report capture, and operator-manual acquisition +have physical evidence. Mutating NODE.DC Platform Ontology now would promote +single-device hypotheses into platform-wide meanings before a second device or +service proves them. + +## Decision + +Mission Core adopts an independently installable Plugin SDK v0alpha2 and a +repository-local experimental vocabulary. It does not modify or depend on +NODE.DC Platform Ontology at this stage. + +The executable package lives in: + +```text +packages/plugin-sdk/ + pyproject.toml + python/missioncore_plugin_sdk/v0alpha2/ +``` + +It contains closed, deeply immutable Pydantic contracts and deterministic JSON +Schema export. Nested mappings and collections are frozen after validation; +`model_copy(update=...)` revalidates its update before returning a new contract. +The contracts cover: + +- device model and device-instance identity; +- transport aliases and execution-node bindings; +- device-session state; +- operation policy, request, acknowledgement, progress, completion, failure, + cancellation and timeout; +- point-cloud, pose, image, encoded-video and device-status stream envelopes; +- payload and evidence handles, evidence records and raw transport records; +- compatibility assessments. + +The experimental semantic definitions and promotion rules live in +`docs/domain-model/mission-core-experimental-vocabulary-v0alpha2.md`. Breaking +wire or semantic changes create a new explicit SDK module. Existing v0alpha2 +documents and retained evidence are never silently reinterpreted. + +### Domain distinctions + +These terms are deliberately not aliases: + +| Term | Meaning in this milestone | +| --- | --- | +| Model | A plugin-owned definition of a supported hardware/product model. It is not physical hardware. | +| Device | One enrolled or provisionally identified physical instance. Its identity outlives a connection when a stable basis is available. | +| Transport alias | A scoped BLE UUID, IP address, SSID, MQTT identity or USB path. It can locate a device but is not automatically a stable `device_id`. | +| Device session | One bounded association between one plugin execution agent and one device instance. Restarting or reconnecting may create a new session. | +| Acquisition | One bounded production of requested sensor data, potentially containing multiple channels and sources. | +| Operation | One requested action with its own ID, deadline, idempotency boundary, progress and terminal outcome. An acknowledgement is not completion. | +| Source/channel | A logical producer or consumable data channel within an acquisition or replay. It is neither the physical device nor the viewer URL. | +| Evidence | Immutable raw or derived bytes plus content identity, lineage, retention and redaction metadata. Evidence is not decoded state or presentation state. | + +Enrollment, connectivity and acquisition remain independent state machines. A +device can be enrolled while offline, connected while idle, or retain identity +after an acquisition failure. + +### Manifest and compatibility profiles + +The manifest catalog accepts both v1alpha1 and v1alpha2 device-plugin manifests. +v1alpha1 remains valid for existing plugins and does not gain profile semantics +implicitly. A v1alpha2 manifest must link every declared model to at least one +plugin-local compatibility profile through `profileId`, `modelId`, and a path +constrained to that plugin's directory. v1alpha1 remains exactly one model; +v1alpha2 can describe more than one independently profiled model. + +The XGRIDS manifest is now `missioncore.nodedc/v1alpha2`, plugin version `0.2.0`, +and links exactly one profile: + +```text +xgrids.lixelkity-k1.fw-3.0.2.direct-lan.v1 +``` + +The profile is evidence-scoped to exact firmware `3.0.2`, one direct-LAN +topology, and the retained laboratory sources. Its independent flags distinguish +`observed`, `decoded`, `replay_verified`, `physical_verified`, and +`write_enabled`. Unknown or unattested firmware fails closed. The current +runtime cannot read firmware from the device; it activates the limited, +read-only profile only after explicit operator attestation of firmware `3.0.2` +and direct-LAN topology, records an `operator-attested` basis, and does not +present that claim as device-derived proof. + +A profile is descriptive compatibility evidence. Loading a manifest or profile +does not grant transport permissions, authorization, or device-write authority. +The XGRIDS v1 profile loader rejects every attempted `write_enabled: true` and +is executed as a fail-closed plugin-activation gate, not only as a test helper. +The separately reviewed BLE Wi-Fi provisioning path remains an explicit legacy +operator-confirmed mutation and is not authorized by this profile. + +### Semantic acquisition lifecycle + +The transitional backend now separates operation metadata from acquisition +state. It assigns provisional device, device-session, acquisition and operation +identifiers and records bounded in-process lifecycle snapshots. + +The current XGRIDS acquisition lifecycle is: + +```text +prepare + -> prepared + -> starting (host receiver is not ready yet) + -> awaiting_external_start + -> acquiring after the first real point frame + -> awaiting_external_stop when graceful stop is requested + -> finalizing + -> completed +``` + +Failure, abort and interruption are distinct terminal states. `capture-only` +stop may terminate the receiver but must report the physical K1 stop as +`unknown`. Graceful stop does not claim success until the operator explicitly +confirms the physical action. + +The bounded `OperationJournal` records lifecycle metadata, sequence, revision, +declared deadline, idempotency key, terminal result/error and evidence references. It is +in memory only, limited to one process and intentionally excludes action inputs +and secrets. Deadline persistence and distributed enforcement are not implied. +It is not a durable audit log, distributed command journal or recovery mechanism. + +HTTP validation failures on the plugin-scoped action endpoint and deprecated +`/api/connect` endpoint return a generic, input-free `422` response. Pydantic +diagnostics and rejected request values are not reflected to the client. This +is defense in depth for the current in-memory credential path, not a secret +vault implementation. + +The v1alpha2 manifest declares semantic acquisition actions alongside legacy +`stream.*` compatibility actions. `acquisition.start` and `acquisition.stop` +currently mutate host-side lifecycle and capture state only. They prompt and +observe the verified physical double-click workflow; they do not publish an +MQTT modeling request to the K1. + +### Data-plane boundary + +The implemented local visualization path is: + +```text +K1 transport message + -> raw-first .k1mqtt evidence capture + -> bounded latest-wins preview queue + -> explicitly injected K1 vendor normalizer + -> transport-neutral DecodedPointCloudView / DecodedPoseView + -> Rerun bridge + -> local embedded Rerun Web Viewer +``` + +`VisualizationRuntime` has no default vendor decoder. Its composition owner must +inject a `CanonicalNormalizer`; the XGRIDS facade explicitly injects +`normalize_k1_message`. The Rerun bridge consumes only decoded local views and +contains no MQTT topics, raw payload access, protobuf/LZ4 decoder imports, or +K1 branches. + +The `src/k1link/data_plane/Decoded*View` classes are allocation-conscious, +in-process consumer projections. They are intentionally not the portable +Plugin SDK `PointCloudFrame`, `PoseFrame`, or `DeviceStatusFrame` wire +contracts. The current hot path does not yet instantiate SDK stream envelopes, +nor does the existing raw capture use the SDK `EvidenceStore` protocol. A later +process boundary will map portable SDK envelopes into local consumer views. + +This transitional distinction avoids both vendor/viewer coupling and the false +claim that cross-process extraction is already complete. + +### Current K1 safety and evidence status + +The current profile and implementation assert only the following: + +- point cloud and pose are observed, bounded-decoded, replay-verified and + physically verified for the exact profile; +- device status and heartbeat are observed raw channels but have no semantic + decoder; +- physical scan start and stop use the verified K1 double-click; +- application-level K1 command publishing is disabled; +- the statically identified modeling topic and action values remain research + evidence only because headers, settings, acknowledgement, error, timeout, + rollback and save-completion contracts are unresolved; +- device calibration command and sensor-to-vehicle extrinsics are unavailable; +- camera transport, endpoint, codec, framing and independent camera channels + remain unknown. One negative MQTT observation does not prove absence. + +## Compatibility with v1alpha1 + +This milestone is additive: + +- the catalog continues to validate v1alpha1 manifests; +- the v1alpha1 `state.read` safety requirement remains; +- the established composition root, plugin-scoped dispatcher/events, inactive + provider behavior and teardown gate remain in force; +- legacy `stream.start-live`, `stream.start-replay`, `stream.stop`, and viewer + settings actions remain compatibility shims for the proven UI and runtime; +- the XGRIDS backend still runs through the reviewed + `transitional-in-process` facade in `src/k1link`. + +SDK v0alpha2 contracts do not imply that every current host endpoint, frontend +state object, or compatibility shim already emits those contracts. + +## Deliberately deferred + +The following are not complete and must not be inferred from this ADR: + +- process isolation, crash containment or signed independently deployed plugin + bundles; +- durable operation journal, restart recovery or distributed idempotency; +- secret vault implementation, RBAC, host policy enforcement or command + approval workflow; +- persistent stable device identity and multi-device/multi-session routing; +- multiple concurrent acquisitions or multiple plugin models per runtime; +- portable SDK-envelope transport in the live hot path; +- SDK `EvidenceStore` integration with the existing `.k1mqtt` recorder; +- remote Edge/Control Station split, authenticated WAN relay or fleet + orchestration; +- automatic K1 acquisition/calibration commands; +- camera discovery, decode, synchronization or visualization; +- verified sensor timestamps, clock synchronization, coordinate convention, + sensor-to-vehicle extrinsics or georeferencing. + +Four contract-compatibility questions are explicitly deferred before portable +SDK envelopes may enter the live hot path: + +- `ClockDomain` names UTC, device-monotonic and host-monotonic domains, while + the current header stores aware datetimes. A later revision must define a + language-neutral monotonic tick/nanosecond representation plus its measured + relationship to UTC; an aware datetime must not be used as native monotonic + time. +- `ReferencedPayload` must distinguish total referenced-object length, segment + offset and segment length. Until that invariant is fixed, a non-zero-offset + shared-memory segment cannot be assumed to satisfy `PointCloudFrame`'s exact + layout-length check. +- the coarse SDK `AcquisitionState` vocabulary and the richer transitional + facade states require an explicit, tested mapping. Matching `v1alpha2` labels + alone do not make their state machines interchangeable. +- the generic manifest catalog currently validates profile link integrity, + model coverage, path confinement and profile identity only. Evidence and + device-specific profile semantics remain the responsibility of the plugin's + fail-closed loader. A future generic profile schema must not be claimed until + that validation boundary is versioned and tested. + +## Consequences + +New K1 protocol discoveries must enter through the compatibility profile, +plugin-owned transport/normalizer code and retained raw evidence. They cannot be +added directly to Rerun, Mission Core navigation, generic lifecycle code, or the +platform ontology. + +The repository can continue laboratory work without prematurely freezing the +platform ontology. A future Edge split can preserve SDK identities, operations, +stream envelopes and evidence lineage while replacing the in-process facade and +local consumer projection. + +The cost is an explicit temporary adapter boundary: the portable SDK contracts, +the in-process lifecycle implementation and the decoded consumer views coexist +until extraction and replay parity are proven. + +## Workspace-repeatable locked acceptance checks + +The architecture gate is hardware-free and must pass from the repository root: + +```bash +uv sync --frozen --group dev +uv run pytest \ + tests/test_plugin_sdk_v0alpha2_contracts.py \ + tests/test_plugin_catalog.py \ + tests/test_xgrids_compatibility_profile.py \ + tests/test_device_lifecycle.py \ + tests/test_xgrids_acquisition_lifecycle.py \ + tests/test_canonical_pipeline.py \ + tests/test_rerun_bridge.py \ + tests/test_web_validation_security.py +uv run ruff check . +uv run mypy +uv run mypy --strict packages/plugin-sdk/python/missioncore_plugin_sdk +uv run python plugins/xgrids-k1/profile_loader.py +uv run python -m missioncore_plugin_sdk.v0alpha2 \ + > /tmp/missioncore-plugin-sdk-v0alpha2.schemas.json +``` + +The frontend compatibility gate remains: + +```bash +cd apps/control-station +npm ci +npm run test:unit +npm run typecheck +npm run build +``` + +No command above scans Bluetooth, contacts the K1, changes the router, or emits +a K1 application command. + +These commands are repeatable in the current workspace, not a standalone +release installation. The frontend uses sibling `file:` dependencies from +`NODEDC_DESIGN_GUIDELINE`, and `package-lock.json` does not pin that checkout's +Git revision or content hash. CI and portable packaging remain blocked until +those packages are published or vendored, or the build enforces an immutable +donor revision plus content verification. The root Python wheel does not yet +bundle plugin manifests, the profile loader, or the built frontend. + +## Next evidence gate + +Communication research begins only after the architecture checks pass and the +existing physical regression is repeated on the exact profile: + +1. verify firmware `3.0.2` and the owner-controlled direct-LAN topology; +2. prepare one operator-manual acquisition; +3. start the K1 by physical double-click; +4. retain raw `.k1mqtt` evidence outside Git; +5. confirm at least one real point frame and pose data reach the viewer through + the injected normalizer with no vendor dependency in Rerun; +6. stop by the operator-confirmed physical workflow and record whether device + stop was confirmed or remains unknown; +7. verify that no MQTT request topic was subscribed to or published and no + application command was emitted. + +Only then may a separate read-only LixelGO/iPhone communication-observation +experiment begin. Its first output is evidence: endpoints, flows, topics, +payload signatures, timing and command/response correlation. It does not enable +camera or command execution. Any state-changing publisher or new BLE write +requires its own profile revision, exact request/acknowledgement/timeout/error +contract, replay fixture, operator gate and physical acceptance. diff --git a/plugins/xgrids-k1/README.md b/plugins/xgrids-k1/README.md index 9a41cc0..dae671f 100644 --- a/plugins/xgrids-k1/README.md +++ b/plugins/xgrids-k1/README.md @@ -1,25 +1,29 @@ # XGRIDS / LixelKity K1 plugin This directory is the package boundary for the first Mission Core device -plugin. It now owns the canonical v1alpha manifest +plugin. It now owns the canonical v1alpha2 manifest [`plugin.manifest.json`](plugin.manifest.json). The statically linked frontend contribution lives in `apps/control-station/src/device-plugins/xgrids-k1/`; the verified backend implementation remains temporarily in `src/k1link` so the real-device path stays operational during extraction. -The v1alpha1 host deliberately assigns one model to one plugin runtime, so this -manifest exposes only `xgrids.lixelkity-k1`. Adding another device model means a -new plugin contribution until session/model routing lands in the next SDK. +The v1alpha2 catalog can describe one or more independently profiled models. +This manifest currently exposes only `xgrids.lixelkity-k1`, and the transitional +runtime still owns one active model/session at a time. Concurrent model/session +routing remains a later supervisor milestone. The plugin owns: +- the exact firmware/topology compatibility profiles under + [`profiles/`](profiles/), with strict evidence flags and a fail-closed loader; - BLE discovery hints and K1 GATT metadata; - the reviewed firmware-3 Wi-Fi provisioning profile; - K1 LAN status and private-address validation; - subscribe-only MQTT transport and report-topic allowlist; - native `.k1mqtt` capture; - firmware-scoped protobuf/LZ4 and legacy codecs; -- normalization of K1 point cloud, pose, status, health, and artifacts; +- normalization of K1 point cloud and pose, plus raw-only preservation of the + still-undecoded status and heartbeat channels; - K1-specific operator instructions and compatibility tests. The plugin does not own: @@ -45,7 +49,26 @@ legacy router; both paths delegate to the same proven `XgridsK1CompatibilityService` methods. Synchronous capture/runtime operations run outside the FastAPI event loop. -Model switching calls the plugin deactivation hook and must receive a successful -`stream.stop` before Mission Core removes this custom UI. BLE, MQTT, codec and +Model switching calls the plugin deactivation hook. An active acquisition uses +semantic `acquisition.stop` in `capture-only` mode; replay and pre-v1alpha2 +sessions retain the legacy `stream.stop` shim. Neither path claims that the +physical K1 stopped without separate operator evidence. BLE, MQTT, codec and evidence modules remain in `src/k1link` until replay parity and another physical K1 regression are complete. + +The compatibility profile is descriptive and cannot itself authorize a vendor +write. The current runtime cannot inspect K1 firmware: it keeps the profile +inactive until the operator explicitly attests firmware `3.0.2` and direct-LAN +topology, and records that basis as `operator-attested` rather than +device-derived evidence. Validate the current exact-match profile without +device I/O with: + +```bash +uv run python plugins/xgrids-k1/profile_loader.py +``` + +The optional owner-controlled iPhone/LixelGO observation tool lives under +[`lab/iphone-capture/`](lab/iphone-capture/). It pins `pymobiledevice3` in a +separate `uv` environment, writes only ignored evidence sessions, and remains a +lab subprocess rather than a plugin/runtime dependency. The first capture is +gated by ADR 0004 and ADR 0005.