4.3 KiB
ADR 0009: device-plugin observation runtime and K1 extraction
- Status: accepted
- Date: 2026-07-17
- Extends: ADR 0003, ADR 0004 and ADR 0008
Context
The manifest/action boundary isolated the browser shell, but the host observation
service still imported K1 archive discovery, .k1mqtt preparation, MQTT timing
metadata and the K1 normalizer. A second sensor could not provide durable replay
without adding vendor branches to SessionStore, SessionRecordingMaterializer
and app.py.
Decision
Every installed device plugin may contribute an optional observation runtime:
- one or more
ObservationArchiveSourcevalues with a plugin ID, archive ID, confined root, discovery callback and recovery callback; - exactly one recording exporter for that plugin ID;
- generic
ObservationSessionCandidate,SessionSourceandObservationArtifactCandidatevalues returned to the host.
The host owns SQLite lifecycle, path confinement, preparation scheduling, cross-process cache locking, atomic publication, RRD delivery, recorded-media admission and browser APIs. A plugin owns native evidence discovery, transport format validation, recovery semantics, timeline-origin extraction and conversion of its primary artifact into the canonical recorded Rerun artifact.
ReplayCommand is now vendor-neutral. It contains an owning plugin_id, an
opaque primary artifact ID, an ordered tuple of confined artifacts and a ready
timeline origin. It contains no filename suffix, MQTT topic or sidecar name.
The materializer selects the exporter by plugin_id and stages every validated
artifact prefix while preserving only its plugin-owned basename.
Derived RRD cache v7 binds the published RRD to:
- plugin ID and primary artifact ID;
- ordered artifact IDs and media types;
- full file identity and replay boundary for every artifact;
- per-artifact prefix digest;
- derived recording digest and zero-based timeline bounds.
Cache v6 and older sidecars are rebuilt once. The native evidence is not rewritten.
The concrete implementation is physically located below:
src/k1link/device_plugins/xgrids_k1/
ble/ mqtt/ protocol/ analyze/ net/ usb/
viewer/
facade.py camera.py archive.py observation.py rrd_export.py cli.py
The manifest entrypoint is
k1link.device_plugins.xgrids_k1.facade:build_xgrids_k1_plugin. Generic
sessions, web/app.py, web/session_api.py and the Rerun consumer do not
import this package or contain K1 formats, topics or identifiers.
Plugin SDK v0alpha2 additionally defines immutable
RuntimeActionInvocation and RuntimeActionResult. The host dispatcher creates
and validates them for every plugin action, including legacy compatibility
routes, before returning the path-free output document.
Acceptance gate
The automated gate builds an unrelated synthetic spatial-sensor plugin which:
- discovers its own native evidence extension;
- reconciles into the unchanged host SQLite catalog;
- produces a generic
ReplayCommand; - materializes and reuses an RRD through its plugin-keyed exporter.
A dependency test also fails if generic observation hot-path files regain K1, XGRIDS, topic, native-format or concrete-plugin imports. Adding a second device therefore requires a manifest/runtime contribution and a plugin-owned reviewed UI contribution, not edits to the session API, preparation queue, Rerun consumer or main application shell. ADR 0010 defines that frontend boundary.
Consequences
The semantic and physical vendor boundary is complete for the current
single-process architecture. Existing K1 raw evidence, camera archives and
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
new physical regression gate; they must not be inferred from module extraction.