docs: record Mission Core K1 architecture milestone

This commit is contained in:
DCCONSTRUCTIONS 2026-07-16 19:44:16 +03:00
parent e6f7648b84
commit 05bdab24a5
8 changed files with 659 additions and 81 deletions

View File

@ -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)

View File

@ -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`.

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.