# 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:

```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/
│           ├── 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:

```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
# 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:

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

selected RTSP producer
  ├─ 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:

```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. 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:

```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/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.
