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