NODEDC_MISSION_CORE/docs/09_OBSERVATION_SESSIONS.md

35 KiB
Raw Permalink Blame History

Observation sessions, playback and workspace layout

Status: implemented for native K1 point/pose evidence and host-side camera archival. Valid K1 ModelingReport scan time, distance and speed are also materialized as recorded Rerun time series. Recorded spatial playback is exposed in the Mission Core observation workspace. The camera archive/player contract is implemented and covered by tests. TEST007 physically accepted shared-timeline playback of the saved cameras and point cloud. It also carries the first optional external-perception result projected into the same Rerun recording; older sessions without canonical camera evidence or an admitted compute result remain fully usable with only their available modalities.

Operator path

  1. Enter the required project name and start a normal acquisition from Парк → Локальное устройство. The NFKC-normalized/trimmed value is local display metadata, not a filesystem path, and becomes the saved-session catalog name. A normal acquisition has no duration deadline and runs until the operator explicitly stops it. Compatibility clients may still request a positive finite duration, without an application-level maximum.
  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 up to 100 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. If a validated derived camera result exists, Распознавание appears in the scene toolbar. It switches the active native Rerun view to camera frames and boxes; Облако точек returns to the 3D view. No button is shown for an absent or rejected result, and the base recording remains usable.
  9. Use the first disk button to save the current workspace layout. The next opening restores display settings and dynamic sensor-source window positions. The transient Движок / Слои / Отображение tool windows are scoped to the mounted spatial workspace and are never persisted. This action saves no sensor evidence.

Mission Core does not switch an active acquisition to another spatial source. Every nonterminal or unknown acquisition state fail-closes saved-session replay, persisted replay reattach and manual RRD/Rerun input/apply/reset with Завершите текущий приём перед сменой источника. A plugin-owned automatic source action clears the older source and opens the scene only after its start returns success; failure leaves the current scene mounted. This internal success-result transition is not an operator switch. The guard does not stop the scanner or local receiver automatically.

Display controls have no separate Apply/Reset transaction. Boolean controls commit immediately; sliders, colors and selects commit on release/blur or after a short quiet period, and concurrent commits collapse to the newest value. A 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. The first scan is only a historical baseline: it never enqueues the existing archive. A session that becomes finalized after that baseline is deduplicated into the 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 publishes the immutable media-manifest sidecar. 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 computation. Reopening, seeking, refreshing the catalog or changing the workspace never reruns a published conversion. After a process restart, a catalog/replay read restores a complete compatible RRD and camera package from its durable sidecars using bounded schema and stat-identity checks; it does not hash native evidence, parse camera indexes or invoke the exporter. A historical session whose package is missing, stale or from an incompatible cache schema remains cold. Only an explicit replay/RRD request schedules that one session for preparation; listing the catalog and finalizing another session do not schedule it.

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.
Solid gray + Подготовить replayable session with no compatible published package No work is running; choosing the row schedules this session only.
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.

Immutable LAB instances

An accepted experiment is published as a new saved-session identity instead of changing the AI result selected for its source recording. A LAB instance binds:

  • a stable LAB E… marker and opaque session id;
  • the immutable source observation session;
  • the exact result, source-result and configuration SHA-256 identities;
  • run and publication timestamps plus JSON provenance;
  • a content-addressed compute job, LiDAR pack and integrated visual result.

The source evidence remains the only raw sensor master. Full-session LAB replay caches and unchanged camera inputs use hard links on the same filesystem; no second copy of the source RRD, video or native K1 capture is created. A bounded experiment instead stores a small derived RRD slice with exact start/end markers and omits the source's unbounded recorded-video descriptor. Its integrated perception overlay owns the matching bounded camera. This prevents Rerun from holding the final AI frame while a longer source cloud continues to play. Derived arrays are stored only when the run did not already persist a viewer-ready form. Deleting a LAB catalog entry cannot delete the referenced source session, and a source session with published LAB instances fails closed on deletion.

The visual result is resolved by the LAB session id, so LAB E19, LAB E21 and the derived temporal comparison LAB E22 can coexist over RAVNOVES00 without “latest accepted result” replacing an earlier experiment. The operator can open any row in Сохранённые сессии and use the same Объекты 2D / Сегментация / Кубы 3D controls. First publication must also warm the result-specific Rerun overlay; later opens reuse that verified cache and do not rerun AI inference.

E21 is a bounded special case: its worker persisted 121 semantic mask hashes, not duplicate mask pixels. Its visual publication materializes a mask only after an exact SHA-256 match against the immutable accepted E19 semantic reference. The seven detector frames replaced by the bounded latest-wins queue remain explicit empty frames. This preserves the measured near-real-time behavior rather than presenting a fabricated gap-free run. The saved-session duration and base RRD range must equal the E21 visual result range; attaching the 60-second result to the complete 8:55 source replay is an invalid presentation because latest-at image data would appear static after the E21 window ends.

E22 is also bounded to the exact E21 60-second range, but it resolves its catalog provenance directly to the physical source session rather than chaining one LAB entry to another. Its temporal result remains a separate immutable integrated overlay. Raw point-cloud, camera, calibration and E21 inference payloads are not replaced. Short held boxes are provenance-marked diagnostic presentation/world-state values, not fresh sensor measurements.

The first preparation of a long capture can take time because every decodable point/pose message and every valid K1 ModelingReport is projected into RRD. Later openings reuse a verified, digest-bound cache. Derived RRD cache v9 is intentionally incompatible with v8 and older generations. In addition to plugin and ordered-artifact identity, it binds the durable clock origin and active envelope and materializes real session_time = 0 origin and sealed completion rows. Older generations remain cold after startup and are rebuilt only when an operator explicitly opens that session. 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:

.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/
│           ├── manifest.redacted.json   # normalized local project display metadata
│           ├── captures/
│           │   └── mqtt_live/
│           │       ├── mqtt.raw.k1mqtt
│           │       ├── mqtt.metadata.jsonl
│           │       ├── mqtt.timeline.origin.json
│           │       ├── mqtt.timeline.json # provisional transport envelope
│           │       ├── mqtt.timeline.session-<sha256>.json # sealed generation
│           │       └── mqtt.summary.json # atomic active-envelope pointer
│           └── media/                  # canonical camera archives
├── recordings/
    ├── .export.lock                     # cross-process conversion/eviction lock
    └── <opaque-session-id>/
        ├── scene.rrd
        └── scene.rrd.cache.json
└── perception-overlays/
    └── <opaque-session-id>/<result-id>/<recording-id>.rrd

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:

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
# Optional operator retention quota; unset means no application byte quota.
# 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. The recorded perception importer reads repo-local ignored .runtime/compute-jobs and .runtime/compute-results; those derived handoff artifacts are likewise private and never become browser paths or Git inputs.

Session source of record

For the current K1 profile:

MQTT callback
  ├─ durable native .k1mqtt + aligned metadata  (source of record)
  ├─ durable clock origin                       (before camera production)
  ├─ provisional + content-addressed envelopes  (transport/session bounds)
  ├─ ModelingReport -> live product metrics     (before visual preview queue)
  └─ bounded latest-wins Rerun live preview     (disposable)

selected RTSP producer
  ├─ 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, the capture-clock artifacts and canonical camera fMP4 archives are the evidence source of truth. The derived RRD contains every decodable point and pose frame plus every valid ModelingReport distance/speed/scan-time sample from that source, but remains a rebuildable view rather than an evidence master. A cache entry is eligible for rebuild only when native source identity/digests change or an incompatible derived-data export revision is introduced. Historical entries are not rebuilt during startup or because another session is finalized; an explicit open schedules the affected entry. A UI blueprint or workspace-layout revision never invalidates or rewrites the data RRD. Cache v8 and older payloads are not reusable as v9 because they do not bind both real capture-envelope endpoints.

ModelingReport time is the device's ScanTime counter at two ticks per second; distance and speed are the reported MoveDistance/MoveSpeed values. Live state keeps the current device scan generation and does not accumulate an earlier distance after the device resets scan time and route distance. These values are not reconstructed from pose integration or a browser timer.

Expensive cache misses run through the single-worker preparation queue and one global cross-process export gate to cap concurrent RAM, CPU and temporary-disk use. Crash leftovers from candidates, exporter temporary files and staged replay prefixes are scavenged under that lock before capacity accounting. Ready cache hits and active response leases do not wait behind that gate. The derived cache has no application byte quota by default, so a single multi-hour RRD is not rejected at 8 GiB. It still preserves a 2 GiB default filesystem reserve. An operator may set MISSIONCORE_RRD_CACHE_MAX_BYTES to enable LRU eviction of derived RRDs only; native evidence is never deleted.

Capture-clock envelope

New K1 captures first publish bounded schema-1 captures/mqtt_live/mqtt.timeline.origin.json with start epoch and monotonic nanoseconds. The writer creates and synchronizes this immutable artifact after its raw/metadata files are open and before camera production is armed. A crash therefore cannot leave retained camera evidence whose intended session zero existed only in memory.

MQTT shutdown then publishes schema-1 mqtt.timeline.json with the same start plus transport completion. Capture-summary schema 2 initially points to it with capture_clock_scope: transport, and binds both origin/envelope names and SHA-256 values. That provisional file is not rewritten.

After the camera archive and MQTT/runtime stop, the acquisition owner creates mqtt.timeline.session-<sha256>.json with the extended session completion and atomically switches mqtt.summary.json to that content-addressed name, digest and capture_clock_scope: session while still holding the active-session lease. Publication order is sealed file first, summary pointer second, so a crash leaves the previous pointer valid and the idempotent seal can converge on retry. The same summary update records session_elapsed_seconds. Files and containing directories are synchronized at their publication boundaries.

Discovery validates the summary-selected filename, both digests, the shared origin and the envelope bounds, then catalogs that exact artifact. The materializer passes its exact staged path to the exporter; it never chooses a sealed generation by glob or “latest file” ordering. Direct/legacy export keeps the fixed provisional-name fallback only for evidence outside the cataloged session contract, and orphan sealed candidates not selected by the validated summary are ignored.

A provisional transport scope is never advertised as combined camera replay. An interrupted origin-only point/pose prefix may use the durable origin and its last validated message as a compatibility end, but it cannot advertise camera media. If the origin, active envelope, summary pointer or digest relationship is missing/corrupt, new-schema discovery fails closed instead of inventing a session boundary.

For a sealed generation, session_time = 0 is the envelope start and the RRD end is the envelope completion. Cache v9 logs real rows at /__mission_core/session_origin and /__mission_core/session_end, so a camera fragment accepted before the first MQTT message or after the last point/pose message can remain inside the declared RRD interval. Valid legacy sessions without this artifact keep their first/last-message compatibility fallback and cannot gain historical camera coverage.

Camera archive contract

New acquisitions archive each selected source and codec epoch below the same private evidence session:

<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. JSONL indexes are read incrementally and no total index, segment-count or archive-byte ceiling is used. 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 durable 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 remains cold until an explicit open schedules the complete session package in the background worker. No catalog, status, manifest or payload request itself 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.
  • New capture-summary schema 2 binds the durable origin and its active envelope by name and SHA-256. A normal combined replay requires the owner-sealed, content-addressed session scope. A crash can leave an origin-only or provisional transport generation, but discovery will not use either to advertise camera coverage. A mismatched origin/envelope/summary fails closed.
  • A native capture with a missing final summary is accepted only when the raw and metadata prefix is aligned and structurally valid. Recovery streams the metadata JSONL one row at a time with no total-byte or message-count ceiling; 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:

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/observation-sessions/{id}/media/{artifact}/epochs/{n}/recording.mp4?generation=<sha256>

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 public compact missioncore.observation-recorded-media/v3 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. Each epoch declares one generation-bound stream_url, media type and aggregate byte length; thousands of internal segment rows never enter browser memory. 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 and manifest identity, but applies no duration, per-source byte or aggregate-session byte admission ceiling.

The <video> element reads the immutable virtual fMP4 through native HTTP Range requests. The server maps each requested interval onto init/segment files, opens them through confined descriptors with no symlink following and verifies the digest of each touched component. It never assembles the full video in backend or JavaScript memory. Responses carry exact Content-Length / Content-Range, a generation-and-epoch ETag, Accept-Ranges: bytes and private immutable no-transform caching. The manifest ETag is the exact generation and its GET requires the matching If-Match; the stream URL binds that same generation in its query. The older init/segment routes remain internal compatibility surfaces. Physical source ids, RTSP addresses and storage paths never cross the API boundary.

Current synchronization boundary

The spatial RRD, trajectory, archived device-metric series and archived fMP4 cameras use the same operator scrubber now. Camera epochs are aligned to zero-based session_time from the shared host-arrival monotonic clock and rendered through the browser's native fMP4/Range pipeline. The metric tab carries device-reported route distance, speed and scan time at their ModelingReport receive times. The client selects a camera epoch only inside its declared inclusive interval and verifies that the decoded native-media seekable duration covers that interval. This remains best-effort correlation: codec PTS, K1 sensor exposure time and LiDAR firing time are not proven to share a device clock. A codec epoch whose init segment does not expose a browser-supported codec or whose timing cannot be proven remains retained evidence and fails closed in the UI/background preparation. Historical sessions with no canonical camera archive honestly show no recorded video. Recorded blueprints can change accumulation, grid visibility, point and trajectory visibility, point radius and a uniform custom point color without 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 boundary — 2026-07-17

The current automated gate covers project-name validation, source-switch fail-closure, optional plugin scene controls, bounded ModelingReport decode, inert start/stop encoding and correlation, capture-clock publication/validation, cache-v9 origin/end materialization and archived metric series. The standard repository gate remains Python tests/Ruff/mypy plus frontend unit tests, TypeScript checking and the Vite production build; exact pass counts belong to the commit's CI/pre-push result rather than this durable architecture contract.

Vite still reports its expected large-chunk warning for the embedded Rerun viewer/WASM payload. That is a packaging optimization item, not a failed gate. No retained physical K1 session contains the new canonical camera archive, so a real point-cloud plus one-camera recorded playback remains an explicit hardware acceptance test. Automated protocol tests also do not authorize K1 modeling publishing: operator-owned Keychain item provisioning, operator-present physical acceptance and durable save remain separate physical/security gates.