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
+16 -12
View File
@@ -36,8 +36,9 @@ The separate SDK v0alpha2 package establishes executable contracts for:
- independently revisioned enrollment, connectivity, and acquisition states;
- operation policy, request, acknowledgement, progress, completion, failure,
timeout, cancellation, secret reference, and idempotency boundaries;
- immutable pre-session runtime-action invocation and result envelopes used by
the active backend dispatcher;
- immutable runtime descriptor, activation handshake, lifecycle health,
action-invocation and action-result envelopes used by the active backend
dispatcher;
- canonical point cloud, pose, image, encoded video, and device-status streams;
- immutable evidence handles, raw transport records, lineage, and store
protocol;
@@ -57,18 +58,21 @@ Current host implementation references:
that frontend surface;
- `apps/control-station/src/core/runtime/` — normalized runtime envelope;
- `src/k1link/web/plugin_catalog.py` — strict backend manifest validation;
- `src/k1link/web/plugin_runtime.py`host-owned allowlisted SDK-envelope action dispatcher;
- `src/k1link/web/plugin_runtime.py`transport protocol, laboratory in-process
implementation and host-owned SDK-envelope action dispatcher;
- `src/k1link/web/device_plugin_composition.py` — manifest factory loader and
startup parity checks;
- `docs/adr/0003-device-plugin-ui-and-runtime-boundary.md` — accepted boundary
and extraction sequence.
fail-closed descriptor/handshake checks;
- `docs/adr/0011-laboratory-plugin-runtime-handshake-and-transport-seam.md`
accepted control-plane seam and its explicit laboratory limits.
The existing lifecycle is fail-closed: inactive provider shells perform no
I/O, events are scoped by plugin ID, and selection cannot change until the
active plugin confirms teardown. Process isolation remains a later host
milestone. v0alpha2 models secret references and operation policy, but host
authorization and the actual secret vault remain separate implementation
responsibilities.
The existing lifecycle is fail-closed: a backend runtime stays `starting` until
its ID, version, host API and exact action set pass the handshake; inactive
provider shells perform no I/O; events are scoped by plugin ID; and selection
cannot change until the active plugin confirms teardown. `ready` health means
only that this control-plane contract is admitted. Process isolation remains a
later host milestone. v0alpha2 models secret references and operation policy,
but host authorization and the actual secret vault remain separate
implementation responsibilities.
The React surface is not yet an independently published npm package. Its alias
is intentionally narrow so plugin source cannot import Control Station
@@ -0,0 +1 @@
@@ -47,7 +47,15 @@ from .operations import (
validate_operation_request,
)
from .payloads import InlinePayload, PayloadHandle, ReferencedPayload
from .runtime import RuntimeActionInvocation, RuntimeActionResult
from .runtime import (
RUNTIME_PROTOCOL_VERSION,
RuntimeActionInvocation,
RuntimeActionResult,
RuntimeHandshakeRequest,
RuntimeHandshakeResult,
RuntimeHealthSnapshot,
RuntimePluginDescriptor,
)
from .schema import contract_json_schemas
from .session import (
AcquisitionState,
@@ -121,8 +129,13 @@ __all__ = [
"Quaternion",
"RawTransportRecord",
"RedactionState",
"RUNTIME_PROTOCOL_VERSION",
"RuntimeActionInvocation",
"RuntimeActionResult",
"RuntimeHandshakeRequest",
"RuntimeHandshakeResult",
"RuntimeHealthSnapshot",
"RuntimePluginDescriptor",
"ReferencedPayload",
"RuleOutcome",
"SecretReference",
@@ -1,12 +1,92 @@
"""Minimal pre-session action envelopes for the executable plugin runtime."""
"""Portable control-plane envelopes for an executable plugin runtime."""
from __future__ import annotations
from typing import Literal
from pydantic import AwareDatetime, Field
from pydantic import AwareDatetime, Field, model_validator
from .common import API_VERSION, ApiVersion, ContractModel, Identifier, JsonObject
from .common import (
API_VERSION,
ApiVersion,
ContractModel,
Identifier,
JsonObject,
ShortText,
)
RuntimeProtocolVersion = Literal["missioncore.nodedc/plugin-runtime/v0alpha1"]
RUNTIME_PROTOCOL_VERSION: RuntimeProtocolVersion = (
"missioncore.nodedc/plugin-runtime/v0alpha1"
)
class RuntimePluginDescriptor(ContractModel):
"""Plugin-owned declaration used before the host admits any action."""
api_version: ApiVersion = API_VERSION
kind: Literal["RuntimePluginDescriptor"] = "RuntimePluginDescriptor"
runtime_protocol_version: RuntimeProtocolVersion = RUNTIME_PROTOCOL_VERSION
plugin_id: Identifier
plugin_version: ShortText
supported_host_api_versions: tuple[Identifier, ...] = Field(min_length=1)
action_ids: tuple[Identifier, ...] = Field(min_length=1)
@model_validator(mode="after")
def declarations_are_unique(self) -> RuntimePluginDescriptor:
if len(self.supported_host_api_versions) != len(
set(self.supported_host_api_versions)
):
raise ValueError("supported host API versions must be unique")
if len(self.action_ids) != len(set(self.action_ids)):
raise ValueError("runtime action ids must be unique")
return self
class RuntimeHandshakeRequest(ContractModel):
"""Host compatibility challenge sent before a runtime becomes ready."""
api_version: ApiVersion = API_VERSION
kind: Literal["RuntimeHandshakeRequest"] = "RuntimeHandshakeRequest"
runtime_protocol_version: RuntimeProtocolVersion = RUNTIME_PROTOCOL_VERSION
handshake_id: Identifier
plugin_id: Identifier
plugin_version: ShortText
host_api_version: Identifier
requested_at: AwareDatetime
required_action_ids: tuple[Identifier, ...] = Field(min_length=1)
@model_validator(mode="after")
def required_actions_are_unique(self) -> RuntimeHandshakeRequest:
if len(self.required_action_ids) != len(set(self.required_action_ids)):
raise ValueError("required runtime action ids must be unique")
return self
class RuntimeHandshakeResult(ContractModel):
"""Runtime response proving identity, protocol and admitted host API."""
api_version: ApiVersion = API_VERSION
kind: Literal["RuntimeHandshakeResult"] = "RuntimeHandshakeResult"
handshake_id: Identifier
runtime_instance_id: Identifier
accepted_host_api_version: Identifier
descriptor: RuntimePluginDescriptor
ready_at: AwareDatetime
class RuntimeHealthSnapshot(ContractModel):
"""Small transport-neutral liveness snapshot for laboratory supervision."""
api_version: ApiVersion = API_VERSION
kind: Literal["RuntimeHealthSnapshot"] = "RuntimeHealthSnapshot"
runtime_instance_id: Identifier
plugin_id: Identifier
plugin_version: ShortText
runtime_protocol_version: RuntimeProtocolVersion = RUNTIME_PROTOCOL_VERSION
status: Literal["starting", "ready", "stopped"]
observed_at: AwareDatetime
detail_code: Identifier | None = None
class RuntimeActionInvocation(ContractModel):
@@ -8,7 +8,14 @@ from .compatibility import CompatibilityAssessment
from .evidence import EvidenceRecord, RawTransportRecord
from .identity import DeviceInstanceRef
from .operations import OperationEvent, OperationPolicy, OperationRequest
from .runtime import RuntimeActionInvocation, RuntimeActionResult
from .runtime import (
RuntimeActionInvocation,
RuntimeActionResult,
RuntimeHandshakeRequest,
RuntimeHandshakeResult,
RuntimeHealthSnapshot,
RuntimePluginDescriptor,
)
from .session import DeviceSessionContext, DeviceSessionSnapshot
from .streams import CanonicalStreamEnvelope
@@ -25,6 +32,10 @@ def contract_json_schemas() -> dict[str, dict[str, object]]:
"OperationEvent": OperationEvent,
"RuntimeActionInvocation": RuntimeActionInvocation,
"RuntimeActionResult": RuntimeActionResult,
"RuntimePluginDescriptor": RuntimePluginDescriptor,
"RuntimeHandshakeRequest": RuntimeHandshakeRequest,
"RuntimeHandshakeResult": RuntimeHandshakeResult,
"RuntimeHealthSnapshot": RuntimeHealthSnapshot,
"CanonicalStreamEnvelope": CanonicalStreamEnvelope,
"EvidenceRecord": EvidenceRecord,
"RawTransportRecord": RawTransportRecord,