129 lines
6.0 KiB
Markdown
129 lines
6.0 KiB
Markdown
# 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 `ObservationArchiveSource` values with a plugin ID, archive ID,
|
|
confined root, discovery callback and recovery callback;
|
|
- exactly one recording exporter for that plugin ID;
|
|
- generic `ObservationSessionCandidate`, `SessionSource` and
|
|
`ObservationArtifactCandidate` values 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.
|
|
|
|
For new K1 sessions, the plugin also contributes confined clock artifacts. A
|
|
durable origin is published before camera production, MQTT shutdown creates a
|
|
transport-scoped provisional envelope, and the acquisition owner publishes a
|
|
content-addressed session envelope plus an atomic summary-pointer switch after
|
|
camera and MQTT/runtime shutdown. A provisional envelope cannot advertise
|
|
combined camera replay. Valid older sessions without this contract keep their
|
|
first/last-message compatibility fallback.
|
|
|
|
`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 v9 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;
|
|
- durable clock origin, active-envelope start/completion, selected clock
|
|
artifact identity and both digests when present;
|
|
- derived recording digest and zero-based timeline bounds whose origin and end
|
|
are both materialized as real RRD rows.
|
|
|
|
Cache v8 and older sidecars are rebuilt once. The native evidence is not
|
|
rewritten.
|
|
|
|
The K1 exporter also recognizes bounded `ModelingReport` messages. It writes the
|
|
reported route distance, speed and scan time as scalar time series below
|
|
`/metrics/device`; it does not infer them from pose integration or a UI timer.
|
|
Live observation consumes the same status through a plugin-injected raw-message
|
|
observer before the four-message visual preview queue. The generic
|
|
`VisualizationRuntime` still has no K1 decoder or topic knowledge.
|
|
|
|
The concrete implementation is physically located below:
|
|
|
|
```text
|
|
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:
|
|
|
|
1. discovers its own native evidence extension;
|
|
2. reconciles into the unchanged host SQLite catalog;
|
|
3. produces a generic `ReplayCommand`;
|
|
4. 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.
|
|
|
|
Additional acceptance tests bind the K1 capture clock into the ordered replay
|
|
artifacts, reject corrupt or provisional new-schema boundaries, materialize real
|
|
origin/end rows and retain archived device-metric samples without traversing the
|
|
lossy live-preview queue.
|
|
|
|
## 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.
|
|
|
|
The operator project name is normalized and validated by the K1 contribution,
|
|
stored as display/catalog metadata and never used as a path component. This ADR
|
|
does not claim that it reaches the scanner: the inert application-control codec
|
|
has no publisher, and automatic K1 writes remain disabled pending reviewed
|
|
operator-owned Keychain item provisioning, reviewed emission and durable
|
|
post-stop save evidence.
|
|
|
|
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.
|