feat(k1): complete primary acquisition lifecycle

This commit is contained in:
DCCONSTRUCTIONS
2026-07-17 23:03:59 +03:00
parent 9d51080d2e
commit aa3680948f
66 changed files with 6093 additions and 544 deletions
@@ -28,23 +28,41 @@ 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 v7 binds the published RRD to:
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;
- derived recording digest and zero-based timeline bounds.
- 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 v6 and older sidecars are rebuilt once. The native evidence is not
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
@@ -79,6 +97,11 @@ 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
@@ -87,6 +110,12 @@ 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 modeling-control codec has
no publisher, and automatic K1 writes remain disabled pending legitimate OpenAPI
credential provisioning 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
@@ -26,10 +26,13 @@ are physically located under:
```text
plugins/xgrids-k1/frontend/src/
plugin.ts
projectName.ts
automaticSourceStart.ts
XgridsK1Connection.tsx
components/
K1ProvisioningPipeline.tsx
K1AcquisitionPipeline.tsx
K1SpatialControls.tsx
K1Diagnostics.tsx
K1Metrics.tsx
```
@@ -43,9 +46,32 @@ NODE.DC UI kit. It may not import Control Station implementation paths.
`apps/control-station/src/composition/devicePlugins.ts` is the only Core file
which imports the concrete XGRIDS frontend. The host owns model selection,
safe deactivation, the `device.connection` slot, navigation, the spatial scene,
saved-session UX and global layout. The plugin owns BLE candidates, exact-profile
attestation, credentials-in-memory form state, K1 endpoint state, connection
instructions and the live/replay acquisition controls.
saved-session UX and global layout. A contribution may additionally expose one
optional `SpatialControlsView`, which the generic scene mounts beside the
viewport without learning its fields or lifecycle wording. The plugin owns BLE
candidates, exact-profile attestation, credentials-in-memory form state, K1
endpoint state, connection instructions, project-name validation, live/replay
acquisition controls and its scene-level status/stop/telemetry block.
The host surface includes `activateAutomaticSpatialSource`. The name refers to
viewer-source ownership, not automatic equipment control. The plugin calls it
only after live or adapter replay start returns success and then opens the scene;
a failed start leaves the existing scene/source intact. The action clears the
earlier manual/archive selection so backend state can supply the successful new
source. A generic fail-closed acquisition guard blocks saved-session switching,
persisted replay reattach and manual source input/apply/reset for every
nonterminal state, cleanup-pending terminal state or unknown acquisition state.
The internal successful-start action is not an operator source switch and
performs no implicit stop.
K1 project names are required, NFKC-normalized and trimmed, rejected for control
or surrogate characters or more than 96 Unicode characters, and sent to the
backend as local display metadata. The active profile has
`vendor_writes_enabled=false`, so its
buttons and scene control say that they prepare/stop local reception. Before an
acquisition exists, device-start wording is gated by active-control capability;
after start, calibration/device-stop wording additionally requires a
plugin-commanded acquisition mode.
Plugin CSS is imported with the contribution and scoped below
`.xgrids-k1-plugin`. A plugin cannot add global navigation, route ownership or
@@ -65,19 +91,24 @@ the correct component for each model. It also fails when:
- generic Core files other than the composition root contain XGRIDS/Lixel/K1
implementation knowledge;
- plugin source imports Control Station implementation paths;
- provisioning, acquisition, diagnostics or scoped-style modules disappear.
- provisioning, acquisition, optional scene controls, diagnostics or
scoped-style modules disappear;
- a nonterminal acquisition can be replaced by a manual or recorded spatial
source, including persisted replay reattach.
The accepted local gate is 114 frontend unit tests, TypeScript strict checking
The accepted local gate is the frontend unit suite, TypeScript strict checking
and a Vite production build. Backend behavior and the physical K1 protocol are
unchanged by this source/UI decomposition.
unchanged by this source/UI decomposition; protocol command authorization is a
separate backend/physical gate.
## Consequences
A second device family can provide a completely different connection pipeline
and UI while reusing the same model catalog, lifecycle transition, observation
scene and saved-session host. Adding it requires its own plugin-owned frontend
contribution and one reviewed composition import, not edits to `App`,
`DeviceWorkspace` or generic runtime state.
and optional scene controls while reusing the same model catalog, lifecycle
transition, observation scene and saved-session host. Adding it requires its own
plugin-owned frontend contribution and one reviewed composition import; the
generic host changes only when a genuinely cross-device slot/action is added,
not for vendor fields or protocol-specific workflow steps.
The remaining isolation gap is deployment-level: frontend contributions are
still compiled into one signed application build, and backend plugins still run