feat(k1): complete primary acquisition lifecycle
This commit is contained in:
@@ -15,27 +15,55 @@ Each gate produces evidence and an explicit GO, PAUSE or BLOCKED result.
|
||||
| Stage 4 artifacts/flows | GO — bounded capture, hashes and negative control |
|
||||
| Stage 5 point cloud | GO — raw-LZ4 protobuf, 1,140 live frames decoded |
|
||||
| Stage 5 pose | GO — 1,215 live frames decoded and motion-correlated |
|
||||
| Stage 5 modeling telemetry | GO (read-only) — bounded `ModelingReport` decoder and device-reported scan time/distance/speed are integrated before the preview queue |
|
||||
| Stage 5 camera | GO (live) — left/right RTSP/H.264 preview observed, read-only runtime adapter and physical UI acceptance completed |
|
||||
| Stage 6 live viewer | GO — React Control Station, embedded self-hosted Rerun cloud/trajectory and Mac pipeline metrics |
|
||||
| Stage 7 observation archive | GO (point/pose) — durable catalog, recovery, background RRD preparation, saved-session timeline and atomic playback verified |
|
||||
| Stage 7 recorded cameras | GO (contract), acceptance pending — acquisition-owned fMP4 archive and player are implemented/tested; no real archived K1 camera session exists yet |
|
||||
| Plugin isolation | GO (laboratory control plane) — vendor backend/frontend are plugin-owned; manifest/runtime descriptor parity, versioned handshake, lifecycle health and transport correlation fail closed while execution remains in-process |
|
||||
| Stage 6 live viewer | GO — React Control Station, embedded self-hosted Rerun cloud/trajectory, plugin-owned spatial controls and live device/Mac metrics |
|
||||
| Stage 7 observation archive | GO (point/pose/telemetry contract) — durable catalog, recovery, capture-clock-bounded RRD preparation, archived metric time series, saved-session timeline and atomic playback are implemented |
|
||||
| Stage 7 recorded cameras | GO (contract), acceptance pending — acquisition-owned fMP4 archive and player are implemented/tested; one real archived K1 camera plus point-cloud session has not passed playback yet |
|
||||
| Plugin isolation | GO (laboratory control plane) — vendor backend/frontend and optional scene controls are plugin-owned; manifest/runtime descriptor parity, versioned handshake, lifecycle health and transport correlation fail closed while execution remains in-process |
|
||||
| Stage 8 product storage | PAUSE — retention, replication, encryption, capacity monitoring and long-run browser/WASM stress remain deployment gates |
|
||||
|
||||
USB project copying remains optional ground truth rather than a blocker for the
|
||||
now-verified network path. Owner-operated LixelGO traffic verifies the MQTT
|
||||
start/stop mapping and RTSP camera transport. MQTT control publishing remains
|
||||
deliberately deferred because complete request/save/rollback semantics are not
|
||||
yet modeled and the physical button remains a known-safe fallback.
|
||||
start/stop mapping and RTSP camera transport. The exact start/stop protobuf
|
||||
encoder, response correlator and device-status state machine now exist without a
|
||||
publisher. Header construction is known: an explicit ASCII device ID and ASCII
|
||||
OpenAPI key are required, while the session ID is derived exactly as
|
||||
`${device_id}:ModelingRequest`. MQTT control publishing remains deliberately
|
||||
disabled because legitimate OpenAPI credential provenance/provisioning and a
|
||||
durable post-stop save gate are not proven. The physical button remains the
|
||||
known-safe fallback.
|
||||
|
||||
The Stage 6 live path uses a bounded raw-first bridge: loss in the visualization
|
||||
queue cannot discard MQTT evidence. Stage 7 adds an independent durable
|
||||
observation-session lifecycle. Completed or recovered native captures are
|
||||
prepared once by a bounded backend worker into digest-bound RRD/cache-v7 and
|
||||
camera-manifest generations; a browser replay request never performs conversion.
|
||||
The saved scene, controller and timeline remain hidden until the complete RRD
|
||||
range and every declared camera pass admission. Sensor-to-display latency and
|
||||
camera/LiDAR sensor-clock alignment remain separate correlation tests.
|
||||
queue cannot discard MQTT evidence. The queue holds four preview messages;
|
||||
`ModelingReport` is consumed by a plugin-injected observer before that queue and
|
||||
does not compete with point/pose frames. A project name is mandatory at
|
||||
preparation, NFKC-normalized/trimmed on both sides, rejected for control
|
||||
or surrogate characters or more than 96 characters, and retained only as
|
||||
display metadata.
|
||||
|
||||
Stage 7 adds an independent durable observation-session lifecycle. New native
|
||||
captures durably publish the clock origin before camera production, retain a
|
||||
transport-scoped provisional envelope and atomically switch the capture-summary
|
||||
pointer to a content-addressed session envelope after all producers stop.
|
||||
Completed or recovered captures are prepared once by a bounded backend worker
|
||||
into digest-bound RRD/cache-v9 and camera-manifest generations;
|
||||
a browser replay request never performs conversion. The RRD materializes real
|
||||
origin/end rows plus archived device-reported distance, speed and scan-time
|
||||
series. The saved scene, controller and timeline remain hidden until the
|
||||
complete RRD range and every declared camera pass admission. Sensor-to-display
|
||||
latency and camera/LiDAR sensor-clock alignment remain separate correlation
|
||||
tests.
|
||||
|
||||
The generic host clears a prior manual or archived spatial source only after a
|
||||
plugin-owned live/file-replay start succeeds; a failed start preserves the
|
||||
current scene. Once an acquisition is nonterminal, a fail-closed guard blocks
|
||||
saved-session switching, persisted replay reattach and manual source
|
||||
input/apply/reset until that acquisition has been finalized. The successful
|
||||
automatic-source action is an internal start-result boundary and intentionally
|
||||
does not masquerade as an operator source switch. The K1 scene-level stop
|
||||
control is plugin-owned; with the active read-only profile it stops and seals
|
||||
local reception only and never claims to stop the scanner.
|
||||
|
||||
## Stage 0 — repository and host baseline
|
||||
|
||||
@@ -221,6 +249,9 @@ useful stream is decoded or structurally identified.
|
||||
- automated scan-button electronics;
|
||||
- OpenWrt/monitor-mode infrastructure;
|
||||
- firmware or internal-Linux analysis;
|
||||
- physical end-to-end replay acceptance for newly archived left/right cameras;
|
||||
- physical end-to-end shared-timeline playback for a newly archived session
|
||||
containing point cloud plus one selected K1 camera;
|
||||
- legitimate K1 OpenAPI credential provisioning, authorized command transport
|
||||
and stable-artifact proof after the observed stop lifecycle;
|
||||
- long-running large-session WebViewer/WASM memory telemetry;
|
||||
- production retention, replication, encryption and cross-platform packaging.
|
||||
|
||||
@@ -129,27 +129,46 @@ orientation is exposed as `(x,y,z,w)`. Unknown tail fields are retained.
|
||||
|
||||
`PrePathArray` is exactly 16 little-endian float64 values.
|
||||
|
||||
## Modeling telemetry
|
||||
|
||||
`lixel/application/report/modeling` contains a bounded `ModelingReport`. Field 2
|
||||
is PGO progress; field 3 is a nested scan status containing float32 move
|
||||
distance, float32 move speed and an int64 scan-time counter. Retained physical
|
||||
captures correlate that counter at exactly two ticks per second. Mission Core
|
||||
consumes this status before the disposable preview queue and exposes the current
|
||||
device-reported scan generation as elapsed seconds, route metres and metres per
|
||||
second. When scan time and distance reset together, the old route is not added
|
||||
to the new generation.
|
||||
|
||||
## Application control boundary
|
||||
|
||||
Static analysis first identified the application start/stop topic as
|
||||
`lixel/application/request/modeling`, QoS 2, with a protobuf
|
||||
`ModelingRequest`. Start action is 1 and stop action is 2. A valid request also
|
||||
contains a device/session/OpenAPI header and, for start, project/record/scan/mount
|
||||
settings.
|
||||
`ModelingRequest`. Start action is 1 and stop action is 2. The retained header
|
||||
contains explicit device identity and OpenAPI value; its session relation is
|
||||
literally `{device_id}:ModelingRequest`, not a caller-selected session. The
|
||||
retained start request carries an operator project name, record mode 2, scan
|
||||
mode 1 and mount mode 0; the retained stop request carries only header and
|
||||
action. QoS is 2, retain is false, and correlated success uses numeric result
|
||||
`302252033` with the same header identity and action.
|
||||
|
||||
Lab 002 then observed both mappings in owner-operated LixelGO traffic and
|
||||
correlated them with device success responses and physical start/stop of the
|
||||
high-rate streams. This promotes the mapping from a static hint to descriptive
|
||||
wire evidence; it does not authorize replay or publishing.
|
||||
|
||||
Publishing is deliberately not implemented. The physical double-click provides
|
||||
a verified autonomous start/stop path and avoids inventing session headers or
|
||||
changing scan settings. A future publisher requires a separate reviewed profile,
|
||||
explicit confirmation, response handling and rollback.
|
||||
The repository now contains an inert bounded encoder/response parser and a
|
||||
fail-closed device-status state machine for this exact profile. They have no
|
||||
MQTT publish dependency. Publishing remains deliberately disabled: the OpenAPI
|
||||
credential's provenance and secure provisioning are unresolved, and the
|
||||
complete `ScanOver`/ready/stable-artifact sequence has not physically proved a
|
||||
durable save result. The verified physical double-click remains the acquisition
|
||||
control until a separate reviewed write gate closes.
|
||||
|
||||
## Decoder and capture bounds
|
||||
## Decoder and raw-capture bounds
|
||||
|
||||
Default defensive limits are applied before allocation or iteration:
|
||||
The semantic decoder applies these default defensive limits before allocation
|
||||
or iteration:
|
||||
|
||||
- 2 MiB maximum MQTT payload;
|
||||
- 1 MiB maximum compressed block;
|
||||
@@ -158,6 +177,10 @@ Default defensive limits are applied before allocation or iteration:
|
||||
- 250,000 points per frame;
|
||||
- bounded protobuf field counts and exact LZ4 decoded length.
|
||||
|
||||
The raw-first evidence recorder has an independent 64 MiB default per-message
|
||||
limit, configurable only up to 256 MiB. That larger transport ceiling does not
|
||||
raise any semantic decoder allocation bound.
|
||||
|
||||
Per-frame failures do not justify firmware writes or speculative recovery.
|
||||
Always preserve the raw MQTT record and report the decoder error separately.
|
||||
|
||||
|
||||
+88
-26
@@ -10,18 +10,22 @@ trajectory, camera frame or latency values.
|
||||
## Active device-to-scene path
|
||||
|
||||
```text
|
||||
K1 lio_pcl / lio_pose
|
||||
K1 lio_pcl / lio_pose / ModelingReport
|
||||
|
|
||||
v
|
||||
read-only MQTT subscription on TCP 1883
|
||||
|
|
||||
+--> raw .k1mqtt + JSONL + SHA-256 summary (written first)
|
||||
+--> raw .k1mqtt + JSONL + durable clock origin (before camera/preview)
|
||||
+--> transport/session envelope + SHA-256 summary (shutdown/seal)
|
||||
|
|
||||
+--> injected K1 ModelingReport observer
|
||||
| `--> device scan time / distance / speed product metrics
|
||||
|
|
||||
v
|
||||
bounded latest-wins preview queue (32 messages)
|
||||
bounded latest-wins visual preview queue (4 messages)
|
||||
|
|
||||
v
|
||||
reviewed raw-LZ4/protobuf decoders
|
||||
reviewed raw-LZ4/protobuf point/pose normalizer
|
||||
|
|
||||
v
|
||||
Rerun Points3D + Transform3D + LineStrips3D
|
||||
@@ -44,6 +48,10 @@ become a durable pair at the bounded group-commit boundary (at most 0.5 s,
|
||||
4 MiB or 32 messages), not at every callback. If visualization cannot keep up,
|
||||
the oldest queued preview is discarded while capture continues. Rerun work
|
||||
stays on the dedicated publisher thread and cannot block that raw-first path.
|
||||
The four-message queue size is independent of the 32-message evidence group
|
||||
commit. A plugin-injected observer consumes bounded K1 `ModelingReport` messages
|
||||
after raw persistence but before visual admission, so status telemetry cannot
|
||||
evict a point-cloud or pose preview.
|
||||
|
||||
The current live/replay runtime instantiates only `RerunBridge`. The former
|
||||
Foxglove implementation is not a parallel runtime and does not listen on TCP
|
||||
@@ -107,14 +115,26 @@ directly and use their sibling metadata receive timestamps when present.
|
||||
complete visible-device list.
|
||||
4. Enter the existing router SSID/password and explicitly authorize the reviewed
|
||||
provisioning write. The backend does not retry the write automatically.
|
||||
5. When K1 reports a non-AP private address, start live reception.
|
||||
6. Wait until the UI reports that the local Rerun bridge is ready. No manual
|
||||
5. Enter the required project name. Mission Core NFKC-normalizes and trims it,
|
||||
rejects control/surrogate characters or more than 96 characters, and stores
|
||||
it as local display metadata rather than a path. The read-only profile does
|
||||
not send it to K1.
|
||||
6. When K1 reports a non-AP private address, choose **Подготовить локальный
|
||||
приём данных**. The stronger “initiate device work” wording appears only for
|
||||
a future profile that actually authorizes a vendor write.
|
||||
7. Wait until the UI reports that the local Rerun bridge is ready. No manual
|
||||
viewer URL is needed.
|
||||
7. Open **Наблюдение → Пространственная сцена**.
|
||||
8. Double-click the physical K1 button to start scanning. Real point frames and
|
||||
8. Open **Наблюдение → Пространственная сцена**. The K1 plugin's optional scene
|
||||
block shows the local preparation/acquisition phase and stop action.
|
||||
9. Double-click the physical K1 button to start scanning. Real point frames and
|
||||
pose/trajectory updates then appear in the embedded viewport.
|
||||
9. Double-click K1 again to stop physical scanning, wait for steady green, then
|
||||
stop the local session so captures and summaries are finalized.
|
||||
10. When K1 emits `ModelingReport`, the same plugin block shows its current scan
|
||||
time, route distance and speed. These are device reports, not values inferred
|
||||
from a browser timer or accumulated across a device counter reset.
|
||||
11. Double-click K1 again to stop physical scanning and wait for steady green,
|
||||
then choose **Остановить локальный приём** in the scene or device workflow
|
||||
so capture, camera archive and summaries are finalized. That UI action does
|
||||
not stop K1 on the active profile.
|
||||
|
||||
Each new live run creates a direct child below `MISSIONCORE_EVIDENCE_DIR`, or
|
||||
`.runtime/mission-core/evidence/sessions/` by default, with raw MQTT frames,
|
||||
@@ -123,6 +143,15 @@ per-message metadata and a hash summary. Repository-level
|
||||
the current writer. The connector subscribes to the fixed report-topic
|
||||
allowlist and does not publish an application request or modeling command.
|
||||
|
||||
The recovered command substrate is intentionally inert. It can encode exact
|
||||
start/stop request bytes, correlate response identity/action/result and classify
|
||||
observed device states without importing MQTT or publishing anything. The header
|
||||
requires explicit ASCII `device_id` and OpenAPI key and derives the session ID
|
||||
exactly as `${device_id}:ModelingRequest`. Legitimate OpenAPI key
|
||||
provenance/provisioning and durable save completion after stop are unresolved,
|
||||
so `vendor_writes_enabled` is false and the physical-button workflow remains
|
||||
canonical.
|
||||
|
||||
## Automatic Rerun source and lifecycle
|
||||
|
||||
On the first live or adapter file-replay session, `RerunBridge`:
|
||||
@@ -149,6 +178,16 @@ served over HTTP(S). No externally hosted viewer UI is involved:
|
||||
application. FastAPI carries control state only; the browser reads the point
|
||||
stream directly from the Rerun gRPC/proxy endpoint.
|
||||
|
||||
`activateAutomaticSpatialSource` is a generic host action, not a scanner-control
|
||||
command. A plugin invokes it only after its live or adapter-replay start returns
|
||||
success, then opens the spatial scene; a failed start preserves the old scene
|
||||
and source. The successful action releases any host-selected archive/manual URL
|
||||
so the new backend source becomes authoritative. After an acquisition becomes
|
||||
nonterminal, a fail-closed host guard blocks saved-session switching, persisted
|
||||
replay reattach and manual RRD/Rerun source input/application/reset until the
|
||||
acquisition ends. This successful-start transition intentionally does not
|
||||
consult the operator-switch guard, and the guard never stops equipment.
|
||||
|
||||
## Rerun entities
|
||||
|
||||
| Entity | Rerun archetype | Meaning |
|
||||
@@ -159,8 +198,11 @@ stream directly from the Rerun gRPC/proxy endpoint.
|
||||
| `/world/trajectory` | `LineStrips3D` | bounded path of up to 2,000 decoded poses |
|
||||
|
||||
Point count, frame rate, queue drops and measured pipeline time remain product
|
||||
metrics in the outer Control Station; they are not logged as Rerun entities and
|
||||
therefore cannot create additional automatic viewer panes.
|
||||
metrics in the outer Control Station. Live K1 scan time, route distance and
|
||||
speed also remain product metrics in the plugin-owned scene block. Saved-session
|
||||
export separately logs those three device-reported values below
|
||||
`/metrics/device` as Rerun scalar time series; this does not add vendor parsing
|
||||
to the generic live bridge.
|
||||
|
||||
Point coordinates remain `(x/scaler, y/scaler, z/scaler)` exactly as decoded.
|
||||
No axis swap, quaternion normalization, scanner-to-vehicle extrinsic or SLAM
|
||||
@@ -210,28 +252,46 @@ opaque same-origin RRD rather than starting a second live bridge.
|
||||
|
||||
Sealing, recovery or legacy import makes the session eligible for the bounded
|
||||
backend preparation worker. The worker reads every native message once and
|
||||
atomically publishes a private cache-v7 RRD using the zero-based `session_time`
|
||||
duration timeline. A SHA sidecar binds the generation to native evidence.
|
||||
atomically publishes a private cache-v9 RRD using the zero-based `session_time`
|
||||
duration timeline. New sessions bind that zero/end to the durable capture-clock
|
||||
envelope; older valid evidence retains the first/last-message compatibility
|
||||
fallback. A SHA sidecar binds the generation to native evidence.
|
||||
Replay-open never performs conversion: it returns HTTP 202 and a preparation
|
||||
handle or reuses the verified artifact. The embedded viewer opens the exact
|
||||
generation through Rerun's native incremental HTTP receiver. The bottom Control
|
||||
Station timeline then controls play/pause and seek directly; it does not
|
||||
approximate time with a React timer.
|
||||
|
||||
The native `.k1mqtt` remains the evidence master. RRD generation does not use
|
||||
the bounded live-preview queue, so it retains every frame accepted by the
|
||||
reviewed normalizer. See
|
||||
The native `.k1mqtt`, aligned metadata and capture-clock envelope remain the
|
||||
spatial evidence master. RRD generation does not use the bounded live-preview
|
||||
queue, so it retains every point/pose frame accepted by the reviewed normalizer
|
||||
and logs every valid `ModelingReport` as distance, speed and elapsed-time scalar
|
||||
rows. See
|
||||
[`09_OBSERVATION_SESSIONS.md`](09_OBSERVATION_SESSIONS.md) for storage,
|
||||
recovery and HTTP Range details.
|
||||
|
||||
## Time and latency semantics
|
||||
|
||||
The Rerun `capture_time` timeline uses
|
||||
`StreamMessage.received_at_epoch_ns`: Unix time when the Mac received the MQTT
|
||||
message. The K1 header timestamp epoch is not proven, so `capture_time` is not
|
||||
a sensor acquisition timestamp. The viewer accumulation window uses
|
||||
`stream_time`, assigned when the current live/replay run publishes a message;
|
||||
therefore replayed historical timestamps do not mix two sequential sessions.
|
||||
The Rerun `capture_time` timeline uses Mac host-clock epoch values. Data rows use
|
||||
`StreamMessage.received_at_epoch_ns`, while new archived sessions also contain a
|
||||
real origin row at the capture-envelope start and a real end row at the sealed
|
||||
session completion. Their zero-based `session_time` is derived from the same
|
||||
monotonic envelope; it can therefore cover a camera fragment before the first
|
||||
MQTT packet or after the last spatial packet without inventing an RRD range.
|
||||
Legacy evidence without an envelope falls back to first/last message bounds.
|
||||
The K1 header timestamp epoch is not proven, so `capture_time` is not a sensor
|
||||
acquisition timestamp. The live viewer accumulation window uses `stream_time`,
|
||||
assigned when the current live/replay run publishes a message; therefore
|
||||
replayed historical timestamps do not mix two sequential sessions.
|
||||
|
||||
Retained read-only physical captures support the profile-scoped telemetry units:
|
||||
in five of six runs, `ScanTime / 2` tracks host monotonic duration within about
|
||||
0.17 seconds across 76–2,294-second spans; the remaining older run contains a
|
||||
counter/distance reset and is treated as a new scan generation. In two moving
|
||||
runs, `ModelingReport.MoveDistance` matches the independent
|
||||
`lio_pose.distance` values at 41.521 m and 26.631 m, while reported peak speeds
|
||||
of 1.382 m/s and 1.291 m/s are physically plausible. This evidence does not turn
|
||||
the counters into a cross-firmware contract.
|
||||
|
||||
For live MQTT only, `pipeline_ms` / `mqtt_to_publish_ms` uses the Mac monotonic
|
||||
clock from MQTT callback receipt through raw disk write, bounded queue wait,
|
||||
@@ -272,7 +332,8 @@ listener and its process memory must be closed unconditionally.
|
||||
separate generic media path. Historical sessions predating that archive have
|
||||
no recoverable video.
|
||||
- Physical double-click remains the K1 scan start/stop control. Any MQTT command
|
||||
publisher needs a separately reviewed state-changing profile.
|
||||
publisher needs a separately reviewed state-changing profile with legitimate
|
||||
OpenAPI key provisioning and a proven durable-save completion gate.
|
||||
- No terrain map, elevation model, obstacle segmentation, localization fusion,
|
||||
mission planner or vehicle control is implemented by this viewer milestone.
|
||||
- Exact coordinate axes and the scanner-to-vehicle transform remain a mounting
|
||||
@@ -297,8 +358,9 @@ embedded Web Viewer path without a viewer-side substitute:
|
||||
|
||||
The 38 point frames and 42 pose frames account for all 80 observed messages.
|
||||
This proves current real-device decoding, bridge publication and embedded-viewer
|
||||
delivery for point cloud plus trajectory. It does not prove a camera channel or
|
||||
sensor-to-screen latency.
|
||||
delivery for point cloud plus trajectory. It does not prove archived playback
|
||||
of one selected camera together with the point cloud or sensor-to-screen
|
||||
latency.
|
||||
|
||||
## Legacy Foxglove regression module
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ and the bounded laboratory runtime seam in
|
||||
| `apps/control-station/src/core/device-plugins/frontendSdk.ts` | Public React host surface for reviewed frontend contributions | Versioned frontend Plugin SDK package surface |
|
||||
| `packages/plugin-sdk/` | Installable v0alpha2 Pydantic contracts and JSON Schema export | Portable host/plugin identity, lifecycle, stream and evidence boundary |
|
||||
| `plugins/xgrids-k1/` | v1alpha2 manifest, exact profile/loader and plugin-owned React connection/acquisition UI | Independently versioned XGRIDS device plugin |
|
||||
| `plugins/xgrids-k1/frontend/` | XGRIDS provisioning, acquisition/replay, diagnostics, metrics, runtime mapping and scoped CSS | Plugin-owned reviewed frontend contribution |
|
||||
| `plugins/xgrids-k1/frontend/` | XGRIDS provisioning, project/acquisition/replay, optional spatial controls, diagnostics, metrics, runtime mapping and scoped CSS | Plugin-owned reviewed frontend contribution |
|
||||
| `src/k1link/web/` | Local FastAPI host, fail-closed runtime handshake/transport seam, in-memory operation/acquisition lifecycle and generic session API | Generic host APIs plus isolated plugin supervisor |
|
||||
| `src/k1link/device_plugins/xgrids_k1/` | Physically isolated K1 BLE/MQTT/protobuf/LZ4/camera/replay/CLI compatibility implementation | Independently built XGRIDS device plugin process |
|
||||
| `src/k1link/data_plane/` | Transport-neutral decoded in-process consumer views | Local projections hydrated from portable SDK envelopes |
|
||||
@@ -32,7 +32,8 @@ The current K1 live/replay data path is:
|
||||
```text
|
||||
raw K1 transport
|
||||
-> raw-first evidence capture
|
||||
-> bounded preview queue
|
||||
-> injected K1 ModelingReport observer (device time/distance/speed)
|
||||
-> bounded four-message visual preview queue
|
||||
-> XGRIDS normalizer injected by the plugin facade
|
||||
-> DecodedPointCloudView / DecodedPoseView
|
||||
-> Rerun bridge
|
||||
@@ -44,12 +45,25 @@ The host-owned recorded path is separate from the disposable live preview:
|
||||
```text
|
||||
sealed or recovered native observation session
|
||||
-> SQLite catalog and bounded single-worker preparation
|
||||
-> atomic RRD cache v7 + immutable recorded-media manifest v2
|
||||
-> capture-clock-bound atomic RRD cache v9 + immutable recorded-media manifest v2
|
||||
-> generation-bound same-origin HTTP
|
||||
-> aggregate RRD/camera admission
|
||||
-> native Rerun HTTP receiver + recorded fMP4 player
|
||||
```
|
||||
|
||||
New K1 source generations durably publish
|
||||
`captures/mqtt_live/mqtt.timeline.origin.json` before camera production. MQTT
|
||||
shutdown adds provisional transport envelope `mqtt.timeline.json`; after camera
|
||||
and MQTT/runtime shutdown, the acquisition owner publishes content-addressed
|
||||
`mqtt.timeline.session-<sha256>.json` and atomically switches the capture-summary
|
||||
pointer while still holding the session lease. Only that `session` scope is
|
||||
eligible to advertise combined media replay. Cache v9 materializes both session
|
||||
envelope endpoints as real RRD rows and logs archived device-reported scan time,
|
||||
route distance and speed as scalar time series. A crash-recovered origin-only
|
||||
point/pose prefix falls back to its last validated message and cannot advertise
|
||||
camera media. Older valid sessions without the clock contract keep their
|
||||
first/last-message fallback and cannot acquire camera evidence retroactively.
|
||||
|
||||
Rerun does not import K1 protocol code, inspect MQTT topics or receive raw
|
||||
payloads. `VisualizationRuntime` cannot choose a vendor decoder implicitly; its
|
||||
composition owner injects one. The local `Decoded*View` classes are in-process
|
||||
@@ -73,7 +87,20 @@ selects a manifest model and mounts its `device.connection` component. Concrete
|
||||
XGRIDS source lives under `plugins/xgrids-k1/frontend`, imports the host only via
|
||||
`@mission-core/plugin-sdk`, and is connected by the single reviewed import in
|
||||
`apps/control-station/src/composition/devicePlugins.ts`. BLE/Wi-Fi provisioning,
|
||||
exact-profile confirmation and acquisition/replay UI are not Core components.
|
||||
exact-profile confirmation, project metadata, acquisition/replay UI and the
|
||||
optional K1 spatial-scene control block are not Core components. The host owns
|
||||
only the optional slot contract and generic actions such as opening the scene or
|
||||
releasing a prior manual/archive selection after a plugin source successfully
|
||||
starts.
|
||||
|
||||
A generic fail-closed guard blocks manual source input/apply/reset,
|
||||
saved-session switching and persisted replay reattach whenever the runtime
|
||||
reports a nonterminal acquisition. Unknown future acquisition states block by
|
||||
default. A plugin clears the previous host-selected source only after its start
|
||||
returns success, preserving the current scene on failure. That internal
|
||||
success-result transition is distinct from an operator switch. The guard does
|
||||
not stop equipment or invent a lifecycle transition; the acquisition must
|
||||
finish through its owning plugin.
|
||||
|
||||
## Experimental vocabulary, not Platform Ontology
|
||||
|
||||
@@ -115,6 +142,17 @@ transport mutation. Application-command publishing remains disabled; the
|
||||
separately reviewed BLE Wi-Fi provisioning write retains its own explicit
|
||||
operator gate.
|
||||
|
||||
An exact but inert K1 modeling-control substrate now lives inside the vendor
|
||||
plugin. It encodes the recovered start/stop protobuf shapes, strictly correlates
|
||||
response identity/action/numeric result and maps bounded device-status values
|
||||
into an observation-only state machine. Header construction is no longer
|
||||
guessed: callers provide explicit ASCII device ID and ASCII OpenAPI key, and the
|
||||
session ID is derived exactly as `${device_id}:ModelingRequest`. No MQTT import
|
||||
or publisher exists in that substrate. Legitimate OpenAPI key
|
||||
provenance/provisioning and stable-artifact proof after the stop lifecycle are
|
||||
still unresolved, so `vendor_writes_enabled=false` and every state-machine
|
||||
snapshot remains false for durable save completion.
|
||||
|
||||
## Semantic lifecycle
|
||||
|
||||
The transitional facade now creates separate provisional device,
|
||||
@@ -123,6 +161,20 @@ wait for receiver readiness, then wait for an external physical start, and becom
|
||||
data, wait for an externally confirmed stop, finalize and complete. Capture-only
|
||||
stop reports the K1 physical state as unknown.
|
||||
|
||||
Preparation requires a project name. The frontend and backend both apply NFKC
|
||||
normalization plus surrounding-whitespace trimming, reject control/surrogate
|
||||
characters and names above 96 Unicode characters, and store the result as
|
||||
session/catalog display metadata. It is not a path component. Because command
|
||||
publishing is off, the current integration does not claim that the name reaches
|
||||
K1.
|
||||
|
||||
The K1 contribution may mount a `SpatialControlsView` beside the host-owned
|
||||
viewport. It presents plugin lifecycle wording, the local stop action and live
|
||||
`ModelingReport` scan time, route distance and speed. Under the current
|
||||
capture-only profile the stop action seals local reception; it does not claim a
|
||||
device stop. `ModelingReport` is consumed before the visual preview queue through
|
||||
an injected observer, preserving the generic normalizer/Rerun boundary.
|
||||
|
||||
The operation journal is bounded and in memory. It records IDs, idempotency,
|
||||
declared deadlines, progress and terminal results without action parameters or secrets.
|
||||
It is not durable, distributed or recoverable after process restart.
|
||||
@@ -140,8 +192,7 @@ independently of browser delivery. The generic recorded player, manifest-v2
|
||||
validation and shared `session_time` controls are connected; historical sessions
|
||||
created before this archive contract contain no recoverable video, so physical
|
||||
recorded-camera acceptance remains open. Portable FFmpeg packaging, disk-backed
|
||||
browser buffering, fan-out and remote delivery also remain open. Device status
|
||||
and heartbeat remain raw observed channels without semantic decoders. Device
|
||||
browser buffering, fan-out and remote delivery also remain open. Device
|
||||
calibration command and sensor-to-vehicle extrinsics are unavailable.
|
||||
|
||||
The 2026-07-17 physical acceptance gate confirmed continuously updating point
|
||||
@@ -149,7 +200,10 @@ clouds, a matching live trajectory, and both camera selections in the same
|
||||
Mission Core acquisition. The embedded Rerun blueprint owns live-edge following
|
||||
on `stream_time`; the React shell selects the timeline once and does not drive it
|
||||
with a timer. Raw captures, camera frames, device identity and network details
|
||||
remain outside Git. See [`ADR 0007`](adr/0007-k1-camera-preview-copy-remux-gateway.md)
|
||||
remain outside Git. The bounded acquisition subset of `DeviceStatusReport` and
|
||||
`ModelingReport` route telemetry are decoded for the exact profile; heartbeat
|
||||
and nested system/RTK status payloads remain raw observed evidence. See
|
||||
[`ADR 0007`](adr/0007-k1-camera-preview-copy-remux-gateway.md)
|
||||
for the measured gate and remaining limits.
|
||||
|
||||
## Remaining extraction order
|
||||
@@ -169,8 +223,9 @@ views.
|
||||
4. Physically accept a newly archived left/right K1 session, then package the
|
||||
read-only RTSP/H.264 adapter for each target OS, add disk-backed sealed media
|
||||
caching and evolve same-host MSE delivery toward an authenticated Edge media
|
||||
plane; keep the modeling-command publisher disabled until its separate safety
|
||||
gate closes.
|
||||
plane. Keep the modeling-command publisher disabled until legitimate OpenAPI
|
||||
key provisioning and durable-save confirmation close its separate safety
|
||||
gate.
|
||||
|
||||
Complex equipment will likely be assembled from separately useful component
|
||||
plugins into configured hardware packs, while standalone equipment remains
|
||||
@@ -209,8 +264,8 @@ single K1 vertical.
|
||||
- remote Edge split, authenticated WAN relay and fleet orchestration;
|
||||
- automatic K1 start/stop/calibration commands;
|
||||
- device-reported camera capability discovery beyond the reviewed K1 profile;
|
||||
- physical acceptance of shared-timeline recorded-camera playback on a newly
|
||||
archived K1 session;
|
||||
- physical acceptance of shared-timeline point-cloud plus one selected-camera
|
||||
playback on a newly archived K1 session;
|
||||
- frame-accurate camera/LiDAR calibration, panoramic stitching, disk-backed
|
||||
browser media cache and remote multi-consumer delivery;
|
||||
- production retention, replication, encryption and long-run WebViewer/WASM
|
||||
|
||||
+124
-41
@@ -1,15 +1,19 @@
|
||||
# Observation sessions, playback and workspace layout
|
||||
|
||||
Status: implemented for native K1 point/pose evidence and host-side camera
|
||||
archival. Recorded spatial playback is exposed in the Mission Core observation
|
||||
workspace. The camera archive/player contract is implemented and covered by
|
||||
tests, but all retained real K1 sessions predate canonical camera archival.
|
||||
Physical archived-camera playback therefore remains an open acceptance gate;
|
||||
historical sessions contain no recoverable video.
|
||||
archival. Valid K1 `ModelingReport` scan time, distance and speed are also
|
||||
materialized as recorded Rerun time series. Recorded spatial playback is exposed
|
||||
in the Mission Core observation workspace. The camera archive/player contract
|
||||
is implemented and covered by tests, but all retained real K1 sessions predate
|
||||
canonical camera archival. Physical shared-timeline playback of one selected
|
||||
camera plus point cloud therefore remains an open acceptance gate; historical
|
||||
sessions contain no recoverable video.
|
||||
|
||||
## Operator path
|
||||
|
||||
1. Start a normal acquisition from **Парк → Локальное устройство**.
|
||||
1. Enter the required project name and start a normal acquisition from **Парк →
|
||||
Локальное устройство**. The NFKC-normalized/trimmed value is local display
|
||||
metadata, not a filesystem path, and becomes the saved-session catalog name.
|
||||
2. Open **Наблюдение → Пространственная сцена**. Live point cloud, trajectory
|
||||
and the selected camera remain live-only while acquisition is running.
|
||||
3. Stop acquisition normally, or allow the local service to recover an
|
||||
@@ -35,6 +39,15 @@ historical sessions contain no recoverable video.
|
||||
opening restores display settings, tool windows and dynamic source-window
|
||||
positions. This action saves no sensor evidence.
|
||||
|
||||
Mission Core does not switch an active acquisition to another spatial source.
|
||||
Every nonterminal or unknown acquisition state fail-closes saved-session replay,
|
||||
persisted replay reattach and manual RRD/Rerun input/apply/reset with
|
||||
**Завершите текущий приём перед сменой источника.** A plugin-owned automatic
|
||||
source action clears the older source and opens the scene only after its start
|
||||
returns success; failure leaves the current scene mounted. This internal
|
||||
success-result transition is not an operator switch. The guard does not stop the
|
||||
scanner or local receiver automatically.
|
||||
|
||||
Display controls have no separate Apply/Reset transaction. Boolean controls
|
||||
commit immediately; sliders, colors and selects commit on release/blur or after
|
||||
a short quiet period, and concurrent commits collapse to the newest value. A
|
||||
@@ -75,12 +88,12 @@ A queued job may legitimately wait behind another export and therefore uses the
|
||||
overall preparation deadline rather than the active-export heartbeat timeout.
|
||||
|
||||
The first preparation of a long capture can take time because every decodable
|
||||
point/pose message is projected into RRD. Later openings reuse a verified,
|
||||
digest-bound cache. Derived RRD cache v7 is intentionally incompatible with v6
|
||||
and older generations. It retains v6's real `session_time = 0` payload anchor
|
||||
and additionally binds the cache to the owning plugin ID, primary artifact and
|
||||
ordered set of confined source artifacts. Older generations are rebuilt once in
|
||||
the background.
|
||||
point/pose message and every valid K1 `ModelingReport` is projected into RRD.
|
||||
Later openings reuse a verified, digest-bound cache. Derived RRD cache v9 is
|
||||
intentionally incompatible with v8 and older generations. In addition to plugin
|
||||
and ordered-artifact identity, it binds the durable clock origin and active
|
||||
envelope and materializes real `session_time = 0` origin and sealed completion
|
||||
rows. Older generations are rebuilt once in the background.
|
||||
The browser may use the strict `source_url` with `If-Match`, while the embedded
|
||||
Rerun loader uses the canonical `viewer_source_url` whose lowercase SHA-256
|
||||
`generation` query is bound to the same launch descriptor. A missing or stale
|
||||
@@ -106,7 +119,15 @@ checkout:
|
||||
│ └── sessions/
|
||||
│ ├── .current_session # present only while a writer owns the root
|
||||
│ └── <UTC>_viewer_live/
|
||||
│ ├── captures/ # native MQTT source evidence
|
||||
│ ├── manifest.redacted.json # normalized local project display metadata
|
||||
│ ├── captures/
|
||||
│ │ └── mqtt_live/
|
||||
│ │ ├── mqtt.raw.k1mqtt
|
||||
│ │ ├── mqtt.metadata.jsonl
|
||||
│ │ ├── mqtt.timeline.origin.json
|
||||
│ │ ├── mqtt.timeline.json # provisional transport envelope
|
||||
│ │ ├── mqtt.timeline.session-<sha256>.json # sealed generation
|
||||
│ │ └── mqtt.summary.json # atomic active-envelope pointer
|
||||
│ └── media/ # canonical camera archives
|
||||
└── recordings/
|
||||
├── .export.lock # cross-process conversion/eviction lock
|
||||
@@ -156,6 +177,9 @@ For the current K1 profile:
|
||||
```text
|
||||
MQTT callback
|
||||
├─ durable native .k1mqtt + aligned metadata (source of record)
|
||||
├─ durable clock origin (before camera production)
|
||||
├─ provisional + content-addressed envelopes (transport/session bounds)
|
||||
├─ ModelingReport -> live product metrics (before visual preview queue)
|
||||
└─ bounded latest-wins Rerun live preview (disposable)
|
||||
|
||||
selected RTSP producer
|
||||
@@ -166,14 +190,21 @@ selected RTSP producer
|
||||
The live preview is intentionally allowed to drop frames under load. Native
|
||||
point/pose evidence and camera archive writes do not traverse that queue.
|
||||
|
||||
Native `.k1mqtt` bytes with aligned metadata and canonical camera fMP4 archives
|
||||
are the evidence source of truth. The derived RRD contains every decodable point
|
||||
and pose frame from that source, but remains a rebuildable view rather than an
|
||||
evidence master. A cache entry is rebuilt only when native source
|
||||
Native `.k1mqtt` bytes with aligned metadata, the capture-clock artifacts and
|
||||
canonical camera fMP4 archives are the evidence source of truth. The derived RRD
|
||||
contains every decodable point and pose frame plus every valid `ModelingReport`
|
||||
distance/speed/scan-time sample from that source, but remains a rebuildable view
|
||||
rather than an evidence master. A cache entry is rebuilt only when native source
|
||||
identity/digests change or an incompatible derived-data export revision is
|
||||
introduced. A UI blueprint or workspace-layout revision never invalidates or
|
||||
rewrites the data RRD. Cache v4/v5 payloads are not reusable as v6 because they
|
||||
do not guarantee the real zero-time anchor.
|
||||
rewrites the data RRD. Cache v8 and older payloads are not reusable as v9 because
|
||||
they do not bind both real capture-envelope endpoints.
|
||||
|
||||
`ModelingReport` time is the device's `ScanTime` counter at two ticks per second;
|
||||
distance and speed are the reported `MoveDistance`/`MoveSpeed` values. Live state
|
||||
keeps the current device scan generation and does not accumulate an earlier
|
||||
distance after the device resets scan time and route distance. These values are
|
||||
not reconstructed from pose integration or a browser timer.
|
||||
|
||||
Expensive cache misses run through the bounded preparation worker and one global
|
||||
cross-process export gate to cap peak RAM, CPU and temporary-disk use. Crash
|
||||
@@ -183,6 +214,52 @@ active response leases do not wait behind that gate. The derived cache has an
|
||||
8 GiB default quota, preserves a 2 GiB default filesystem reserve and evicts
|
||||
least-recently-used RRDs only; it never deletes native evidence.
|
||||
|
||||
## Capture-clock envelope
|
||||
|
||||
New K1 captures first publish bounded schema-1
|
||||
`captures/mqtt_live/mqtt.timeline.origin.json` with start epoch and monotonic
|
||||
nanoseconds. The writer creates and synchronizes this immutable artifact after
|
||||
its raw/metadata files are open and before camera production is armed. A crash
|
||||
therefore cannot leave retained camera evidence whose intended session zero
|
||||
existed only in memory.
|
||||
|
||||
MQTT shutdown then publishes schema-1 `mqtt.timeline.json` with the same start
|
||||
plus transport completion. Capture-summary schema 2 initially points to it with
|
||||
`capture_clock_scope: transport`, and binds both origin/envelope names and
|
||||
SHA-256 values. That provisional file is not rewritten.
|
||||
|
||||
After the camera archive and MQTT/runtime stop, the acquisition owner creates
|
||||
`mqtt.timeline.session-<sha256>.json` with the extended session completion and
|
||||
atomically switches `mqtt.summary.json` to that content-addressed name, digest
|
||||
and `capture_clock_scope: session` while still holding the active-session lease.
|
||||
Publication order is sealed file first, summary pointer second, so a crash leaves
|
||||
the previous pointer valid and the idempotent seal can converge on retry. The
|
||||
same summary update records `session_elapsed_seconds`. Files and containing
|
||||
directories are synchronized at their publication boundaries.
|
||||
|
||||
Discovery validates the summary-selected filename, both digests, the shared
|
||||
origin and the envelope bounds, then catalogs that exact artifact. The
|
||||
materializer passes its exact staged path to the exporter; it never chooses a
|
||||
sealed generation by glob or “latest file” ordering. Direct/legacy export keeps
|
||||
the fixed provisional-name fallback only for evidence outside the cataloged
|
||||
session contract, and orphan sealed candidates not selected by the validated
|
||||
summary are ignored.
|
||||
|
||||
A provisional `transport` scope is never advertised as combined camera replay.
|
||||
An interrupted origin-only point/pose prefix may use the durable origin and its
|
||||
last validated message as a compatibility end, but it cannot advertise camera
|
||||
media. If the origin, active envelope, summary pointer or digest relationship is
|
||||
missing/corrupt, new-schema discovery fails closed instead of inventing a
|
||||
session boundary.
|
||||
|
||||
For a sealed generation, `session_time = 0` is the envelope start and the RRD
|
||||
end is the envelope completion. Cache v9 logs real rows at
|
||||
`/__mission_core/session_origin` and `/__mission_core/session_end`, so a camera
|
||||
fragment accepted before the first MQTT message or after the last point/pose
|
||||
message can remain inside the declared RRD interval. Valid legacy sessions
|
||||
without this artifact keep their first/last-message compatibility fallback and
|
||||
cannot gain historical camera coverage.
|
||||
|
||||
## Camera archive contract
|
||||
|
||||
New acquisitions archive each selected source and codec epoch below the same
|
||||
@@ -275,6 +352,11 @@ camera acceptance run retained point/pose evidence only.
|
||||
process or power failure may discard the final uncommitted group. Raw bytes
|
||||
from an interrupted commit window may survive beyond the durable metadata;
|
||||
replay uses only the last validated metadata-aligned raw boundary.
|
||||
- New capture-summary schema 2 binds the durable origin and its active envelope
|
||||
by name and SHA-256. A normal combined replay requires the owner-sealed,
|
||||
content-addressed `session` scope. A crash can leave an origin-only or
|
||||
provisional `transport` generation, but discovery will not use either to
|
||||
advertise camera coverage. A mismatched origin/envelope/summary fails closed.
|
||||
- A native capture with a missing final summary is accepted only when the raw
|
||||
and metadata prefix is bounded, aligned and structurally valid. It is cataloged
|
||||
as `interrupted`, never silently promoted to `ready`.
|
||||
@@ -385,15 +467,17 @@ API boundary.
|
||||
|
||||
## Current limit
|
||||
|
||||
The spatial RRD, trajectory and archived fMP4 cameras use the same operator
|
||||
scrubber now. Camera epochs are aligned to zero-based `session_time` from the
|
||||
shared host-arrival monotonic clock and rendered through MSE. The client selects
|
||||
an epoch only inside its declared inclusive interval and verifies that the
|
||||
The spatial RRD, trajectory, archived device-metric series and archived fMP4
|
||||
cameras use the same operator scrubber now. Camera epochs are aligned to
|
||||
zero-based `session_time` from the shared host-arrival monotonic clock and
|
||||
rendered through MSE. The metric tab carries device-reported route distance,
|
||||
speed and scan time at their `ModelingReport` receive times. The client selects
|
||||
a camera epoch only inside its declared inclusive interval and verifies that the
|
||||
decoded MSE seekable duration covers that interval. This remains best-effort
|
||||
correlation: codec PTS and K1 sensor exposure time are not proven to share the
|
||||
LiDAR clock. A codec epoch whose init segment does not expose a browser-supported
|
||||
codec or whose timing cannot be proven remains retained evidence and fails
|
||||
closed in the UI/background preparation.
|
||||
correlation: codec PTS, K1 sensor exposure time and LiDAR firing time are not
|
||||
proven to share a device clock. A codec epoch whose init segment does not expose
|
||||
a browser-supported codec or whose timing cannot be proven remains retained
|
||||
evidence and fails closed in the UI/background preparation.
|
||||
Historical sessions with no canonical camera archive honestly show no recorded
|
||||
video. Recorded blueprints can change accumulation, grid visibility, point and
|
||||
trajectory visibility, point radius and a uniform custom point color without
|
||||
@@ -403,21 +487,20 @@ declared RRD has been received and decoded. Height, intensity, distance and RGB
|
||||
palettes remain baked into current RRD rows; fully dynamic recoloring requires
|
||||
exporting the corresponding scalar components in a future recording schema.
|
||||
|
||||
## Verification checkpoint — 2026-07-17
|
||||
## Verification boundary — 2026-07-17
|
||||
|
||||
The repository state described above passed the complete local pre-push gate:
|
||||
|
||||
- `uv sync --frozen --group dev` completed against the locked Python graph;
|
||||
- `uv run pytest`: **284 passed**;
|
||||
- `uv run ruff check .`: clean;
|
||||
- project mypy and strict plugin-SDK mypy: clean across 51 and 12 source files;
|
||||
- the XGRIDS K1 profile loader and emitted v0alpha2 JSON schema validated;
|
||||
- clean `npm ci` ran the required Rerun 0.34.1 postinstall patch;
|
||||
- frontend unit suite: **111 passed, 0 failed**;
|
||||
- TypeScript project build and Vite production build completed;
|
||||
- the generated `dist/index.html` and non-empty Rerun WASM artifact were verified.
|
||||
The current automated gate covers project-name validation, source-switch
|
||||
fail-closure, optional plugin scene controls, bounded `ModelingReport` decode,
|
||||
inert start/stop encoding and correlation, capture-clock publication/validation,
|
||||
cache-v9 origin/end materialization and archived metric series. The standard
|
||||
repository gate remains Python tests/Ruff/mypy plus frontend unit tests,
|
||||
TypeScript checking and the Vite production build; exact pass counts belong to
|
||||
the commit's CI/pre-push result rather than this durable architecture contract.
|
||||
|
||||
Vite still reports its expected large-chunk warning for the embedded Rerun
|
||||
viewer/WASM payload. That is a packaging optimization item, not a failed gate.
|
||||
No retained physical K1 session contains the new canonical camera archive yet,
|
||||
so real recorded-camera playback remains an explicit hardware acceptance test.
|
||||
No retained physical K1 session contains the new canonical camera archive, so a
|
||||
real point-cloud plus one-camera recorded playback remains an explicit hardware
|
||||
acceptance test. Automated protocol tests also do not authorize K1 modeling
|
||||
publishing: legitimate OpenAPI key provisioning and durable save completion
|
||||
remain separate physical/security gates.
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user