docs(observation): document durable sessions and viewer lifecycle
This commit is contained in:
parent
e94c64eebd
commit
f9ffb7bd1c
69
README.md
69
README.md
|
|
@ -10,9 +10,11 @@ The first proven hardware vertical is the XGRIDS/LixelKity K1 plugin. On firmwar
|
||||||
3.0.2 the host provisions the scanner onto an existing LAN without LixelGO,
|
3.0.2 the host provisions the scanner onto an existing LAN without LixelGO,
|
||||||
connects to its MQTT broker, persists each raw frame before preview work, decodes
|
connects to its MQTT broker, persists each raw frame before preview work, decodes
|
||||||
point cloud and pose, and renders the real cloud plus trajectory through an
|
point cloud and pose, and renders the real cloud plus trajectory through an
|
||||||
embedded self-hosted Rerun Web Viewer. Capture files are `fsync`ed on clean
|
embedded self-hosted Rerun Web Viewer. Native MQTT persistence uses bounded
|
||||||
close; per-frame power-loss durability is not claimed. The former Foxglove
|
group commit (at most 0.5 seconds, 4 MiB or 32 messages), and camera archives
|
||||||
bridge remains only as a legacy regression module.
|
commit complete fMP4 segments before their index rows. These are explicit
|
||||||
|
crash-RPO bounds, not a zero-loss or disk-replication claim. The former
|
||||||
|
Foxglove bridge remains only as a legacy regression module.
|
||||||
|
|
||||||
The repository is intentionally migrating in stages. The current `src/k1link`
|
The repository is intentionally migrating in stages. The current `src/k1link`
|
||||||
package is the compatibility implementation of the first plugin path; vendor
|
package is the compatibility implementation of the first plugin path; vendor
|
||||||
|
|
@ -124,19 +126,24 @@ revision or content hash. Publishing/vendoring those packages or enforcing an
|
||||||
immutable donor revision remains a packaging and CI prerequisite.
|
immutable donor revision remains a packaging and CI prerequisite.
|
||||||
|
|
||||||
The Observation spatial workspace embeds the open-source Rerun Web Viewer
|
The Observation spatial workspace embeds the open-source Rerun Web Viewer
|
||||||
inside the Mission Core shell. It can open a compatible RRD file over HTTP(S) or a
|
inside the Mission Core shell. It can open a compatible RRD over same-origin
|
||||||
Rerun gRPC/proxy source such as `rerun+http://127.0.0.1:9876/proxy`. It does not
|
HTTP or a Rerun gRPC/proxy source such as
|
||||||
use an external hosted viewer UI. Dynamic point-cloud and camera source
|
`rerun+http://127.0.0.1:9876/proxy`; no external hosted viewer UI is used.
|
||||||
composition, host-owned window layout and the current live-only timeline contract
|
Dynamic point-cloud/camera composition and the live source contract are fixed in
|
||||||
are fixed in [`ADR 0006`](docs/adr/0006-vendor-neutral-observation-sources-and-live-only-timeline.md).
|
[`ADR 0006`](docs/adr/0006-vendor-neutral-observation-sources-and-live-only-timeline.md).
|
||||||
|
The durable session catalog, recorded `session_time` scrubber and versioned
|
||||||
|
workspace-layout profile are fixed in
|
||||||
|
[`ADR 0008`](docs/adr/0008-durable-observation-sessions-and-workspace-layout.md).
|
||||||
|
|
||||||
The first K1 live session or replay in a `k1link serve` process creates one local
|
The first K1 live session or adapter file-replay (`.k1mqtt`/reviewed TSV) in a
|
||||||
Rerun `RecordingStream`, starts its gRPC/proxy server on TCP 9876 and publishes
|
`k1link serve` process creates one local Rerun `RecordingStream`, starts its
|
||||||
the resulting URL through control-plane state. Later sessions reset their
|
gRPC/proxy server on TCP 9876 and publishes the resulting URL through
|
||||||
session-local scene and metrics and reuse that process-wide stream; this avoids
|
control-plane state. Later live/file-replay sessions reset their session-local
|
||||||
restarting the native listener while the embedded browser remains connected.
|
scene and metrics and reuse that process-wide stream. Saved observation sessions
|
||||||
Unless an operator has entered a manual source, the React application assigns
|
do not reuse this listener: they open a private digest-bound RRD generation over
|
||||||
that URL to the embedded viewer. The complete runtime path is K1 MQTT → raw-first
|
same-origin HTTP through Rerun's native incremental receiver. Unless an operator
|
||||||
|
has entered a manual source, the React application assigns the applicable source
|
||||||
|
to the embedded viewer. The complete live runtime path is K1 MQTT → raw-first
|
||||||
evidence capture → bounded latest-wins preview queue → explicitly injected K1
|
evidence capture → bounded latest-wins preview queue → explicitly injected K1
|
||||||
protobuf/LZ4 normalizer → transport-neutral decoded local views → Rerun
|
protobuf/LZ4 normalizer → transport-neutral decoded local views → Rerun
|
||||||
`Points3D`, `Transform3D` and `LineStrips3D` → embedded Web Viewer. Rerun does
|
`Points3D`, `Transform3D` and `LineStrips3D` → embedded Web Viewer. Rerun does
|
||||||
|
|
@ -146,15 +153,21 @@ portable Plugin SDK wire envelopes.
|
||||||
The default Rerun blueprint shows a 12-second sliding accumulation of real point
|
The default Rerun blueprint shows a 12-second sliding accumulation of real point
|
||||||
frames. Product controls are connected for point size, intensity/height/distance
|
frames. Product controls are connected for point size, intensity/height/distance
|
||||||
or available RGB coloring, Turbo/Viridis/Plasma/grayscale/custom palettes,
|
or available RGB coloring, Turbo/Viridis/Plasma/grayscale/custom palettes,
|
||||||
point and trajectory visibility, and the scene grid. Projection, custom
|
point and trajectory visibility, and the scene grid. The first disk action saves
|
||||||
timeline transport and saved layout remain later product work. No synthetic
|
and restores the versioned spatial layout without mutating sensor evidence.
|
||||||
point cloud, trajectory, camera frame or latency value is generated.
|
Saved native point/pose sessions are materialized losslessly into private,
|
||||||
|
digest-bound RRD recordings and can be played, paused and scrubbed on a
|
||||||
|
zero-based `session_time` timeline. No synthetic point cloud, trajectory,
|
||||||
|
camera frame or latency value is generated.
|
||||||
|
|
||||||
A powered-device checkpoint passed 80 real MQTT messages through the current
|
A powered-device checkpoint passed 80 real MQTT messages through the current
|
||||||
Rerun runtime: 38 point-cloud frames, 42 pose frames, 2,775 points in the last
|
Rerun runtime: 38 point-cloud frames, 42 pose frames, 2,775 points in the last
|
||||||
cloud and zero decode errors. The later RTSP camera preview is not yet wired into
|
cloud and zero decode errors. The later RTSP camera preview is available through
|
||||||
the Rerun/runtime path. Rerun `capture_time` is the Mac receive timestamp, not a
|
the generic floating observation windows and new acquisitions archive its fMP4
|
||||||
proven K1 sensor timestamp or photon-to-screen measurement.
|
segments independently of browser delivery. Historical sessions recorded before
|
||||||
|
that archive contract contain no video. Rerun `capture_time` and the camera
|
||||||
|
index use Mac receive/arrival timestamps, not proven K1 sensor timestamps or a
|
||||||
|
photon-to-screen measurement.
|
||||||
|
|
||||||
The old Foxglove implementation is retained only in
|
The old Foxglove implementation is retained only in
|
||||||
`src/k1link/viewer/foxglove_bridge.py` and its regression tests. The current
|
`src/k1link/viewer/foxglove_bridge.py` and its regression tests. The current
|
||||||
|
|
@ -162,6 +175,8 @@ live/replay runtime does not start it or use TCP 8765. The
|
||||||
[live viewer runbook](docs/06_K1_LIVE_VIEWER.md) records the active Rerun path and
|
[live viewer runbook](docs/06_K1_LIVE_VIEWER.md) records the active Rerun path and
|
||||||
its timing/security boundaries; the frontend contract is documented in
|
its timing/security boundaries; the frontend contract is documented in
|
||||||
[`apps/control-station/README.md`](apps/control-station/README.md).
|
[`apps/control-station/README.md`](apps/control-station/README.md).
|
||||||
|
The host storage layout, recovery rules, replay API and operator path are in
|
||||||
|
[`docs/09_OBSERVATION_SESSIONS.md`](docs/09_OBSERVATION_SESSIONS.md).
|
||||||
|
|
||||||
The FastAPI application and credential endpoint bind to loopback, but the Rerun
|
The FastAPI application and credential endpoint bind to loopback, but the Rerun
|
||||||
gRPC server currently binds TCP 9876 on all network interfaces even though its
|
gRPC server currently binds TCP 9876 on all network interfaces even though its
|
||||||
|
|
@ -214,9 +229,15 @@ present.
|
||||||
- [Verified MQTT stream profile](docs/05_K1_MQTT_STREAM_PROFILE.md)
|
- [Verified MQTT stream profile](docs/05_K1_MQTT_STREAM_PROFILE.md)
|
||||||
- [Live console and embedded Rerun runbook](docs/06_K1_LIVE_VIEWER.md)
|
- [Live console and embedded Rerun runbook](docs/06_K1_LIVE_VIEWER.md)
|
||||||
- [Mission Core monorepo and plugin boundary](docs/07_MISSION_CORE_MONOREPO.md)
|
- [Mission Core monorepo and plugin boundary](docs/07_MISSION_CORE_MONOREPO.md)
|
||||||
|
- [Owner-controlled LixelGO/iPhone observation](docs/08_LIXELGO_IPHONE_OBSERVATION.md)
|
||||||
|
- [Observation sessions, playback and workspace layout](docs/09_OBSERVATION_SESSIONS.md)
|
||||||
- [Monorepo architecture decision](docs/adr/0002-mission-core-monorepo.md)
|
- [Monorepo architecture decision](docs/adr/0002-mission-core-monorepo.md)
|
||||||
- [Device plugin UI and runtime boundary](docs/adr/0003-device-plugin-ui-and-runtime-boundary.md)
|
- [Device plugin UI and runtime boundary](docs/adr/0003-device-plugin-ui-and-runtime-boundary.md)
|
||||||
- [Plugin SDK v0alpha2 and experimental device lifecycle](docs/adr/0004-plugin-sdk-v0alpha2-and-experimental-device-lifecycle.md)
|
- [Plugin SDK v0alpha2 and experimental device lifecycle](docs/adr/0004-plugin-sdk-v0alpha2-and-experimental-device-lifecycle.md)
|
||||||
|
- [Owner-controlled LixelGO observation decision](docs/adr/0005-owner-controlled-lixelgo-iphone-observation.md)
|
||||||
|
- [Vendor-neutral live observation sources](docs/adr/0006-vendor-neutral-observation-sources-and-live-only-timeline.md)
|
||||||
|
- [K1 camera preview copy-remux gateway](docs/adr/0007-k1-camera-preview-copy-remux-gateway.md)
|
||||||
|
- [Durable observation sessions and workspace layout](docs/adr/0008-durable-observation-sessions-and-workspace-layout.md)
|
||||||
- [Redacted live lab report](docs/lab/001_K1_LIVE_MQTT_20260715.redacted.md)
|
- [Redacted live lab report](docs/lab/001_K1_LIVE_MQTT_20260715.redacted.md)
|
||||||
- [Session manifest schema](schemas/session-manifest.schema.json)
|
- [Session manifest schema](schemas/session-manifest.schema.json)
|
||||||
- [Reference input provenance](docs/reference/README.md)
|
- [Reference input provenance](docs/reference/README.md)
|
||||||
|
|
@ -240,6 +261,6 @@ reviewed step. Random writes, fuzzing, brute force, firmware operations,
|
||||||
destructive file access and credential guessing remain out of scope.
|
destructive file access and credential guessing remain out of scope.
|
||||||
|
|
||||||
Real captures, projects, router metadata, serials, credentials, maps, images,
|
Real captures, projects, router metadata, serials, credentials, maps, images,
|
||||||
and logs are ignored by normal Git. Redacted manifests and SHA-256 inventories
|
and logs under `.runtime/`, canonical evidence roots and legacy `sessions/` are
|
||||||
are committed; encrypted artifact storage will be selected only when real data
|
ignored by normal Git. Redacted manifests and SHA-256 inventories are committed;
|
||||||
exists.
|
encrypted/replicated artifact storage remains a deployment decision.
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,10 @@ device adapter, но структура интерфейса от него не
|
||||||
|
|
||||||
Внутри пространственной рабочей поверхности встроен открытый Rerun Web Viewer.
|
Внутри пространственной рабочей поверхности встроен открытый Rerun Web Viewer.
|
||||||
Это self-hosted frontend-компонент из npm-пакета `@rerun-io/web-viewer`, а не
|
Это self-hosted frontend-компонент из npm-пакета `@rerun-io/web-viewer`, а не
|
||||||
переход во внешний облачный интерфейс. При запуске K1 live/replay backend сам
|
переход во внешний облачный интерфейс. Для K1 live и adapter file-replay backend
|
||||||
создаёт Rerun gRPC/proxy source; ручной адрес нужен только для другого Rerun
|
сам создаёт Rerun gRPC/proxy source. Saved observation sessions открывают
|
||||||
потока или совместимой RRD-записи.
|
проверенную digest-bound RRD generation через same-origin HTTP; ручной адрес
|
||||||
|
нужен только для другого совместимого Rerun source.
|
||||||
|
|
||||||
## Текущее состояние
|
## Текущее состояние
|
||||||
|
|
||||||
|
|
@ -20,11 +21,13 @@ device adapter, но структура интерфейса от него не
|
||||||
| Локальный control plane | Реализован | React получает состояние и выполняет операции через FastAPI REST и WebSocket на loopback. |
|
| Локальный control plane | Реализован | React получает состояние и выполняет операции через FastAPI REST и WebSocket на loopback. |
|
||||||
| K1 BLE → Wi-Fi | Реализован | Реальный BLE-поиск всех видимых устройств и одна подтверждённая provisioning-запись выбранному устройству. |
|
| K1 BLE → Wi-Fi | Реализован | Реальный BLE-поиск всех видимых устройств и одна подтверждённая provisioning-запись выбранному устройству. |
|
||||||
| K1 live/replay MQTT | Реализован | Read-only приём, raw-first сохранение, декодирование облака точек и позы, реальные метрики. |
|
| K1 live/replay MQTT | Реализован | Read-only приём, raw-first сохранение, декодирование облака точек и позы, реальные метрики. |
|
||||||
| Автоматический MQTT → Rerun | Реализован | Первый live/replay поднимает process-wide `RecordingStream` и gRPC/proxy на TCP 9876; следующие сессии сбрасывают сцену и метрики и переиспользуют его. |
|
| Автоматический MQTT → Rerun | Реализован | Первый live/adapter file-replay поднимает process-wide `RecordingStream` и gRPC/proxy на TCP 9876; следующие такие сессии переиспользуют его. Saved observation replay использует отдельный immutable HTTP RRD path. |
|
||||||
| Встроенный Rerun Viewer | Реализован | Self-hosted npm-компонент автоматически открывает текущий gRPC source внутри Control Station; внешний viewer не используется. |
|
| Встроенный Rerun Viewer | Реализован | Self-hosted npm-компонент автоматически открывает текущий gRPC source внутри Control Station; внешний viewer не используется. |
|
||||||
| Контролы сцены → Rerun | Реализованы для текущей геометрии | Работают размер и видимость точек, атрибут цвета, палитра, окно накопления, траектория и сетка. Проекция, собственный timeline и сохранённые layout-профили ещё не подключены. |
|
| Контролы сцены → Rerun | Реализованы для текущей геометрии | Работают размер и видимость точек, атрибут цвета, палитра, окно накопления, траектория, сетка, host timeline и сохранение/восстановление spatial layout. Проекция и семантические слои ещё не подключены. |
|
||||||
|
| Сохранённые observation sessions | Реализованы для point/pose | Три последние сессии, background preparation, cache v6, generation-bound RRD, atomic admission, autoplay, play/pause/seek и controlled switching больших записей. |
|
||||||
|
| K1 camera preview и archive | Live реализован; recorded contract реализован | Обе RTSP/H.264 камеры физически приняты в live UI. Новые acquisition-owned fMP4 archives не зависят от browser windows; recorded player подключён, но реальная архивная K1 camera-session ещё не прошла physical acceptance. |
|
||||||
| Legacy Foxglove module | Только regression | Модуль и тесты сохранены для сравнения декодирования. Текущий live/replay runtime не запускает Foxglove WebSocket и не использует TCP 8765. |
|
| Legacy Foxglove module | Только regression | Модуль и тесты сохранены для сравнения декодирования. Текущий live/replay runtime не запускает Foxglove WebSocket и не использует TCP 8765. |
|
||||||
| Камеры, карты и миссии | Интерфейс готов | Серверная логика и реальные каналы для этих рабочих поверхностей ещё не подключены. |
|
| Карты и миссии | Интерфейсный каркас | Реальные map/mission backends и vehicle control ещё не подключены. |
|
||||||
|
|
||||||
Приложение не генерирует демонстрационное облако, траекторию, кадры или
|
Приложение не генерирует демонстрационное облако, траекторию, кадры или
|
||||||
метрики. Если реальных данных нет, область сцены остаётся пустой, а числовые поля
|
метрики. Если реальных данных нет, область сцены остаётся пустой, а числовые поля
|
||||||
|
|
@ -48,9 +51,16 @@ Mission Core Control Station ←→ REST /api/v1/device-plugins/*
|
||||||
+ plugin-scoped WebSocket events
|
+ plugin-scoped WebSocket events
|
||||||
FastAPI на 127.0.0.1:8000
|
FastAPI на 127.0.0.1:8000
|
||||||
CoreBluetooth + live/replay runtime
|
CoreBluetooth + live/replay runtime
|
||||||
|
|
||||||
|
sealed/recovered observation session
|
||||||
|
└── SQLite catalog + bounded background preparation
|
||||||
|
└── atomic RRD cache v6 + recorded-media manifest v2
|
||||||
|
└── generation-bound same-origin HTTP
|
||||||
|
└── aggregate admission
|
||||||
|
└── Rerun native receiver + recorded fMP4 player
|
||||||
```
|
```
|
||||||
|
|
||||||
В момент готовности `RerunBridge` backend публикует адрес вида
|
В момент готовности live/file-replay `RerunBridge` backend публикует адрес вида
|
||||||
`rerun+http://127.0.0.1:9876/proxy`. Frontend автоматически назначает его сцене,
|
`rerun+http://127.0.0.1:9876/proxy`. Frontend автоматически назначает его сцене,
|
||||||
если оператор не указал ручной source. После остановки приёма URL и встроенный
|
если оператор не указал ручной source. После остановки приёма URL и встроенный
|
||||||
viewer остаются активны, а следующая сессия сбрасывает session-local геометрию,
|
viewer остаются активны, а следующая сессия сбрасывает session-local геометрию,
|
||||||
|
|
@ -65,7 +75,7 @@ viewer остаются активны, а следующая сессия сб
|
||||||
Shell собран из локальных NODE.DC UI packages и сохраняет одну структуру для
|
Shell собран из локальных NODE.DC UI packages и сохраняет одну структуру для
|
||||||
всех функциональных модулей:
|
всех функциональных модулей:
|
||||||
|
|
||||||
1. `AppHeader` — марка NODEDC MISSION CORE, выбор архитектурного раздела и состояние
|
1. `AppHeader` — марка NODE DC, выбор архитектурного раздела и состояние
|
||||||
локального backend.
|
локального backend.
|
||||||
2. `AdminNavigationPanel` — контекст аппарата и список рабочих поверхностей
|
2. `AdminNavigationPanel` — контекст аппарата и список рабочих поверхностей
|
||||||
выбранного раздела.
|
выбранного раздела.
|
||||||
|
|
@ -78,8 +88,10 @@ Shell собран из локальных NODE.DC UI packages и сохраня
|
||||||
blueprint-, selection- и time-панели скрыты, чтобы продуктовые действия жили в
|
blueprint-, selection- и time-панели скрыты, чтобы продуктовые действия жили в
|
||||||
Control Station. Размер точек, способ окрашивания и палитра, 12-секундное по
|
Control Station. Размер точек, способ окрашивания и палитра, 12-секундное по
|
||||||
умолчанию накопление, видимость облака и траектории и сетка связаны с backend и
|
умолчанию накопление, видимость облака и траектории и сетка связаны с backend и
|
||||||
Rerun Blueprint. Кнопки собственного timeline, смена 2D/3D/карты и сохранение
|
Rerun Blueprint. Host timeline управляет сохранённым `session_time`, а spatial
|
||||||
layout пока остаются интерфейсным контрактом.
|
layout сохраняет display settings, tool windows и source-window geometry через
|
||||||
|
revisioned API. Смена 2D/3D/карты и семантические слои пока остаются
|
||||||
|
интерфейсным контрактом.
|
||||||
|
|
||||||
## Архитектурные разделы
|
## Архитектурные разделы
|
||||||
|
|
||||||
|
|
@ -139,10 +151,15 @@ cd apps/control-station
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Vite слушает `http://127.0.0.1:5173` и проксирует весь `/api` (включая WebSocket) на
|
Для полного live-viewer контура Vite должен открыться на
|
||||||
`http://127.0.0.1:8000`. Другой локальный backend можно указать переменной
|
`http://127.0.0.1:5173`: только 5173, production preview 4173 и backend 8000
|
||||||
`VITE_API_TARGET`. Preview production-сборки запускается командой
|
входят в текущий Rerun CORS allowlist. Если Vite сообщает fallback на 5174 или
|
||||||
`npm run preview` на `http://127.0.0.1:4173`.
|
выше, освободите 5173 и перезапустите `npm run dev`; REST/WebSocket proxy на
|
||||||
|
fallback-порту может работать, но прямой browser → Rerun 9876 будет отклонён.
|
||||||
|
Vite проксирует весь `/api` (включая WebSocket) на `http://127.0.0.1:8000`.
|
||||||
|
Другой локальный backend можно указать переменной `VITE_API_TARGET`. Preview
|
||||||
|
production-сборки запускается командой `npm run preview` на
|
||||||
|
`http://127.0.0.1:4173`.
|
||||||
|
|
||||||
## Операторский путь для текущего K1 adapter
|
## Операторский путь для текущего K1 adapter
|
||||||
|
|
||||||
|
|
@ -167,6 +184,9 @@ Vite слушает `http://127.0.0.1:5173` и проксирует весь `/a
|
||||||
9. Открыть **Наблюдение → Пространственная сцена**. Реальные облако и траектория,
|
9. Открыть **Наблюдение → Пространственная сцена**. Реальные облако и траектория,
|
||||||
частота, число точек, задержка и пропуски preview появятся после прихода
|
частота, число точек, задержка и пропуски preview появятся после прихода
|
||||||
сообщений K1.
|
сообщений K1.
|
||||||
|
10. После нормального stop или recovery открыть **Сохранённые сессии**. Дождаться
|
||||||
|
состояния **Готово**, выбрать запись и использовать host timeline. Evidence
|
||||||
|
сохраняется автоматически; disk action сохраняет только workspace layout.
|
||||||
|
|
||||||
В проверочном live-сеансе через этот путь прошло 80 реальных MQTT-сообщений:
|
В проверочном live-сеансе через этот путь прошло 80 реальных MQTT-сообщений:
|
||||||
38 кадров `lio_pcl`, 42 кадра `lio_pose`, 2 775 точек в последнем облаке и
|
38 кадров `lio_pcl`, 42 кадра `lio_pose`, 2 775 точек в последнем облаке и
|
||||||
|
|
@ -203,9 +223,12 @@ Plasma, grayscale и custom, накопление `0–120` секунд, а т
|
||||||
декодированном формате. Custom/class сейчас означает выбранный сплошной цвет,
|
декодированном формате. Custom/class сейчас означает выбранный сплошной цвет,
|
||||||
а не готовую семантическую классификацию.
|
а не готовую семантическую классификацию.
|
||||||
|
|
||||||
Custom timeline, переключатель 2D/3D/карты, семантические слои и сохранение RBL
|
Для saved session host timeline вызывает Rerun play/pause/seek только после
|
||||||
пока не вызывают Blueprint/playback API. Черновик компоновки фиксируется только
|
полного admission. Accumulation и display settings применяются через отдельный
|
||||||
в памяти текущей страницы и не записывается на диск.
|
маленький blueprint RRD и не заменяют основную запись. Versioned
|
||||||
|
`observation.spatial` layout сохраняется на host с optimistic revision; он не
|
||||||
|
содержит sensor evidence. Переключатель 2D/3D/карты и семантические слои пока не
|
||||||
|
вызывают готовый presentation backend.
|
||||||
|
|
||||||
## Контракт локального API
|
## Контракт локального API
|
||||||
|
|
||||||
|
|
@ -216,6 +239,12 @@ Custom timeline, переключатель 2D/3D/карты, семантиче
|
||||||
| `GET` | `/api/v1/device-models` | Backend-каталог моделей из валидированных manifests; UI-каталог текущей сборки формируется отдельным static composition root. |
|
| `GET` | `/api/v1/device-models` | Backend-каталог моделей из валидированных manifests; UI-каталог текущей сборки формируется отдельным static composition root. |
|
||||||
| `POST` | `/api/v1/device-plugins/{pluginId}/actions/{actionId}` | Namespaced действие через host allowlist; тело `{ "input": { ... } }`. |
|
| `POST` | `/api/v1/device-plugins/{pluginId}/actions/{actionId}` | Namespaced действие через host allowlist; тело `{ "input": { ... } }`. |
|
||||||
| `WS` | `/api/v1/device-plugins/{pluginId}/events` | Plugin-scoped snapshots с `pluginId` и монотонным `sequence`. |
|
| `WS` | `/api/v1/device-plugins/{pluginId}/events` | Plugin-scoped snapshots с `pluginId` и монотонным `sequence`. |
|
||||||
|
| `GET` | `/api/v1/observation-sessions` | Последние cataloged sessions и authoritative preparation state. |
|
||||||
|
| `POST` | `/api/v1/observation-sessions/{id}/replay` | Verified replay launch либо HTTP 202 preparation handle. |
|
||||||
|
| `GET` | `/api/v1/observation-sessions/{id}/recording-preparation` | Exact-job polling с `If-Match`. |
|
||||||
|
| `GET` | `/api/v1/observation-sessions/{id}/recording.rrd` | Immutable generation-bound RRD; arbitrary chunked `send_rrd` не используется. |
|
||||||
|
| `GET` | `/api/v1/observation-sessions/{id}/media/{artifact}/...` | Manifest/init/segments записанных камер по opaque identifiers. |
|
||||||
|
| `GET`, `PUT` | `/api/v1/workspace-layouts/observation.spatial` | Versioned host layout с optimistic revision. |
|
||||||
|
|
||||||
Frontend XGRIDS-плагина использует только v1alpha namespaced routes. Старые
|
Frontend XGRIDS-плагина использует только v1alpha namespaced routes. Старые
|
||||||
`/api/state`, `/api/events`, `/api/ble/scan`, `/api/connect`, `/api/session/*` и
|
`/api/state`, `/api/events`, `/api/ble/scan`, `/api/connect`, `/api/session/*` и
|
||||||
|
|
@ -257,7 +286,12 @@ facts и старые presentation-поля `phase`, `message`, `devices`,
|
||||||
| `src/workspaces/DeviceWorkspace.tsx` | Generic выбор модели и `device.connection` slot. |
|
| `src/workspaces/DeviceWorkspace.tsx` | Generic выбор модели и `device.connection` slot. |
|
||||||
| `src/device-plugins/xgrids-k1/` | Реальный K1 BLE/Wi-Fi/live/replay UI, client и compatibility mapper. |
|
| `src/device-plugins/xgrids-k1/` | Реальный K1 BLE/Wi-Fi/live/replay UI, client и compatibility mapper. |
|
||||||
| `src/workspaces/Workspaces.tsx` | Оперативный обзор, spatial viewport и остальные продуктовые поверхности. |
|
| `src/workspaces/Workspaces.tsx` | Оперативный обзор, spatial viewport и остальные продуктовые поверхности. |
|
||||||
| `src/components/RerunViewport.tsx` | Жизненный цикл встроенного Rerun Web Viewer и selection events. |
|
| `src/components/RerunViewport.tsx` | Live/recorded lifecycle WebViewer, native RRD open, atomic admission, playback и selection events. |
|
||||||
|
| `src/components/ObservationSessionSelect.tsx` | Три последние сессии, состояния `Готово` / `Обработка` / `Ошибка`. |
|
||||||
|
| `src/components/RecordedFmp4Player.tsx` | Проверенный generation-bound MSE playback архивных камер. |
|
||||||
|
| `src/core/observation/sessionArchive.ts` | Строгий wire contract catalog/preparation/replay/media API. |
|
||||||
|
| `src/core/observation/recordedSessionAdmission.ts` | Общий RRD/camera gate до публикации recorded workspace. |
|
||||||
|
| `src/core/observation/workspaceLayout.ts` | Versioned spatial layout и optimistic revision. |
|
||||||
| `src/sceneSettings.ts` | Типизированный UI-профиль пространственной сцены. |
|
| `src/sceneSettings.ts` | Типизированный UI-профиль пространственной сцены. |
|
||||||
| `src/presentation.ts` | Vendor-neutral подписи lifecycle и форматирование нормализованных метрик. |
|
| `src/presentation.ts` | Vendor-neutral подписи lifecycle и форматирование нормализованных метрик. |
|
||||||
| `src/styles.css`, `src/styles/*` | Компоновка shell и рабочих поверхностей. |
|
| `src/styles.css`, `src/styles/*` | Компоновка shell и рабочих поверхностей. |
|
||||||
|
|
@ -277,14 +311,17 @@ facts и старые presentation-поля `phase`, `message`, `devices`,
|
||||||
случайные GATT writes и автоматические повторы запрещены.
|
случайные GATT writes и автоматические повторы запрещены.
|
||||||
- MQTT live/replay не публикует команды устройству. Запуск и остановка
|
- MQTT live/replay не публикует команды устройству. Запуск и остановка
|
||||||
физического сканирования остаются за кнопкой K1.
|
физического сканирования остаются за кнопкой K1.
|
||||||
- Live-сессии сначала сохраняют сырые сообщения, затем формируют preview. При
|
- Live-сессии сначала сохраняют сырые сообщения и camera segments, затем
|
||||||
перегрузке preview может быть отброшен, raw evidence сохраняется.
|
формируют disposable preview. При перегрузке preview может быть отброшен,
|
||||||
|
native evidence сохраняется. MQTT durability имеет bounded group-commit RPO,
|
||||||
|
camera durability — текущий незавершённый fragment RPO; это не zero-loss claim.
|
||||||
- Timeline `capture_time` сохраняет Unix-время приёма сообщения Mac, а окно
|
- Timeline `capture_time` сохраняет Unix-время приёма сообщения Mac, а окно
|
||||||
накопления viewer использует session-local `stream_time`. `capture_time` — не
|
накопления viewer использует session-local `stream_time`. `capture_time` — не
|
||||||
доказанный timestamp сенсора K1 и не photon-to-screen latency.
|
доказанный timestamp сенсора K1 и не photon-to-screen latency.
|
||||||
- Панорамный камерный поток в наблюдавшихся MQTT report topics отсутствует;
|
- Панорамный камерный поток в MQTT report topics отсутствует; отдельные
|
||||||
текущая Rerun-сцена содержит только облако точек и позу/траекторию, а
|
left/right RTSP preview доступны через generic camera windows. LiDAR и camera
|
||||||
операционные метрики отображаются внешней оболочкой Mission Core.
|
сейчас синхронизированы только по host arrival, не по доказанным sensor clocks.
|
||||||
- `sessions/` игнорируется Git и может содержать адреса, идентификаторы,
|
- `.runtime/`, canonical evidence roots и legacy `sessions/` игнорируются Git и
|
||||||
траекторию и карту помещения. В репозиторий попадают только redacted manifests
|
могут содержать адреса, изображения, идентификаторы, траекторию и карту
|
||||||
и безопасная документация.
|
помещения. В репозиторий попадают только код, тесты, redacted manifests и
|
||||||
|
безопасная документация.
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,12 @@ All 1,140 captured `lio_pcl` frames decoded as raw-LZ4 protobuf blocks, yielding
|
||||||
4,165,862 points. All 1,215 `lio_pose` messages decoded, and the resulting
|
4,165,862 points. All 1,215 `lio_pose` messages decoded, and the resulting
|
||||||
approximately 1.566 m displacement matched the controlled movement. A later
|
approximately 1.566 m displacement matched the controlled movement. A later
|
||||||
owner-operated LixelGO/iPhone capture also observed left/right RTSP/H.264 camera
|
owner-operated LixelGO/iPhone capture also observed left/right RTSP/H.264 camera
|
||||||
preview and the remote start/stop wire mapping. The remaining camera work is a
|
preview and the remote start/stop wire mapping. Mission Core now has a bounded
|
||||||
bounded Mission Core receiver, not transport discovery.
|
read-only receiver, physical live left/right UI acceptance, acquisition-owned
|
||||||
|
fMP4 archival and a generation-bound recorded player. The remaining camera gate
|
||||||
|
is a newly recorded real K1 session played end-to-end with its point/pose
|
||||||
|
timeline. Full-resolution/raw panorama, device-clock synchronization,
|
||||||
|
intrinsics/extrinsics and remote delivery are still unproven.
|
||||||
|
|
||||||
The supplied Bible is useful as an OSINT dossier. It is not an executable plan
|
The supplied Bible is useful as an OSINT dossier. It is not an executable plan
|
||||||
for the actual stand because many experiments assume a phone and LixelGO. The
|
for the actual stand because many experiments assume a phone and LixelGO. The
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
This plan supersedes the app-dependent experiment order in the reference Bible.
|
This plan supersedes the app-dependent experiment order in the reference Bible.
|
||||||
Each gate produces evidence and an explicit GO, PAUSE or BLOCKED result.
|
Each gate produces evidence and an explicit GO, PAUSE or BLOCKED result.
|
||||||
|
|
||||||
## Live checkpoint — 2026-07-15
|
## Current checkpoint — 2026-07-17
|
||||||
|
|
||||||
| Stage | Result |
|
| Stage | Result |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|
|
@ -15,8 +15,11 @@ Each gate produces evidence and an explicit GO, PAUSE or BLOCKED result.
|
||||||
| Stage 4 artifacts/flows | GO — bounded capture, hashes and negative control |
|
| 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 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 pose | GO — 1,215 live frames decoded and motion-correlated |
|
||||||
| Stage 5 camera | GO (discovery) — left/right RTSP/H.264 preview observed; runtime adapter pending |
|
| 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 console, Foxglove cloud/path and Mac latency metrics |
|
| 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 |
|
||||||
|
| 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
|
USB project copying remains optional ground truth rather than a blocker for the
|
||||||
now-verified network path. Owner-operated LixelGO traffic verifies the MQTT
|
now-verified network path. Owner-operated LixelGO traffic verifies the MQTT
|
||||||
|
|
@ -24,10 +27,14 @@ start/stop mapping and RTSP camera transport. MQTT control publishing remains
|
||||||
deliberately deferred because complete request/save/rollback semantics are not
|
deliberately deferred because complete request/save/rollback semantics are not
|
||||||
yet modeled and the physical button remains a known-safe fallback.
|
yet modeled and the physical button remains a known-safe fallback.
|
||||||
|
|
||||||
The Stage 6 alpha uses a bounded raw-first bridge: loss in the visualization
|
The Stage 6 live path uses a bounded raw-first bridge: loss in the visualization
|
||||||
queue cannot discard MQTT evidence. Acceptance is replay of the full captured
|
queue cannot discard MQTT evidence. Stage 7 adds an independent durable
|
||||||
scan followed by a live ideal-LAN run with measured host pipeline latency.
|
observation-session lifecycle. Completed or recovered native captures are
|
||||||
Sensor-to-display latency remains a separate clock-correlation test.
|
prepared once by a bounded backend worker into digest-bound RRD/cache-v6 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.
|
||||||
|
|
||||||
## Stage 0 — repository and host baseline
|
## Stage 0 — repository and host baseline
|
||||||
|
|
||||||
|
|
@ -213,4 +220,6 @@ useful stream is decoded or structurally identified.
|
||||||
- automated scan-button electronics;
|
- automated scan-button electronics;
|
||||||
- OpenWrt/monitor-mode infrastructure;
|
- OpenWrt/monitor-mode infrastructure;
|
||||||
- firmware or internal-Linux analysis;
|
- firmware or internal-Linux analysis;
|
||||||
- camera branch if no external frame stream is evidenced.
|
- physical end-to-end replay acceptance for newly archived left/right cameras;
|
||||||
|
- long-running large-session WebViewer/WASM memory telemetry;
|
||||||
|
- production retention, replication, encryption and cross-platform packaging.
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
# K1 live console and embedded Rerun bridge
|
# K1 live console and embedded Rerun bridge
|
||||||
|
|
||||||
Status: the Rerun alpha milestone is implemented for the verified firmware-3
|
Status: the Rerun live and saved-session milestones are implemented for the
|
||||||
MQTT streams. Live capture and replay use real K1 point-cloud and pose messages;
|
verified firmware-3 point/pose streams. Live and adapter file-replay use the
|
||||||
the application does not generate a placeholder cloud, trajectory, camera frame
|
process-wide gRPC source; saved observation sessions use private digest-bound
|
||||||
or latency value.
|
RRD over same-origin HTTP. Left/right RTSP preview is delivered separately from
|
||||||
|
the spatial Rerun stream. The application does not generate placeholder cloud,
|
||||||
|
trajectory, camera frame or latency values.
|
||||||
|
|
||||||
## Active device-to-scene path
|
## Active device-to-scene path
|
||||||
|
|
||||||
|
|
@ -36,10 +38,12 @@ React console <-- REST + WebSocket state --> FastAPI on 127.0.0.1:8000
|
||||||
```
|
```
|
||||||
|
|
||||||
The Paho MQTT callback does not decode or render the point cloud. It first
|
The Paho MQTT callback does not decode or render the point cloud. It first
|
||||||
writes and flushes the raw frame and metadata, then enqueues a preview message.
|
appends the raw frame, flushes it to the operating-system page cache and stages
|
||||||
If visualization cannot keep up, the oldest queued preview is discarded while
|
its aligned metadata, then enqueues a preview message. Raw bytes and metadata
|
||||||
the raw capture continues. Rerun work stays on the dedicated publisher thread
|
become a durable pair at the bounded group-commit boundary (at most 0.5 s,
|
||||||
and cannot block raw-first evidence capture.
|
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 current live/replay runtime instantiates only `RerunBridge`. The former
|
The current live/replay runtime instantiates only `RerunBridge`. The former
|
||||||
Foxglove implementation is not a parallel runtime and does not listen on TCP
|
Foxglove implementation is not a parallel runtime and does not listen on TCP
|
||||||
|
|
@ -53,7 +57,7 @@ Prerequisites are the repository-local Python environment and Node.js 20.19+ or
|
||||||
```bash
|
```bash
|
||||||
uv sync --group dev
|
uv sync --group dev
|
||||||
cd apps/control-station
|
cd apps/control-station
|
||||||
npm install
|
npm ci
|
||||||
npm run typecheck
|
npm run typecheck
|
||||||
npm run build
|
npm run build
|
||||||
cd ../..
|
cd ../..
|
||||||
|
|
@ -112,18 +116,20 @@ directly and use their sibling metadata receive timestamps when present.
|
||||||
9. Double-click K1 again to stop physical scanning, wait for steady green, then
|
9. Double-click K1 again to stop physical scanning, wait for steady green, then
|
||||||
stop the local session so captures and summaries are finalized.
|
stop the local session so captures and summaries are finalized.
|
||||||
|
|
||||||
Each live run creates an ignored `sessions/<UTC>_viewer_live/` directory with a
|
Each new live run creates a direct child below `MISSIONCORE_EVIDENCE_DIR`, or
|
||||||
redacted manifest, operator notes, raw MQTT frames, per-message metadata and a
|
`.runtime/mission-core/evidence/sessions/` by default, with raw MQTT frames,
|
||||||
hash summary. The connector subscribes to the fixed report-topic allowlist and
|
per-message metadata and a hash summary. Repository-level
|
||||||
does not publish an application request or modeling command.
|
`sessions/*_viewer_live` is legacy import-only evidence and is never selected by
|
||||||
|
the current writer. The connector subscribes to the fixed report-topic
|
||||||
|
allowlist and does not publish an application request or modeling command.
|
||||||
|
|
||||||
## Automatic Rerun source and lifecycle
|
## Automatic Rerun source and lifecycle
|
||||||
|
|
||||||
On the first live or replay session, `RerunBridge`:
|
On the first live or adapter file-replay session, `RerunBridge`:
|
||||||
|
|
||||||
- creates an explicit `RecordingStream("nodedc_mission_core_spatial")`;
|
- creates an explicit `RecordingStream("nodedc_mission_core_spatial")`;
|
||||||
- installs the default spatial blueprint;
|
- installs the default spatial blueprint;
|
||||||
- starts the gRPC/proxy server on TCP 9876 with a 512 MiB late-client buffer;
|
- starts the gRPC/proxy server on TCP 9876 with a 32 MiB late-client buffer;
|
||||||
- reports `rerun+http://127.0.0.1:9876/proxy` only after the server is ready;
|
- reports `rerun+http://127.0.0.1:9876/proxy` only after the server is ready;
|
||||||
- accepts the local development and production browser origins used by this
|
- accepts the local development and production browser origins used by this
|
||||||
repository;
|
repository;
|
||||||
|
|
@ -150,7 +156,7 @@ stream directly from the Rerun gRPC/proxy endpoint.
|
||||||
| `/world` | `ViewCoordinates` | right-handed Z-up display convention |
|
| `/world` | `ViewCoordinates` | right-handed Z-up display convention |
|
||||||
| `/world/points` | `Points3D` | decoded metric XYZ with computed or available RGB colors |
|
| `/world/points` | `Points3D` | decoded metric XYZ with computed or available RGB colors |
|
||||||
| `/world/sensor_pose` | `Transform3D` + `TransformAxes3D` | current decoded translation, xyzw quaternion and pose axes |
|
| `/world/sensor_pose` | `Transform3D` + `TransformAxes3D` | current decoded translation, xyzw quaternion and pose axes |
|
||||||
| `/world/trajectory` | `LineStrips3D` | bounded path of up to 20,000 decoded poses |
|
| `/world/trajectory` | `LineStrips3D` | bounded path of up to 2,000 decoded poses |
|
||||||
|
|
||||||
Point count, frame rate, queue drops and measured pipeline time remain product
|
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
|
metrics in the outer Control Station; they are not logged as Rerun entities and
|
||||||
|
|
@ -189,9 +195,34 @@ shows only the current frame. RGB mode falls back to the verified scalar
|
||||||
coloring path when the active point format has no RGB fields. The current
|
coloring path when the active point format has no RGB fields. The current
|
||||||
`class`/custom behavior is a selected solid color, not semantic segmentation.
|
`class`/custom behavior is a selected solid color, not semantic segmentation.
|
||||||
|
|
||||||
Projection switching, a custom playback timeline, semantic object/mask layers,
|
Projection switching, semantic object/mask layers and camera frustums are not
|
||||||
camera frustums and persisted RBL/layout profiles are not wired yet and must not
|
wired yet. The host-owned `observation.spatial` layout profile does persist the
|
||||||
be inferred from the implemented controls above.
|
implemented scene controls, tool-window state, dynamic source visibility and
|
||||||
|
normalized floating-window geometry. It is separate from Rerun's internal
|
||||||
|
blueprint and from observation evidence.
|
||||||
|
|
||||||
|
## Saved observation sessions
|
||||||
|
|
||||||
|
Every completed native live run is indexed by the local host session store. A
|
||||||
|
valid crash prefix without its final summary is indexed as `interrupted`. The
|
||||||
|
**Сохранённые сессии** control shows the three newest runs and launches an
|
||||||
|
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-v6 RRD using the zero-based `session_time`
|
||||||
|
duration timeline. 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
|
||||||
|
[`09_OBSERVATION_SESSIONS.md`](09_OBSERVATION_SESSIONS.md) for storage,
|
||||||
|
recovery and HTTP Range details.
|
||||||
|
|
||||||
## Time and latency semantics
|
## Time and latency semantics
|
||||||
|
|
||||||
|
|
@ -228,7 +259,7 @@ and host firewall as appropriate, and never forward it directly to the public
|
||||||
Internet or cellular WAN. Remote operation requires an authenticated TLS reverse
|
Internet or cellular WAN. Remote operation requires an authenticated TLS reverse
|
||||||
proxy or another reviewed secure transport before deployment.
|
proxy or another reviewed secure transport before deployment.
|
||||||
|
|
||||||
The 512 MiB Rerun server buffer limits retained late-client data but is not an
|
The 32 MiB Rerun server buffer limits retained late-client data but is not an
|
||||||
access-control mechanism. The listener and embedded viewer intentionally remain
|
access-control mechanism. The listener and embedded viewer intentionally remain
|
||||||
ready between acquisition sessions. Stop `k1link serve` when the network
|
ready between acquisition sessions. Stop `k1link serve` when the network
|
||||||
listener and its process memory must be closed unconditionally.
|
listener and its process memory must be closed unconditionally.
|
||||||
|
|
@ -236,8 +267,10 @@ listener and its process memory must be closed unconditionally.
|
||||||
## Current boundaries
|
## Current boundaries
|
||||||
|
|
||||||
- Raw panoramic camera frames were absent from the observed MQTT report topics.
|
- Raw panoramic camera frames were absent from the observed MQTT report topics.
|
||||||
This milestone contains point cloud and pose/trajectory; operational metrics
|
The Rerun spatial recording contains point cloud and pose/trajectory;
|
||||||
are rendered by the outer Control Station.
|
left/right compressed RTSP preview and acquisition-owned fMP4 archive use a
|
||||||
|
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
|
- 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.
|
||||||
- No terrain map, elevation model, obstacle segmentation, localization fusion,
|
- No terrain map, elevation model, obstacle segmentation, localization fusion,
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@ already exist. The accepted device-lifecycle decision is recorded in
|
||||||
| `src/k1link/protocol/` | Firmware-scoped K1 codecs and explicit vendor normalizer | Plugin-owned protocol adapter |
|
| `src/k1link/protocol/` | Firmware-scoped K1 codecs and explicit vendor normalizer | Plugin-owned protocol adapter |
|
||||||
| `src/k1link/data_plane/` | Transport-neutral decoded in-process consumer views | Local projections hydrated from portable SDK envelopes |
|
| `src/k1link/data_plane/` | Transport-neutral decoded in-process consumer views | Local projections hydrated from portable SDK envelopes |
|
||||||
| `src/k1link/viewer/` | Rerun consumer, visualization runtime and legacy Foxglove regression module | Replaceable canonical scene sink and presentation adapters |
|
| `src/k1link/viewer/` | Rerun consumer, visualization runtime and legacy Foxglove regression module | Replaceable canonical scene sink and presentation adapters |
|
||||||
|
| `src/k1link/sessions/` | Host-owned SQLite catalog, evidence discovery/recovery, background preparation and derived-cache lifecycle | Device-neutral observation archive service |
|
||||||
|
| `src/k1link/web/session_api.py` | Opaque saved-session, immutable RRD/media and workspace-layout API | Versioned Control/Edge observation contract |
|
||||||
|
| `apps/control-station/src/core/observation/` | Session selection, replay admission, recorded-source and layout state | Device-neutral observation UI runtime |
|
||||||
| `docs/domain-model/` | Versioned experimental Mission Core vocabulary | Promotion source for meanings proven across devices/services |
|
| `docs/domain-model/` | Versioned experimental Mission Core vocabulary | Promotion source for meanings proven across devices/services |
|
||||||
|
|
||||||
## Implemented boundary
|
## Implemented boundary
|
||||||
|
|
@ -33,6 +36,17 @@ raw K1 transport
|
||||||
-> embedded local viewer
|
-> embedded local viewer
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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 v6 + immutable recorded-media manifest v2
|
||||||
|
-> generation-bound same-origin HTTP
|
||||||
|
-> aggregate RRD/camera admission
|
||||||
|
-> native Rerun HTTP receiver + recorded fMP4 player
|
||||||
|
```
|
||||||
|
|
||||||
Rerun does not import K1 protocol code, inspect MQTT topics or receive raw
|
Rerun does not import K1 protocol code, inspect MQTT topics or receive raw
|
||||||
payloads. `VisualizationRuntime` cannot choose a vendor decoder implicitly; its
|
payloads. `VisualizationRuntime` cannot choose a vendor decoder implicitly; its
|
||||||
composition owner injects one. The local `Decoded*View` classes are in-process
|
composition owner injects one. The local `Decoded*View` classes are in-process
|
||||||
|
|
@ -52,6 +66,8 @@ The local vocabulary distinguishes:
|
||||||
```text
|
```text
|
||||||
model != device != transport alias != device session
|
model != device != transport alias != device session
|
||||||
acquisition != operation != source/channel != evidence
|
acquisition != operation != source/channel != evidence
|
||||||
|
observation session != preparation job != replay launch != viewer instance
|
||||||
|
workspace layout != sensor evidence
|
||||||
acknowledgement != completion
|
acknowledgement != completion
|
||||||
raw evidence != decoded data != viewer state
|
raw evidence != decoded data != viewer state
|
||||||
```
|
```
|
||||||
|
|
@ -103,10 +119,14 @@ by physical double-click; no modeling request is published.
|
||||||
Left/right camera preview transport, endpoint paths and H.264 framing are now
|
Left/right camera preview transport, endpoint paths and H.264 framing are now
|
||||||
observed under the exact compatibility profile. The local read-only adapter
|
observed under the exact compatibility profile. The local read-only adapter
|
||||||
copy-remuxes one selected RTSP producer into bounded fMP4/WebSocket delivery for
|
copy-remuxes one selected RTSP producer into bounded fMP4/WebSocket delivery for
|
||||||
the generic MSE UI. Portable FFmpeg packaging, fan-out and remote delivery remain
|
the generic MSE UI. New acquisitions archive selected-camera init/segments/index
|
||||||
open. Device status and heartbeat remain raw observed channels without semantic
|
independently of browser delivery. The generic recorded player, manifest-v2
|
||||||
decoders. Device calibration command and sensor-to-vehicle extrinsics are
|
validation and shared `session_time` controls are connected; historical sessions
|
||||||
unavailable.
|
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
|
||||||
|
calibration command and sensor-to-vehicle extrinsics are unavailable.
|
||||||
|
|
||||||
The 2026-07-17 physical acceptance gate confirmed continuously updating point
|
The 2026-07-17 physical acceptance gate confirmed continuously updating point
|
||||||
clouds, a matching live trajectory, and both camera selections in the same
|
clouds, a matching live trajectory, and both camera selections in the same
|
||||||
|
|
@ -128,14 +148,17 @@ for the measured gate and remaining limits.
|
||||||
5. Replace compatibility routes and singleton state with multi-device session
|
5. Replace compatibility routes and singleton state with multi-device session
|
||||||
routing.
|
routing.
|
||||||
6. Split Edge execution from the Control Station behind authenticated transport.
|
6. Split Edge execution from the Control Station behind authenticated transport.
|
||||||
7. Package the read-only RTSP/H.264 camera adapter for each target OS and evolve
|
7. Physically accept a newly archived left/right K1 session, then package the
|
||||||
same-host MSE delivery toward an authenticated Edge media plane; keep the
|
read-only RTSP/H.264 adapter for each target OS, add disk-backed sealed media
|
||||||
modeling-command publisher disabled until its separate safety gate closes.
|
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.
|
||||||
|
|
||||||
## Invariants
|
## Invariants
|
||||||
|
|
||||||
- Raw evidence is persisted before preview work and is never overwritten by a
|
- Raw bytes are appended and OS-flushed before preview work; aligned raw and
|
||||||
decode result.
|
metadata become durable together at the bounded group-commit boundary and
|
||||||
|
are never overwritten by a decode result.
|
||||||
- Mission Core starts when the XGRIDS manifest is absent. Formal installed
|
- Mission Core starts when the XGRIDS manifest is absent. Formal installed
|
||||||
plugin quarantine remains a future supervisor feature.
|
plugin quarantine remains a future supervisor feature.
|
||||||
- Core code does not branch on XGRIDS IDs, fields, topics or firmware.
|
- Core code does not branch on XGRIDS IDs, fields, topics or firmware.
|
||||||
|
|
@ -160,8 +183,13 @@ for the measured gate and remaining limits.
|
||||||
- complete SDK-envelope/EvidenceStore hot-path integration;
|
- complete SDK-envelope/EvidenceStore hot-path integration;
|
||||||
- remote Edge split, authenticated WAN relay and fleet orchestration;
|
- remote Edge split, authenticated WAN relay and fleet orchestration;
|
||||||
- automatic K1 start/stop/calibration commands;
|
- automatic K1 start/stop/calibration commands;
|
||||||
- automatic camera capability discovery, synchronized recording/rewind,
|
- device-reported camera capability discovery beyond the reviewed K1 profile;
|
||||||
calibration, panoramic stitching and multi-consumer delivery.
|
- physical acceptance of shared-timeline recorded-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
|
||||||
|
memory acceptance for large observation sessions.
|
||||||
|
|
||||||
## Workspace-repeatable locked architecture gate
|
## Workspace-repeatable locked architecture gate
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,421 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
## Operator path
|
||||||
|
|
||||||
|
1. Start a normal acquisition from **Парк → Локальное устройство**.
|
||||||
|
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
|
||||||
|
unexpected interruption on its next start. Session recording is automatic;
|
||||||
|
the disk action is not required.
|
||||||
|
4. Open **Сохранённые сессии** in the observation header. The menu shows the
|
||||||
|
three most recent indexed runs, their date, duration, state, modalities and
|
||||||
|
background preparation state.
|
||||||
|
5. Choose a replayable run. Opening a replay never performs conversion in the
|
||||||
|
request. A ready recording opens immediately; otherwise the client receives
|
||||||
|
HTTP 202, keeps the current scene mounted and polls the preparation status.
|
||||||
|
Only after the server returns a verified launch descriptor does Mission Core
|
||||||
|
pause and unload the previous viewer.
|
||||||
|
6. The client then receives and decodes the complete RRD. The new viewport and
|
||||||
|
timeline stay hidden until the declared recording range is fully buffered
|
||||||
|
(`fullyBuffered`). Playback starts once at that point. Partial frames are
|
||||||
|
never shown. If the run contains canonical camera archives, its recorded
|
||||||
|
camera sources replace all live device overlays.
|
||||||
|
7. Use **Воспроизвести / Пауза**, the scrubber, **К началу** and **К концу** on
|
||||||
|
the right half of the bottom timeline. The left half changes point-cloud
|
||||||
|
accumulation. The recorded timeline is zero-based `session_time`.
|
||||||
|
8. Use the first disk button to save the current workspace layout. The next
|
||||||
|
opening restores display settings, tool windows and dynamic source-window
|
||||||
|
positions. This action saves no sensor evidence.
|
||||||
|
|
||||||
|
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
|
||||||
|
layout save flushes and awaits this queue before serializing the confirmed
|
||||||
|
settings. Recorded display updates reuse stable Rerun scene identifiers and do
|
||||||
|
not mutate playback state, so changing accumulation, size, color or visibility
|
||||||
|
does not pause the active replay or replace its camera view.
|
||||||
|
|
||||||
|
Completed and recovered sessions are discovered on startup and by the catalog
|
||||||
|
reconciler, then deduplicated into one bounded, single-worker preparation queue.
|
||||||
|
The worker validates the native source, exports point/pose data, verifies source
|
||||||
|
stability and both SHA-256 digests, finalizes once, and atomically publishes the
|
||||||
|
digest-bound derived RRD plus its cache sidecar. Before the same job becomes
|
||||||
|
`ready`, it also parses and validates every archived camera index and retains
|
||||||
|
the immutable manifest generation in memory. The catalog exposes preparation
|
||||||
|
state and progress while this runs; a recording URL and camera descriptors are
|
||||||
|
returned only after the complete launch generation has passed validation.
|
||||||
|
|
||||||
|
Preparation is a backend lifecycle, not a viewer action: sealing or recovering
|
||||||
|
a native session makes it eligible for the reconciler, which builds the derived
|
||||||
|
RRD once in the background. Reopening, seeking or changing the workspace reads
|
||||||
|
that prepared artifact and never reruns conversion. A cold process may validate
|
||||||
|
an existing cache and rebuild its in-memory camera manifest in the worker, but
|
||||||
|
no replay, status, RRD or manifest request hashes native evidence or parses a
|
||||||
|
camera index synchronously.
|
||||||
|
|
||||||
|
The session menu uses three operator indicators:
|
||||||
|
|
||||||
|
| Indicator | Catalog/preparation state | Meaning |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Solid green + `Готово` | `ready` | Verified RRD is available. |
|
||||||
|
| Pulsing green + `Обработка` | `queued`, `validating`, `exporting`, `finalizing` | Background preparation is active. |
|
||||||
|
| Dim gray + `Ошибка` | failed, cancelled, non-replayable or invalid session | No launchable recording; inspect the message or retry. |
|
||||||
|
|
||||||
|
Saved-session status never uses yellow or red. Switching sessions aborts only
|
||||||
|
the obsolete browser poll; it does not cancel the process-owned conversion.
|
||||||
|
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 v6 is intentionally incompatible with v4
|
||||||
|
and v5 because only v6 guarantees that the payload itself contains a real
|
||||||
|
`session_time = 0` anchor; 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
|
||||||
|
generation fails with `412`; a matching URL is served with exact length, strong
|
||||||
|
ETag and private immutable/no-transform caching. Every cache pin is released when the
|
||||||
|
HTTP response completes, disconnects or fails, so an interrupted switch cannot
|
||||||
|
make a derived recording permanently non-evictable. A bounded 120-second launch
|
||||||
|
reservation also protects the artifact between the verified launch response and
|
||||||
|
the WebViewer's subsequent RRD request, including a cold WASM startup; it
|
||||||
|
expires automatically if that request never arrives.
|
||||||
|
|
||||||
|
## Storage roots
|
||||||
|
|
||||||
|
By default, both catalog state and new source evidence are private to the
|
||||||
|
checkout:
|
||||||
|
|
||||||
|
```text
|
||||||
|
.runtime/mission-core/
|
||||||
|
├── mission-core.sqlite3
|
||||||
|
├── mission-core.sqlite3-shm
|
||||||
|
├── mission-core.sqlite3-wal
|
||||||
|
├── evidence/
|
||||||
|
│ └── sessions/
|
||||||
|
│ ├── .current_session # present only while a writer owns the root
|
||||||
|
│ └── <UTC>_viewer_live/
|
||||||
|
│ ├── captures/ # native MQTT source evidence
|
||||||
|
│ └── media/ # canonical camera archives
|
||||||
|
└── recordings/
|
||||||
|
├── .export.lock # cross-process conversion/eviction lock
|
||||||
|
└── <opaque-session-id>/
|
||||||
|
├── scene.rrd
|
||||||
|
└── scene.rrd.cache.json
|
||||||
|
```
|
||||||
|
|
||||||
|
`MISSIONCORE_DATA_DIR` relocates the catalog and derived cache. Unless it is
|
||||||
|
overridden separately, new evidence is written to
|
||||||
|
`$MISSIONCORE_DATA_DIR/evidence/sessions`. `MISSIONCORE_EVIDENCE_DIR` can select
|
||||||
|
another absolute MQTT evidence root. In the current K1 integration the camera
|
||||||
|
gateway additionally confines its recording directory to the repository
|
||||||
|
checkout, so a full point-plus-camera acquisition must keep the evidence root
|
||||||
|
inside that checkout. An external evidence volume currently supports MQTT
|
||||||
|
capture/cataloging but camera archival will fail closed until the gateway gets
|
||||||
|
a separately attested storage root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export MISSIONCORE_DATA_DIR=/absolute/private/path/mission-core
|
||||||
|
# Full K1 point-plus-camera evidence must currently remain below the checkout.
|
||||||
|
export MISSIONCORE_EVIDENCE_DIR=/absolute/path/to/NODEDC_MISSION_CORE/.runtime/mission-core/evidence/sessions
|
||||||
|
export MISSIONCORE_RRD_CACHE_MAX_BYTES=8589934592
|
||||||
|
export MISSIONCORE_RRD_FREE_SPACE_RESERVE_BYTES=2147483648
|
||||||
|
uv run k1link serve
|
||||||
|
```
|
||||||
|
|
||||||
|
The directories and derived recording cache use owner-only permissions where
|
||||||
|
the host filesystem permits them. A new acquisition writer is assigned only a
|
||||||
|
direct child of the private evidence root; it no longer writes a new run to the
|
||||||
|
repository-level `sessions/` directory.
|
||||||
|
|
||||||
|
Repository-level `sessions/*_viewer_live` runs are a legacy, import-only source
|
||||||
|
for the catalog. They are discovered and confined in place: refresh does not
|
||||||
|
copy, move or migrate their large payloads, and no current writer is assigned
|
||||||
|
that root. Startup may recovery-seal an already existing, incomplete canonical
|
||||||
|
camera epoch there by preserving its valid segment prefix and writing an
|
||||||
|
`interrupted` summary; this is evidence recovery, not a new acquisition write.
|
||||||
|
|
||||||
|
Never add `.runtime/`, `sessions/`, raw captures, RRD files or camera media to
|
||||||
|
Git. They can contain mapped interiors, trajectories and identifiable images.
|
||||||
|
|
||||||
|
## Session source of record
|
||||||
|
|
||||||
|
For the current K1 profile:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MQTT callback
|
||||||
|
├─ durable native .k1mqtt + aligned metadata (source of record)
|
||||||
|
└─ bounded latest-wins Rerun live preview (disposable)
|
||||||
|
|
||||||
|
selected RTSP producer
|
||||||
|
├─ durable init + fMP4 segments + JSONL index (camera evidence)
|
||||||
|
└─ bounded WebSocket/MSE preview (disposable)
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
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.
|
||||||
|
|
||||||
|
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
|
||||||
|
leftovers from candidates, exporter temporary files and staged replay prefixes
|
||||||
|
are scavenged under that lock before quota accounting. Ready cache hits and
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Camera archive contract
|
||||||
|
|
||||||
|
New acquisitions archive each selected source and codec epoch below the same
|
||||||
|
private evidence session:
|
||||||
|
|
||||||
|
```text
|
||||||
|
<evidence-root>/sessions/<session-id>/media/<stable-source-id>/epoch-1/
|
||||||
|
├── init.mp4
|
||||||
|
├── segments/
|
||||||
|
│ ├── 1.m4s
|
||||||
|
│ └── ...
|
||||||
|
├── index.jsonl
|
||||||
|
└── summary.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Each index row binds a segment sequence, byte length, SHA-256 and host arrival
|
||||||
|
epoch/monotonic timestamps. Camera durability uses a
|
||||||
|
`per-segment-fsync` policy: `init.mp4` and every complete media segment are
|
||||||
|
individually fsynced and their directory entries synchronized before the
|
||||||
|
matching JSONL index row is committed. The index and an atomic `interrupted`
|
||||||
|
checkpoint summary are fsynced before the append returns. The old interval and
|
||||||
|
byte constructor options are compatibility-only and cannot weaken this
|
||||||
|
segment-bound RPO. A power failure may still lose or leave uncommitted the
|
||||||
|
fragment currently being produced; it cannot make a committed index row point
|
||||||
|
past durable media. Camera windows may close or reconnect without terminating
|
||||||
|
archival while the owning acquisition remains active.
|
||||||
|
|
||||||
|
Synchronization is `host-arrival-best-effort`: LiDAR/MQTT and camera segments
|
||||||
|
share the Mac host clock boundary, but K1 sensor exposure time and LiDAR firing
|
||||||
|
time are not proven to use a shared device clock. Do not infer frame-accurate
|
||||||
|
calibration from the playback timeline.
|
||||||
|
|
||||||
|
Finalized media is prepared once by the same process-owned background job that
|
||||||
|
materializes the RRD. The gateway records host time only after a complete
|
||||||
|
`moof+mdat` fragment has arrived, so that timestamp is an availability/end
|
||||||
|
anchor, never a fragment-start timestamp. Preparation reads and SHA-verifies
|
||||||
|
every fragment, parses bounded ISO-BMFF timing tables (`mdhd`, `trex`, `tfhd`,
|
||||||
|
`trun`), anchors the epoch at
|
||||||
|
`max(0, first_arrival - first_fragment_duration)`, and sets its end to that start
|
||||||
|
plus the checked sum of every decoded fragment duration. The declared interval
|
||||||
|
therefore has exactly the duration MSE is expected to expose and is not stretched
|
||||||
|
by host scheduling jitter.
|
||||||
|
|
||||||
|
Epoch arrivals must be strictly monotonic; epoch intervals must be finite,
|
||||||
|
monotonic and non-overlapping. Replay v2 also requires every media interval to
|
||||||
|
fit inside the spatial RRD interval with a 50 ms numeric tolerance; it fails
|
||||||
|
preparation instead of clamping unreachable evidence. A future replay v3 must
|
||||||
|
separate `spatial_range` from a session-wide union range before out-of-RRD camera
|
||||||
|
coverage can be navigated. A missing, ambiguous, oversized or otherwise
|
||||||
|
unparseable timing table fails preparation; the archive remains evidence but is
|
||||||
|
never advertised as seekable media.
|
||||||
|
|
||||||
|
The prepared path-free v2 descriptor and full source stat identity (native raw
|
||||||
|
and timing metadata plus every camera summary, index, init and segment file) are
|
||||||
|
written under the private derived cache with a schema, generation and checksum.
|
||||||
|
Publication uses a private temporary file, file and directory `fsync`, and atomic
|
||||||
|
rename; startup scavenges crash-left temporary files. A restart reuses this
|
||||||
|
sidecar after confined O(n) stat validation, without rereading, hashing or parsing
|
||||||
|
media. A missing, corrupt or stale sidecar is rebuilt only by the background
|
||||||
|
worker. No replay, status, manifest or payload request performs conversion or
|
||||||
|
recalculates these intervals.
|
||||||
|
|
||||||
|
Every derived RRD contains a real `session_time = 0` row at the internal
|
||||||
|
`/__mission_core/session_origin` entity. The anchor is deliberately outside
|
||||||
|
`/world`, so it establishes the actual recording time range without creating a
|
||||||
|
3D layer or drawable scene object. Preparation verifies the derived recording
|
||||||
|
against the declared spatial range rather than relying on summary metadata
|
||||||
|
alone.
|
||||||
|
|
||||||
|
Sessions made before this archive contract have no recoverable video even if a
|
||||||
|
camera preview was visible at the time. In particular, the 2026-07-16 browser
|
||||||
|
camera acceptance run retained point/pose evidence only.
|
||||||
|
|
||||||
|
## Crash and interruption behavior
|
||||||
|
|
||||||
|
- SQLite uses WAL, foreign keys and full synchronous durability.
|
||||||
|
- Before creating a session directory, the acquisition takes an exclusive,
|
||||||
|
cross-process lease by atomically creating and locking
|
||||||
|
`<evidence-root>/.current_session`. The marker contains only the direct child
|
||||||
|
session name. While the lock is held, a second writer fails closed and
|
||||||
|
discovery excludes that in-progress session from replay.
|
||||||
|
- A process exit releases the operating-system lock. On the next service start,
|
||||||
|
stale-marker recovery removes the marker only after it can take the lock and
|
||||||
|
revalidate the marker inode without following symlinks. It never deletes the
|
||||||
|
interrupted session directory; normal catalog recovery then evaluates the
|
||||||
|
durable prefix. A locked/live or suspicious marker is left untouched.
|
||||||
|
- Native MQTT writes use a bounded group commit: at most 0.5 seconds, 4 MiB or
|
||||||
|
32 messages per group. Raw bytes are fsynced before their metadata rows are
|
||||||
|
written and fsynced. This is a bounded RPO, not a zero-loss guarantee: a hard
|
||||||
|
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.
|
||||||
|
- 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`.
|
||||||
|
- A non-newline metadata crash tail can be ignored. Newline-terminated or
|
||||||
|
mid-file corruption fails closed.
|
||||||
|
- Camera recovery retains a contiguous valid segment prefix, quarantines
|
||||||
|
non-contiguous/orphan fragments instead of deleting them, rebuilds the index
|
||||||
|
when necessary and writes an `interrupted` summary. Unindexed bytes are not
|
||||||
|
presented as valid media.
|
||||||
|
- If a valid normal summary appears later, repeat discovery updates the same
|
||||||
|
session to `ready`.
|
||||||
|
- Materialization detects a native capture changing during export and refuses
|
||||||
|
to publish the derived RRD. It reopens the prepared raw source with no symlink
|
||||||
|
following inside the cataloged session roots, and interrupted recordings are
|
||||||
|
materialized from the last validated raw/metadata boundary only.
|
||||||
|
- Internal preparation cancellation is cooperative. A queued job cancels
|
||||||
|
immediately; an active job stops at a safe checkpoint, removes its candidate
|
||||||
|
and never publishes a partial RRD. The operator UI does not cancel automatic
|
||||||
|
preparation when switching sessions or closing a tab: preparation belongs to
|
||||||
|
the application worker, not to an HTTP request.
|
||||||
|
- `failed` and `cancelled` are terminal, retryable states. The API exposes a
|
||||||
|
sanitized error, and an explicit retry creates a fresh job for the current
|
||||||
|
source identity. A stalled poll or failed switch leaves the current scene
|
||||||
|
mounted; the latest operator selection wins over obsolete responses.
|
||||||
|
- A lifecycle shutdown marks only its own interrupted work for automatic
|
||||||
|
reconciliation after restart. An operator cancellation and a genuine failed
|
||||||
|
export remain terminal and are never retried forever by the reconciler.
|
||||||
|
|
||||||
|
These rules provide crash recovery, not replication. A single host disk failure
|
||||||
|
can still destroy local data. Vehicle deployment must add independent onboard
|
||||||
|
and control-station copies, capacity monitoring and a documented retention
|
||||||
|
policy.
|
||||||
|
|
||||||
|
## HTTP API
|
||||||
|
|
||||||
|
All paths are same-origin and expose opaque identifiers only:
|
||||||
|
|
||||||
|
```text
|
||||||
|
GET /api/v1/observation-sessions?limit=3
|
||||||
|
GET /api/v1/observation-sessions/{id}
|
||||||
|
POST /api/v1/observation-sessions/{id}/replay
|
||||||
|
GET /api/v1/observation-sessions/{id}/recording-preparation
|
||||||
|
DELETE /api/v1/observation-sessions/{id}/recording-preparation
|
||||||
|
GET /api/v1/observation-sessions/{id}/recording.rrd
|
||||||
|
POST /api/v1/observation-sessions/{id}/blueprint.rrd
|
||||||
|
GET /api/v1/observation-sessions/{id}/media/{artifact}/manifest
|
||||||
|
GET /api/v1/observation-sessions/{id}/media/{artifact}/epochs/{n}/init.mp4
|
||||||
|
GET /api/v1/observation-sessions/{id}/media/{artifact}/epochs/{n}/segments/{m}.m4s
|
||||||
|
|
||||||
|
GET /api/v1/workspace-layouts/observation.spatial
|
||||||
|
PUT /api/v1/workspace-layouts/observation.spatial
|
||||||
|
```
|
||||||
|
|
||||||
|
The catalog embeds each replayable session's preparation state and progress.
|
||||||
|
`POST .../replay` returns either a verified replay v2 launch document or HTTP
|
||||||
|
202 with the preparation v1 document, `Location`, `Retry-After`, an exact quoted
|
||||||
|
preparation `ETag` and a same-origin status URL. Polling
|
||||||
|
`GET .../recording-preparation` sends that value as `If-Match` and returns HTTP
|
||||||
|
202 while the job is active, HTTP 409 for retryable `failed`/`cancelled` states,
|
||||||
|
and the launch document only when the same job and artifact are ready; every
|
||||||
|
response echoes the same `ETag`. `DELETE` also requires `If-Match`, so a stale
|
||||||
|
tab cannot cancel a replacement job. Conversion is never executed synchronously
|
||||||
|
by a replay-open request. Playback speed and loop are request-local launch
|
||||||
|
policy; they are not stored on or shared through a preparation job.
|
||||||
|
|
||||||
|
Production `recording.rrd` access is bound to the launch generation. The client
|
||||||
|
can use the strict query-free URL with exact strong
|
||||||
|
`If-Match: "sha256:<launch.sha256>"`; a request with neither an `If-Match` nor a
|
||||||
|
generation returns 428. The embedded Rerun receiver instead gets only the
|
||||||
|
canonical `viewer_source_url = source_url + "?generation=<launch.sha256>"`.
|
||||||
|
The server returns 412 for a malformed or replaced generation before opening
|
||||||
|
the body. A successful response echoes the strong ETag, exact `Content-Length`,
|
||||||
|
`application/vnd.rerun.rrd` and immutable private/no-transform cache policy.
|
||||||
|
Rerun's native HTTP receiver owns incremental decoding; `LogChannel.send_rrd`
|
||||||
|
is reserved for independently complete RRD payloads such as the small generated
|
||||||
|
blueprint and must never receive arbitrary HTTP byte slices. The canvas,
|
||||||
|
timeline, controller and autoplay remain closed until the decoded spatial range
|
||||||
|
exactly matches the launch descriptor and every declared camera is ready. API
|
||||||
|
documents never contain local paths. Layout updates require the quoted current
|
||||||
|
revision in `If-Match`; stale writers receive HTTP 412 rather than overwriting
|
||||||
|
another saved profile.
|
||||||
|
|
||||||
|
Recorded media routes expose only opaque catalog identifiers and ordinal codec
|
||||||
|
epochs. The required `missioncore.observation-recorded-media/v2` manifest carries
|
||||||
|
a strong `generation_sha256`, exact JS-safe aggregate `byte_length`, and finite
|
||||||
|
`timeline_start_seconds` / `timeline_end_seconds` for every epoch. Epoch ends
|
||||||
|
participate in the generation digest. Every epoch also declares its init byte
|
||||||
|
length/digest and a complete contiguous segment list with sequence, URL, byte
|
||||||
|
length and digest. The launch source repeats the same aggregate `byte_length`
|
||||||
|
and uses exactly `max(epoch.timeline_end_seconds)` as its end; the spatial RRD
|
||||||
|
end must never pad camera coverage. The browser cross-checks launch, manifest and
|
||||||
|
component lengths. The current browser laboratory policy accepts at most 16
|
||||||
|
camera sources, 128 MiB per source and 512 MiB across the session. This launch
|
||||||
|
preflight finishes before the first camera manifest/init/segment GET; admitted
|
||||||
|
cameras are then downloaded and decode-probed one at a time. Verified raw
|
||||||
|
buffers remain immutable so route changes and player remounts cannot append
|
||||||
|
emptied data. This bounded in-memory strategy is deliberate for the laboratory
|
||||||
|
milestone; an OPFS-backed sealed-generation cache is the next scaling step for
|
||||||
|
larger rigs and must preserve the same launch/manifest/hash admission contract.
|
||||||
|
|
||||||
|
The manifest response ETag is the exact generation, and the manifest GET itself
|
||||||
|
requires that generation in `If-Match`. Init and media GETs likewise require the
|
||||||
|
descriptor's exact SHA `If-Match`, then open through a confined directory file
|
||||||
|
descriptor with no symlink following, verify the declared SHA-256 and serve
|
||||||
|
exact-length bytes with immutable private `no-transform` caching and byte-range
|
||||||
|
support. Physical source ids, RTSP addresses and storage paths never cross the
|
||||||
|
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
|
||||||
|
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.
|
||||||
|
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
|
||||||
|
rewriting the recording. The client deliberately has no progressive recorded
|
||||||
|
mode: the viewport, timeline and autoplay gate remain closed until the complete
|
||||||
|
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
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
@ -2,6 +2,11 @@
|
||||||
|
|
||||||
Status: accepted for the experimental Mission Core runtime, 2026-07-16.
|
Status: accepted for the experimental Mission Core runtime, 2026-07-16.
|
||||||
|
|
||||||
|
Scope update, 2026-07-17: the live device descriptors in this ADR remain
|
||||||
|
live-only. ADR 0008 adds a separate host-owned recorded-source catalog,
|
||||||
|
seekable `session_time` playback and workspace layout; it does not retroactively
|
||||||
|
turn a live RTSP lease into recorded evidence.
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
||||||
The spatial scene and camera workspace must accept one device, a custom rig or a
|
The spatial scene and camera workspace must accept one device, a custom rig or a
|
||||||
|
|
@ -68,10 +73,12 @@ RTSP/FFmpeg generation before a new browser lease is issued.
|
||||||
|
|
||||||
The local laboratory adapter now terminates RTSP/RTP with FFmpeg, copy-remuxes
|
The local laboratory adapter now terminates RTSP/RTP with FFmpeg, copy-remuxes
|
||||||
H.264 High L4 without transcoding and publishes complete bounded fMP4 segments
|
H.264 High L4 without transcoding and publishes complete bounded fMP4 segments
|
||||||
to a generic MSE player. Portable FFmpeg packaging, multi-consumer fan-out and
|
to a generic MSE player. ADR 0008 subsequently added acquisition-owned durable
|
||||||
remote/WAN delivery are not claimed by this milestone. Synchronized rewind
|
segments, a prepared recorded-media manifest and host-arrival-best-effort rewind
|
||||||
becomes eligible only after persisted buffering and an evidenced mapping between
|
on the saved-session timeline. This is not proof of a shared device clock or
|
||||||
the RTP 90 kHz clock and the spatial stream clock. See ADR 0007.
|
frame-accurate LiDAR/camera alignment. Portable FFmpeg packaging,
|
||||||
|
multi-consumer fan-out and remote/WAN delivery remain open. See ADR 0007 and
|
||||||
|
ADR 0008.
|
||||||
|
|
||||||
## Code anchors
|
## Code anchors
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,11 @@
|
||||||
|
|
||||||
Status: accepted for the local laboratory runtime, 2026-07-16.
|
Status: accepted for the local laboratory runtime, 2026-07-16.
|
||||||
|
|
||||||
|
Supersession note, 2026-07-17: the live copy-remux and lease decisions remain
|
||||||
|
active. ADR 0008 extends the same acquisition-owned producer with durable fMP4
|
||||||
|
epochs and a generic recorded player. That extension does not weaken the
|
||||||
|
single-live-producer or opaque transport boundary defined here.
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
||||||
The exact K1 firmware 3.0.2 profile exposes two RTSP/TCP H.264 preview paths.
|
The exact K1 firmware 3.0.2 profile exposes two RTSP/TCP H.264 preview paths.
|
||||||
|
|
@ -73,16 +78,22 @@ timeline once when the recording opens and does not poll or force the cursor.
|
||||||
- Same-origin local WebSocket delivery is not the future WAN transport. A later
|
- Same-origin local WebSocket delivery is not the future WAN transport. A later
|
||||||
Edge/Control Station split should use an authenticated WebRTC/WHEP or equivalent
|
Edge/Control Station split should use an authenticated WebRTC/WHEP or equivalent
|
||||||
congestion-aware media plane behind the same descriptor contract.
|
congestion-aware media plane behind the same descriptor contract.
|
||||||
- Camera recording, synchronized rewind, calibration, frustums, panoramic
|
- Durable camera recording and host-arrival-best-effort saved-session rewind are
|
||||||
stitching and full-resolution raw imagery remain unavailable.
|
implemented under ADR 0008, but no retained real K1 session made after that
|
||||||
|
archive contract has completed physical recorded-camera acceptance yet.
|
||||||
|
- Device-clock synchronization, calibration, frustums, panoramic stitching,
|
||||||
|
full-resolution raw imagery and remote multi-consumer delivery remain
|
||||||
|
unavailable.
|
||||||
- A longer physical soak test is still required to characterize sustained
|
- A longer physical soak test is still required to characterize sustained
|
||||||
preview backpressure and rendering cost on target Edge hardware.
|
preview backpressure and rendering cost on target Edge hardware.
|
||||||
|
|
||||||
## Code anchors
|
## Code anchors
|
||||||
|
|
||||||
- `src/k1link/web/xgrids_k1_camera.py`
|
- `src/k1link/web/xgrids_k1_camera.py`
|
||||||
|
- `src/k1link/web/camera_archive.py`
|
||||||
- `src/k1link/web/xgrids_k1_facade.py`
|
- `src/k1link/web/xgrids_k1_facade.py`
|
||||||
- `apps/control-station/src/components/MseFmp4WebSocketPlayer.tsx`
|
- `apps/control-station/src/components/MseFmp4WebSocketPlayer.tsx`
|
||||||
|
- `apps/control-station/src/components/RecordedFmp4Player.tsx`
|
||||||
- `apps/control-station/src/core/observation/useObservationLayout.ts`
|
- `apps/control-station/src/core/observation/useObservationLayout.ts`
|
||||||
- `apps/control-station/src/device-plugins/xgrids-k1/observationSources.ts`
|
- `apps/control-station/src/device-plugins/xgrids-k1/observationSources.ts`
|
||||||
- `tests/test_xgrids_camera_gateway.py`
|
- `tests/test_xgrids_camera_gateway.py`
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,230 @@
|
||||||
|
# ADR 0008: durable observation sessions and workspace layout
|
||||||
|
|
||||||
|
- Status: accepted for the laboratory Control Station
|
||||||
|
- Date: 2026-07-17
|
||||||
|
- Scope: host observation storage, recorded spatial playback, camera evidence,
|
||||||
|
and operator workspace layout
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Mission Core previously retained K1 live point/pose captures as ignored
|
||||||
|
laboratory directories, while the browser showed only the current process-wide
|
||||||
|
Rerun stream. Camera preview was copy-remuxed for a connected browser but was
|
||||||
|
not part of the durable session. A browser refresh, local service restart or an
|
||||||
|
unexpected process exit therefore made a completed run difficult to discover
|
||||||
|
and made camera history impossible to recover. The disk icon in the spatial
|
||||||
|
workspace also had no stable distinction between saving sensor evidence and
|
||||||
|
saving an operator's layout.
|
||||||
|
|
||||||
|
These are two separate product records:
|
||||||
|
|
||||||
|
1. an **observation session** is evidence from all sources active during one
|
||||||
|
acquisition and must be recorded automatically; and
|
||||||
|
2. a **workspace layout profile** is an operator preference and is saved
|
||||||
|
explicitly.
|
||||||
|
|
||||||
|
Conflating them would allow a UI action to decide whether evidence exists and
|
||||||
|
would make a layout save duplicate or mutate sensor data.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
### Host-owned catalog and private evidence root
|
||||||
|
|
||||||
|
The Control Station owns a private storage root selected by
|
||||||
|
`MISSIONCORE_DATA_DIR`; the repository-local fallback is
|
||||||
|
`.runtime/mission-core/`. The root is outside Git and contains a SQLite catalog
|
||||||
|
plus derived playback artifacts. New source evidence is written to the root
|
||||||
|
selected by `MISSIONCORE_EVIDENCE_DIR`, falling back to
|
||||||
|
`<MISSIONCORE_DATA_DIR>/evidence/sessions`. The current K1 camera gateway still
|
||||||
|
requires that root to remain inside the repository checkout; an external root
|
||||||
|
can retain MQTT evidence but camera archival fails closed. Treat support for an
|
||||||
|
independently attested external camera-evidence root as an open extraction item,
|
||||||
|
not as implemented behavior. SQLite uses foreign keys, WAL journaling and full
|
||||||
|
synchronous durability. Public APIs expose opaque session identifiers and
|
||||||
|
metadata, never filesystem paths.
|
||||||
|
|
||||||
|
Current acquisition writers create only direct children of the private
|
||||||
|
evidence root. Existing repository-level `sessions/*_viewer_live` runs are a
|
||||||
|
legacy import-only source: they are indexed in place, never copied or moved,
|
||||||
|
and are not assigned to a new writer. Their native `.k1mqtt` capture remains
|
||||||
|
the source of record. Import is repeatable and runs in a background reconciler,
|
||||||
|
so completed evidence becomes visible without a restart and HTTP catalog reads
|
||||||
|
never scan the evidence roots. Startup recovery may
|
||||||
|
seal an already incomplete canonical camera epoch under the legacy root; it
|
||||||
|
does not start or append a new acquisition there.
|
||||||
|
|
||||||
|
Before a new session directory is created, the writer atomically creates and
|
||||||
|
locks `<evidence-root>/.current_session`. The marker names the direct child and
|
||||||
|
prevents a second process from writing or replaying the active session. Process
|
||||||
|
exit releases the operating-system lock. On startup, an unlocked stale marker
|
||||||
|
is removed only after no-follow inode revalidation; the interrupted evidence
|
||||||
|
directory is preserved for normal recovery and cataloging.
|
||||||
|
|
||||||
|
If a process exits after raw and metadata creation but before a final summary,
|
||||||
|
the importer accepts only a bounded, structurally valid prefix with aligned
|
||||||
|
metadata and marks the run `interrupted`. It does not invent completion data.
|
||||||
|
When the valid final summary later appears, repeat import promotes the same
|
||||||
|
session to `ready`.
|
||||||
|
|
||||||
|
Native MQTT persistence uses a bounded group commit of at most 0.5 seconds,
|
||||||
|
4 MiB or 32 messages. Raw bytes are fsynced before their aligned metadata rows.
|
||||||
|
The final uncommitted group may be lost, and a raw tail may survive without
|
||||||
|
committed metadata; replay stops at the last validated aligned boundary.
|
||||||
|
|
||||||
|
### Recorded spatial playback
|
||||||
|
|
||||||
|
When a native capture is normally sealed or recovery-sealed, a bounded
|
||||||
|
single-worker reconciler automatically prepares its private derived `.rrd`.
|
||||||
|
Preparation is a backend lifecycle and is never executed by an HTTP replay
|
||||||
|
request. Reopening, seeking, switching tabs or changing display settings reads
|
||||||
|
the already prepared artifact and does not rerun conversion. Export is lossless
|
||||||
|
with respect to every decodable point and pose message in the native capture;
|
||||||
|
it does not pass through the bounded live-preview queue. The RRD uses the
|
||||||
|
recording-local `session_time` duration timeline, while the receive wall clock
|
||||||
|
remains secondary evidence.
|
||||||
|
|
||||||
|
Derived files are published atomically, hashed and accompanied by a cache
|
||||||
|
sidecar bound to the native file identity and digest. A process-owned queue and
|
||||||
|
cross-process file lock prevent overlapping exporters; crash candidates are
|
||||||
|
scavenged before quota accounting. A stale or inconsistent cache is rejected
|
||||||
|
and rebuilt. The browser receives the RRD only through the same-origin opaque
|
||||||
|
session API. It keeps the viewport and timeline hidden until the complete
|
||||||
|
declared recording has been downloaded and decoded, then publishes the scene
|
||||||
|
atomically and starts playback.
|
||||||
|
|
||||||
|
### Camera recording
|
||||||
|
|
||||||
|
Camera acquisition is owned by the device acquisition session, not by a
|
||||||
|
browser window. Preview remains bounded and disposable; archive writes occur
|
||||||
|
before preview fan-out. Closing or refreshing a camera window must not stop the
|
||||||
|
archive while acquisition is active.
|
||||||
|
|
||||||
|
Each codec epoch is stored below the observation session as an initialization
|
||||||
|
segment, independently addressable fMP4 media segments, an append-only JSONL
|
||||||
|
index and an atomic summary. Host arrival epoch and monotonic timestamps are
|
||||||
|
recorded for every segment. The `per-segment-fsync` contract makes `init.mp4`
|
||||||
|
and each complete media segment plus its directory entry durable before its
|
||||||
|
matching index row is committed; the index and interrupted checkpoint are
|
||||||
|
fsynced before append returns. Therefore camera RPO is bounded by the fragment
|
||||||
|
currently being produced, not by an interval or byte batch. Startup recovery
|
||||||
|
retains a contiguous valid prefix, quarantines orphan fragments and marks an
|
||||||
|
unfinalized epoch `interrupted`.
|
||||||
|
|
||||||
|
The background recording-preparation worker also prepares immutable camera
|
||||||
|
descriptors before publishing `ready`. Complete-fragment arrival timestamps are
|
||||||
|
end/availability anchors, so the worker SHA-verifies and parses every fragment,
|
||||||
|
anchors the first decoded sample at `first_arrival - first_duration`, and derives
|
||||||
|
coverage from the exact sum of ISO-BMFF sample durations. It computes finite
|
||||||
|
non-overlapping epoch coverage ends and exact aggregate bytes, then binds both to
|
||||||
|
recorded-media manifest v2's generation digest. Replay v2 additionally requires
|
||||||
|
camera coverage to stay inside the spatial RRD range with a 50 ms tolerance; a
|
||||||
|
future replay v3 will need distinct spatial and session-union ranges.
|
||||||
|
|
||||||
|
The path-free descriptor is atomically persisted in the private derived cache
|
||||||
|
with its checksum and a stat identity covering the native timing origin and
|
||||||
|
every camera summary, index, init and segment file. Restart performs confined
|
||||||
|
stat validation and reuses an unchanged descriptor without reading/hashing media;
|
||||||
|
a missing, corrupt or stale sidecar is rebuilt in the background. An unparseable
|
||||||
|
or ambiguous fragment fails readiness rather than exposing a fake seekable
|
||||||
|
camera. Replay requests consume the prepared descriptor and never repeat media
|
||||||
|
conversion or timing analysis. Launch and manifest aggregate byte counts must
|
||||||
|
agree before the browser admits camera payload downloads.
|
||||||
|
|
||||||
|
The derived RRD writes an actual zero-time anchor at the internal
|
||||||
|
`/__mission_core/session_origin` entity. Keeping it outside `/world` prevents a
|
||||||
|
synthetic visualization layer while ensuring that the decoded RRD timeline,
|
||||||
|
not merely its summary document, begins at session time zero. This changes the
|
||||||
|
derived payload contract: cache v6 rejects v4/v5 sidecars and performs one
|
||||||
|
background rebuild instead of silently reusing an archive without that row.
|
||||||
|
|
||||||
|
Replay v2 exposes two paths to the same pinned generation. `source_url` retains
|
||||||
|
the strict `If-Match: "sha256:…"` contract for Mission Core clients;
|
||||||
|
`viewer_source_url` appends the exact lowercase SHA-256 as a `generation` query
|
||||||
|
for Rerun's native URL loader, which cannot attach custom headers. The server
|
||||||
|
accepts either precondition, rejects a stale query with `412`, and keeps the
|
||||||
|
matching file pinned for the entire immutable/no-transform response lifecycle.
|
||||||
|
|
||||||
|
This is host-arrival synchronization, not proven sensor-clock synchronization.
|
||||||
|
It is sufficient to correlate current K1 point/pose and preview evidence at the
|
||||||
|
known clock boundary, but must not be described as frame-accurate optical/LiDAR
|
||||||
|
alignment.
|
||||||
|
|
||||||
|
Historical runs made before this ADR contain no camera archive. The fact that a
|
||||||
|
camera was visible in the old browser preview does not make video recoverable;
|
||||||
|
the catalog must not manufacture a camera modality for those sessions.
|
||||||
|
|
||||||
|
### Workspace layout
|
||||||
|
|
||||||
|
The first disk action in the spatial workspace saves only the versioned
|
||||||
|
`observation.spatial` layout document:
|
||||||
|
|
||||||
|
- scene display settings;
|
||||||
|
- open tool windows and their z-order;
|
||||||
|
- visible dynamic source identifiers;
|
||||||
|
- active floating source;
|
||||||
|
- source-window rectangles normalized to the observed viewport.
|
||||||
|
|
||||||
|
The document uses optimistic revision control (`ETag` and `If-Match`) and is
|
||||||
|
restored automatically on the next opening. Unknown source identifiers remain
|
||||||
|
desired state so a temporarily disconnected camera can recover its saved
|
||||||
|
window when that source returns. The save action never starts, completes or
|
||||||
|
modifies an observation session.
|
||||||
|
|
||||||
|
### API boundary
|
||||||
|
|
||||||
|
The stable host routes are:
|
||||||
|
|
||||||
|
- `GET /api/v1/observation-sessions` for the recent catalog;
|
||||||
|
- `GET /api/v1/observation-sessions/{id}` for modality/artifact metadata;
|
||||||
|
- `POST /api/v1/observation-sessions/{id}/replay` for a ready launch or a
|
||||||
|
preparation handle;
|
||||||
|
- `GET /api/v1/observation-sessions/{id}/recording-preparation` for exact-job
|
||||||
|
status polling;
|
||||||
|
- `GET /api/v1/observation-sessions/{id}/recording.rrd` for seekable spatial data;
|
||||||
|
- opaque manifest/init/segment routes below
|
||||||
|
`/api/v1/observation-sessions/{id}/media/{artifact}` for recorded cameras;
|
||||||
|
- `GET|PUT /api/v1/workspace-layouts/observation.spatial` for the layout profile.
|
||||||
|
|
||||||
|
The session API precedes the static frontend mount. Recording paths and storage
|
||||||
|
roots are server implementation details.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
- A completed or interrupted spatial run is discovered and prepared
|
||||||
|
automatically, then can be replayed after browser and service restarts.
|
||||||
|
- A service crash can lose the final uncommitted MQTT group and the camera
|
||||||
|
fragment currently being produced; it cannot make an index point beyond
|
||||||
|
durable camera media or replay a raw tail beyond aligned MQTT metadata.
|
||||||
|
- Raw K1 evidence remains authoritative and derived RRD files can be discarded
|
||||||
|
and regenerated.
|
||||||
|
- Browser performance no longer controls whether camera evidence is retained.
|
||||||
|
- Layout restoration is portable across viewport sizes and dynamic device
|
||||||
|
source catalogs.
|
||||||
|
- Replay builds a separate recorded-source catalog, so an active device camera
|
||||||
|
can never be mixed into the evidence of a saved session. Archived fMP4 codec
|
||||||
|
epochs follow Rerun play/pause/seek through host-arrival best-effort offsets.
|
||||||
|
- A preparation ETag binds polling and cancellation to one exact job; a short
|
||||||
|
launch reservation prevents cache eviction between the launch response and
|
||||||
|
the viewer's first RRD request. Browser session switching never cancels the
|
||||||
|
shared backend preparation.
|
||||||
|
- Storage capacity, retention, quota, encryption-at-rest and export policy are
|
||||||
|
still deployment responsibilities; this ADR does not silently delete source
|
||||||
|
evidence.
|
||||||
|
- Recorded camera playback is confined to canonical archives with a supported
|
||||||
|
fMP4 codec declaration and provable sample duration. Missing historical media,
|
||||||
|
unsupported codec epochs and ambiguous timing fail closed; none is substituted
|
||||||
|
with a live preview.
|
||||||
|
|
||||||
|
## Rejected alternatives
|
||||||
|
|
||||||
|
- **Use browser cache or IndexedDB for sessions.** This loses the evidence when
|
||||||
|
the browser profile is cleared and cannot guarantee acquisition durability.
|
||||||
|
- **Save a session only when the operator presses the disk icon.** This makes
|
||||||
|
safety evidence optional and fails on crashes or forgotten clicks.
|
||||||
|
- **Store only RRD.** This discards the native evidence needed to review future
|
||||||
|
decoders and protocol assumptions.
|
||||||
|
- **Keep camera acquisition coupled to WebSocket viewers.** A UI disconnect
|
||||||
|
would stop recording and recreate the observed data-loss bug.
|
||||||
|
- **Copy every legacy run into the private catalog root.** This doubles large
|
||||||
|
artifacts without improving integrity; confined zero-copy indexing is
|
||||||
|
sufficient for the current laboratory store.
|
||||||
|
|
@ -24,13 +24,16 @@ The plugin owns:
|
||||||
- firmware-scoped protobuf/LZ4 and legacy codecs;
|
- firmware-scoped protobuf/LZ4 and legacy codecs;
|
||||||
- normalization of K1 point cloud and pose, plus raw-only preservation of the
|
- normalization of K1 point cloud and pose, plus raw-only preservation of the
|
||||||
still-undecoded status and heartbeat channels;
|
still-undecoded status and heartbeat channels;
|
||||||
|
- exact-profile left/right RTSP producer selection, H.264 copy-remux preview and
|
||||||
|
the handoff of acquisition-owned init/fMP4/index evidence to the host archive;
|
||||||
- K1-specific operator instructions and compatibility tests.
|
- K1-specific operator instructions and compatibility tests.
|
||||||
|
|
||||||
The plugin does not own:
|
The plugin does not own:
|
||||||
|
|
||||||
- Mission Core navigation, fleet, missions, users, roles, or audit;
|
- Mission Core navigation, fleet, missions, users, roles, or audit;
|
||||||
- the generic spatial scene or Rerun Web Viewer;
|
- the generic spatial scene or Rerun Web Viewer;
|
||||||
- platform storage, remote transport, or other devices.
|
- the host observation catalog, RRD preparation/cache, recorded-camera player,
|
||||||
|
workspace layout, platform storage, remote transport, or other devices.
|
||||||
|
|
||||||
Until extraction is complete, `src/k1link` is the compatibility source of
|
Until extraction is complete, `src/k1link` is the compatibility source of
|
||||||
truth and every move into this package must preserve replay and real-device
|
truth and every move into this package must preserve replay and real-device
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue