feat(plugins): add runtime handshake boundary
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user