# NODEDC MISSION CORE monorepo

This document maps the current repository to the target Mission Core
architecture without claiming that deferred package and process boundaries
already exist. The device-lifecycle decision is recorded in
[`ADR 0004`](adr/0004-plugin-sdk-v0alpha2-and-experimental-device-lifecycle.md),
and the bounded laboratory runtime seam in
[`ADR 0011`](adr/0011-laboratory-plugin-runtime-handshake-and-transport-seam.md).

## Current layout

| Path | Current responsibility | Target responsibility |
| --- | --- | --- |
| `apps/control-station/` | React shell, model catalog, plugin host slots, spatial scene | Vendor-neutral operator application |
| `apps/control-station/src/core/device-plugins/frontendSdk.ts` | Public React host surface for reviewed frontend contributions | Versioned frontend Plugin SDK package surface |
| `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 profile/loader and plugin-owned React connection/acquisition UI | Independently versioned XGRIDS device plugin |
| `plugins/xgrids-k1/frontend/` | XGRIDS provisioning, project/acquisition/replay, optional spatial controls, diagnostics, metrics, runtime mapping and scoped CSS | Plugin-owned reviewed frontend contribution |
| `src/k1link/web/` | Local FastAPI host, fail-closed runtime handshake/transport seam, in-memory operation/acquisition lifecycle and generic session API | Generic host APIs plus isolated plugin supervisor |
| `src/k1link/device_plugins/xgrids_k1/` | Physically isolated K1 BLE/MQTT/protobuf/LZ4/camera/replay/CLI compatibility implementation | Independently built XGRIDS device plugin process |
| `src/k1link/data_plane/` | Transport-neutral decoded in-process consumer views | Local projections hydrated from portable SDK envelopes |
| `src/k1link/viewer/` | Vendor-neutral Rerun consumer, metrics and recorded blueprint | Replaceable canonical scene sink and presentation adapters |
| `src/k1link/sessions/` | Host-owned SQLite catalog, evidence discovery/recovery, background preparation and derived-cache lifecycle | Device-neutral observation archive service |
| `src/k1link/web/session_api.py` | Opaque saved-session, immutable RRD/media and workspace-layout API | Versioned Control/Edge observation contract |
| `apps/control-station/src/core/observation/` | Session selection, replay admission, recorded-source and layout state | Device-neutral observation UI runtime |
| `docs/domain-model/` | Versioned experimental Mission Core vocabulary | Promotion source for meanings proven across devices/services |

## Implemented boundary

The current K1 live/replay data path is:

```text
raw K1 transport
  -> raw-first evidence capture
  -> injected K1 ModelingReport observer (device time/distance/speed)
  -> bounded four-message visual preview queue
  -> XGRIDS normalizer injected by the plugin facade
  -> DecodedPointCloudView / DecodedPoseView
  -> Rerun bridge
  -> embedded local viewer
```

The host-owned recorded path is separate from the disposable live preview:

```text
sealed or recovered native observation session
  -> SQLite catalog and bounded single-worker preparation
  -> capture-clock-bound atomic RRD cache v9 + immutable recorded-media manifest v2
  -> generation-bound same-origin HTTP
  -> aggregate RRD/camera admission
  -> native Rerun HTTP receiver + recorded fMP4 player
```

New K1 source generations durably publish
`captures/mqtt_live/mqtt.timeline.origin.json` before camera production. MQTT
shutdown adds provisional transport envelope `mqtt.timeline.json`; after camera
and MQTT/runtime shutdown, the acquisition owner publishes content-addressed
`mqtt.timeline.session-<sha256>.json` and atomically switches the capture-summary
pointer while still holding the session lease. Only that `session` scope is
eligible to advertise combined media replay. Cache v9 materializes both session
envelope endpoints as real RRD rows and logs archived device-reported scan time,
route distance and speed as scalar time series. A crash-recovered origin-only
point/pose prefix falls back to its last validated message and cannot advertise
camera media. Older valid sessions without the clock contract keep their
first/last-message fallback and cannot acquire camera evidence retroactively.

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`. A runtime-owned descriptor must match the
manifest ID, version, host API and exact action set, then complete the
`plugin-runtime/v0alpha1` handshake before its lifecycle health becomes ready.
Backend actions instantiate immutable SDK `RuntimeActionInvocation` and
`RuntimeActionResult` envelopes through the replaceable transport seam, and the
host rejects uncorrelated results. The current transport is still in-process.
The live spatial path does not yet instantiate portable SDK stream envelopes or
use the SDK `EvidenceStore` protocol.

Frontend device workflows follow the same ownership rule. The generic host
selects a manifest model and mounts its `device.connection` component. Concrete
XGRIDS source lives under `plugins/xgrids-k1/frontend`, imports the host only via
`@mission-core/plugin-sdk`, and is connected by the single reviewed import in
`apps/control-station/src/composition/devicePlugins.ts`. BLE/Wi-Fi provisioning,
exact-profile confirmation, project metadata, acquisition/replay UI and the
optional K1 spatial-scene control block are not Core components. The host owns
only the optional slot contract and generic actions such as opening the scene or
releasing a prior manual/archive selection after a plugin source successfully
starts.

A generic fail-closed guard blocks manual source input/apply/reset,
saved-session switching and persisted replay reattach whenever the runtime
reports a nonterminal acquisition. Unknown future acquisition states block by
default. A plugin clears the previous host-selected source only after its start
returns success, preserving the current scene on failure. That internal
success-result transition is distinct from an operator switch. The guard does
not stop equipment or invent a lifecycle transition; the acquisition must
finish through its owning plugin.

## Experimental vocabulary, not Platform Ontology

The local vocabulary distinguishes:

```text
model != device != transport alias != device session
acquisition != operation != source/channel != evidence
observation session != preparation job != replay launch != viewer instance
workspace layout != sensor 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;
- wire-level platform type `A4`;
- firmware `3.0.2`;
- direct-LAN topology;
- one retained physical laboratory evidence scope.

Unknown or mismatched firmware fails closed. The UI selects the profile and the
canonical bootstrap verifies model, platform type, activation and firmware from
a correlated live `DeviceInfo` before START. The profile independently records observation, decode, replay,
physical-verification and write evidence. Loading it cannot authorize a
transport mutation. BLE Wi-Fi provisioning and application control retain
separate explicit operator gates.

An exact K1 application-control substrate now lives inside the vendor
plugin. It byte-matches the retained ten-request pre-START dialogue plus
START/STOP, correlates live DeviceInfo/modeling responses and maps bounded
device-status values into a fail-closed state machine. The OpenAPI value
is one private application-level authority; vendor ID and serial come from the
BLE-selected live K1 and are cross-checked against status. Plugin v0.5.0 owns a
single interactive MQTT session from connection through STOP/standby, with one
explicit UI action per recorded lifecycle boundary and no automatic retry. A
fixed Keychain loader and dormant shadow coordinator remain present. The staged
physical START/live/STOP/unbound-READY cycle is accepted on the reviewed unit;
write capability is exposed only while that interactive socket owner is active.

## 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. Plugin-commanded STOP finalizes automatically after the same bound control
session reports unbound READY. Operator-manual capture-only stop still reports
the K1 physical state as unknown.

Preparation requires a project name. The frontend and backend both apply NFKC
normalization plus surrounding-whitespace trimming, reject control/surrogate
characters and names above 96 Unicode characters, and store the result as
session/catalog display metadata. It is not a path component. The accepted
interactive START carries this exact validated value to K1.

The K1 contribution may mount a `SpatialControlsView` beside the host-owned
viewport. It presents plugin lifecycle wording, the stop action and live
`ModelingReport` scan time, route distance and speed. In plugin-commanded mode
the stop action sends the canonical device STOP; in operator-manual mode it
seals only local reception. `ModelingReport` is consumed before the visual preview queue through
an injected observer, preserving the generic normalizer/Rerun boundary.

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 the exact accepted profile, a plugin-commanded
acquisition owns canonical START and STOP plus local reception. Physical
double-click remains the independently verified device-local fallback.

Left/right camera preview transport, endpoint paths and H.264 framing are now
observed under the exact compatibility profile. The local read-only adapter
copy-remuxes one selected RTSP producer into bounded fMP4/WebSocket delivery for
the generic MSE UI. New acquisitions archive selected-camera init/segments/index
independently of browser delivery. The generic recorded player, manifest-v2
validation and shared `session_time` controls are connected; historical sessions
created before this archive contract contain no recoverable video. The TEST007
recorded-camera package and browser playback are physically accepted. Portable FFmpeg packaging, disk-backed
browser buffering, fan-out and remote delivery also remain open. Device
calibration command and sensor-to-vehicle extrinsics are unavailable.

The 2026-07-17 physical acceptance gate confirmed continuously updating point
clouds, a matching live trajectory, and both camera selections in the same
Mission Core acquisition. The embedded Rerun blueprint owns live-edge following
on `stream_time`; the React shell selects the timeline once and does not drive it
with a timer. Raw captures, camera frames, device identity and network details
remain outside Git. The bounded acquisition subset of `DeviceStatusReport` and
`ModelingReport` route telemetry are decoded for the exact profile; heartbeat
and nested system/RTK status payloads remain raw observed evidence. See
[`ADR 0007`](adr/0007-k1-camera-preview-copy-remux-gateway.md)
for the measured gate and remaining limits.

## Remaining extraction order

Native replay parity, exact-profile physical point/pose regression, immutable
SDK runtime-action envelopes, fail-closed runtime handshake and physical
extraction of K1 transports/codecs are complete. Portable SDK stream/evidence
envelopes remain separate from the allocation-conscious in-process preview
views.

1. When a real deployment or second execution target requires it, implement a
   subprocess transport behind the current seam, then add heartbeat timeout,
   restart policy and resource limits as separately accepted behavior.
2. Replace compatibility routes and singleton state with multi-device session
   routing.
3. Split Edge execution from the Control Station behind authenticated transport.
4. Physically accept a newly archived left/right K1 session, then package the
   read-only RTSP/H.264 adapter for each target OS, add disk-backed sealed media
   caching and evolve same-host MSE delivery toward an authenticated Edge media
   plane. Keep modeling commands limited to the installed operator-present,
   exact-profile acceptance transport until durable-save confirmation is
   physically accepted.

Complex equipment will likely be assembled from separately useful component
plugins into configured hardware packs, while standalone equipment remains
available for engineering work. That direction is intentionally not a current
domain schema: pack ownership, roles, compatibility and lifecycle must be
derived from real additional component families rather than inferred from the
single K1 vertical.

## Invariants

- Raw bytes are appended and OS-flushed before preview work; aligned raw and
  metadata become durable together at the bounded group-commit boundary and
  are 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;
- device-reported camera capability discovery beyond the reviewed K1 profile;
- physical acceptance of shared-timeline point-cloud plus one selected-camera
  playback on a newly archived K1 session;
- frame-accurate camera/LiDAR calibration, panoramic stitching, disk-backed
  browser media cache and remote multi-consumer delivery;
- production retention, replication, encryption and long-run WebViewer/WASM
  memory acceptance for large observation sessions.

## 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_runtime.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.
