# NODEDC MISSION CORE

NODEDC MISSION CORE is the vendor-neutral control, observation, mission-planning,
recording, and integration platform for NODEDC autonomous systems. This
repository is the canonical early-stage monorepo: it contains the Mission Core
Control Station, the current local control plane, shared contracts as they are
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, 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. Native MQTT persistence uses bounded
group commit (at most 0.5 seconds, 4 MiB or 32 messages), and camera archives
commit complete fMP4 segments before their index rows. These are explicit
crash-RPO bounds, not a zero-loss or disk-replication claim. The former
Foxglove bridge remains only as a legacy regression module.

The backend vendor implementation is physically isolated below
`src/k1link/device_plugins/xgrids_k1/`; its reviewed manifest and compatibility
profile remain below `plugins/xgrids-k1/`. Mission Core host code discovers
archive sources, recovery hooks and recording exporters only through the plugin
runtime contribution. The verified wire protocol and raw evidence format are
unchanged.

The K1 acquisition now also requires an operator project name. Frontend and
backend normalize it with NFKC plus surrounding-whitespace trimming, reject
control/surrogate characters and values above 96 Unicode characters, and
preserve it as local session/catalog display metadata. It is never used as a
filesystem path. The physically accepted interactive control path carries the
same validated value in its single canonical START; there is no separate
project-name write.

Plugin SDK v0alpha2 now provides executable, vendor-neutral identity, session,
operation, runtime-action, stream, evidence and compatibility contracts. Every
backend runtime must now pass a versioned descriptor/handshake against its
manifest before actions are admitted. Every action then crosses immutable SDK
`RuntimeActionInvocation` and `RuntimeActionResult` validation through a
replaceable runtime transport seam. The meanings remain a local experimental
vocabulary rather than a mutation of NODE.DC Platform Ontology. The current
transport is deliberately 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 application-control bootstrap reads and correlates live `DeviceInfo` before
START. The exact profile requires model `LixelKity K1`, observed platform type
`A4`, activated state, firmware `3.0.2`, and direct-LAN topology. The UI selects
that profile but does not ask the operator to attest firmware manually.

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
through a hidden local macOS dialog. MQTT capture and decoding are read-only.
Nothing changes router settings, firmware or global Python packages.

## Verified XGRIDS K1 stand

- one XGRIDS/LixelKity K1;
- one Apple Silicon MacBook running macOS;
- one ordinary TP-Link Deco/mesh network used by other devices;
- 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
counterproductive if Deco isolates clients; Mac and K1 must ultimately be able
to reach each other.

## What is actually being proved

The project has three independent gates:

1. K1 is operational and can record a project autonomously.
2. Mac can discover and inspect the K1 BLE/GATT surface safely.
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 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. 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

The project uses Python 3.12 in a repository-local `.venv` managed by `uv`.
This does not install Python packages globally and does not modify neighboring
repositories.

```bash
cd /Users/dcconstructions/Downloads/mnt/NODEDC/NODEDC_MISSION_CORE
uv sync --frozen --group dev
uv run k1link doctor
uv run pytest
```

## External perception worker

Mission Core can now package one sealed camera epoch as an immutable,
content-addressed `missioncore.compute-job/v1` without changing the source
evidence. The first recorded TEST007 job was digest-verified and decoded on a
separate Windows RTX 4090 worker, executed through pinned Triton/YOLOX-S and
returned a content-addressed `missioncore.compute-result/v1` containing 56
session-timestamped generic COCO detections. The worker never connects to K1 and
does not own the raw archive.

Mission Core now validates and projects that optional result into the same saved
Rerun recording. Open TEST007 and use **Распознавание** to see the recorded
left-camera frames and boxes; **Облако точек** returns to 3D. This remains a
recorded camera-only acceptance, not a navigation stack: large-epoch
performance, direct worker transport and bounded live fan-out remain open. See
[ADR 0014](docs/adr/0014-bounded-external-perception-worker.md)
and the [external worker contract](docs/10_EXTERNAL_PERCEPTION_WORKER.md).

## Simulation Polygon

Mission Core now has a parallel Polygon product branch for reproducible
autonomy qualification. Mission Core owns scenarios, qualification-run
lifecycle, authority, canonical contracts, provenance and reports; Gazebo, PX4
SITL, ROS 2, Nav2 and viewers remain replaceable providers. Simulation, replay,
digital twin, HIL and physical shadow keep distinct causal run kinds.

SIM S0 is accepted on the reviewed D-only worker. Its strict profile fixes
loopback-only process isolation, exact provider pins, a 2 ms physics step,
1×/2× RTF/resource budgets and disabled real/direct actuator authority. Two
accepted-profile stock-rover runs and nine digest-bound evidence claims passed;
the target doctor returned `GO`. The doctor remains read-only and a development
host without the private evidence pack still returns `INCOMPLETE`:

```bash
uv run missioncore-sim s0 doctor \
  --evidence /path/to/private/evidence.yaml \
  --json
```

This accepts simulation infrastructure, not navigation behavior, safety
behavior, S1 command authority or real actuator control.

See the [Polygon product/SRS](docs/12_SIMULATION_POLYGON_PRODUCT_AND_SRS.md),
[ADR 0015](docs/adr/0015-simulation-polygon-qualification-boundary.md) and the
[SIM S0 worker runbook](docs/runbooks/SIM_S0_AI_WORKER.md).

## Mission Core Control Station and visualization adapters

The browser application is the universal Mission Core Control Station rather than
a K1-specific Foxglove launcher. Its fixed shell contains six architectural
sections — Center, Fleet, Observation, Missions, Data and System — while the K1
BLE/Wi-Fi/live workflow remains isolated as the first real device adapter. Its
React provisioning, acquisition/replay and diagnostic blocks live beside the
plugin manifest under `plugins/xgrids-k1/frontend`; the generic application
mounts them through one reviewed composition import.

Install, type-check, build and serve the complete local application from the
repository root:

```bash
uv sync --frozen --group dev
cd apps/control-station
npm ci
npm run test:unit
npm run typecheck
npm run build
cd ../..
uv run k1link serve
```

Open `http://127.0.0.1:8000`. The static application, REST/WebSocket control
plane and credential endpoint bind to loopback only. The current K1 adapter
still provides real CoreBluetooth discovery, one operator-triggered reviewed
BLE Wi-Fi provisioning write, a one-shot CoreWLAN K1-AP association path,
read-only MQTT live capture, native `.k1mqtt` and
reviewed-TSV replay, raw-first evidence storage, device-reported scan
time/distance/speed and measured preview metrics. Its frontend contribution now
also owns an optional spatial-scene control block, including acquisition phase,
telemetry and the stop action. Operator-manual acquisition still finalizes only
local reception; plugin-commanded v0.5.0 acquisition uses the separately gated
canonical K1 START/STOP dialogue.

Plugin v0.6.0 makes the local connection direction explicit. Bridge remains the
default and accepted product path; Direct Connect sends the reviewed station
provisioning frame for an already-running controller hotspot. Quick Connect
sends one separately reviewed AP-enable frame and can associate a prepared Mac
through CoreWLAN. Its device activation and prepared-host association were
physically accepted, but credential bootstrap is not portable: a clean host
cannot acquire the firmware-defined AP material from the reviewed BLE protocol.
Quick Connect therefore remains laboratory functionality, with no automatic
firmware download, iPhone extraction or hard-coded fallback. Each mode has a
distinct topology attestation and no automatic retry; see
[ADR 0013](docs/adr/0013-k1-local-connection-matrix.md).

The exact recovered `ModelingRequest` start/stop encoder, response correlator
and device-status state machine are installed in the operator-gated interactive
control path.
The plugin also contains the exact ten-request pre-START bootstrap recovered
from retained LixelGO traffic. Its first `DeviceInfoRequest` has no device ID;
the response supplies live vendor identity, serial, model, activation and
version facts for the BLE-selected K1. Static and wire evidence identify OpenAPI
as one private application-level value in the observed client, not a manual
per-scanner profile. The whole pre-START order and START/STOP payloads are
byte-matched offline. A fixed macOS Keychain loader now supplies the exact
application authority without env/file/browser fallback, and a five-batch
orchestrator passes all retained response barriers. The legacy shadow publisher
boundary remains structurally write-disabled and cannot call its sink. Its
dormant coordinator can hold authority
for at most 300 seconds and expiry/disarm/reprovision/acquisition/shutdown revoke
it together with the orchestrator. Plugin v0.5.0 installs a separate
physical-acceptance transport reproducing MQTT 3.1.1 `clean_session=false`,
keepalive 60, exact
response subscriptions, QoS2 completion and the five response-gated batches.
It consumes operation keys before publish and poisons unknown outcomes without
retry. One background owner services that socket through response-gated
connection, workspace, project, START, STOP and device-standby stages. The normal
UI collapses only the pre-START controls into one explicit operator intent;
navigation cannot emit a command. The first operator-present physical attempt on 2026-07-18 emitted only
the first six bootstrap requests, then failed closed while correlating the third
response batch; START was not emitted. A second one-shot attempt correlated all
ten pre-START requests and the START acknowledgement, but incorrectly collapsed
the whole exchange into 365 ms and ended the control lifecycle before the
retained post-START reads. K1 stayed in `SCAN_STARTING` for about 24 seconds,
reported `ALGORITHM_ERROR` twice, emitted no point/pose data and showed steady
red until an operator power cycle restored steady green. The collapsed executor
is now disabled. The offline acceptance executor maps connection requests 1–6,
workspace-entry request 7, project-prompt reads 8–10, user-confirmed START 11,
immediate status read 12 and state-gated reads 13–14 onto one continuously owned
MQTT session. No captured operator delay is a protocol timer: the final reads
wait for live `SCANNING`, project binding and `init_ready`, while STOP/save waits
for live unbound `READY`. Standalone START and STOP
are both rejected. The same socket is serviced throughout launch and STOP
confirmation, and no captured human delay is replayed as a protocol timer.
Plugin v0.5.0 wires this executor to the facade and plugin UI. The full staged
START/live/STOP/save cycle is physically accepted on the reviewed unit. START
and STOP each retain an explicit operator action; final local sealing now follows
the scanner's protocol-reported unbound READY without a redundant second click.

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.

Polygon is planned as a seventh section, but UI delivery is gated on the
accepted clock/frame/authority contracts and a proven S1 backend lifecycle with
persisted run history. It is not present in the current shell.

The Observation spatial workspace embeds the open-source Rerun Web Viewer
inside the Mission Core shell. It can open a compatible RRD over same-origin
HTTP or a Rerun gRPC/proxy source such as
`rerun+http://127.0.0.1:9876/proxy`; no external hosted viewer UI is used.
Dynamic point-cloud/camera composition and the live source contract are fixed in
[`ADR 0006`](docs/adr/0006-vendor-neutral-observation-sources-and-live-only-timeline.md).
The durable session catalog, recorded `session_time` scrubber and versioned
workspace-layout profile are fixed in
[`ADR 0008`](docs/adr/0008-durable-observation-sessions-and-workspace-layout.md).

The first K1 live session or adapter file-replay (`.k1mqtt`/reviewed TSV) in a
`k1link serve` process creates one local Rerun `RecordingStream`, starts its
gRPC/proxy server on TCP 9876 and publishes the resulting URL through
control-plane state. Later live/file-replay sessions reset their session-local
scene and metrics and reuse that process-wide stream. Saved observation sessions
do not reuse this listener: they open a private digest-bound cache-v9 RRD
generation over same-origin HTTP through Rerun's native incremental receiver.
Unless an operator has entered a manual source, the React application assigns
the applicable source to the embedded viewer. A generic host action clears a
previous manual/archive selection only after a plugin-owned live or file-replay
start succeeds; a failed start leaves the current scene mounted. A fail-closed
guard blocks operator source switches for every nonterminal acquisition. The
complete visual live runtime path is K1 MQTT → raw-first evidence capture →
bounded four-message 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. K1 `ModelingReport` status is consumed by an
explicitly injected pre-preview observer, so scan telemetry cannot evict point
or pose frames from that four-message queue.

The default Rerun blueprint shows a 12-second sliding accumulation of real point
frames. Product controls are connected for point size, intensity/height/distance
or available RGB coloring, Turbo/Viridis/Plasma/grayscale/custom palettes,
point and trajectory visibility, and the scene grid. The first disk action saves
and restores the versioned spatial layout without mutating sensor evidence.
Saved native point/pose sessions are materialized losslessly into private,
digest-bound RRD recordings and can be played, paused and scrubbed on a
zero-based `session_time` timeline. New captures durably publish a clock origin
before camera production, retain a transport-scoped provisional envelope and
atomically point their summary to a content-addressed session envelope after all
producers stop. That session envelope becomes the real RRD origin/end range;
archived `ModelingReport` distance, speed and scan time are logged as scalar
time series. No synthetic 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. The later RTSP camera preview is available through
the generic floating observation windows and new acquisitions archive its fMP4
segments independently of browser delivery. Historical sessions recorded before
that archive contract contain no video. A real archived session containing one
camera plus point cloud has not yet passed physical shared-timeline playback;
the implementation and test contract do not close that hardware gate. Rerun
`capture_time` and the camera index use Mac receive/arrival timestamps, not
proven K1 sensor timestamps or a photon-to-screen measurement.

The old Foxglove implementation is retained only in
`src/k1link/device_plugins/xgrids_k1/viewer/foxglove_bridge.py` and its regression tests. The current
live/replay runtime does not start it or use TCP 8765. The
[live viewer runbook](docs/06_K1_LIVE_VIEWER.md) records the active Rerun path and
its timing/security boundaries; the frontend contract is documented in
[`apps/control-station/README.md`](apps/control-station/README.md).
The host storage layout, recovery rules, replay API and operator path are in
[`docs/09_OBSERVATION_SESSIONS.md`](docs/09_OBSERVATION_SESSIONS.md).

The FastAPI application and credential endpoint bind to loopback, but the Rerun
gRPC server currently binds TCP 9876 on all network interfaces even though its
reported source URL contains `127.0.0.1`. It has no connector-level
authentication or TLS. Use it only on a trusted laboratory LAN, do not expose
9876 to the public Internet or a cellular WAN, and add an authenticated secure
proxy before any remote deployment. Stopping acquisition keeps the local scene
server and its URL available for the next session. Stop `k1link serve` to close
the listener and release its retained memory.

`doctor` is intentionally non-invasive. It checks the local Python environment
and reports external tools; it does not request Bluetooth permission, scan the
LAN, touch the K1, alter Homebrew, or change capture permissions.

The implemented laboratory commands include:

```bash
uv run k1link ble scan --duration 30 --out sessions/<id>/captures/ble.json
uv run k1link ble gatt-dump --device <corebluetooth-uuid> \
  --out sessions/<id>/captures/gatt.json
uv run k1link ble wifi-configure --device <corebluetooth-uuid> \
  --profile xgrids-k1-fw3-wifi-v1 --write-mode with_response \
  --confirm-write --out sessions/<id>/captures/wifi.sensitive.json
uv run k1link net snapshot --out sessions/<id>/captures/network.json
uv run k1link net mqtt-capture --host <confirmed-private-k1-ip> \
  --confirm-owned-device --duration 180 \
  --out sessions/<id>/captures/mqtt-run
uv run k1link analyze mqtt-streams \
  --capture sessions/<id>/captures/mqtt-run/mqtt.raw.k1mqtt \
  --out sessions/<id>/analysis/mqtt-streams.summary.json
```

On macOS the BLE scan is active CoreBluetooth discovery, but it does not connect
to or modify devices. `gatt-dump` connects and performs service discovery only.
`mqtt-capture` accepts only a literal RFC1918 target, uses a fixed report-topic
allowlist, never publishes and never reconnects. It writes a length-framed raw
file, JSONL metadata and an integrity summary with mode `0600`.

Session output is sensitive and ignored by Git. It can contain device identity,
trajectory, mapped interiors and local addressing even when no credentials are
present.

## Documentation

- [Technical audit](docs/00_TECHNICAL_AUDIT.md)
- [Implementation gates](docs/01_IMPLEMENTATION_PLAN.md)
- [First lab runbook](docs/02_FIRST_LAB_RUNBOOK.md)
- [Artifact and secret policy](docs/03_ARTIFACT_POLICY.md)
- [Reviewed BLE Wi-Fi profile](docs/04_K1_WIFI_PROVISIONING_PROFILE.md)
- [Verified MQTT stream profile](docs/05_K1_MQTT_STREAM_PROFILE.md)
- [Live console and embedded Rerun runbook](docs/06_K1_LIVE_VIEWER.md)
- [Mission Core monorepo and plugin boundary](docs/07_MISSION_CORE_MONOREPO.md)
- [Owner-controlled LixelGO/iPhone observation](docs/08_LIXELGO_IPHONE_OBSERVATION.md)
- [K1 application-control physical acceptance](docs/lab/003_K1_CONTROL_ACCEPTANCE_20260718.redacted.md)
- [Observation sessions, playback and workspace layout](docs/09_OBSERVATION_SESSIONS.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)
- [Owner-controlled LixelGO observation decision](docs/adr/0005-owner-controlled-lixelgo-iphone-observation.md)
- [Vendor-neutral live observation sources](docs/adr/0006-vendor-neutral-observation-sources-and-live-only-timeline.md)
- [K1 camera preview copy-remux gateway](docs/adr/0007-k1-camera-preview-copy-remux-gateway.md)
- [Durable observation sessions and workspace layout](docs/adr/0008-durable-observation-sessions-and-workspace-layout.md)
- [Device-plugin observation runtime and K1 extraction](docs/adr/0009-device-plugin-observation-runtime-and-k1-extraction.md)
- [Plugin-owned frontend device workflows](docs/adr/0010-plugin-owned-frontend-device-workflows.md)
- [Laboratory plugin runtime handshake and transport seam](docs/adr/0011-laboratory-plugin-runtime-handshake-and-transport-seam.md)
- [Device-bound K1 command authority](docs/adr/0012-device-bound-k1-command-authority.md)
- [Redacted live lab report](docs/lab/001_K1_LIVE_MQTT_20260715.redacted.md)
- [Canonical control and durable archive milestone](docs/lab/004_K1_CANONICAL_CONTROL_ARCHIVE_20260719.redacted.md)
- [Session manifest schema](schemas/session-manifest.schema.json)
- [Reference input provenance](docs/reference/README.md)

The two supplied source documents are retained unchanged under
`docs/reference/`. Corrections and decisions are recorded separately so their
provenance remains clear.

## Safety boundary

Allowed initial work is non-mutating discovery, standard device-information reads,
controlled notification listening, autonomous button operation, targeted
capture of traffic to or from the confirmed K1 address, and offline analysis of
owned artifacts.

The reviewed provisioning write requires its named profile and explicit operator
confirmation. The interactive application-control publisher is enabled only for
the exact K1/A4/FW 3.0.2 direct-LAN profile and only inside one operator-opened,
response-gated session; physical double-click remains the independent fallback.
The legacy shadow publisher remains structurally disabled. Any broader firmware,
topology, scanner-family or command surface requires its own evidence and
reviewed step. Random writes, fuzzing, brute force, firmware operations,
destructive file access and credential guessing remain out of scope.

Real captures, projects, router metadata, serials, credentials, maps, images,
and logs under `.runtime/`, canonical evidence roots and legacy `sessions/` are
ignored by normal Git. Redacted manifests and SHA-256 inventories are committed;
encrypted/replicated artifact storage remains a deployment decision.
