feat(plugins): add runtime handshake boundary

This commit is contained in:
DCCONSTRUCTIONS
2026-07-17 19:50:01 +03:00
parent 24a47318f2
commit 9d51080d2e
22 changed files with 856 additions and 138 deletions
+1
View File
@@ -19,6 +19,7 @@ Each gate produces evidence and an explicit GO, PAUSE or BLOCKED result.
| Stage 6 live viewer | GO — React Control Station, embedded self-hosted Rerun cloud/trajectory and Mac pipeline metrics |
| Stage 7 observation archive | GO (point/pose) — durable catalog, recovery, background RRD preparation, saved-session timeline and atomic playback verified |
| Stage 7 recorded cameras | GO (contract), acceptance pending — acquisition-owned fMP4 archive and player are implemented/tested; no real archived K1 camera session exists yet |
| Plugin isolation | GO (laboratory control plane) — vendor backend/frontend are plugin-owned; manifest/runtime descriptor parity, versioned handshake, lifecycle health and transport correlation fail closed while execution remains in-process |
| Stage 8 product storage | PAUSE — retention, replication, encryption, capacity monitoring and long-run browser/WASM stress remain deployment gates |
USB project copying remains optional ground truth rather than a blocker for the
+28 -11
View File
@@ -2,8 +2,10 @@
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).
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
@@ -14,7 +16,7 @@ already exist. The accepted device-lifecycle decision is recorded in
| `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, acquisition/replay, diagnostics, metrics, runtime mapping and scoped CSS | Plugin-owned reviewed frontend contribution |
| `src/k1link/web/` | Local FastAPI host, plugin composition, in-memory operation/acquisition lifecycle and generic session API | Generic host APIs plus isolated plugin supervisor |
| `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 |
@@ -57,10 +59,14 @@ 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`. Backend actions now instantiate immutable
SDK `RuntimeActionInvocation` and `RuntimeActionResult` envelopes in the real
dispatcher path. The live spatial path does not yet instantiate portable SDK
stream envelopes or use the SDK `EvidenceStore` protocol.
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
@@ -149,11 +155,14 @@ for the measured gate and remaining limits.
## Remaining extraction order
Native replay parity, exact-profile physical point/pose regression, immutable
SDK runtime-action envelopes and physical extraction of K1 transports/codecs are
complete. Portable SDK stream/evidence envelopes remain separate from the
allocation-conscious in-process preview views.
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. Add process isolation and a durable operation/evidence supervisor.
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.
@@ -163,6 +172,13 @@ allocation-conscious in-process preview views.
plane; keep the modeling-command publisher disabled until its separate safety
gate closes.
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
@@ -208,6 +224,7 @@ From the repository root:
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 \
@@ -26,11 +26,12 @@ navigation, workspaces, and generic runtime code do not change. Runtime discover
of arbitrary JavaScript is deliberately not supported.
Backend factories are loaded only from validated, repository-local manifests.
Startup fails unless every catalog manifest produces exactly one adapter with
the same plugin ID and action set. The catalog is then frozen for the life of
the process, so a backend-catalog model cannot appear without an executable
runtime. Frontend availability remains the separate reviewed static-composition
step described below.
Startup fails unless every catalog manifest produces exactly one runtime with
the same plugin ID, version, supported host API and action set. ADR 0011 adds a
versioned descriptor/handshake before the runtime becomes ready. The catalog is
then frozen for the life of the process, so a backend-catalog model cannot
appear without an executable runtime. Frontend availability remains the
separate reviewed static-composition step described below.
The local-device UX is:
@@ -76,20 +77,23 @@ firmware-specific phases remain inside the XGRIDS plugin adapter.
`GET /api/v1/device-models`.
- Host-owned action dispatcher:
`POST /api/v1/device-plugins/{pluginId}/actions/{actionId}`.
- Runtime lifecycle health:
`GET /api/v1/device-plugin-runtimes`.
- Plugin-scoped event stream:
`WS /api/v1/device-plugins/{pluginId}/events`, with plugin ID and sequence.
- Manifest factory composition:
`src/k1link/web/device_plugin_composition.py`.
- Existing K1 action endpoints are compatibility shims over that dispatcher, so
the proven physical implementation is unchanged.
- Existing K1 action endpoints are compatibility shims over the same admitted
runtime transport, so the proven physical implementation is unchanged.
The XGRIDS frontend code is physically plugin-owned and statically linked into
the Control Station during this v1alpha milestone. Its provisioning,
acquisition/replay and diagnostics blocks are separate components, and it may
import the host only through `@mission-core/plugin-sdk`. It is not yet a
separately built or signed npm package. Backend composition is manifest-driven;
frontend/backend installed-set parity is a reviewed build-time responsibility
until the host gains signed plugin bundles and a startup compatibility handshake.
separately built or signed npm package. Backend composition is manifest-driven
and performs its control-plane handshake; frontend/backend installed-set parity
is still a reviewed build-time responsibility until the host gains signed
plugin bundles and frontend loading policy.
## Required dependency rules
@@ -102,7 +106,7 @@ until the host gains signed plugin bundles and a startup compatibility handshake
- Passwords and other secrets cannot enter manifests, runtime snapshots, event
payloads, or browser persistence.
- Invalid or duplicate plugin/model IDs fail registry construction.
- Manifest/runtime ID or action drift fails backend startup.
- Manifest/runtime ID, version, host API or action drift fails backend startup.
- Every v1alpha1 plugin declares exactly one model and a non-mutating,
secret-free `state.read` action.
- A plugin must stop or cancel active work before model selection can change.
@@ -217,6 +217,11 @@ SDK v0alpha2 runtime-action contracts are active in the backend hot path. This
does not imply that frontend state objects, live spatial frames or stored raw
evidence already use every SDK contract.
ADR 0011 later adds runtime-owned descriptor, handshake and lifecycle-health
contracts plus a transport seam around this action path. Its current
implementation remains in-process and does not revise the device/acquisition
semantics accepted here.
## Deliberately deferred
The following are not complete and must not be inferred from this ADR:
@@ -87,6 +87,11 @@ operator workflows remain compatible. The `k1link` distribution name and CLI
command remain transitional compatibility names; their implementation is now
plugin-owned.
ADR 0011 subsequently places the action control plane behind a versioned
descriptor/handshake/health transport seam. Observation discovery and export
remain host-called plugin contributions and are not yet portable process
transport contracts.
This ADR does not claim process isolation, crash containment, a durable operation
journal, signed plugin bundles, multi-device concurrency, portable SDK stream
envelopes or SDK `EvidenceStore` persistence. Those require a supervisor and a
@@ -81,6 +81,7 @@ contribution and one reviewed composition import, not edits to `App`,
The remaining isolation gap is deployment-level: frontend contributions are
still compiled into one signed application build, and backend plugins still run
inside the control-plane process. Signed bundles, compatibility handshake,
dynamic loading policy and process/restart containment belong to the supervisor
milestone.
inside the control-plane process. ADR 0011 adds a fail-closed backend runtime
descriptor/handshake and replaceable control-plane transport seam, but not a
subprocess. Signed bundles, frontend loading/realm policy and process/restart
containment remain later deployment milestones.
@@ -0,0 +1,103 @@
# ADR 0011: laboratory plugin runtime handshake and transport seam
- Status: accepted
- Date: 2026-07-17
- Extends: ADR 0003, ADR 0004, ADR 0009 and ADR 0010
## Context
Backend and frontend vendor code are physically plugin-owned, but the backend
dispatcher still received a raw in-process action adapter. Manifest parity was
checked through Python attributes rather than a runtime-owned wire contract.
Replacing that adapter with a later subprocess transport would therefore have
changed the dispatcher and compatibility routes as well as composition.
Mission Core is still a development-source laboratory sandbox. The final Edge,
Control Station and equipment-composition architecture is not known. Complex
hardware packs will probably combine separately useful components, but there is
not yet enough evidence to define pack roles, lifecycle or compatibility
matrices. The next step must preserve the proven plugin boundary without
pretending that production supervision is already designed.
## Decision
The executable control plane gains one minimal, versioned runtime protocol:
```text
missioncore.nodedc/plugin-runtime/v0alpha1
```
Plugin SDK v0alpha2 defines closed immutable contracts for:
- `RuntimePluginDescriptor` — plugin ID/version, supported host APIs and the
exact action set owned by the runtime;
- `RuntimeHandshakeRequest` and `RuntimeHandshakeResult` — an activation
challenge correlated by handshake ID and runtime instance ID;
- `RuntimeHealthSnapshot` — lifecycle health only: `starting`, `ready` or
`stopped`;
- the existing `RuntimeActionInvocation` and `RuntimeActionResult` envelopes.
The generic host depends on `DevicePluginRuntimeTransport`, whose control-plane
surface is `handshake`, `health`, `invoke` and `close`. A plugin factory returns
this transport inside `DevicePluginRuntimeContribution`; it no longer exposes a
raw action adapter to composition or the dispatcher.
Before admitting a plugin, composition validates all of the following:
1. manifest ID equals runtime descriptor ID;
2. manifest version equals runtime descriptor version;
3. the manifest API is explicitly supported by the runtime;
4. manifest and runtime action sets are exactly equal;
5. handshake response correlates to the request and descriptor;
6. runtime lifecycle health is `ready`.
Any failure closes the runtime and aborts composition. The dispatcher addresses
only admitted transports and rejects an action result whose invocation, plugin
or action correlation differs from the request.
`InProcessDevicePluginRuntime` implements the protocol for the current lab. It
wraps the XGRIDS action adapter, performs no serialization and provides no crash
boundary. The deprecated XGRIDS action routes now use the same admitted
transport instead of invoking the adapter directly. The plugin-local camera
gateway remains a same-process data-plane route; portable stream/media IPC is a
separate future gate.
`GET /api/v1/device-plugin-runtimes` exposes the small lifecycle snapshots. The
main health response reports ready/total runtime counts. These values do not
claim device connectivity, sensor health, process containment or durable
supervision.
## Acceptance
The automated gate proves that:
- two unrelated synthetic plugin runtimes complete independent handshakes and
dispatch through the unchanged generic host;
- actions are rejected before successful activation;
- an unsupported host API fails closed without changing runtime health to
ready;
- uncorrelated action results are rejected;
- close is idempotent and lifecycle health becomes stopped;
- SDK handshake contracts remain closed, immutable and exported as JSON Schema.
## Consequences
The generic action path now has a transport-replaceable seam. A later
subprocess implementation can satisfy the same host protocol while moving
serialization, heartbeat, restart and resource policy behind the transport.
This is useful architectural order even while the product topology is still
being discovered.
This ADR does **not** implement or promise:
- a subprocess supervisor, crash containment, heartbeat timeout or restart;
- CPU, memory, device-permission or filesystem limits;
- dynamic plugin discovery, quarantine or hot reload;
- bundle signing, frontend realm isolation, installers or updates;
- durable operation recovery or portable stream/EvidenceStore transport;
- multi-device routing or hardware-pack composition semantics.
The plugin remains the smallest canonical vendor-isolation unit. Whether a
future hardware pack is a host-owned composition of component plugins, a
deployable profile or another domain object is intentionally deferred until
real additional equipment makes that choice testable.